305 lines
11 KiB
Python
Executable File
305 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from gi.repository import Gtk, GLib, Gio
|
|
import threading
|
|
from pydbus import SystemBus
|
|
import subprocess
|
|
|
|
|
|
class BluetoothMenu(Gtk.Application):
|
|
def __init__(self):
|
|
super().__init__(application_id="org.example.BluetoothMenu")
|
|
self.bus = SystemBus()
|
|
self.adapter = self.bus.get("org.bluez", "/org/bluez/hci0")
|
|
self.object_manager = self.bus.get("org.bluez", "/")
|
|
self.window = None
|
|
self.device_window = None
|
|
self.scanning = False
|
|
self.discovered_devices = {}
|
|
|
|
def do_activate(self):
|
|
if not self.window:
|
|
self.window = Gtk.ApplicationWindow(application=self)
|
|
self.window.set_default_size(200, 150)
|
|
self.window.set_title("Bluetooth Menu")
|
|
self.vbox = Gtk.Box(
|
|
orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
|
|
|
if hasattr(self.window, "set_child"):
|
|
self.window.set_child(self.vbox)
|
|
else:
|
|
self.window.add(self.vbox)
|
|
|
|
self.build_menu()
|
|
self.update_bluetooth_buttons()
|
|
|
|
self.window.present()
|
|
|
|
def build_menu(self):
|
|
self.item_scan = Gtk.Button(label='Escanear Dispositivos')
|
|
self.item_scan.connect('clicked', self.scan_and_show_devices)
|
|
self.add_to_box(self.vbox, self.item_scan)
|
|
|
|
self.item_on = Gtk.Button(label='Encender Bluetooth')
|
|
self.item_on.connect('clicked', self.turn_on_bluetooth)
|
|
self.add_to_box(self.vbox, self.item_on)
|
|
|
|
self.item_off = Gtk.Button(label='Apagar Bluetooth')
|
|
self.item_off.connect('clicked', self.turn_off_bluetooth)
|
|
self.add_to_box(self.vbox, self.item_off)
|
|
|
|
self.item_send_file = Gtk.Button(label='Enviar Archivo')
|
|
self.item_send_file.connect('clicked', self.send_file_dialog)
|
|
self.add_to_box(self.vbox, self.item_send_file)
|
|
|
|
item_quit = Gtk.Button(label='Cerrar')
|
|
item_quit.connect('clicked', self.exit_app)
|
|
self.add_to_box(self.vbox, item_quit)
|
|
|
|
def add_to_box(self, box, widget):
|
|
if hasattr(box, "append"):
|
|
box.append(widget)
|
|
else:
|
|
box.pack_start(widget, True, True, 0)
|
|
|
|
def send_file_dialog(self, _):
|
|
dialog = Gtk.FileChooserDialog(
|
|
title="Selecciona un archivo para enviar",
|
|
transient_for=self.window,
|
|
action=Gtk.FileChooserAction.OPEN,
|
|
modal=True
|
|
)
|
|
dialog.add_buttons(
|
|
"Cancelar", Gtk.ResponseType.CANCEL,
|
|
"Abrir", Gtk.ResponseType.OK
|
|
)
|
|
|
|
dialog.connect("response", self.on_file_chosen)
|
|
dialog.show()
|
|
|
|
def on_file_chosen(self, dialog, response):
|
|
if response == Gtk.ResponseType.OK:
|
|
file = dialog.get_file() # Obtenemos el archivo directamente
|
|
if file:
|
|
file_path = file.get_path() # Obtenemos la ruta del archivo
|
|
|
|
# Oculta la ventana principal antes de abrir la ventana de selección de dispositivos
|
|
self.window.hide()
|
|
|
|
# Llama a la función para seleccionar el dispositivo y enviar el archivo
|
|
self.select_device_for_file_transfer(file_path)
|
|
|
|
dialog.destroy()
|
|
|
|
def select_device_for_file_transfer(self, file_path):
|
|
if self.device_window:
|
|
self.device_window.destroy()
|
|
|
|
self.device_window = Gtk.Window(title="Seleccionar dispositivo")
|
|
self.device_window.set_default_size(300, 400)
|
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
|
self.device_window.set_child(vbox)
|
|
|
|
devices = self.get_discovered_devices()
|
|
for name, address in devices:
|
|
button = Gtk.Button(label=f"{name} ({address})")
|
|
button.connect("clicked", self.send_file_to_device,
|
|
file_path, address)
|
|
vbox.append(button)
|
|
|
|
# Añade un botón "Cerrar" al final de la ventana de selección de dispositivos
|
|
close_button = Gtk.Button(label="Cerrar")
|
|
close_button.connect("clicked", lambda _: self.device_window.destroy())
|
|
vbox.append(close_button)
|
|
|
|
# Muestra la ventana de selección de dispositivos
|
|
self.device_window.show()
|
|
|
|
# Restaura la ventana principal al cerrar la ventana de selección de dispositivos
|
|
self.device_window.connect("destroy", lambda w: self.window.show())
|
|
|
|
def send_file_to_device(self, button, file_path, address):
|
|
try:
|
|
# Ejecuta bluetooth-sendto con la dirección del dispositivo y el archivo
|
|
result = subprocess.run(
|
|
['bluetooth-sendto', '--device=' + address, file_path],
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Verifica si se produjo un error en la ejecución
|
|
if result.returncode == 0:
|
|
self.show_notification(f"Archivo enviado a {address}")
|
|
else:
|
|
self.show_notification(
|
|
f"Error al enviar archivo: {result.stderr}")
|
|
|
|
except Exception as e:
|
|
self.show_notification(f"Error al ejecutar bluetooth-sendto: {e}")
|
|
|
|
def update_bluetooth_buttons(self):
|
|
powered = self.adapter.Powered
|
|
self.item_on.set_sensitive(not powered)
|
|
self.item_off.set_sensitive(powered)
|
|
|
|
def scan_and_show_devices(self, _):
|
|
try:
|
|
# Inicia el escaneo solo si no está en proceso
|
|
if not self.scanning:
|
|
self.adapter.StartDiscovery()
|
|
self.scanning = True
|
|
self.show_notification(
|
|
"Escaneo de dispositivos Bluetooth iniciado")
|
|
|
|
# Muestra la ventana de dispositivos en tiempo real
|
|
self.show_device_list_window()
|
|
except Exception as e:
|
|
self.show_notification(f"Error iniciando el escaneo: {e}")
|
|
|
|
def show_device_list_window(self):
|
|
if self.device_window:
|
|
self.device_window.destroy()
|
|
|
|
self.device_window = Gtk.Window(title="Dispositivos Disponibles")
|
|
self.device_window.set_default_size(400, 500)
|
|
self.device_window.set_resizable(True)
|
|
self.device_window.set_transient_for(self.window)
|
|
|
|
scrolled_window = Gtk.ScrolledWindow()
|
|
scrolled_window.set_policy(
|
|
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
scrolled_window.set_min_content_height(400)
|
|
|
|
self.device_list_vbox = Gtk.Box(
|
|
orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
if hasattr(scrolled_window, "set_child"):
|
|
scrolled_window.set_child(self.device_list_vbox)
|
|
else:
|
|
scrolled_window.add(self.device_list_vbox)
|
|
|
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
self.add_to_box(vbox, scrolled_window)
|
|
|
|
refresh_button = Gtk.Button(label="Refrescar")
|
|
refresh_button.connect("clicked", self.refresh_device_list)
|
|
self.add_to_box(vbox, refresh_button)
|
|
|
|
close_button = Gtk.Button(label="Cerrar")
|
|
close_button.connect("clicked", self.stop_scan_and_close)
|
|
self.add_to_box(vbox, close_button)
|
|
|
|
if hasattr(self.device_window, "set_child"):
|
|
self.device_window.set_child(vbox)
|
|
else:
|
|
self.device_window.add(vbox)
|
|
|
|
self.device_window.present()
|
|
|
|
# Asegura que el temporizador se ejecute para actualizar la lista en tiempo real
|
|
if not hasattr(self, 'update_devices_timer'):
|
|
self.update_devices_timer = GLib.timeout_add_seconds(
|
|
2, self.update_device_list)
|
|
|
|
def get_discovered_devices(self):
|
|
devices = []
|
|
managed_objects = self.object_manager.GetManagedObjects()
|
|
for path, interfaces in managed_objects.items():
|
|
if "org.bluez.Device1" in interfaces:
|
|
device_properties = interfaces["org.bluez.Device1"]
|
|
if device_properties.get("Connected") or device_properties.get("Paired"):
|
|
# Omitir dispositivos emparejados o ya conectados
|
|
continue
|
|
name = device_properties.get("Name", "Desconocido")
|
|
address = device_properties.get("Address")
|
|
devices.append((name, address))
|
|
return devices
|
|
|
|
def refresh_device_list(self, _):
|
|
self.discovered_devices.clear()
|
|
if self.device_list_vbox and self.device_window:
|
|
self.clear_box_children(self.device_list_vbox)
|
|
self.show_notification("Lista de dispositivos actualizada")
|
|
|
|
def clear_box_children(self, box):
|
|
if hasattr(box, 'get_children'):
|
|
for child in box.get_children():
|
|
box.remove(child)
|
|
else:
|
|
child = box.get_first_child()
|
|
while child:
|
|
next_child = child.get_next_sibling()
|
|
box.remove(child)
|
|
child = next_child
|
|
|
|
def update_device_list(self):
|
|
devices = self.get_discovered_devices()
|
|
for name, address in devices:
|
|
if address not in self.discovered_devices:
|
|
self.discovered_devices[address] = name
|
|
if self.device_window and self.device_list_vbox:
|
|
button = Gtk.Button(label=f"{name} ({address})")
|
|
button.connect("clicked", self.pair_trust_connect, address)
|
|
self.add_to_box(self.device_list_vbox, button)
|
|
return True
|
|
|
|
def stop_scan_and_close(self, _):
|
|
if self.device_window:
|
|
self.device_window.destroy()
|
|
self.device_window = None
|
|
self.discovered_devices.clear()
|
|
|
|
def get_discovered_devices(self):
|
|
devices = []
|
|
managed_objects = self.object_manager.GetManagedObjects()
|
|
for path, interfaces in managed_objects.items():
|
|
if "org.bluez.Device1" in interfaces:
|
|
device_properties = interfaces["org.bluez.Device1"]
|
|
name = device_properties.get("Name", "Desconocido")
|
|
address = device_properties.get("Address")
|
|
devices.append((name, address))
|
|
return devices
|
|
|
|
def pair_trust_connect(self, button, address):
|
|
threading.Thread(target=self.pair_trust_connect_thread,
|
|
args=(address,)).start()
|
|
|
|
def pair_trust_connect_thread(self, address):
|
|
device_path = f"/org/bluez/hci0/dev_{address.replace(':', '_')}"
|
|
device = self.bus.get("org.bluez", device_path)
|
|
try:
|
|
device.Pair()
|
|
device.Trusted = True
|
|
device.Connect()
|
|
self.show_notification(f"Conectado a {address}")
|
|
except Exception as e:
|
|
self.show_notification(f"Error al conectar con {address}: {e}")
|
|
|
|
def turn_on_bluetooth(self, _):
|
|
self.adapter.Powered = True
|
|
self.show_notification("Bluetooth Encendido")
|
|
self.update_bluetooth_buttons()
|
|
|
|
def turn_off_bluetooth(self, _):
|
|
self.adapter.Powered = False
|
|
self.show_notification("Bluetooth Apagado")
|
|
self.update_bluetooth_buttons()
|
|
|
|
def exit_app(self, _):
|
|
if self.scanning:
|
|
self.adapter.StopDiscovery()
|
|
self.scanning = False
|
|
if hasattr(self, 'update_devices_timer'):
|
|
GLib.source_remove(self.update_devices_timer)
|
|
self.quit()
|
|
|
|
def show_notification(self, message):
|
|
subprocess.run(['notify-send', message])
|
|
|
|
|
|
def main():
|
|
app = BluetoothMenu()
|
|
app.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|