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 ManagerDeviceMenu(Gtk.Menu): __ops__ = {} __instances__ = [] def __init__(self, blueman): super(ManagerDeviceMenu, self).__init__() self.set_name("ManagerDeviceMenu") self.Blueman = blueman self.SelectedDevice = None self.is_popup = False self._device_property_changed_signal = self.Blueman.List.connect( "device-property-changed", self.on_device_property_changed) self._selection_done_signal = None ManagerDeviceMenu.__instances__.append(self) self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_service_property_changed) self._any_device = AnyDevice() self._any_device.connect_signal('property-changed', self._on_service_property_changed) try: self._appl = AppletService() except DBusProxyFailed: logging.error("** Failed to connect to applet", exc_info=True) self._appl = None self.generate() def __del__(self): logging.debug("deleting devicemenu") def popup(self, *args): self.is_popup = True if not self._device_property_changed_signal: self._device_property_changed_signal = self.Blueman.List.connect( "device-property-changed", self.on_device_property_changed) if not self._selection_done_signal: def disconnectall(x): self.disconnect(self._device_property_changed_signal) self.disconnect(self._selection_done_signal) self._selection_done_signal = self.connect("selection-done", disconnectall) self.generate() super().popup(*args) def clear(self): def each(child, data): self.remove(child) child.destroy() self.foreach(each, None) def set_op(self, device, message): ManagerDeviceMenu.__ops__[device.get_object_path()] = message for inst in ManagerDeviceMenu.__instances__: logging.info("op: regenerating instance %s" % inst) if inst.SelectedDevice == self.SelectedDevice and not ( inst.is_popup and not inst.props.visible): inst.generate() def get_op(self, device): try: return ManagerDeviceMenu.__ops__[device.get_object_path()] except KeyError: return None def unset_op(self, device): del ManagerDeviceMenu.__ops__[device.get_object_path()] for inst in ManagerDeviceMenu.__instances__: logging.info("op: regenerating instance %s" % inst) if inst.SelectedDevice == self.SelectedDevice and not ( inst.is_popup and not inst.props.visible): inst.generate() def _on_service_property_changed(self, _service, key, _value, _path): if key == "Connected": self.generate() def on_connect(self, _item, service): device = service.device def success(obj, result, _user_data): logging.info("success") prog.message(_("Success!")) if isinstance(service, SerialPort ) and SERIAL_PORT_SVCLASS_ID == service.short_uuid: MessageArea.show_message( _("Serial port connected to %s") % result, None, "dialog-information") else: MessageArea.close() self.unset_op(device) def fail(obj, result, _user_data): prog.message(_("Failed")) self.unset_op(device) logging.warning("fail %s" % result) msg, tb = e_(result.message) MessageArea.show_message(_("Connection Failed: ") + msg, tb) self.set_op(device, _("Connecting...")) prog = ManagerProgressbar(self.Blueman, False) if self._appl is None: fail(None, GLib.Error('Applet DBus Service not available'), None) return try: self._appl.SetTimeHint('(u)', Gtk.get_current_event_time()) except Exception as e: logging.exception(e) self._appl.connect_service('(os)', device.get_object_path(), service.uuid, result_handler=success, error_handler=fail, timeout=GLib.MAXINT) prog.start() def on_disconnect(self, item, service, port=0): def ok(obj, result, user_date): logging.info("disconnect success") self.generate() def err(obj, result, user_date): logging.warning("disconnect failed %s" % result) msg, tb = e_(result.message) MessageArea.show_message(_("Disconnection Failed: ") + msg, tb) self.generate() if self._appl is None: err(None, GLib.Error('Applet DBus Service not available'), None) return self._appl.disconnect_service('(osd)', service.device.get_object_path(), service.uuid, port, result_handler=ok, error_handler=err) def on_device_property_changed(self, lst, device, tree_iter, key_value): key, value = key_value # print "menu:", key, value if lst.compare(tree_iter, lst.selected()): if key in ("Connected", "UUIDs", "Trusted", "Paired"): self.generate() def _generic_connect(self, item, device, connect): def fail(obj, result, user_date): logging.info("fail", result) prog.message(_("Failed")) self.unset_op(device) msg, tb = e_(result.message) MessageArea.show_message(_("Connection Failed: ") + msg) def success(obj, result, user_data): logging.info("success") prog.message(_("Success!")) MessageArea.close() self.unset_op(device) if connect: self.set_op(self.SelectedDevice, _("Connecting...")) self._appl.connect_service("(os)", device.get_object_path(), '00000000-0000-0000-0000-000000000000', result_handler=success, error_handler=fail, timeout=GLib.MAXINT) else: self.set_op(self.SelectedDevice, _("Disconnecting...")) self._appl.disconnect_service( "(osd)", device.get_object_path(), '00000000-0000-0000-0000-000000000000', 0, result_handler=success, error_handler=fail, timeout=GLib.MAXINT) prog = ManagerProgressbar(self.Blueman, False) prog.start() def generate(self): self.clear() items = [] if not self.is_popup or self.props.visible: selected = self.Blueman.List.selected() if not selected: return row = self.Blueman.List.get(selected, "alias", "paired", "connected", "trusted", "objpush", "device") else: (x, y) = self.Blueman.List.get_pointer() path = self.Blueman.List.get_path_at_pos(x, y) if path is not None: row = self.Blueman.List.get(path[0], "alias", "paired", "connected", "trusted", "objpush", "device") else: return self.SelectedDevice = row["device"] op = self.get_op(self.SelectedDevice) if op is not None: item = create_menuitem(op, "network-transmit-recieve") item.props.sensitive = False item.show() self.append(item) return # Generic (dis)connect # LE devices do not appear to expose certain properties like uuids until connect to at least once. # show generic connect for these as we will not show any way to connect otherwise. device_uuids = self.SelectedDevice['UUIDs'] show_generic_connect = not device_uuids for uuid in device_uuids: service_uuid = ServiceUUID(uuid) if service_uuid.short_uuid in (AUDIO_SOURCE_SVCLASS_ID, AUDIO_SINK_SVCLASS_ID, HANDSFREE_AGW_SVCLASS_ID, HANDSFREE_SVCLASS_ID, HEADSET_SVCLASS_ID, HID_SVCLASS_ID, 0x1812): show_generic_connect = True break elif not service_uuid.reserved: if uuid == '03b80e5a-ede8-4b33-a751-6ce34ec4c700': show_generic_connect = True break if not row["connected"] and show_generic_connect: connect_item = create_menuitem(_("_Connect"), "blueman") connect_item.connect("activate", self._generic_connect, self.SelectedDevice, True) connect_item.props.tooltip_text = _( "Connects auto connect profiles A2DP source, A2DP sink, and HID" ) connect_item.show() self.append(connect_item) elif show_generic_connect: connect_item = create_menuitem(_("_Disconnect"), "network-offline") connect_item.props.tooltip_text = _( "Forcefully disconnect the device") connect_item.connect("activate", self._generic_connect, self.SelectedDevice, False) connect_item.show() self.append(connect_item) rets = self.Blueman.Plugins.run("on_request_menu_items", self, self.SelectedDevice) for ret in rets: if ret: for (item, pos) in ret: items.append((pos, item)) logging.debug(row["alias"]) have_disconnectables = False have_connectables = False if True in map(lambda x: 100 <= x[0] < 200, items): have_disconnectables = True if True in map(lambda x: x[0] < 100, items): have_connectables = True if True in map(lambda x: x[0] >= 200, items) and (have_connectables or have_disconnectables): item = Gtk.SeparatorMenuItem() item.show() items.append((199, item)) if have_connectables: item = Gtk.MenuItem() label = Gtk.Label() label.set_markup(_("<b>Connect To:</b>")) label.props.xalign = 0.0 label.show() item.add(label) item.props.sensitive = False item.show() items.append((0, item)) if have_disconnectables: item = Gtk.MenuItem() label = Gtk.Label() label.set_markup(_("<b>Disconnect:</b>")) label.props.xalign = 0.0 label.show() item.add(label) item.props.sensitive = False item.show() items.append((99, item)) items.sort(key=itemgetter(0)) for priority, item in items: self.append(item) if items: item = Gtk.SeparatorMenuItem() item.show() self.append(item) del items send_item = create_menuitem(_("Send a _File..."), "edit-copy") send_item.props.sensitive = False self.append(send_item) send_item.show() if row["objpush"]: send_item.connect("activate", lambda x: self.Blueman.send(self.SelectedDevice)) send_item.props.sensitive = True item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Pair"), "dialog-password") item.props.tooltip_text = _("Create pairing with the device") self.append(item) item.show() if not row["paired"]: item.connect("activate", lambda x: self.Blueman.bond(self.SelectedDevice)) else: item.props.sensitive = False if not row["trusted"]: item = create_menuitem(_("_Trust"), "blueman-trust") item.connect( "activate", lambda x: self.Blueman.toggle_trust(self.SelectedDevice)) self.append(item) item.show() else: item = create_menuitem(_("_Untrust"), "blueman-untrust") self.append(item) item.connect( "activate", lambda x: self.Blueman.toggle_trust(self.SelectedDevice)) item.show() item.props.tooltip_text = _("Mark/Unmark this device as trusted") item = create_menuitem(_("_Setup..."), "document-properties") self.append(item) item.connect("activate", lambda x: self.Blueman.setup(self.SelectedDevice)) item.show() item.props.tooltip_text = _("Run the setup assistant for this device") def on_rename(_item, device): def on_response(dialog, response_id): if response_id == Gtk.ResponseType.ACCEPT: device.set('Alias', alias_entry.get_text()) elif response_id == 1: device.set('Alias', '') dialog.destroy() builder = Gtk.Builder() builder.set_translation_domain("blueman") bind_textdomain_codeset("blueman", "UTF-8") builder.add_from_file(UI_PATH + "/rename-device.ui") dialog = builder.get_object("dialog") dialog.set_transient_for(self.Blueman) dialog.props.icon_name = "blueman" alias_entry = builder.get_object("alias_entry") alias_entry.set_text(device['Alias']) dialog.connect("response", on_response) dialog.present() item = Gtk.MenuItem.new_with_mnemonic("R_ename device...") item.connect('activate', on_rename, self.SelectedDevice) self.append(item) item.show() item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Remove..."), "edit-delete") item.connect("activate", lambda x: self.Blueman.remove(self.SelectedDevice)) self.append(item) item.show() item.props.tooltip_text = _( "Remove this device from the known devices list")
class ManagerDeviceMenu(Gtk.Menu): __ops__ = {} __instances__ = [] def __init__(self, blueman): super(ManagerDeviceMenu, self).__init__() self.set_name("ManagerDeviceMenu") self.Blueman = blueman self.SelectedDevice = None self.is_popup = False self._device_property_changed_signal = self.Blueman.List.connect("device-property-changed", self.on_device_property_changed) self._selection_done_signal = None ManagerDeviceMenu.__instances__.append(self) self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_service_property_changed) self._any_device = AnyDevice() self._any_device.connect_signal('property-changed', self._on_service_property_changed) try: self._appl = AppletService() except Exception: logging.error("** Failed to connect to applet", exc_info=True) self._appl = None self.Generate() def __del__(self): logging.debug("deleting devicemenu") def popup(self, *args): self.is_popup = True if not self._device_property_changed_signal: self._device_property_changed_signal = self.Blueman.List.connect("device-property-changed", self.on_device_property_changed) if not self._selection_done_signal: def disconnectall(x): self.disconnect(self._device_property_changed_signal) self.disconnect(self._selection_done_signal) self._selection_done_signal = self.connect("selection-done", disconnectall) self.Generate() super().popup(*args) def clear(self): def each(child, data): self.remove(child) child.destroy() self.foreach(each, None) def set_op(self, device, message): ManagerDeviceMenu.__ops__[device.get_object_path()] = message for inst in ManagerDeviceMenu.__instances__: logging.info("op: regenerating instance %s" % inst) if inst.SelectedDevice == self.SelectedDevice and not (inst.is_popup and not inst.props.visible): inst.Generate() def get_op(self, device): try: return ManagerDeviceMenu.__ops__[device.get_object_path()] except KeyError: return None def unset_op(self, device): del ManagerDeviceMenu.__ops__[device.get_object_path()] for inst in ManagerDeviceMenu.__instances__: logging.info("op: regenerating instance %s" % inst) if inst.SelectedDevice == self.SelectedDevice and not (inst.is_popup and not inst.props.visible): inst.Generate() def _on_service_property_changed(self, _service, key, _value, _path): if key == "Connected": self.Generate() def on_connect(self, _item, service): device = service.device def success(obj, result, _user_data): logging.info("success") prog.message(_("Success!")) if isinstance(service, SerialPort) and SERIAL_PORT_SVCLASS_ID == service.short_uuid: MessageArea.show_message(_("Serial port connected to %s") % result, None, "dialog-information") else: MessageArea.close() self.unset_op(device) def fail(obj, result, _user_data): prog.message(_("Failed")) self.unset_op(device) logging.warning("fail %s" % result) msg, tb = e_(result.message) MessageArea.show_message(_("Connection Failed: ") + msg, tb) self.set_op(device, _("Connecting...")) prog = ManagerProgressbar(self.Blueman, False) if self._appl is None: fail(None, GLib.Error('Applet DBus Service not available'), None) return try: self._appl.SetTimeHint('(u)', Gtk.get_current_event_time()) except Exception as e: logging.exception(e) self._appl.connect_service('(os)', device.get_object_path(), service.uuid, result_handler=success, error_handler=fail, timeout=GLib.MAXINT) prog.start() def on_disconnect(self, item, service, port=0): def ok(obj, result, user_date): logging.info("disconnect success") self.Generate() def err(obj, result, user_date): logging.warning("disconnect failed %s" % result) msg, tb = e_(result.message) MessageArea.show_message(_("Disconnection Failed: ") + msg, tb) self.Generate() if self._appl is None: err(None, GLib.Error('Applet DBus Service not available'), None) return self._appl.disconnect_service('(osd)', service.device.get_object_path(), service.uuid, port, result_handler=ok, error_handler=err) def on_device_property_changed(self, List, device, tree_iter, key_value): key, value = key_value # print "menu:", key, value if List.compare(tree_iter, List.selected()): if key in ("Connected", "UUIDs", "Trusted", "Paired"): self.Generate() def _generic_connect(self, item, device, connect): def fail(obj, result, user_date): logging.info("fail", result) prog.message(_("Failed")) self.unset_op(device) msg, tb = e_(result.message) MessageArea.show_message(_("Connection Failed: ") + msg) def success(obj, result, user_data): logging.info("success") prog.message(_("Success!")) MessageArea.close() self.unset_op(device) if connect: self.set_op(self.SelectedDevice, _("Connecting...")) self._appl.connect_service("(os)", device.get_object_path(), '00000000-0000-0000-0000-000000000000', result_handler=success, error_handler=fail, timeout=GLib.MAXINT) else: self.set_op(self.SelectedDevice, _("Disconnecting...")) self._appl.disconnect_service("(osd)", device.get_object_path(), '00000000-0000-0000-0000-000000000000', 0, result_handler=success, error_handler=fail, timeout=GLib.MAXINT) prog = ManagerProgressbar(self.Blueman, False) prog.start() def Generate(self): self.clear() items = [] if not self.is_popup or self.props.visible: selected = self.Blueman.List.selected() if not selected: return row = self.Blueman.List.get(selected, "alias", "paired", "connected", "trusted", "objpush", "device") else: (x, y) = self.Blueman.List.get_pointer() path = self.Blueman.List.get_path_at_pos(x, y) if path is not None: row = self.Blueman.List.get(path[0], "alias", "paired", "connected", "trusted", "objpush", "device") else: return self.SelectedDevice = row["device"] op = self.get_op(self.SelectedDevice) if op is not None: item = create_menuitem(op, "network-transmit-recieve") item.props.sensitive = False item.show() self.append(item) return # Generic (dis)connect # LE devices do not appear to expose certain properties like uuids until connect to at least once. # show generic connect for these as we will not show any way to connect otherwise. device_uuids = self.SelectedDevice['UUIDs'] show_generic_connect = not device_uuids for uuid in device_uuids: service_uuid = ServiceUUID(uuid) if service_uuid.short_uuid in ( AUDIO_SOURCE_SVCLASS_ID, AUDIO_SINK_SVCLASS_ID, HANDSFREE_AGW_SVCLASS_ID, HANDSFREE_SVCLASS_ID, HEADSET_SVCLASS_ID, HID_SVCLASS_ID, 0x1812 ): show_generic_connect = True break elif not service_uuid.reserved: show_generic_connect = uuid == '03b80e5a-ede8-4b33-a751-6ce34ec4c700' break if not row["connected"] and show_generic_connect: connect_item = create_menuitem(_("_Connect"), "blueman") connect_item.connect("activate", self._generic_connect, self.SelectedDevice, True) connect_item.props.tooltip_text = _("Connects auto connect profiles A2DP source, A2DP sink, and HID") connect_item.show() self.append(connect_item) elif show_generic_connect: connect_item = create_menuitem(_("_Disconnect"), "network-offline") connect_item.props.tooltip_text = _("Forcefully disconnect the device") connect_item.connect("activate", self._generic_connect, self.SelectedDevice, False) connect_item.show() self.append(connect_item) rets = self.Blueman.Plugins.Run("on_request_menu_items", self, self.SelectedDevice) for ret in rets: if ret: for (item, pos) in ret: items.append((pos, item)) logging.debug(row["alias"]) have_disconnectables = False have_connectables = False if True in map(lambda x: 100 <= x[0] < 200, items): have_disconnectables = True if True in map(lambda x: x[0] < 100, items): have_connectables = True if True in map(lambda x: x[0] >= 200, items) and (have_connectables or have_disconnectables): item = Gtk.SeparatorMenuItem() item.show() items.append((199, item)) if have_connectables: item = Gtk.MenuItem() label = Gtk.Label() label.set_markup(_("<b>Connect To:</b>")) label.props.xalign = 0.0 label.show() item.add(label) item.props.sensitive = False item.show() items.append((0, item)) if have_disconnectables: item = Gtk.MenuItem() label = Gtk.Label() label.set_markup(_("<b>Disconnect:</b>")) label.props.xalign = 0.0 label.show() item.add(label) item.props.sensitive = False item.show() items.append((99, item)) items.sort(key=itemgetter(0)) for priority, item in items: self.append(item) if items: item = Gtk.SeparatorMenuItem() item.show() self.append(item) del items send_item = create_menuitem(_("Send a _File..."), "edit-copy") send_item.props.sensitive = False self.append(send_item) send_item.show() if row["objpush"]: send_item.connect("activate", lambda x: self.Blueman.send(self.SelectedDevice)) send_item.props.sensitive = True item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Pair"), "dialog-password") item.props.tooltip_text = _("Create pairing with the device") self.append(item) item.show() if not row["paired"]: item.connect("activate", lambda x: self.Blueman.bond(self.SelectedDevice)) else: item.props.sensitive = False if not row["trusted"]: item = create_menuitem(_("_Trust"), "blueman-trust") item.connect("activate", lambda x: self.Blueman.toggle_trust(self.SelectedDevice)) self.append(item) item.show() else: item = create_menuitem(_("_Untrust"), "blueman-untrust") self.append(item) item.connect("activate", lambda x: self.Blueman.toggle_trust(self.SelectedDevice)) item.show() item.props.tooltip_text = _("Mark/Unmark this device as trusted") item = create_menuitem(_("_Setup..."), "document-properties") self.append(item) item.connect("activate", lambda x: self.Blueman.setup(self.SelectedDevice)) item.show() item.props.tooltip_text = _("Run the setup assistant for this device") def on_rename(_item, device): def on_response(dialog, response_id): if response_id == Gtk.ResponseType.ACCEPT: device.set('Alias', alias_entry.get_text()) elif response_id == 1: device.set('Alias', '') dialog.destroy() builder = Gtk.Builder() builder.set_translation_domain("blueman") bind_textdomain_codeset("blueman", "UTF-8") builder.add_from_file(UI_PATH + "/rename-device.ui") dialog = builder.get_object("dialog") dialog.set_transient_for(self.Blueman) dialog.props.icon_name = "blueman" alias_entry = builder.get_object("alias_entry") alias_entry.set_text(device['Alias']) dialog.connect("response", on_response) dialog.present() item = Gtk.MenuItem.new_with_mnemonic("R_ename device...") item.connect('activate', on_rename, self.SelectedDevice) self.append(item) item.show() item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Remove..."), "edit-delete") item.connect("activate", lambda x: self.Blueman.remove(self.SelectedDevice)) self.append(item) item.show() item.props.tooltip_text = _("Remove this device from the known devices list")
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 ManagerDeviceMenu(Gtk.Menu): __ops__: Dict[str, str] = {} __instances__: List["ManagerDeviceMenu"] = [] SelectedDevice: Device def __init__(self, blueman: "Blueman") -> None: super().__init__() self.set_name("ManagerDeviceMenu") self.Blueman = blueman self.is_popup = False self._device_property_changed_signal = self.Blueman.List.connect("device-property-changed", self.on_device_property_changed) ManagerDeviceMenu.__instances__.append(self) self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_service_property_changed) self._any_device = AnyDevice() self._any_device.connect_signal('property-changed', self._on_service_property_changed) try: self._appl: Optional[AppletService] = AppletService() except DBusProxyFailed: logging.error("** Failed to connect to applet", exc_info=True) self._appl = None self.generate() def __del__(self) -> None: logging.debug("deleting devicemenu") def popup_at_pointer(self, event: Optional[Gdk.Event]) -> None: self.is_popup = True self.generate() super().popup_at_pointer(event) def clear(self) -> None: def remove_and_destroy(child: Gtk.Widget) -> None: self.remove(child) child.destroy() self.foreach(remove_and_destroy) def set_op(self, device: Device, message: str) -> None: ManagerDeviceMenu.__ops__[device.get_object_path()] = message for inst in ManagerDeviceMenu.__instances__: logging.info(f"op: regenerating instance {inst}") if inst.SelectedDevice == self.SelectedDevice and not (inst.is_popup and not inst.props.visible): inst.generate() def get_op(self, device: Device) -> Optional[str]: try: return ManagerDeviceMenu.__ops__[device.get_object_path()] except KeyError: return None def unset_op(self, device: Device) -> None: del ManagerDeviceMenu.__ops__[device.get_object_path()] for inst in ManagerDeviceMenu.__instances__: logging.info(f"op: regenerating instance {inst}") if inst.SelectedDevice == self.SelectedDevice and not (inst.is_popup and not inst.props.visible): inst.generate() def _on_service_property_changed(self, _service: Union[AnyNetwork, AnyDevice], key: str, _value: object, _path: str) -> None: if key == "Connected": self.generate() GENERIC_CONNECT = "00000000-0000-0000-0000-000000000000" def connect_service(self, device: Device, uuid: str = GENERIC_CONNECT) -> None: def success(_obj: AppletService, _result: None, _user_data: None) -> None: logging.info("success") prog.message(_("Success!")) MessageArea.close() self.unset_op(device) def fail(_obj: Optional[AppletService], result: GLib.Error, _user_data: None) -> None: prog.message(_("Failed")) self.unset_op(device) logging.warning(f"fail {result}") self._handle_error_message(result) self.set_op(device, _("Connecting…")) prog = ManagerProgressbar(self.Blueman, cancellable=uuid == self.GENERIC_CONNECT) if uuid == self.GENERIC_CONNECT: prog.connect("cancelled", lambda x: self.disconnect_service(device)) if self._appl is None: fail(None, GLib.Error('Applet DBus Service not available'), None) return def connect(error_handler: Callable[[AppletService, GLib.Error, None], None]) -> None: assert self._appl is not None # https://github.com/python/mypy/issues/2608 self._appl.ConnectService('(os)', device.get_object_path(), uuid, result_handler=success, error_handler=error_handler, timeout=GLib.MAXINT) def initial_error_handler(obj: AppletService, result: GLib.Error, user_date: None) -> None: # There are (Intel) drivers that fail to connect while a discovery is running if self._get_errno(result) == errno.EAGAIN: assert self.Blueman.List.Adapter is not None self.Blueman.List.Adapter.stop_discovery() connect(fail) else: fail(obj, result, user_date) connect(initial_error_handler) prog.start() def disconnect_service(self, device: Device, uuid: str = GENERIC_CONNECT, port: int = 0) -> None: def ok(_obj: AppletService, _result: None, _user_date: None) -> None: logging.info("disconnect success") self.generate() def err(_obj: Optional[AppletService], result: GLib.Error, _user_date: None) -> None: logging.warning(f"disconnect failed {result}") msg, tb = e_(result.message) MessageArea.show_message(_("Disconnection Failed: ") + msg, tb) self.generate() if self._appl is None: err(None, GLib.Error('Applet DBus Service not available'), None) return self._appl.DisconnectService('(osd)', device.get_object_path(), uuid, port, result_handler=ok, error_handler=err, timeout=GLib.MAXINT) def on_device_property_changed(self, lst: "ManagerDeviceList", _device: Device, tree_iter: Gtk.TreeIter, key_value: Tuple[str, object]) -> None: key, value = key_value # print "menu:", key, value if lst.compare(tree_iter, lst.selected()): if key in ("Connected", "UUIDs", "Trusted", "Paired", "Blocked"): self.generate() def _handle_error_message(self, error: GLib.Error) -> None: err = self._get_errno(error) if err == errno.ENOPROTOOPT: logging.warning("No audio endpoints registered to bluetoothd. " "Pulseaudio Bluetooth module, bluez-alsa, PipeWire or other audio support missing.") msg = _("No audio endpoints registered") elif err == errno.EIO: logging.warning("bluetoothd reported input/output error. Check its logs for context.") msg = _("Input/output error") elif err == errno.EHOSTDOWN: msg = _("Device did not respond") elif err == errno.EAGAIN: logging.warning("bluetoothd reported resource temporarily unavailable. " "Retry or check its logs for context.") msg = _("Resource temporarily unavailable") else: msg = error.message.split(":", 3)[-1].strip() if msg != "Cancelled": MessageArea.show_message(_("Connection Failed: ") + msg) @staticmethod def _get_errno(error: GLib.Error) -> Optional[int]: msg = error.message.split(":", 3)[-1].strip() # https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/gnu/errlist.h # https://git.musl-libc.org/cgit/musl/tree/src/errno/__strerror.h # https://git.uclibc.org/uClibc/tree/libc/string/_string_syserrmsgs.c if msg == "Protocol not available": return errno.ENOPROTOOPT if msg in ("Input/output error", "I/O error"): return errno.EIO if msg == "Host is down": # Bluetooth errors 0x04 (Page Timeout) or 0x3c (Advertising Timeout) return errno.EHOSTDOWN if msg == "Resource temporarily unavailable": return errno.EAGAIN return None def show_generic_connect_calc(self, device_uuids: Iterable[str]) -> bool: # Generic (dis)connect for uuid in device_uuids: service_uuid = ServiceUUID(uuid) if service_uuid.short_uuid in ( AUDIO_SOURCE_SVCLASS_ID, AUDIO_SINK_SVCLASS_ID, HANDSFREE_AGW_SVCLASS_ID, HANDSFREE_SVCLASS_ID, HEADSET_SVCLASS_ID, HID_SVCLASS_ID, 0x1812 ): return True elif not service_uuid.reserved: if uuid == '03b80e5a-ede8-4b33-a751-6ce34ec4c700': return True # LE devices do not appear to expose certain properties like uuids until connect to at least once. return not device_uuids def generate(self) -> None: self.clear() if not self.is_popup or self.props.visible: selected = self.Blueman.List.selected() if not selected: return row = self.Blueman.List.get(selected, "alias", "paired", "connected", "trusted", "objpush", "device", "blocked") else: (x, y) = self.Blueman.List.get_pointer() path = self.Blueman.List.get_path_at_pos(x, y) if path is not None: assert path[0] is not None row = self.Blueman.List.get(path[0], "alias", "paired", "connected", "trusted", "objpush", "device", "blocked") else: return self.SelectedDevice = row["device"] op = self.get_op(self.SelectedDevice) if op is not None: item: Gtk.MenuItem = create_menuitem(op, "network-transmit-receive-symbolic") item.props.sensitive = False item.show() self.append(item) return show_generic_connect = self.show_generic_connect_calc(self.SelectedDevice['UUIDs']) if not row["connected"] and show_generic_connect: connect_item = create_menuitem(_("<b>_Connect</b>"), "bluetooth-symbolic") connect_item.connect("activate", lambda _item: self.connect_service(self.SelectedDevice)) connect_item.props.tooltip_text = _("Connects auto connect profiles A2DP source, A2DP sink, and HID") connect_item.show() self.append(connect_item) elif show_generic_connect: connect_item = create_menuitem(_("<b>_Disconnect</b>"), "bluetooth-disabled-symbolic") connect_item.props.tooltip_text = _("Forcefully disconnect the device") connect_item.connect("activate", lambda _item: self.disconnect_service(self.SelectedDevice)) connect_item.show() self.append(connect_item) logging.debug(row["alias"]) items = [item for plugin in self.Blueman.Plugins.get_loaded_plugins(MenuItemsProvider) for item in plugin.on_request_menu_items(self, self.SelectedDevice)] connect_items = [i for i in items if i.group == DeviceMenuItem.Group.CONNECT] disconnect_items = [i for i in items if i.group == DeviceMenuItem.Group.DISCONNECT] autoconnect_items = [item for item in items if item.group == DeviceMenuItem.Group.AUTOCONNECT] action_items = [i for i in items if i.group == DeviceMenuItem.Group.ACTIONS] if connect_items: self.append(self._create_header(_("<b>Connect To:</b>"))) for it in sorted(connect_items, key=lambda i: i.position): self.append(it.item) if disconnect_items: self.append(self._create_header(_("<b>Disconnect:</b>"))) for it in sorted(disconnect_items, key=lambda i: i.position): self.append(it.item) config = AutoConnectConfig() generic_service = ServiceUUID("00000000-0000-0000-0000-000000000000") generic_autoconnect = (self.SelectedDevice.get_object_path(), str(generic_service)) in set(config["services"]) if row["connected"] or generic_autoconnect or autoconnect_items: self.append(self._create_header(_("<b>Auto-connect:</b>"))) if row["connected"] or generic_autoconnect: item = Gtk.CheckMenuItem(label=generic_service.name) config.bind_to_menuitem(item, self.SelectedDevice, str(generic_service)) item.show() self.append(item) for it in sorted(autoconnect_items, key=lambda i: i.position): self.append(it.item) if show_generic_connect or connect_items or disconnect_items or autoconnect_items: item = Gtk.SeparatorMenuItem() item.show() self.append(item) for it in sorted(action_items, key=lambda i: i.position): self.append(it.item) send_item = create_menuitem(_("Send a _File…"), "blueman-send-symbolic") send_item.props.sensitive = False self.append(send_item) send_item.show() if row["objpush"]: send_item.connect("activate", lambda x: self.Blueman.send(self.SelectedDevice)) send_item.props.sensitive = True item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Pair"), "blueman-pair-symbolic") item.props.tooltip_text = _("Create pairing with the device") self.append(item) item.show() if not row["paired"]: item.connect("activate", lambda x: self.Blueman.bond(self.SelectedDevice)) else: item.props.sensitive = False if not row["trusted"]: item = create_menuitem(_("_Trust"), "blueman-trust-symbolic") item.connect("activate", lambda x: self.Blueman.toggle_trust(self.SelectedDevice)) self.append(item) item.show() else: item = create_menuitem(_("_Untrust"), "blueman-untrust-symbolic") self.append(item) item.connect("activate", lambda x: self.Blueman.toggle_trust(self.SelectedDevice)) item.show() item.props.tooltip_text = _("Mark/Unmark this device as trusted") if not row["blocked"]: item = create_menuitem(_("_Block"), "blueman-block-symbolic") item.connect("activate", lambda x: self.Blueman.toggle_blocked(self.SelectedDevice)) self.append(item) item.show() else: item = create_menuitem(_("_Unblock"), "blueman-block-symbolic") self.append(item) item.connect("activate", lambda x: self.Blueman.toggle_blocked(self.SelectedDevice)) item.show() item.props.tooltip_text = _("Block/Unblock this device") def on_rename(_item: Gtk.MenuItem, device: Device) -> None: def on_response(dialog: Gtk.Dialog, response_id: int) -> None: if response_id == Gtk.ResponseType.ACCEPT: assert isinstance(alias_entry, Gtk.Entry) # https://github.com/python/mypy/issues/2608 device.set('Alias', alias_entry.get_text()) elif response_id == 1: device.set('Alias', '') dialog.destroy() builder = Builder("rename-device.ui") dialog = builder.get_widget("dialog", Gtk.Dialog) dialog.set_transient_for(self.Blueman.window) dialog.props.icon_name = "blueman" alias_entry = builder.get_widget("alias_entry", Gtk.Entry) alias_entry.set_text(device['Alias']) dialog.connect("response", on_response) dialog.present() item = Gtk.MenuItem.new_with_mnemonic(_("R_ename device…")) item.connect('activate', on_rename, self.SelectedDevice) self.append(item) item.show() item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Remove…"), "list-remove-symbolic") item.connect("activate", lambda x: self.Blueman.remove(self.SelectedDevice)) self.append(item) item.show() item.props.tooltip_text = _("Remove this device from the known devices list") @staticmethod def _create_header(text: str) -> Gtk.MenuItem: item = Gtk.MenuItem() label = Gtk.Label() label.set_markup(text) label.props.xalign = 0.0 label.show() item.add(label) item.props.sensitive = False item.show() return item
class ManagerDeviceMenu(Gtk.Menu): __ops__: Dict[str, str] = {} __instances__: List["ManagerDeviceMenu"] = [] SelectedDevice: Device def __init__(self, blueman: "Blueman") -> None: super().__init__() self.set_name("ManagerDeviceMenu") self.Blueman = blueman self.is_popup = False self._device_property_changed_signal = self.Blueman.List.connect( "device-property-changed", self.on_device_property_changed) ManagerDeviceMenu.__instances__.append(self) self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_service_property_changed) self._any_device = AnyDevice() self._any_device.connect_signal('property-changed', self._on_service_property_changed) try: self._appl: Optional[AppletService] = AppletService() except DBusProxyFailed: logging.error("** Failed to connect to applet", exc_info=True) self._appl = None self.generate() def __del__(self) -> None: logging.debug("deleting devicemenu") def popup_at_pointer(self, event: Gdk.Event) -> None: self.is_popup = True self.generate() super().popup_at_pointer(event) def clear(self) -> None: def remove_and_destroy(child: Gtk.Widget) -> None: self.remove(child) child.destroy() self.foreach(remove_and_destroy) def set_op(self, device: Device, message: str) -> None: ManagerDeviceMenu.__ops__[device.get_object_path()] = message for inst in ManagerDeviceMenu.__instances__: logging.info(f"op: regenerating instance {inst}") if inst.SelectedDevice == self.SelectedDevice and not ( inst.is_popup and not inst.props.visible): inst.generate() def get_op(self, device: Device) -> Optional[str]: try: return ManagerDeviceMenu.__ops__[device.get_object_path()] except KeyError: return None def unset_op(self, device: Device) -> None: del ManagerDeviceMenu.__ops__[device.get_object_path()] for inst in ManagerDeviceMenu.__instances__: logging.info(f"op: regenerating instance {inst}") if inst.SelectedDevice == self.SelectedDevice and not ( inst.is_popup and not inst.props.visible): inst.generate() def _on_service_property_changed(self, _service: Union[AnyNetwork, AnyDevice], key: str, _value: object, _path: str) -> None: if key == "Connected": self.generate() def on_connect(self, _item: Gtk.MenuItem, service: Service) -> None: device = service.device def success(_obj: AppletService, _result: None, _user_data: None) -> None: logging.info("success") prog.message(_("Success!")) MessageArea.close() self.unset_op(device) def fail(_obj: Optional[AppletService], result: GLib.Error, _user_data: None) -> None: prog.message(_("Failed")) self.unset_op(device) logging.warning(f"fail {result}") msg, tb = e_(result.message) MessageArea.show_message(_("Connection Failed: ") + msg, tb) self.set_op(device, _("Connecting…")) prog = ManagerProgressbar(self.Blueman, False) if self._appl is None: fail(None, GLib.Error('Applet DBus Service not available'), None) return self._appl.ConnectService('(os)', device.get_object_path(), service.uuid, result_handler=success, error_handler=fail, timeout=GLib.MAXINT) prog.start() def on_disconnect(self, _item: Gtk.MenuItem, service: Service, port: int = 0) -> None: def ok(_obj: AppletService, _result: None, _user_date: None) -> None: logging.info("disconnect success") self.generate() def err(_obj: Optional[AppletService], result: GLib.Error, _user_date: None) -> None: logging.warning(f"disconnect failed {result}") msg, tb = e_(result.message) MessageArea.show_message(_("Disconnection Failed: ") + msg, tb) self.generate() if self._appl is None: err(None, GLib.Error('Applet DBus Service not available'), None) return self._appl.DisconnectService('(osd)', service.device.get_object_path(), service.uuid, port, result_handler=ok, error_handler=err) def on_device_property_changed(self, lst: "ManagerDeviceList", _device: Device, tree_iter: Gtk.TreeIter, key_value: Tuple[str, object]) -> None: key, value = key_value # print "menu:", key, value if lst.compare(tree_iter, lst.selected()): if key in ("Connected", "UUIDs", "Trusted", "Paired"): self.generate() def generic_connect(self, _item: Optional[Gtk.MenuItem], device: Device, connect: bool) -> None: def fail(_obj: AppletService, result: GLib.Error, _user_data: None) -> None: logging.info(f"fail: {result}") prog.message(_("Failed")) self.unset_op(device) msg, tb = e_(result.message) MessageArea.show_message(_("Connection Failed: ") + msg) def success(_obj: AppletService, _result: None, _user_data: None) -> None: logging.info("success") prog.message(_("Success!")) MessageArea.close() self.unset_op(device) assert self._appl if connect: self.set_op(self.SelectedDevice, _("Connecting…")) self._appl.ConnectService("(os)", device.get_object_path(), '00000000-0000-0000-0000-000000000000', result_handler=success, error_handler=fail, timeout=GLib.MAXINT) else: self.set_op(self.SelectedDevice, _("Disconnecting…")) self._appl.DisconnectService( "(osd)", device.get_object_path(), '00000000-0000-0000-0000-000000000000', 0, result_handler=success, error_handler=fail, timeout=GLib.MAXINT) prog = ManagerProgressbar(self.Blueman, False) prog.start() def show_generic_connect_calc(self, device_uuids: Iterable[str]) -> bool: # Generic (dis)connect for uuid in device_uuids: service_uuid = ServiceUUID(uuid) if service_uuid.short_uuid in (AUDIO_SOURCE_SVCLASS_ID, AUDIO_SINK_SVCLASS_ID, HANDSFREE_AGW_SVCLASS_ID, HANDSFREE_SVCLASS_ID, HEADSET_SVCLASS_ID, HID_SVCLASS_ID, 0x1812): return True elif not service_uuid.reserved: if uuid == '03b80e5a-ede8-4b33-a751-6ce34ec4c700': return True # LE devices do not appear to expose certain properties like uuids until connect to at least once. return not device_uuids def generate(self) -> None: self.clear() items: List[Tuple[int, Gtk.MenuItem]] = [] if not self.is_popup or self.props.visible: selected = self.Blueman.List.selected() if not selected: return row = self.Blueman.List.get(selected, "alias", "paired", "connected", "trusted", "objpush", "device") else: (x, y) = self.Blueman.List.get_pointer() path = self.Blueman.List.get_path_at_pos(x, y) if path is not None: row = self.Blueman.List.get(path[0], "alias", "paired", "connected", "trusted", "objpush", "device") else: return self.SelectedDevice = row["device"] op = self.get_op(self.SelectedDevice) if op is not None: item = create_menuitem(op, "network-transmit-receive") item.props.sensitive = False item.show() self.append(item) return show_generic_connect = self.show_generic_connect_calc( self.SelectedDevice['UUIDs']) if not row["connected"] and show_generic_connect: connect_item = create_menuitem(_("_<b>Connect</b>"), "blueman") connect_item.connect("activate", self.generic_connect, self.SelectedDevice, True) connect_item.props.tooltip_text = _( "Connects auto connect profiles A2DP source, A2DP sink, and HID" ) connect_item.show() self.append(connect_item) elif show_generic_connect: connect_item = create_menuitem(_("_<b>Disconnect</b>"), "network-offline") connect_item.props.tooltip_text = _( "Forcefully disconnect the device") connect_item.connect("activate", self.generic_connect, self.SelectedDevice, False) connect_item.show() self.append(connect_item) for plugin in self.Blueman.Plugins.get_loaded_plugins( MenuItemsProvider): for item, pos in plugin.on_request_menu_items( self, self.SelectedDevice): items.append((pos, item)) logging.debug(row["alias"]) have_disconnectables = False have_connectables = False if True in map(lambda x: 100 <= x[0] < 200, items): have_disconnectables = True if True in map(lambda x: x[0] < 100, items): have_connectables = True if True in map(lambda x: x[0] >= 200, items) and (have_connectables or have_disconnectables): item = Gtk.SeparatorMenuItem() item.show() items.append((199, item)) if have_connectables: item = Gtk.MenuItem() label = Gtk.Label() label.set_markup(_("<b>Connect To:</b>")) label.props.xalign = 0.0 label.show() item.add(label) item.props.sensitive = False item.show() items.append((0, item)) if have_disconnectables: item = Gtk.MenuItem() label = Gtk.Label() label.set_markup(_("<b>Disconnect:</b>")) label.props.xalign = 0.0 label.show() item.add(label) item.props.sensitive = False item.show() items.append((99, item)) items.sort(key=itemgetter(0)) for priority, item in items: self.append(item) if items: item = Gtk.SeparatorMenuItem() item.show() self.append(item) del items send_item = create_menuitem(_("Send a _File…"), "edit-copy") send_item.props.sensitive = False self.append(send_item) send_item.show() if row["objpush"]: send_item.connect("activate", lambda x: self.Blueman.send(self.SelectedDevice)) send_item.props.sensitive = True item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Pair"), "dialog-password") item.props.tooltip_text = _("Create pairing with the device") self.append(item) item.show() if not row["paired"]: item.connect("activate", lambda x: self.Blueman.bond(self.SelectedDevice)) else: item.props.sensitive = False if not row["trusted"]: item = create_menuitem(_("_Trust"), "blueman-trust") item.connect( "activate", lambda x: self.Blueman.toggle_trust(self.SelectedDevice)) self.append(item) item.show() else: item = create_menuitem(_("_Untrust"), "blueman-untrust") self.append(item) item.connect( "activate", lambda x: self.Blueman.toggle_trust(self.SelectedDevice)) item.show() item.props.tooltip_text = _("Mark/Unmark this device as trusted") item = create_menuitem(_("_Set up…"), "document-properties") self.append(item) item.connect("activate", lambda x: self.Blueman.setup(self.SelectedDevice)) item.show() item.props.tooltip_text = _("Run the setup assistant for this device") def on_rename(_item: Gtk.MenuItem, device: Device) -> None: def on_response(dialog: Gtk.Dialog, response_id: int) -> None: if response_id == Gtk.ResponseType.ACCEPT: device.set('Alias', alias_entry.get_text()) elif response_id == 1: device.set('Alias', '') dialog.destroy() builder = Gtk.Builder() builder.set_translation_domain("blueman") builder.add_from_file(UI_PATH + "/rename-device.ui") dialog = builder.get_object("dialog") dialog.set_transient_for(self.Blueman.window) dialog.props.icon_name = "blueman" alias_entry: Gtk.Entry = builder.get_object("alias_entry") alias_entry.set_text(device['Alias']) dialog.connect("response", on_response) dialog.present() item = Gtk.MenuItem.new_with_mnemonic(_("R_ename device…")) item.connect('activate', on_rename, self.SelectedDevice) self.append(item) item.show() item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Remove…"), "edit-delete") item.connect("activate", lambda x: self.Blueman.remove(self.SelectedDevice)) self.append(item) item.show() item.props.tooltip_text = _( "Remove this device from the known devices list")
class ManagerDeviceMenu(Gtk.Menu): __ops__ = {} __instances__ = [] def __init__(self, blueman): super(ManagerDeviceMenu, self).__init__() self.set_name("ManagerDeviceMenu") self.Blueman = blueman self.SelectedDevice = None self.is_popup = False self._device_property_changed_signal = self.Blueman.List.connect("device-property-changed", self.on_device_property_changed) self._selection_done_signal = None ManagerDeviceMenu.__instances__.append(self) self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_service_property_changed) self._any_device = AnyDevice() self._any_device.connect_signal('property-changed', self._on_service_property_changed) self.Generate() def __del__(self): dprint("deleting devicemenu") def popup(self, *args): self.is_popup = True if not self._device_property_changed_signal: self._device_property_changed_signal = self.Blueman.List.connect("device-property-changed", self.on_device_property_changed) if not self._selection_done_signal: def disconnectall(x): self.disconnect(self._device_property_changed_signal) self.disconnect(self._selection_done_signal) self._selection_done_signal = self.connect("selection-done", disconnectall) self.Generate() Gtk.Menu.popup(self, *args) def clear(self): def each(child, data): self.remove(child) child.destroy() self.foreach(each, None) def set_op(self, device, message): ManagerDeviceMenu.__ops__[device.get_object_path()] = message for inst in ManagerDeviceMenu.__instances__: dprint("op: regenerating instance", inst) if inst.SelectedDevice == self.SelectedDevice and not (inst.is_popup and not inst.props.visible): inst.Generate() def get_op(self, device): try: return ManagerDeviceMenu.__ops__[device.get_object_path()] except: return None def unset_op(self, device): del ManagerDeviceMenu.__ops__[device.get_object_path()] for inst in ManagerDeviceMenu.__instances__: dprint("op: regenerating instance", inst) if inst.SelectedDevice == self.SelectedDevice and not (inst.is_popup and not inst.props.visible): inst.Generate() def _on_service_property_changed(self, _service, key, _value, _path): if key == "Connected": self.Generate() def on_connect(self, _item, service): device = service.device def success(*args2): dprint("success", " ".join(args2)) prog.message(_("Success!")) if isinstance(service, SerialPort) and SERIAL_PORT_SVCLASS_ID == uuid128_to_uuid16(service.uuid): MessageArea.show_message(_("Serial port connected to %s") % args2[0], "dialog-information") else: MessageArea.close() self.unset_op(device) def fail(*args): prog.message(_("Failed")) self.unset_op(device) dprint("fail", args) MessageArea.show_message(_("Connection Failed: ") + e_(str(args[0]))) self.set_op(device, _("Connecting...")) prog = ManagerProgressbar(self.Blueman, False) try: appl = AppletService() except: dprint("** Failed to connect to applet") fail() return try: appl.SetTimeHint(Gtk.get_current_event_time()) except: pass appl.connect_service(device.get_object_path(), service.uuid, reply_handler=success, error_handler=fail, timeout=200) prog.start() def on_disconnect(self, item, service, port=0): try: appl = AppletService() except: dprint("** Failed to connect to applet") return appl.disconnect_service(service.device.get_object_path(), service.uuid, port) self.Generate() def on_device_property_changed(self, List, device, tree_iter, key_value): key, value = key_value # print "menu:", key, value if List.compare(tree_iter, List.selected()): if key == "Connected" \ or key == "UUIDs" \ or key == "Trusted" \ or key == "Paired": self.Generate() def Generate(self): self.clear() appl = AppletService() items = [] if not self.is_popup or self.props.visible: selected = self.Blueman.List.selected() if not selected: return row = self.Blueman.List.get(selected, "alias", "bonded", "connected", "trusted", "objpush", "device") else: (x, y) = self.Blueman.List.get_pointer() path = self.Blueman.List.get_path_at_pos(x, y) if path is not None: row = self.Blueman.List.get(path[0], "alias", "bonded", "connected", "trusted", "objpush", "device") else: return self.SelectedDevice = row["device"] op = self.get_op(self.SelectedDevice) if op is not None: item = create_menuitem(op, get_icon("network-transmit-recieve", 16)) item.props.sensitive = False item.show() self.append(item) return rets = self.Blueman.Plugins.Run("on_request_menu_items", self, self.SelectedDevice) for ret in rets: if ret: for (item, pos) in ret: items.append((pos, item)) dprint(row["alias"]) have_disconnectables = False have_connectables = False if True in map(lambda x: 100 <= x[0] < 200, items): have_disconnectables = True if True in map(lambda x: x[0] < 100, items): have_connectables = True if True in map(lambda x: x[0] >= 200, items) and (have_connectables or have_disconnectables): item = Gtk.SeparatorMenuItem() item.show() items.append((199, item)) if have_connectables: item = Gtk.MenuItem() label = Gtk.Label() label.set_markup(_("<b>Connect To:</b>")) label.props.xalign = 0.0 label.show() item.add(label) item.props.sensitive = False item.show() items.append((0, item)) if have_disconnectables: item = Gtk.MenuItem() label = Gtk.Label() label.set_markup(_("<b>Disconnect:</b>")) label.props.xalign = 0.0 label.show() item.add(label) item.props.sensitive = False item.show() items.append((99, item)) items.sort(key=itemgetter(0)) for priority, item in items: self.append(item) if items: item = Gtk.SeparatorMenuItem() item.show() self.append(item) del items send_item = create_menuitem(_("Send a _File..."), get_icon("edit-copy", 16)) send_item.props.sensitive = False self.append(send_item) send_item.show() if row["objpush"]: send_item.connect("activate", lambda x: self.Blueman.send(self.SelectedDevice)) send_item.props.sensitive = True item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Pair"), get_icon("dialog-password", 16)) item.props.tooltip_text = _("Create pairing with the device") self.append(item) item.show() if not row["bonded"]: item.connect("activate", lambda x: self.Blueman.bond(self.SelectedDevice)) else: item.props.sensitive = False if not row["trusted"]: item = create_menuitem(_("_Trust"), get_icon("blueman-trust", 16)) item.connect("activate", lambda x: self.Blueman.toggle_trust(self.SelectedDevice)) self.append(item) item.show() else: item = create_menuitem(_("_Untrust"), get_icon("blueman-untrust", 16)) self.append(item) item.connect("activate", lambda x: self.Blueman.toggle_trust(self.SelectedDevice)) item.show() item.props.tooltip_text = _("Mark/Unmark this device as trusted") item = create_menuitem(_("_Setup..."), get_icon("document-properties", 16)) self.append(item) item.connect("activate", lambda x: self.Blueman.setup(self.SelectedDevice)) item.show() item.props.tooltip_text = _("Run the setup assistant for this device") def on_rename(_item, device): def on_response(dialog, response_id): if response_id == Gtk.ResponseType.ACCEPT: device.set('Alias', alias_entry.get_text()) elif response_id == 1: device.set('Alias', '') dialog.destroy() builder = Gtk.Builder() builder.set_translation_domain("blueman") bind_textdomain_codeset("blueman", "UTF-8") builder.add_from_file(UI_PATH + "/rename-device.ui") dialog = builder.get_object("dialog") dialog.set_transient_for(self.Blueman) dialog.props.icon_name = "blueman" alias_entry = builder.get_object("alias_entry") alias_entry.set_text(device['Alias']) dialog.connect("response", on_response) dialog.present() item = Gtk.MenuItem.new_with_label("Rename device...") item.connect('activate', on_rename, self.SelectedDevice) self.append(item) item.show() item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Remove..."), get_icon("edit-delete", 16)) item.connect("activate", lambda x: self.Blueman.remove(self.SelectedDevice)) self.append(item) item.show() item.props.tooltip_text = _("Remove this device from the known devices list") item = Gtk.SeparatorMenuItem() item.show() self.append(item) item = create_menuitem(_("_Disconnect"), get_icon("network-offline", 16)) item.props.tooltip_text = _("Forcefully disconnect the device") self.append(item) item.show() def on_disconnect(item): def finished(*args): self.unset_op(self.SelectedDevice) self.set_op(self.SelectedDevice, _("Disconnecting...")) self.Blueman.disconnect(self.SelectedDevice, reply_handler=finished, error_handler=finished) if row['connected']: item.connect("activate", on_disconnect) else: item.props.sensitive = False
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)