2025-05-28 18:27:10 +02:00

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()