def notify(self, object_path, uuid): device = Device(object_path) logging.info("%s %s" % (device, uuid)) item = {} try: adapter = Adapter(device['Adapter']) except: logging.warning("adapter not found") return item["adapter"] = adapter["Address"] item["address"] = device['Address'] item["alias"] = device['Alias'] item["icon"] = device['Icon'] item["name"] = ServiceUUID(uuid).name item["uuid"] = uuid item["time"] = time.time() item["device"] = object_path item["mitem"] = None #menu item object for i in self.items: if i["adapter"] == item["adapter"] and \ i["address"] == item["address"] and \ i["uuid"] == item["uuid"]: i["time"] = item["time"] i["device"] = item["device"] self.initialize() return self.items.append(item) self.initialize() self.store_state()
def notify(self, object_path, uuid): device = Device(obj_path=object_path) logging.info(f"{device} {uuid}") item = {} try: adapter = self.parent.Manager.get_adapter(device['Adapter']) except DBusNoSuchAdapterError: logging.warning("adapter not found") return item["adapter"] = adapter["Address"] item["address"] = device['Address'] item["alias"] = device['Alias'] item["icon"] = device['Icon'] item["name"] = ServiceUUID(uuid).name item["uuid"] = uuid item["time"] = time.time() item["device"] = object_path item["mitem"] = None # menu item object for i in self.items: if i["adapter"] == item["adapter"] and \ i["address"] == item["address"] and \ i["uuid"] == item["uuid"]: i["time"] = item["time"] i["device"] = item["device"] self.initialize() return self.items.append(item) self.initialize() self.store_state()
def on_request_menu_items(self, manager_menu: ManagerDeviceMenu, device: Device) -> List[DeviceMenuItem]: audio_source = False for uuid in device['UUIDs']: if ServiceUUID(uuid).short_uuid in (AUDIO_SOURCE_SVCLASS_ID, AUDIO_SINK_SVCLASS_ID): audio_source = True break if device['Connected'] and audio_source: pa = PulseAudioUtils() if not pa.connected: self.deferred.append(device) return [] item = create_menuitem(_("Audio Profile"), "audio-card") item.props.tooltip_text = _("Select audio profile for PulseAudio") if not device['Address'] in self.devices: self.query_pa(device, item) else: self.generate_menu(device, item) else: return [] return [DeviceMenuItem(item, DeviceMenuItem.Group.ACTIONS, 300)]
def notify(self, object_path: str, uuid: str) -> None: device = Device(obj_path=object_path) logging.info(f"{device} {uuid}") try: adapter = self.parent.Manager.get_adapter(device['Adapter']) except DBusNoSuchAdapterError: logging.warning("adapter not found") return item: "Item" = { "adapter": adapter["Address"], "address": device['Address'], "alias": device['Alias'], "icon": device['Icon'], "name": ServiceUUID(uuid).name, "uuid": uuid, "time": time.time(), "device": object_path, "mitem": None } for i in self.items: if i["adapter"] == item["adapter"] and \ i["address"] == item["address"] and \ i["uuid"] == item["uuid"]: i["time"] = item["time"] i["device"] = item["device"] self.initialize() return self.items.append(item) self.initialize() self._store_state()
def _on_authorize_service( self, device: str, uuid: str, ok: Callable[[], None], err: Callable[[BluezErrorRejected], None]) -> None: def on_auth_action(action: str) -> None: logging.info(action) if action == "always": Device(obj_path=device).set("Trusted", True) if action == "always" or action == "accept": ok() else: err(BluezErrorRejected("Rejected")) logging.info("Agent.Authorize") dev_str = self.get_device_string(device) service = ServiceUUID(uuid).name notify_message = \ _("Authorization request for:") + f"\n{dev_str}\n" + _("Service:") + f" <b>{service}</b>" actions = [("always", _("Always accept")), ("accept", _("Accept")), ("deny", _("Deny"))] n = Notification(_("Bluetooth Authentication"), notify_message, 0, actions, on_auth_action, icon_name="blueman") n.show()
def _on_authorize_service(self, parameters, invocation): def on_auth_action(action): logging.info(action) if action == "always": device = bluez.Device(n._device) device.set("Trusted", True) if action == "always" or action == "accept": invocation.return_value(GLib.Variant('()', ())) else: invocation.return_dbus_error('org.bluez.Error.Rejected', 'Rejected') self.n = None device, uuid = parameters.unpack() logging.info("Agent.Authorize") dev_str = self.get_device_string(device) service = ServiceUUID(uuid).name notify_message = \ (_("Authorization request for:") + "\n%s\n" + _("Service:") + " <b>%s</b>") % (dev_str, service) actions = [["always", _("Always accept")], ["accept", _("Accept")], ["deny", _("Deny")]] n = Notification(_("Bluetooth Authentication"), notify_message, 0, actions, on_auth_action, icon_name="blueman") n.show() n._device = device
def _on_authorize_service(self, device, uuid, ok, err): def on_auth_action(action): logging.info(action) if action == "always": device = Device(obj_path=n._device) device.set("Trusted", True) if action == "always" or action == "accept": ok() else: err(BluezErrorRejected("Rejected")) logging.info("Agent.Authorize") dev_str = self.get_device_string(device) service = ServiceUUID(uuid).name notify_message = \ (_("Authorization request for:") + "\n%s\n" + _("Service:") + " <b>%s</b>") % (dev_str, service) actions = [["always", _("Always accept")], ["accept", _("Accept")], ["deny", _("Deny")]] n = Notification(_("Bluetooth Authentication"), notify_message, 0, actions, on_auth_action, icon_name="blueman") n.show() n._device = device
def notify(self, object_path: str, uuid: str) -> None: device = Device(obj_path=object_path) logging.info(f"{device} {uuid}") try: adapter = self.parent.Manager.get_adapter(device['Adapter']) except DBusNoSuchAdapterError: logging.warning("adapter not found") return item = { "adapter": adapter["Address"], "address": device['Address'], "alias": device['Alias'], "icon": device['Icon'], "name": ServiceUUID(uuid).name, "uuid": uuid, "time": str(time.time()), } stored_items = self.get_option("recent-connections") for i in stored_items: if i["adapter"] == item["adapter"] and \ i["address"] == item["address"] and \ i["uuid"] == item["uuid"]: i["time"] = item["time"] i["device"] = object_path break else: stored_items.append(item) self.set_option("recent-connections", stored_items) self._rebuild()
def _has_objpush(self, device): if device is None: return False for uuid in device["UUIDs"]: if ServiceUUID(uuid).short_uuid == OBEX_OBJPUSH_SVCLASS_ID: return True return False
def reply(*_args): Notification( _("Connected"), _("Automatically connected to %(service)s on %(device)s") % { "service": ServiceUUID(uuid).name, "device": device["Alias"] }, icon_name=device["Icon"]).show()
def reply(dev: Optional[Device] = device, service_name: str = ServiceUUID(uuid).name) -> None: assert isinstance( dev, Device) # https://github.com/python/mypy/issues/2608 Notification( _("Connected"), _("Automatically connected to %(service)s on %(device)s") % { "service": service_name, "device": dev["Alias"] }, icon_name=dev["Icon"]).show()
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 request_device_profile_menu(self, device: "Device") -> None: audio_source = False for uuid in device['UUIDs']: if ServiceUUID(uuid).short_uuid in (AUDIO_SOURCE_SVCLASS_ID, AUDIO_SINK_SVCLASS_ID): audio_source = True break if device['Connected'] and audio_source: pa = PulseAudioUtils() if not pa.connected: return if not device['Address'] in self._devices: self.query_pa(device) else: self.add_device_profile_menu(device)
def _update_power_levels(self, tree_iter: Gtk.TreeIter, device: Device, cinfo: conn_info) -> None: row = self.get(tree_iter, "cell_fader", "battery", "rssi", "lq", "tpl") bars = {} if device["ServicesResolved"] and any(ServiceUUID(uuid).short_uuid == BATTERY_SERVICE_SVCLASS_ID for uuid in device["UUIDs"]): bars["battery"] = Battery(obj_path=device.get_object_path())["Percentage"] # cinfo init may fail for bluetooth devices version 4 and up # FIXME Workaround is horrible and we should show something better if cinfo.failed: if not bars: bars = {"rssi": 100.0, "tpl": 100.0, "lq": 100.0} else: try: bars["rssi"] = max(50 + float(cinfo.get_rssi()) / 127 * 50, 10) except ConnInfoReadError: bars["rssi"] = 50 try: bars["lq"] = max(50 + float(cinfo.get_lq()) / 127 * 50, 10) except ConnInfoReadError: bars["lq"] = 50 try: bars["tpl"] = max(float(cinfo.get_tpl()) / 255 * 100, 10) except ConnInfoReadError: bars["tpl"] = 0 if row["battery"] == row["rssi"] == row["tpl"] == row["lq"] == 0: self._prepare_fader(row["cell_fader"]).animate(start=0.0, end=1.0, duration=400) w = 14 * self.get_scale_factor() h = 48 * self.get_scale_factor() for (name, perc) in bars.items(): if round(row[name], -1) != round(perc, -1): icon_name = f"blueman-{name}-{int(round(perc, -1))}.png" icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(os.path.join(PIXMAP_PATH, icon_name), w, h, True) self.set(tree_iter, **{name: perc, f"{name}_pb": icon})
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")
def applicable(device: Device) -> bool: return any( ServiceUUID(uuid).short_uuid == BATTERY_SERVICE_SVCLASS_ID for uuid in device["UUIDs"])
def format_uuids(uuids): return "\n".join( [uuid + ' ' + ServiceUUID(uuid).name for uuid in uuids])
def get_service(device, uuid): for name, cls in inspect.getmembers(blueman.services, inspect.isclass): if ServiceUUID(uuid).short_uuid == cls.__svclass_id__: return cls(device, uuid)
def format_uuids(uuids: Iterable[str]) -> str: return "\n".join([uuid + ' ' + ServiceUUID(uuid).name for uuid in uuids])
def short_uuid(self): return ServiceUUID(self.__uuid).short_uuid
def name(self): return ServiceUUID(self.__uuid).name
def get_service(device: Device, uuid: str) -> Optional[Service]: for name, cls in inspect.getmembers(blueman.services, inspect.isclass): if ServiceUUID(uuid).short_uuid == cls.__svclass_id__: svc: Service = cls(device, uuid) return svc return None
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")
def short_uuid(self) -> Optional[int]: return ServiceUUID(self.__uuid).short_uuid