#!/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()