class ConnectionNotifier(AppletPlugin): __author__ = "cschramm" __icon__ = "bluetooth-symbolic" __description__ = _( "Shows desktop notifications when devices get connected or disconnected." ) _sig = None _notifications: Dict[str, Union[_NotificationBubble, _NotificationDialog]] = {} def on_load(self) -> None: self._manager = Manager() self._sig = self._manager.connect_signal("battery-created", self._on_battery_created) def on_unload(self) -> None: if self._sig is not None: self._manager.disconnect_signal(self._sig) def on_device_property_changed(self, path: str, key: str, value: Any) -> None: device = Device(obj_path=path) battery = Battery(obj_path=path) if key == "Connected": if value: self._notifications[path] = notification = Notification( device["Alias"], _('Connected'), icon_name=device["Icon"]) notification.show() sig = battery.connect_signal("property-changed", self._on_battery_property_changed) def disconnect_signal() -> bool: battery.disconnect_signal(sig) return False GLib.timeout_add_seconds(5, disconnect_signal) else: Notification(device["Alias"], _('Disconnected'), icon_name=device["Icon"]).show() def _on_battery_created(self, _manager: Manager, obj_path: str) -> None: battery = Battery(obj_path=obj_path) self._on_battery_property_changed(battery, "Percentage", battery["Percentage"], obj_path) def _on_battery_property_changed(self, _battery: Battery, key: str, value: Any, path: str) -> None: if key == "Percentage": notification = self._notifications[path] if notification: notification.set_message(f"{_('Connected')} {value}%") notification.set_notification_icon("battery")
class DeviceList(GenericList): __gsignals__: GSignals = { # @param: device TreeIter # note: None None is given when there ar no more rows, or when selected device is removed 'device-selected': (GObject.SignalFlags.RUN_LAST, None, (Device, Gtk.TreeIter,)), # @param: device, TreeIter, (key, value) 'device-property-changed': (GObject.SignalFlags.RUN_LAST, None, (Device, Gtk.TreeIter, object,)), # @param: adapter, (key, value) 'adapter-property-changed': (GObject.SignalFlags.RUN_LAST, None, (Adapter, object,)), # @param: progress (0 to 1) 'discovery-progress': (GObject.SignalFlags.RUN_LAST, None, (float,)), # @param: new adapter path, None if there are no more adapters 'adapter-changed': (GObject.SignalFlags.RUN_LAST, None, (str,)), # @param: adapter path 'adapter-added': (GObject.SignalFlags.RUN_LAST, None, (str,)), 'adapter-removed': (GObject.SignalFlags.RUN_LAST, None, (str,)), } def __del__(self): logging.debug("deleting mainlist") super().__del__() def __init__(self, adapter_name=None, tabledata=None, **kwargs): if not tabledata: tabledata = [] # cache for fast lookup in the list self.path_to_row: Dict[str, Gtk.TreeRowReference] = {} self.monitored_devices: List[str] = [] self.manager = Manager() self._managerhandlers: List[int] = [] self._managerhandlers.append(self.manager.connect_signal('adapter-removed', self.__on_manager_signal, 'adapter-removed')) self._managerhandlers.append(self.manager.connect_signal('adapter-added', self.__on_manager_signal, 'adapter-added')) self._managerhandlers.append(self.manager.connect_signal('device-created', self.__on_manager_signal, 'device-created')) self._managerhandlers.append(self.manager.connect_signal('device-removed', self.__on_manager_signal, 'device-removed')) self.any_device = AnyDevice() self._anydevhandler = self.any_device.connect_signal("property-changed", self._on_device_property_changed) self.__discovery_time = 0 self.__adapter_path = None self.Adapter = None self.discovering = False data = tabledata + [ {"id": "device", "type": object}, {"id": "dbus_path", "type": str}, {"id": "timestamp", "type": float} ] super().__init__(data, **kwargs) self.set_name("DeviceList") self.set_adapter(adapter_name) self._any_adapter = AnyAdapter() self._anyadapterhandler = self._any_adapter.connect_signal("property-changed", self._on_property_changed) self._selectionhandler = self.selection.connect('changed', self.on_selection_changed) self.icon_theme = Gtk.IconTheme.get_default() self.icon_theme.prepend_search_path(ICON_PATH) # handle icon theme changes self.icon_theme.connect("changed", self.on_icon_theme_changed) def destroy(self): self.any_device.disconnect(self._anydevhandler) self._any_adapter.disconnect(self._anyadapterhandler) self.selection.disconnect(self._selectionhandler) for handler in self._managerhandlers: self.manager.disconnect(handler) super().destroy() def __on_manager_signal(self, manager, path, signal_name): if signal_name == 'adapter-removed': self.emit("adapter-removed", path) if path == self.__adapter_path: self.clear() self.Adapter = None self.set_adapter() if signal_name == 'adapter-added': if self.Adapter is None: self.set_adapter(path) self.emit("adapter-added", path) if signal_name == 'device-created': tree_iter = self.find_device_by_path(path) if tree_iter is None: dev = Device(obj_path=path) self.device_add_event(dev) if signal_name == 'device-removed': tree_iter = self.find_device_by_path(path) if tree_iter: row = self.get(tree_iter, "device") dev = row["device"] self.device_remove_event(dev) def on_selection_changed(self, selection): _model, tree_iter = selection.get_selected() if tree_iter: row = self.get(tree_iter, "device") dev = row["device"] self.emit("device-selected", dev, tree_iter) def _on_property_changed(self, _adapter, key, value, path): if not self.Adapter or self.Adapter.get_object_path() != path: return if key == "Discovering": if not value and self.discovering: self.stop_discovery() self.emit("adapter-property-changed", self.Adapter, (key, value)) def _on_device_property_changed(self, _device, key, value, path): tree_iter = self.find_device_by_path(path) if tree_iter is not None: dev = self.get(tree_iter, "device")["device"] self.row_update_event(tree_iter, key, value) self.emit("device-property-changed", dev, tree_iter, (key, value)) if key == "Connected": if value: self.monitor_power_levels(dev) else: r = Gtk.TreeRowReference.new(self.get_model(), self.props.model.get_path(tree_iter)) self.level_setup_event(r, dev, None) # Override when subclassing def on_icon_theme_changed(self, widget): logging.warning("Icons may not be updated with icon theme changes") def monitor_power_levels(self, device): def update(row_ref, cinfo, address): if not row_ref.valid(): logging.warning("stopping monitor (row does not exist)") cinfo.deinit() self.monitored_devices.remove(address) return False if not self.get_model(): self.monitored_devices.remove(address) return False if not device['Connected']: logging.info("stopping monitor (not connected)") cinfo.deinit() self.level_setup_event(row_ref, device, None) self.monitored_devices.remove(address) return False else: self.level_setup_event(row_ref, device, cinfo) return True bt_address = device["Address"] if device["Connected"] and bt_address not in self.monitored_devices: logging.info("starting monitor") tree_iter = self.find_device(device) assert self.Adapter is not None hci = os.path.basename(self.Adapter.get_object_path()) cinfo = conn_info(bt_address, hci) try: cinfo.init() except ConnInfoReadError: logging.warning("Failed to get power levels, probably a LE device.") r = Gtk.TreeRowReference.new(self.get_model(), self.get_model().get_path(tree_iter)) self.level_setup_event(r, device, cinfo) GLib.timeout_add(1000, update, r, cinfo, bt_address) self.monitored_devices.append(bt_address) # ##### virtual funcs ##### # called when power levels need updating # if cinfo is None then info icons need to be removed def level_setup_event(self, tree_iter, device, cinfo): pass # called when row needs to be initialized def row_setup_event(self, tree_iter, device): pass # called when a property for a device changes def row_update_event(self, tree_iter, key, value): pass # called when device needs to be added to the list def device_add_event(self, device): self.add_device(device) def device_remove_event(self, device): logging.debug(device) tree_iter = self.find_device(device) if self.compare(self.selected(), tree_iter): self.emit("device-selected", None, None) self.delete(tree_iter) ######################### def set_adapter(self, adapter=None): self.clear() if self.discovering: self.stop_discovery() self.emit("adapter-property-changed", self.Adapter, ("Discovering", False)) adapter = adapter_path_to_name(adapter) logging.debug(f"Setting adapter to: {adapter}") if adapter is not None: try: self.Adapter = self.manager.get_adapter(adapter) self.__adapter_path = self.Adapter.get_object_path() except DBusNoSuchAdapterError: logging.warning('Failed to set adapter, trying first available.') self.set_adapter(None) return else: adapters = self.manager.get_adapters() if len(adapters) > 0: self.Adapter = adapters[0] self.__adapter_path = self.Adapter.get_object_path() else: self.Adapter = None self.__adapter_path = None self.emit("adapter-changed", self.__adapter_path) def update_progress(self, time, totaltime): if not self.discovering: return False self.__discovery_time += time progress = self.__discovery_time / totaltime if progress >= 1.0: progress = 1.0 if self.__discovery_time >= totaltime: self.stop_discovery() return False self.emit("discovery-progress", progress) return True def add_device(self, device): # device belongs to another adapter if not self.Adapter or not device['Adapter'] == self.Adapter.get_object_path(): return logging.info("adding new device") tree_iter = self.liststore.append() self.set(tree_iter, device=device) self.row_setup_event(tree_iter, device) object_path = device.get_object_path() timestamp = datetime.strftime(datetime.now(), '%Y%m%d%H%M%S%f') self.set(tree_iter, dbus_path=object_path, timestamp=float(timestamp)) if device["Connected"]: self.monitor_power_levels(device) def display_known_devices(self, autoselect=False): self.clear() if self.Adapter: devices = self.manager.get_devices(self.Adapter.get_object_path()) for device in devices: self.device_add_event(device) if autoselect: self.selection.select_path(0) def discover_devices(self, time=10.24): if not self.discovering: self.__discovery_time = 0 if self.Adapter is not None: self.Adapter.start_discovery() self.discovering = True t = 1.0 / 15 * 1000 GLib.timeout_add(int(t), self.update_progress, t / 1000, time) def is_valid_adapter(self): if self.Adapter is None: return False else: return True def get_adapter_path(self): if self.is_valid_adapter(): return self.__adapter_path def stop_discovery(self): self.discovering = False if self.Adapter is not None: self.Adapter.stop_discovery() def get_selected_device(self): selected = self.selected() if selected is not None: row = self.get(selected, "device") device = row["device"] return device def clear(self): if len(self.liststore): for i in self.liststore: tree_iter = i.iter device = self.get(tree_iter, "device")["device"] self.device_remove_event(device) self.liststore.clear() self.emit("device-selected", None, None) self.path_to_row = {} def find_device(self, device): object_path = device.get_object_path() try: row = self.path_to_row[object_path] if row.valid(): path = row.get_path() tree_iter = self.liststore.get_iter(path) return tree_iter else: del self.path_to_row[object_path] return None except KeyError: return None def find_device_by_path(self, path): try: row = self.path_to_row[path] if row.valid(): path = row.get_path() tree_iter = self.liststore.get_iter(path) return tree_iter else: del self.path_to_row[path] return None except KeyError: return None def do_cache(self, tree_iter, kwargs): object_path = None if "device" in kwargs: if kwargs["device"]: object_path = kwargs['device'].get_object_path() elif "dbus_path" in kwargs: if kwargs["dbus_path"]: object_path = kwargs['dbus_path'] else: existing = self.get(tree_iter, "dbus_path")["dbus_path"] if existing is not None: del self.path_to_row[existing] if object_path: logging.info(f"Caching new device {object_path}") self.path_to_row[object_path] = Gtk.TreeRowReference.new(self.liststore, self.liststore.get_path(tree_iter)) def append(self, **columns): tree_iter = GenericList.append(self, **columns) self.do_cache(tree_iter, columns) def prepend(self, **columns): tree_iter = GenericList.prepend(self, **columns) self.do_cache(tree_iter, columns) def set(self, tree_iter, **kwargs): GenericList.set(self, tree_iter, **kwargs) self.do_cache(tree_iter, kwargs)
class BluemanAdapters(Gtk.Window): def __init__(self, selected_hci_dev, socket_id): super().__init__(title=_("Bluetooth Adapters"), border_width=5, resizable=False, icon_name="blueman-device", name="BluemanAdapters") self.connect("delete-event", self._on_close) self.notebook = Gtk.Notebook(visible=True) if socket_id: plug = Gtk.Plug.new(socket_id) plug.show() plug.connect('delete-event', self._on_close) plug.add(self.notebook) else: self.add(self.notebook) self.connect("delete-event", self._on_close) self.show() self.tabs: Dict[str, "Tab"] = {} self._adapters: Dict[str, Adapter] = {} setup_icon_path() Manager.watch_name_owner(self._on_dbus_name_appeared, self._on_dbus_name_vanished) self.manager = Manager() check_single_instance("blueman-adapters", lambda time: self.present_with_time(time)) check_bluetooth_status( _("Bluetooth needs to be turned on for the adapter manager to work" ), lambda: exit()) adapters = self.manager.get_adapters() if not adapters: logging.error('No adapter(s) found') exit(1) self.manager.connect_signal('adapter-added', self.on_adapter_added) self.manager.connect_signal('adapter-removed', self.on_adapter_removed) for adapter in adapters: path = adapter.get_object_path() hci_dev = os.path.basename(path) self._adapters[hci_dev] = adapter self.on_adapter_added(self.manager, path) # activate a particular tab according to command line option if selected_hci_dev is not None: if selected_hci_dev in self.tabs: hci_dev_num = int(selected_hci_dev[3:]) self.notebook.set_current_page(hci_dev_num) else: logging.error('Error: the selected adapter does not exist') self.notebook.show_all() def _on_close(self, *args, **kwargs): Gtk.main_quit() def on_property_changed(self, adapter, name, value, path): hci_dev = os.path.basename(path) if name == "Discoverable" and value == 0: self.tabs[hci_dev]["hidden_radio"].set_active(True) elif name == "Alias": self.tabs[hci_dev]["label"].set_text(value) def on_adapter_added(self, _manager, adapter_path): hci_dev = os.path.basename(adapter_path) if hci_dev not in self._adapters: self._adapters[hci_dev] = Adapter(obj_path=adapter_path) self._adapters[hci_dev].connect_signal("property-changed", self.on_property_changed) self.add_to_notebook(self._adapters[hci_dev]) def on_adapter_removed(self, _manager, adapter_path): hci_dev = os.path.basename(adapter_path) self.remove_from_notebook(self._adapters[hci_dev]) def _on_dbus_name_appeared(self, _connection, name, owner): logging.info("%s %s" % (name, owner)) def _on_dbus_name_vanished(self, _connection, name): logging.info(name) # FIXME: show error dialog and exit def build_adapter_tab(self, adapter): def on_hidden_toggle(radio): if not radio.props.active: return adapter['DiscoverableTimeout'] = 0 adapter['Discoverable'] = False hscale.set_sensitive(False) def on_always_toggle(radio): if not radio.props.active: return adapter['DiscoverableTimeout'] = 0 adapter['Discoverable'] = True hscale.set_sensitive(False) def on_temporary_toggle(radio): if not radio.props.active: return adapter['Discoverable'] = True hscale.set_sensitive(True) hscale.set_value(3) def on_scale_format_value(scale, value): if value == 0: if adapter['Discoverable']: return _("Always") else: return _("Hidden") else: return gettext.ngettext("%d Minute", "%d Minutes", value) % value def on_scale_value_changed(scale): val = scale.get_value() logging.info('value: %s' % val) if val == 0 and adapter['Discoverable']: always_radio.props.active = True timeout = int(val * 60) adapter['DiscoverableTimeout'] = timeout def on_name_changed(entry): adapter['Alias'] = entry.get_text() ui = {} builder = Gtk.Builder() builder.set_translation_domain("blueman") builder.add_from_file(UI_PATH + "/adapters-tab.ui") hscale = builder.get_object("hscale") hscale.connect("format-value", on_scale_format_value) hscale.connect("value-changed", on_scale_value_changed) hscale.set_range(0, 30) hscale.set_increments(1, 1) hidden_radio = builder.get_object("hidden") always_radio = builder.get_object("always") temporary_radio = builder.get_object("temporary") if adapter['Discoverable'] and adapter['DiscoverableTimeout'] > 0: temporary_radio.set_active(True) hscale.set_value(adapter['DiscoverableTimeout']) hscale.set_sensitive(True) elif adapter['Discoverable'] and adapter['DiscoverableTimeout'] == 0: always_radio.set_active(True) else: hidden_radio.set_active(True) name_entry = builder.get_object("name_entry") name_entry.set_text(adapter.get_name()) hidden_radio.connect("toggled", on_hidden_toggle) always_radio.connect("toggled", on_always_toggle) temporary_radio.connect("toggled", on_temporary_toggle) name_entry.connect("changed", on_name_changed) ui['grid'] = builder.get_object("grid") ui["hidden_radio"] = hidden_radio ui["always_radio"] = always_radio ui["temparary_radio"] = temporary_radio return ui def add_to_notebook(self, adapter): hci_dev = os.path.basename(adapter.get_object_path()) hci_dev_num = int(hci_dev[3:]) if hci_dev not in self.tabs: self.tabs[hci_dev] = self.build_adapter_tab(adapter) else: if self.tabs[hci_dev]['visible']: return # might need to update settings at this point ui = self.tabs[hci_dev] ui['visible'] = True name = adapter.get_name() if name == '': name = _('Adapter') + ' %d' % (hci_dev_num + 1) label = Gtk.Label(label=name) ui['label'] = label label.set_max_width_chars(20) label.props.hexpand = True label.set_ellipsize(Pango.EllipsizeMode.END) self.notebook.insert_page(ui['grid'], label, hci_dev_num) def remove_from_notebook(self, adapter): hci_dev = os.path.basename(adapter.get_object_path()) hci_dev_num = int(hci_dev[3:]) self.tabs[hci_dev]['visible'] = False self.notebook.remove_page(hci_dev_num)
class DeviceList(GenericList): __gsignals__: GSignals = { # @param: device TreeIter # note: None None is given when there ar no more rows, or when selected device is removed 'device-selected': (GObject.SignalFlags.RUN_LAST, None, ( Device, Gtk.TreeIter, )), # @param: device, TreeIter, (key, value) 'device-property-changed': (GObject.SignalFlags.RUN_LAST, None, ( Device, Gtk.TreeIter, object, )), # @param: adapter, (key, value) 'adapter-property-changed': (GObject.SignalFlags.RUN_LAST, None, ( Adapter, object, )), # @param: progress (0 to 1) 'discovery-progress': (GObject.SignalFlags.RUN_LAST, None, (float, )), # @param: new adapter path, None if there are no more adapters 'adapter-changed': (GObject.SignalFlags.RUN_LAST, None, (str, )), # @param: adapter path 'adapter-added': (GObject.SignalFlags.RUN_LAST, None, (str, )), 'adapter-removed': (GObject.SignalFlags.RUN_LAST, None, (str, )), } def __init__(self, adapter_name: Optional[str] = None, tabledata: Optional[List[ListDataDict]] = None, headers_visible: bool = True) -> None: if not tabledata: tabledata = [] # cache for fast lookup in the list self.path_to_row: Dict[str, Gtk.TreeRowReference] = {} self.manager = Manager() self._managerhandlers: List[int] = [] self._managerhandlers.append( self.manager.connect_signal('adapter-removed', self.__on_manager_signal, 'adapter-removed')) self._managerhandlers.append( self.manager.connect_signal('adapter-added', self.__on_manager_signal, 'adapter-added')) self._managerhandlers.append( self.manager.connect_signal('device-created', self.__on_manager_signal, 'device-created')) self._managerhandlers.append( self.manager.connect_signal('device-removed', self.__on_manager_signal, 'device-removed')) self.any_device = AnyDevice() self._anydevhandler = self.any_device.connect_signal( "property-changed", self._on_device_property_changed) self.__discovery_time: float = 0 self.__adapter_path: Optional[str] = None self.Adapter: Optional[Adapter] = None self.discovering = False data = tabledata + [{ "id": "device", "type": object }, { "id": "dbus_path", "type": str }, { "id": "timestamp", "type": float }] super().__init__(data, headers_visible=headers_visible) self.set_name("DeviceList") self.set_adapter(adapter_name) self._any_adapter = AnyAdapter() self._anyadapterhandler = self._any_adapter.connect_signal( "property-changed", self._on_property_changed) self._selectionhandler = self.selection.connect( 'changed', self.on_selection_changed) self.icon_theme = Gtk.IconTheme.get_default() self.icon_theme.prepend_search_path(ICON_PATH) # handle icon theme changes self.icon_theme.connect("changed", self.on_icon_theme_changed) def destroy(self) -> None: self.any_device.disconnect(self._anydevhandler) self._any_adapter.disconnect(self._anyadapterhandler) self.selection.disconnect(self._selectionhandler) for handler in self._managerhandlers: self.manager.disconnect(handler) super().destroy() def __on_manager_signal(self, _manager: Manager, path: str, signal_name: str) -> None: if signal_name == 'adapter-removed': self.emit("adapter-removed", path) if path == self.__adapter_path: self.clear() self.Adapter = None self.set_adapter() if signal_name == 'adapter-added': if self.Adapter is None: self.set_adapter(path) self.emit("adapter-added", path) if signal_name == 'device-created': tree_iter = self.find_device_by_path(path) if tree_iter is None: dev = Device(obj_path=path) self.device_add_event(dev) if signal_name == 'device-removed': tree_iter = self.find_device_by_path(path) if tree_iter: row = self.get(tree_iter, "device") dev = row["device"] self.device_remove_event(dev) def on_selection_changed(self, selection: Gtk.TreeSelection) -> None: _model, tree_iter = selection.get_selected() if tree_iter: row = self.get(tree_iter, "device") dev = row["device"] self.emit("device-selected", dev, tree_iter) def _on_property_changed(self, _adapter: AnyAdapter, key: str, value: object, path: str) -> None: if not self.Adapter or self.Adapter.get_object_path() != path: return if key == "Discovering" and not value: self.discovering = False self.emit("adapter-property-changed", self.Adapter, (key, value)) def _on_device_property_changed(self, _device: AnyDevice, key: str, value: object, path: str) -> None: tree_iter = self.find_device_by_path(path) if tree_iter is not None: dev = self.get(tree_iter, "device")["device"] self.row_update_event(tree_iter, key, value) self.emit("device-property-changed", dev, tree_iter, (key, value)) # Override when subclassing def on_icon_theme_changed(self, _icon_them: Gtk.IconTheme) -> None: logging.warning("Icons may not be updated with icon theme changes") # ##### virtual funcs ##### # called when row needs to be initialized def row_setup_event(self, tree_iter: Gtk.TreeIter, device: Device) -> None: pass # called when a property for a device changes def row_update_event(self, tree_iter: Gtk.TreeIter, key: str, value: Any) -> None: pass # called when device needs to be added to the list def device_add_event(self, device: Device) -> None: self.add_device(device) def device_remove_event(self, device: Device) -> None: logging.debug(device) tree_iter = self.find_device(device) assert tree_iter is not None if self.compare(self.selected(), tree_iter): self.emit("device-selected", None, None) self.delete(tree_iter) ######################### def set_adapter(self, adapter: Optional[str] = None) -> None: self.clear() if self.discovering: self.stop_discovery() self.emit("adapter-property-changed", self.Adapter, ("Discovering", False)) adapter = adapter_path_to_name(adapter) logging.debug(f"Setting adapter to: {adapter}") if adapter is not None: try: self.Adapter = self.manager.get_adapter(adapter) self.__adapter_path = self.Adapter.get_object_path() except DBusNoSuchAdapterError: logging.warning( 'Failed to set adapter, trying first available.') self.set_adapter(None) return else: adapters = self.manager.get_adapters() if len(adapters) > 0: self.Adapter = adapters[0] self.__adapter_path = self.Adapter.get_object_path() else: self.Adapter = None self.__adapter_path = None self.emit("adapter-changed", self.__adapter_path) def update_progress(self, time: float, totaltime: float) -> bool: if not self.discovering: return False self.__discovery_time += time progress = self.__discovery_time / totaltime if progress >= 1.0: progress = 1.0 if self.__discovery_time >= totaltime: self.stop_discovery() return False self.emit("discovery-progress", progress) return True def add_device(self, device: Device) -> None: # device belongs to another adapter if not self.Adapter or not device[ 'Adapter'] == self.Adapter.get_object_path(): return logging.info("adding new device") tree_iter = self.liststore.append() self.set(tree_iter, device=device) self.row_setup_event(tree_iter, device) object_path = device.get_object_path() timestamp = datetime.strftime(datetime.now(), '%Y%m%d%H%M%S%f') self.set(tree_iter, dbus_path=object_path, timestamp=float(timestamp)) def display_known_devices(self, autoselect: bool = False) -> None: self.clear() if self.Adapter: devices = self.manager.get_devices(self.Adapter.get_object_path()) for device in devices: self.device_add_event(device) if autoselect: self.selection.select_path(0) def discover_devices( self, time: float = 10.24, error_handler: Optional[Callable[[BluezDBusException], None]] = None ) -> None: if not self.discovering: self.__discovery_time = 0 if self.Adapter is not None: self.Adapter.start_discovery(error_handler=error_handler) self.discovering = True t = 1.0 / 15 * 1000 GLib.timeout_add(int(t), self.update_progress, t / 1000, time) def is_valid_adapter(self) -> bool: if self.Adapter is None: return False else: return True def get_adapter_path(self) -> Optional[str]: return self.__adapter_path if self.is_valid_adapter() else None def stop_discovery(self) -> None: self.discovering = False if self.Adapter is not None: self.Adapter.stop_discovery() def get_selected_device(self) -> Optional[Device]: selected = self.selected() if selected is not None: row = self.get(selected, "device") device: Device = row["device"] return device return None def clear(self) -> None: if len(self.liststore): for i in self.liststore: tree_iter = i.iter device = self.get(tree_iter, "device")["device"] self.device_remove_event(device) self.liststore.clear() self.emit("device-selected", None, None) self.path_to_row = {} def find_device(self, device: Device) -> Optional[Gtk.TreeIter]: object_path = device.get_object_path() try: row = self.path_to_row[object_path] if row.valid(): path = row.get_path() assert path is not None tree_iter = self.liststore.get_iter(path) return tree_iter else: del self.path_to_row[object_path] return None except KeyError: return None def find_device_by_path(self, path: str) -> Optional[Gtk.TreeIter]: try: row = self.path_to_row[path] if row.valid(): path_ = row.get_path() assert path_ is not None tree_iter = self.liststore.get_iter(path_) return tree_iter else: del self.path_to_row[path] return None except KeyError: return None def do_cache(self, tree_iter: Gtk.TreeIter, kwargs: Dict[str, Any]) -> None: object_path = None if "device" in kwargs: if kwargs["device"]: object_path = kwargs['device'].get_object_path() elif "dbus_path" in kwargs: if kwargs["dbus_path"]: object_path = kwargs['dbus_path'] else: existing = self.get(tree_iter, "dbus_path")["dbus_path"] if existing is not None: del self.path_to_row[existing] if object_path: logging.info(f"Caching new device {object_path}") self.path_to_row[object_path] = Gtk.TreeRowReference.new( self.liststore, self.liststore.get_path(tree_iter)) def append(self, **columns: object) -> Gtk.TreeIter: tree_iter = GenericList.append(self, **columns) self.do_cache(tree_iter, columns) return tree_iter def prepend(self, **columns: object) -> Gtk.TreeIter: tree_iter = GenericList.prepend(self, **columns) self.do_cache(tree_iter, columns) return tree_iter # FIXME: GenericList.set accepts int and str as iterid, DeviceList does not def set(self, iterid: Gtk.TreeIter, **kwargs: object) -> None: # type: ignore GenericList.set(self, iterid, **kwargs) self.do_cache(iterid, kwargs)
class BluemanApplet(Gio.Application): def __init__(self): super().__init__(application_id="org.blueman.Applet", flags=Gio.ApplicationFlags.FLAGS_NONE) setup_icon_path() self.plugin_run_state_changed = False self.manager_state = False self._active = False self.Manager = Manager() self.Manager.connect_signal('adapter-added', self.on_adapter_added) self.Manager.connect_signal('adapter-removed', self.on_adapter_removed) self.Manager.connect_signal('device-created', self.on_device_created) self.Manager.connect_signal('device-removed', self.on_device_removed) self.DbusSvc = DbusService("org.blueman.Applet", "org.blueman.Applet", "/org/blueman/Applet", Gio.BusType.SESSION) self.DbusSvc.register() self.Plugins = PersistentPluginManager(AppletPlugin, blueman.plugins.applet, self) self.Plugins.load_plugin() self.Plugins.run("on_plugins_loaded") self.Manager.watch_name_owner(self._on_dbus_name_appeared, self._on_dbus_name_vanished) self._any_adapter = AnyAdapter() self._any_adapter.connect_signal('property-changed', self._on_adapter_property_changed) self._any_device = AnyDevice() self._any_device.connect_signal('property-changed', self._on_device_property_changed) def do_activate(self): if not self._active: self.hold() self._active = True def _on_dbus_name_appeared(self, _connection, name, owner): logging.info(f"{name} {owner}") self.manager_state = True self.plugin_run_state_changed = True self.Plugins.run("on_manager_state_changed", self.manager_state) def _on_dbus_name_vanished(self, _connection, name): logging.info(name) self.manager_state = False self.plugin_run_state_changed = True self.Plugins.run("on_manager_state_changed", self.manager_state) def _on_adapter_property_changed(self, _adapter, key, value, path): self.Plugins.run("on_adapter_property_changed", path, key, value) def _on_device_property_changed(self, _device, key, value, path): self.Plugins.run("on_device_property_changed", path, key, value) def on_adapter_added(self, _manager, path): logging.info(f"Adapter added {path}") self.Plugins.run("on_adapter_added", path) def on_adapter_removed(self, _manager, path): logging.info(f"Adapter removed {path}") self.Plugins.run("on_adapter_removed", path) def on_device_created(self, _manager, path): logging.info(f"Device created {path}") self.Plugins.run("on_device_created", path) def on_device_removed(self, _manager, path): logging.info(f"Device removed {path}") self.Plugins.run("on_device_removed", path)
class BluemanAdapters(Gtk.Application): def __init__(self, selected_hci_dev: Optional[str], socket_id: Optional[int]) -> None: super().__init__(application_id="org.blueman.Adapters") def do_quit(_: object) -> bool: self.quit() return False s = GLib.unix_signal_source_new(signal.SIGINT) s.set_callback(do_quit) s.attach() self.socket_id = socket_id self.selected_hci_dev = selected_hci_dev self.notebook = Gtk.Notebook(visible=True) self.window: Optional[Gtk.ApplicationWindow] = None self.tabs: Dict[str, "Tab"] = {} self._adapters: Dict[str, Adapter] = {} setup_icon_path() Manager.watch_name_owner(self._on_dbus_name_appeared, self._on_dbus_name_vanished) self.manager = Manager() check_bluetooth_status( _("Bluetooth needs to be turned on for the adapter manager to work" ), bmexit) adapters = self.manager.get_adapters() if not adapters: logging.error('No adapter(s) found') bmexit() self.manager.connect_signal('adapter-added', self.on_adapter_added) self.manager.connect_signal('adapter-removed', self.on_adapter_removed) for adapter in adapters: path = adapter.get_object_path() self.on_adapter_added(self.manager, path) # activate a particular tab according to command line option if selected_hci_dev is not None: if selected_hci_dev in self.tabs: hci_dev_num = int(selected_hci_dev[3:]) self.notebook.set_current_page(hci_dev_num) else: logging.error('Error: the selected adapter does not exist') self.notebook.show_all() def do_activate(self) -> None: def app_release(_plug: Gtk.Plug, _event: Gdk.Event) -> bool: self.release() return False if self.socket_id: self.hold() plug = Gtk.Plug.new(self.socket_id) plug.show() plug.connect('delete-event', app_release) plug.add(self.notebook) return if not self.window: self.window = Gtk.ApplicationWindow(application=self, title=_("Bluetooth Adapters"), border_width=10, resizable=False, icon_name="blueman-device", name="BluemanAdapters") self.window.add(self.notebook) self.window.set_position(Gtk.WindowPosition.CENTER) self.window.present_with_time(Gtk.get_current_event_time()) def on_property_changed(self, _adapter: Adapter, name: str, value: Any, path: str) -> None: hci_dev = os.path.basename(path) if name == "Discoverable" and value == 0: self.tabs[hci_dev]["hidden_radio"].set_active(True) elif name == "Alias": self.tabs[hci_dev]["label"].set_text(value) def on_adapter_added(self, _manager: Manager, adapter_path: str) -> None: hci_dev = os.path.basename(adapter_path) if hci_dev not in self._adapters: self._adapters[hci_dev] = Adapter(obj_path=adapter_path) self._adapters[hci_dev].connect_signal("property-changed", self.on_property_changed) self.add_to_notebook(self._adapters[hci_dev]) def on_adapter_removed(self, _manager: Manager, adapter_path: str) -> None: hci_dev = os.path.basename(adapter_path) self.remove_from_notebook(self._adapters[hci_dev]) def _on_dbus_name_appeared(self, _connection: Gio.DBusConnection, name: str, owner: str) -> None: logging.info(f"{name} {owner}") def _on_dbus_name_vanished(self, _connection: Gio.DBusConnection, name: str) -> None: logging.info(name) # FIXME: show error dialog and exit def build_adapter_tab(self, adapter: Adapter) -> "Tab": def on_hidden_toggle(radio: Gtk.RadioButton) -> None: if not radio.props.active: return adapter['DiscoverableTimeout'] = 0 adapter['Discoverable'] = False hscale.set_sensitive(False) def on_always_toggle(radio: Gtk.RadioButton) -> None: if not radio.props.active: return adapter['DiscoverableTimeout'] = 0 adapter['Discoverable'] = True hscale.set_sensitive(False) def on_temporary_toggle(radio: Gtk.RadioButton) -> None: if not radio.props.active: return adapter['Discoverable'] = True hscale.set_sensitive(True) hscale.set_value(3) def on_scale_format_value(_scale: Gtk.Scale, value: float) -> str: if value == 0: if adapter['Discoverable']: return _("Always") else: return _("Hidden") else: return gettext.ngettext("%(minutes)d Minute", "%(minutes)d Minutes", int(value)) % { "minutes": value } def on_scale_value_changed(scale: Gtk.Scale) -> None: val = scale.get_value() logging.info(f"value: {val}") if val == 0 and adapter['Discoverable']: always_radio.props.active = True timeout = int(val * 60) adapter['DiscoverableTimeout'] = timeout def on_name_changed(entry: Gtk.Entry) -> None: adapter['Alias'] = entry.get_text() builder = Builder("adapters-tab.ui") hscale = builder.get_widget("hscale", Gtk.Scale) hscale.connect("format-value", on_scale_format_value) hscale.connect("value-changed", on_scale_value_changed) hscale.set_range(0, 30) hscale.set_increments(1, 1) hidden_radio = builder.get_widget("hidden", Gtk.RadioButton) always_radio = builder.get_widget("always", Gtk.RadioButton) temporary_radio = builder.get_widget("temporary", Gtk.RadioButton) if adapter['Discoverable'] and adapter['DiscoverableTimeout'] > 0: temporary_radio.set_active(True) hscale.set_value(adapter['DiscoverableTimeout']) hscale.set_sensitive(True) elif adapter['Discoverable'] and adapter['DiscoverableTimeout'] == 0: always_radio.set_active(True) else: hidden_radio.set_active(True) name_entry = builder.get_widget("name_entry", Gtk.Entry) name_entry.set_text(adapter.get_name()) hidden_radio.connect("toggled", on_hidden_toggle) always_radio.connect("toggled", on_always_toggle) temporary_radio.connect("toggled", on_temporary_toggle) name_entry.connect("changed", on_name_changed) return { "grid": builder.get_widget("grid", Gtk.Grid), "hidden_radio": hidden_radio, "always_radio": always_radio, "temparary_radio": temporary_radio, "visible": False, "label": Gtk.Label() } def add_to_notebook(self, adapter: Adapter) -> None: hci_dev = os.path.basename(adapter.get_object_path()) hci_dev_num = int(hci_dev[3:]) if hci_dev not in self.tabs: self.tabs[hci_dev] = self.build_adapter_tab(adapter) else: if self.tabs[hci_dev]['visible']: return # might need to update settings at this point ui = self.tabs[hci_dev] ui['visible'] = True name = adapter.get_name() if name == '': name = _('Adapter') + ' %d' % (hci_dev_num + 1) label = Gtk.Label(label=name) ui['label'] = label label.set_max_width_chars(20) label.props.hexpand = True label.set_ellipsize(Pango.EllipsizeMode.END) self.notebook.insert_page(ui['grid'], label, hci_dev_num) def remove_from_notebook(self, adapter: Adapter) -> None: hci_dev = os.path.basename(adapter.get_object_path()) hci_dev_num = int(hci_dev[3:]) self.tabs[hci_dev]['visible'] = False self.notebook.remove_page(hci_dev_num)
class ManagerMenu: def __init__(self, blueman): self.blueman = blueman self.Config = Config("org.blueman.general") self.adapter_items: Dict[str, Tuple[Gtk.RadioMenuItem, Adapter]] = {} self._adapters_group: List[Gtk.RadioMenuItem] = [] self._insert_adapter_item_pos = 2 self.Search = None self.item_adapter = self.blueman.Builder.get_object("item_adapter") self.item_device = self.blueman.Builder.get_object("item_device") self.item_view = self.blueman.Builder.get_object("item_view") self.item_help = self.blueman.Builder.get_object("item_help") help_menu = Gtk.Menu() self.item_help.set_submenu(help_menu) help_menu.show() report_item = create_menuitem(_("_Report a Problem"), "dialog-warning") report_item.show() help_menu.append(report_item) report_item.connect("activate", lambda x: launch(f"xdg-open {WEBSITE}/issues")) sep = Gtk.SeparatorMenuItem() sep.show() help_menu.append(sep) help_item = create_menuitem(_("_Help"), "help-about") help_item.show() help_menu.append(help_item) help_item.connect( "activate", lambda x: show_about_dialog('Blueman ' + _('Device Manager'), parent=self.blueman.get_toplevel())) view_menu = Gtk.Menu() self.item_view.set_submenu(view_menu) view_menu.show() item_toolbar = Gtk.CheckMenuItem.new_with_mnemonic(_("Show _Toolbar")) item_toolbar.show() view_menu.append(item_toolbar) self.blueman.Config.bind_to_widget("show-toolbar", item_toolbar, "active") item_statusbar = Gtk.CheckMenuItem.new_with_mnemonic( _("Show _Statusbar")) item_statusbar.show() view_menu.append(item_statusbar) self.blueman.Config.bind_to_widget("show-statusbar", item_statusbar, "active") item_services = Gtk.SeparatorMenuItem() view_menu.append(item_services) item_services.show() sorting_group: List[Gtk.RadioMenuItem] = [] item_sort = Gtk.MenuItem.new_with_mnemonic(_("S_ort By")) view_menu.append(item_sort) item_sort.show() sorting_menu = Gtk.Menu() item_sort.set_submenu(sorting_menu) self._sort_alias_item = Gtk.RadioMenuItem.new_with_mnemonic( sorting_group, _("_Name")) self._sort_alias_item.show() sorting_group = self._sort_alias_item.get_group() sorting_menu.append(self._sort_alias_item) self._sort_timestamp_item = Gtk.RadioMenuItem.new_with_mnemonic( sorting_group, _("_Added")) self._sort_timestamp_item.show() sorting_menu.append(self._sort_timestamp_item) sort_config = self.Config['sort-by'] if sort_config == "alias": self._sort_alias_item.props.active = True else: self._sort_timestamp_item.props.active = True sort_sep = Gtk.SeparatorMenuItem() sort_sep.show() sorting_menu.append(sort_sep) self._sort_type_item = Gtk.CheckMenuItem.new_with_mnemonic( _("_Descending")) self._sort_type_item.show() sorting_menu.append(self._sort_type_item) if self.Config['sort-order'] == "ascending": self._sort_type_item.props.active = False else: self._sort_type_item.props.active = True sep = Gtk.SeparatorMenuItem() sep.show() view_menu.append(sep) item_plugins = create_menuitem(_("_Plugins"), 'blueman-plugin') item_plugins.show() view_menu.append(item_plugins) item_plugins.connect('activate', self._on_plugin_dialog_activate) item_services = create_menuitem( _("_Local Services") + "…", "preferences-desktop") item_services.connect( 'activate', lambda *args: launch("blueman-services", name=_("Service Preferences"))) view_menu.append(item_services) item_services.show() adapter_menu = Gtk.Menu() self.item_adapter.set_submenu(adapter_menu) self.item_adapter.props.sensitive = False search_item = create_menuitem(_("_Search"), "edit-find") search_item.connect("activate", lambda x: self.blueman.inquiry()) search_item.show() adapter_menu.prepend(search_item) self.Search = search_item sep = Gtk.SeparatorMenuItem() sep.show() adapter_menu.append(sep) sep = Gtk.SeparatorMenuItem() sep.show() adapter_menu.append(sep) adapter_settings = create_menuitem(_("_Preferences"), "preferences-system") adapter_settings.connect("activate", lambda x: self.blueman.adapter_properties()) adapter_settings.show() adapter_menu.append(adapter_settings) sep = Gtk.SeparatorMenuItem() sep.show() adapter_menu.append(sep) exit_item = create_menuitem(_("_Exit"), "application-exit") exit_item.connect("activate", lambda x: Gtk.main_quit()) exit_item.show() adapter_menu.append(exit_item) self.item_adapter.show() self.item_view.show() self.item_help.show() self.item_device.show() self.item_device.props.sensitive = False self._manager = Manager() self._manager.connect_signal("adapter-added", self.on_adapter_added) self._manager.connect_signal("adapter-removed", self.on_adapter_removed) blueman.List.connect("device-selected", self.on_device_selected) for adapter in self._manager.get_adapters(): self.on_adapter_added(None, adapter.get_object_path()) self.device_menu = None self.Config.connect("changed", self._on_settings_changed) self._sort_alias_item.connect("activate", self._on_sorting_changed, "alias") self._sort_timestamp_item.connect("activate", self._on_sorting_changed, "timestamp") self._sort_type_item.connect("activate", self._on_sorting_changed, "sort-type") def _on_sorting_changed(self, btn, sort_opt): if sort_opt == 'alias' and btn.props.active: self.Config['sort-by'] = "alias" elif sort_opt == "timestamp" and btn.props.active: self.Config['sort-by'] = "timestamp" elif sort_opt == 'sort-type': # FIXME bind widget to gsetting if btn.props.active: self.Config["sort-order"] = "descending" else: self.Config["sort-order"] = "ascending" def _on_settings_changed(self, settings, key): value = settings[key] if key == 'sort-by': if value == "alias": if not self._sort_alias_item.props.active: self._sort_alias_item.props.active = True elif value == "timestamp": if not self._sort_timestamp_item.props.active: self._sort_timestamp_item.props.active = True elif key == "sort-type": if value == "ascending": if not self._sort_type_item.props.active: self._sort_type_item.props.active = True else: if not self._sort_type_item.props.active: self._sort_type_item.props.active = False def on_device_selected(self, lst, device, tree_iter): if tree_iter and device: self.item_device.props.sensitive = True if self.device_menu is None: self.device_menu = ManagerDeviceMenu(self.blueman) self.item_device.set_submenu(self.device_menu) else: GLib.idle_add(self.device_menu.generate, priority=GLib.PRIORITY_LOW) else: self.item_device.props.sensitive = False def on_adapter_property_changed(self, _adapter, name, value, path): if name == "Name" or name == "Alias": item = self.adapter_items[path][0] item.set_label(value) elif name == "Discovering": if self.Search: if value: self.Search.props.sensitive = False else: self.Search.props.sensitive = True def on_adapter_selected(self, menuitem, adapter_path): if menuitem.props.active: if adapter_path != self.blueman.List.Adapter.get_object_path(): logging.info(f"selected {adapter_path}") self.blueman.Config["last-adapter"] = adapter_path_to_name( adapter_path) self.blueman.List.set_adapter(adapter_path) def on_adapter_added(self, _manager, adapter_path): adapter = Adapter(obj_path=adapter_path) menu = self.item_adapter.get_submenu() object_path = adapter.get_object_path() item = Gtk.RadioMenuItem.new_with_label(self._adapters_group, adapter.get_name()) item.show() self._adapters_group = item.get_group() self._itemhandler = item.connect("activate", self.on_adapter_selected, object_path) self._adapterhandler = adapter.connect_signal( "property-changed", self.on_adapter_property_changed) menu.insert(item, self._insert_adapter_item_pos) self._insert_adapter_item_pos += 1 self.adapter_items[object_path] = (item, adapter) if adapter_path == self.blueman.List.Adapter.get_object_path(): item.props.active = True if len(self.adapter_items) > 0: self.item_adapter.props.sensitive = True def on_adapter_removed(self, _manager, adapter_path): item, adapter = self.adapter_items.pop(adapter_path) menu = self.item_adapter.get_submenu() item.disconnect(self._itemhandler) adapter.disconnect(self._adapterhandler) menu.remove(item) self._insert_adapter_item_pos -= 1 if len(self.adapter_items) == 0: self.item_adapter.props.sensitive = False def _on_plugin_dialog_activate(self, *args): def cb(*args): pass self.blueman.Applet.OpenPluginDialog(result_handler=cb)
class ManagerMenu: def __init__(self, blueman: "Blueman"): self.blueman = blueman self.Config = Config("org.blueman.general") self.adapter_items: Dict[str, Tuple[Gtk.RadioMenuItem, Adapter]] = {} self._adapters_group: Sequence[Gtk.RadioMenuItem] = [] self._insert_adapter_item_pos = 2 self.Search = None self.item_adapter = self.blueman.builder.get_widget("item_adapter", Gtk.MenuItem) self.item_device = self.blueman.builder.get_widget("item_device", Gtk.MenuItem) self.item_view = self.blueman.builder.get_widget("item_view", Gtk.MenuItem) self.item_help = self.blueman.builder.get_widget("item_help", Gtk.MenuItem) help_menu = Gtk.Menu() self.item_help.set_submenu(help_menu) help_menu.show() report_item = create_menuitem(_("_Report a Problem"), "dialog-warning-symbolic") report_item.show() help_menu.append(report_item) report_item.connect("activate", lambda x: launch(f"xdg-open {WEBSITE}/issues")) sep = Gtk.SeparatorMenuItem() sep.show() help_menu.append(sep) help_item = create_menuitem(_("_Help"), "help-about-symbolic") help_item.show() help_menu.append(help_item) assert self.blueman.window is not None widget = self.blueman.window.get_toplevel() assert isinstance(widget, Gtk.Window) window = widget help_item.connect("activate", lambda x: show_about_dialog('Blueman ' + _('Device Manager'), parent=window)) view_menu = Gtk.Menu() self.item_view.set_submenu(view_menu) view_menu.show() item_toolbar = Gtk.CheckMenuItem.new_with_mnemonic(_("Show _Toolbar")) item_toolbar.show() view_menu.append(item_toolbar) self.blueman.Config.bind_to_widget("show-toolbar", item_toolbar, "active") item_statusbar = Gtk.CheckMenuItem.new_with_mnemonic(_("Show _Statusbar")) item_statusbar.show() view_menu.append(item_statusbar) self.blueman.Config.bind_to_widget("show-statusbar", item_statusbar, "active") item_unnamed = Gtk.CheckMenuItem.new_with_mnemonic(_("Hide _unnamed devices")) item_unnamed.show() view_menu.append(item_unnamed) self.blueman.Config.bind_to_widget("hide-unnamed", item_unnamed, "active") item_services: Gtk.MenuItem = Gtk.SeparatorMenuItem() view_menu.append(item_services) item_services.show() sorting_group: Sequence[Gtk.RadioMenuItem] = [] item_sort = Gtk.MenuItem.new_with_mnemonic(_("S_ort By")) view_menu.append(item_sort) item_sort.show() sorting_menu = Gtk.Menu() item_sort.set_submenu(sorting_menu) self._sort_alias_item = Gtk.RadioMenuItem.new_with_mnemonic(sorting_group, _("_Name")) self._sort_alias_item.show() sorting_group = self._sort_alias_item.get_group() sorting_menu.append(self._sort_alias_item) self._sort_timestamp_item = Gtk.RadioMenuItem.new_with_mnemonic(sorting_group, _("_Added")) self._sort_timestamp_item.show() sorting_menu.append(self._sort_timestamp_item) sort_config = self.Config['sort-by'] if sort_config == "alias": self._sort_alias_item.props.active = True else: self._sort_timestamp_item.props.active = True sort_sep = Gtk.SeparatorMenuItem() sort_sep.show() sorting_menu.append(sort_sep) self._sort_type_item = Gtk.CheckMenuItem.new_with_mnemonic(_("_Descending")) self._sort_type_item.show() sorting_menu.append(self._sort_type_item) if self.Config['sort-order'] == "ascending": self._sort_type_item.props.active = False else: self._sort_type_item.props.active = True sep = Gtk.SeparatorMenuItem() sep.show() view_menu.append(sep) item_plugins = create_menuitem(_("_Plugins"), 'application-x-addon-symbolic') item_plugins.show() view_menu.append(item_plugins) item_plugins.connect('activate', self._on_plugin_dialog_activate) item_services = create_menuitem(_("_Local Services") + "…", "document-properties-symbolic") item_services.connect('activate', lambda *args: launch("blueman-services", name=_("Service Preferences"))) view_menu.append(item_services) item_services.show() adapter_menu = Gtk.Menu() self.item_adapter.set_submenu(adapter_menu) self.item_adapter.props.sensitive = False search_item = create_menuitem(_("_Search"), "edit-find-symbolic") search_item.connect("activate", lambda x: self.blueman.inquiry()) search_item.show() adapter_menu.prepend(search_item) self.Search = search_item sep = Gtk.SeparatorMenuItem() sep.show() adapter_menu.append(sep) sep = Gtk.SeparatorMenuItem() sep.show() adapter_menu.append(sep) adapter_settings = create_menuitem(_("_Preferences"), "document-properties-symbolic") adapter_settings.connect("activate", lambda x: self.blueman.adapter_properties()) adapter_settings.show() adapter_menu.append(adapter_settings) sep = Gtk.SeparatorMenuItem() sep.show() adapter_menu.append(sep) exit_item = create_menuitem(_("_Exit"), "application-exit-symbolic") exit_item.connect("activate", lambda x: self.blueman.quit()) exit_item.show() adapter_menu.append(exit_item) self.item_adapter.show() self.item_view.show() self.item_help.show() self.item_device.show() self.item_device.props.sensitive = False self._manager = Manager() self._manager.connect_signal("adapter-added", self.on_adapter_added) self._manager.connect_signal("adapter-removed", self.on_adapter_removed) blueman.List.connect("device-selected", self.on_device_selected) for adapter in self._manager.get_adapters(): self.on_adapter_added(None, adapter.get_object_path()) self.device_menu: Optional[ManagerDeviceMenu] = None self.Config.connect("changed", self._on_settings_changed) self._sort_alias_item.connect("activate", self._on_sorting_changed, "alias") self._sort_timestamp_item.connect("activate", self._on_sorting_changed, "timestamp") self._sort_type_item.connect("activate", self._on_sorting_changed, "sort-type") def _on_sorting_changed(self, btn: Gtk.CheckMenuItem, sort_opt: str) -> None: if sort_opt == 'alias' and btn.props.active: self.Config['sort-by'] = "alias" elif sort_opt == "timestamp" and btn.props.active: self.Config['sort-by'] = "timestamp" elif sort_opt == 'sort-type': # FIXME bind widget to gsetting if btn.props.active: self.Config["sort-order"] = "descending" else: self.Config["sort-order"] = "ascending" def _on_settings_changed(self, settings: Config, key: str) -> None: value = settings[key] if key == 'sort-by': if value == "alias": if not self._sort_alias_item.props.active: self._sort_alias_item.props.active = True elif value == "timestamp": if not self._sort_timestamp_item.props.active: self._sort_timestamp_item.props.active = True elif key == "sort-type": if value == "ascending": if not self._sort_type_item.props.active: self._sort_type_item.props.active = True else: if not self._sort_type_item.props.active: self._sort_type_item.props.active = False elif key == "hide-unnamed": logging.debug("refilter") self.blueman.List.filter.refilter() def on_device_selected(self, _lst: ManagerDeviceList, device: Device, tree_iter: Gtk.TreeIter) -> None: if tree_iter and device: self.item_device.props.sensitive = True if self.device_menu is None: self.device_menu = ManagerDeviceMenu(self.blueman) self.item_device.set_submenu(self.device_menu) else: def idle() -> bool: assert self.device_menu is not None # https://github.com/python/mypy/issues/2608 self.device_menu.generate() return False GLib.idle_add(idle, priority=GLib.PRIORITY_LOW) else: self.item_device.props.sensitive = False def on_adapter_property_changed(self, _adapter: Adapter, name: str, value: Any, path: str) -> None: if name == "Name" or name == "Alias": item = self.adapter_items[path][0] item.set_label(value) elif name == "Discovering": if self.Search: if value: self.Search.props.sensitive = False else: self.Search.props.sensitive = True def on_adapter_selected(self, menuitem: Gtk.CheckMenuItem, adapter_path: str) -> None: if menuitem.props.active: assert self.blueman.List.Adapter is not None if adapter_path != self.blueman.List.Adapter.get_object_path(): logging.info(f"selected {adapter_path}") self.blueman.Config["last-adapter"] = adapter_path_to_name(adapter_path) self.blueman.List.set_adapter(adapter_path) def on_adapter_added(self, _manager: Optional[Manager], adapter_path: str) -> None: adapter = Adapter(obj_path=adapter_path) menu = self.item_adapter.get_submenu() assert isinstance(menu, Gtk.Menu) item = Gtk.RadioMenuItem.new_with_label(self._adapters_group, adapter.get_name()) item.show() self._adapters_group = item.get_group() self._itemhandler = item.connect("activate", self.on_adapter_selected, adapter_path) self._adapterhandler = adapter.connect_signal("property-changed", self.on_adapter_property_changed) menu.insert(item, self._insert_adapter_item_pos) self._insert_adapter_item_pos += 1 self.adapter_items[adapter_path] = (item, adapter) assert self.blueman.List.Adapter is not None if adapter_path == self.blueman.List.Adapter.get_object_path(): item.props.active = True if len(self.adapter_items) > 0: self.item_adapter.props.sensitive = True def on_adapter_removed(self, _manager: Manager, adapter_path: str) -> None: item, adapter = self.adapter_items.pop(adapter_path) menu = self.item_adapter.get_submenu() assert isinstance(menu, Gtk.Menu) item.disconnect(self._itemhandler) adapter.disconnect(self._adapterhandler) menu.remove(item) self._insert_adapter_item_pos -= 1 if len(self.adapter_items) == 0: self.item_adapter.props.sensitive = False def _on_plugin_dialog_activate(self, _item: Gtk.MenuItem) -> None: def cb(_proxy: Gio.DBusProxy, _res: Any, _userdata: Any) -> None: pass self.blueman.Applet.OpenPluginDialog(result_handler=cb)
class BluemanApplet(object): def __init__(self): setup_icon_path() check_single_instance("blueman-applet") self.plugin_run_state_changed = False self.manager_state = False self.Manager = Manager() self.Manager.connect_signal('adapter-added', self.on_adapter_added) self.Manager.connect_signal('adapter-removed', self.on_adapter_removed) self.Manager.connect_signal('device-created', self.on_device_created) self.Manager.connect_signal('device-removed', self.on_device_removed) self.DbusSvc = DbusService("org.blueman.Applet", "org.blueman.Applet", "/org/blueman/applet", Gio.BusType.SESSION) self.Plugins = PersistentPluginManager(AppletPlugin, blueman.plugins.applet, self) self.Plugins.load_plugin() self.Plugins.run("on_plugins_loaded") self.Manager.watch_name_owner(self._on_dbus_name_appeared, self._on_dbus_name_vanished) self._any_adapter = AnyAdapter() self._any_adapter.connect_signal('property-changed', self._on_adapter_property_changed) self._any_device = AnyDevice() self._any_device.connect_signal('property-changed', self._on_device_property_changed) self.DbusSvc.register() Gtk.main() def _on_dbus_name_appeared(self, _connection, name, owner): logging.info("%s %s" % (name, owner)) self.manager_state = True self.plugin_run_state_changed = True self.Plugins.run("on_manager_state_changed", self.manager_state) def _on_dbus_name_vanished(self, _connection, name): logging.info(name) self.manager_state = False self.plugin_run_state_changed = True self.Plugins.run("on_manager_state_changed", self.manager_state) def _on_adapter_property_changed(self, _adapter, key, value, path): self.Plugins.run("on_adapter_property_changed", path, key, value) def _on_device_property_changed(self, _device, key, value, path): self.Plugins.run("on_device_property_changed", path, key, value) def on_adapter_added(self, _manager, path): logging.info("Adapter added %s" % path) self.Plugins.run("on_adapter_added", path) def on_adapter_removed(self, _manager, path): logging.info("Adapter removed %s" % path) self.Plugins.run("on_adapter_removed", path) def on_device_created(self, _manager, path): logging.info("Device created %s" % path) self.Plugins.run("on_device_created", path) def on_device_removed(self, _manager, path): logging.info("Device removed %s" % path) self.Plugins.run("on_device_removed", path)