class NetUsage(AppletPlugin, GObject.GObject): __depends__ = ["Menu"] __icon__ = "network-wireless" __description__ = _( "Allows you to monitor your (mobile broadband) network traffic usage. Useful for limited " "data access plans. This plugin tracks every device seperately.") __author__ = "Walmis" __autoload__ = False __gsignals__: GSignals = { 'monitor-added': (GObject.SignalFlags.NO_HOOKS, None, (Monitor, )), 'monitor-removed': (GObject.SignalFlags.NO_HOOKS, None, (Monitor, )), # monitor, tx, rx 'stats': (GObject.SignalFlags.NO_HOOKS, None, (Monitor, int, int)), } _any_network = None def on_load(self): GObject.GObject.__init__(self) self.monitors: List[Monitor] = [] self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_network_property_changed) self.parent.Plugins.Menu.add(self, 84, text=_("Network _Usage"), icon_name="network-wireless", tooltip=_("Shows network traffic usage"), callback=self.activate_ui) def _on_network_property_changed(self, _network, key, value, path): if key == "Interface" and value != "": d = Device(obj_path=path) self.monitor_interface(Monitor, d, value) def activate_ui(self): Dialog(self) def on_unload(self): del self._any_network self.parent.Plugins.Menu.unregister(self) def monitor_interface(self, montype, *args): m = montype(*args) self.monitors.append(m) m.connect("stats", self.on_stats) m.connect("disconnected", self.on_monitor_disconnected) self.emit("monitor-added", m) def on_ppp_connected(self, device, rfcomm, ppp_port): self.monitor_interface(Monitor, device, ppp_port) def on_monitor_disconnected(self, monitor): self.monitors.remove(monitor) self.emit("monitor-removed", monitor) def on_stats(self, monitor, tx, rx): self.emit("stats", monitor, tx, rx)
class NetUsage(AppletPlugin, GObject.GObject): __depends__ = ["Menu"] __icon__ = "network-wireless" __description__ = _("Allows you to monitor your (mobile broadband) network traffic usage. Useful for limited " "data access plans. This plugin tracks every device seperately.") __author__ = "Walmis" __autoload__ = False __gsignals__ = { 'monitor-added': (GObject.SignalFlags.NO_HOOKS, None, (GObject.TYPE_PYOBJECT,)), 'monitor-removed': (GObject.SignalFlags.NO_HOOKS, None, (GObject.TYPE_PYOBJECT,)), # monitor, tx, rx 'stats': (GObject.SignalFlags.NO_HOOKS, None, (GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT,)), } _any_network = None def on_load(self): GObject.GObject.__init__(self) self.monitors = [] self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_network_property_changed) self.parent.Plugins.Menu.add(self, 84, text=_("Network _Usage"), icon_name="network-wireless", tooltip=_("Shows network traffic usage"), callback=self.activate_ui) def _on_network_property_changed(self, _network, key, value, path): if key == "Interface" and value != "": d = Device(path) self.monitor_interface(Monitor, d, value) def activate_ui(self): Dialog(self) def on_unload(self): del self._any_network self.parent.Plugins.Menu.unregister(self) def monitor_interface(self, montype, *args): m = montype(*args) self.monitors.append(m) m.connect("stats", self.on_stats) m.connect("disconnected", self.on_monitor_disconnected) self.emit("monitor-added", m) def on_ppp_connected(self, device, rfcomm, ppp_port): self.monitor_interface(Monitor, device, ppp_port) def on_monitor_disconnected(self, monitor): self.monitors.remove(monitor) self.emit("monitor-removed", monitor) def on_stats(self, monitor, tx, rx): self.emit("stats", monitor, tx, rx)
class DhcpClient(AppletPlugin): __description__ = _("Provides a basic dhcp client for Bluetooth PAN connections.") __icon__ = "network" __author__ = "Walmis" _any_network = None def on_load(self, applet): self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_network_prop_changed) self.quering = [] def on_unload(self): del self._any_network @dbus.service.method('org.blueman.Applet', in_signature="s") def DhcpClient(self, interface): self.dhcp_acquire(interface) def _on_network_prop_changed(self, _network, key, value, _path): if key == "Interface": if value != "": self.dhcp_acquire(value) def dhcp_acquire(self, device): if device not in self.quering: self.quering.append(device) else: return if device != "": def reply(ip_address): Notification(_("Bluetooth Network"), _("Interface %(0)s bound to IP address %(1)s") % {"0": device, "1": ip_address}, pixbuf=get_icon("network-workgroup", 48), status_icon=self.Applet.Plugins.StatusIcon) self.quering.remove(device) def err(msg): dprint(msg) Notification(_("Bluetooth Network"), _("Failed to obtain an IP address on %s") % (device), pixbuf=get_icon("network-workgroup", 48), status_icon=self.Applet.Plugins.StatusIcon) self.quering.remove(device) Notification(_("Bluetooth Network"), _("Trying to obtain an IP address on %s\nPlease wait..." % device), pixbuf=get_icon("network-workgroup", 48), status_icon=self.Applet.Plugins.StatusIcon) m = Mechanism() m.DhcpClient(device, reply_handler=reply, error_handler=err, timeout=120)
class DhcpClient(AppletPlugin): __description__ = _("Provides a basic dhcp client for Bluetooth PAN connections.") __icon__ = "network" __author__ = "Walmis" _any_network = None def on_load(self, applet): self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_network_prop_changed) self.quering = [] def on_unload(self): del self._any_network @dbus.service.method('org.blueman.Applet', in_signature="s") def DhcpClient(self, interface): self.dhcp_acquire(interface) def _on_network_prop_changed(self, _network, key, value, _path): if key == "Interface": if value != "": self.dhcp_acquire(value) def dhcp_acquire(self, device): if device not in self.quering: self.quering.append(device) else: return if device != "": def reply(_obj, result, _user_data): logging.info(result) Notification(_("Bluetooth Network"), _("Interface %(0)s bound to IP address %(1)s") % {"0": device, "1": result}, icon_name="network-workgroup", pos_hint=self.Applet.Plugins.StatusIcon.geometry).show() self.quering.remove(device) def err(_obj, result, _user_data): logging.warning(result) Notification(_("Bluetooth Network"), _("Failed to obtain an IP address on %s") % (device), icon_name="network-workgroup", pos_hint=self.Applet.Plugins.StatusIcon.geometry).show() self.quering.remove(device) Notification(_("Bluetooth Network"), _("Trying to obtain an IP address on %s\nPlease wait..." % device), icon_name="network-workgroup", pos_hint=self.Applet.Plugins.StatusIcon.geometry).show() m = Mechanism() m.DhcpClient(str('(s)'), device, result_handler=reply, error_handler=err, timeout=120)
class DhcpClient(AppletPlugin): __description__ = _("Provides a basic dhcp client for Bluetooth PAN connections.") __icon__ = "network" __author__ = "Walmis" _any_network = None def on_load(self): self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_network_prop_changed) self.quering = [] def on_unload(self): del self._any_network @dbus.service.method('org.blueman.Applet', in_signature="s") def DhcpClient(self, interface): self.dhcp_acquire(interface) def _on_network_prop_changed(self, _network, key, value, _path): if key == "Interface": if value != "": self.dhcp_acquire(value) def dhcp_acquire(self, device): if device not in self.quering: self.quering.append(device) else: return if device != "": def reply(_obj, result, _user_data): logging.info(result) Notification(_("Bluetooth Network"), _("Interface %(0)s bound to IP address %(1)s") % {"0": device, "1": result}, icon_name="network-workgroup").show() self.quering.remove(device) def err(_obj, result, _user_data): logging.warning(result) Notification(_("Bluetooth Network"), _("Failed to obtain an IP address on %s") % device, icon_name="network-workgroup").show() self.quering.remove(device) Notification(_("Bluetooth Network"), _("Trying to obtain an IP address on %s\nPlease wait…" % device), icon_name="network-workgroup").show() m = Mechanism() m.DhcpClient('(s)', device, result_handler=reply, error_handler=err, timeout=120)
class DhcpClient(AppletPlugin): __description__ = _("Provides a basic dhcp client for Bluetooth PAN connections.") __icon__ = "network-workgroup" __author__ = "Walmis" _any_network = None def on_load(self) -> None: self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_network_prop_changed) self.quering: List[str] = [] self._add_dbus_method("DhcpClient", ("s",), "", self.dhcp_acquire) def on_unload(self) -> None: del self._any_network def _on_network_prop_changed(self, _network: AnyNetwork, key: str, value: Any, _path: str) -> None: if key == "Interface": if value != "": self.dhcp_acquire(value) def dhcp_acquire(self, device: str) -> None: if device not in self.quering: self.quering.append(device) else: return if device != "": def reply(_obj: Mechanism, result: str, _user_data: None) -> None: logging.info(result) Notification(_("Bluetooth Network"), _("Interface %(0)s bound to IP address %(1)s") % {"0": device, "1": result}, icon_name="network-workgroup").show() self.quering.remove(device) def err(_obj: Mechanism, result: GLib.Error, _user_data: None) -> None: logging.warning(result) Notification(_("Bluetooth Network"), _("Failed to obtain an IP address on %s") % device, icon_name="network-workgroup").show() self.quering.remove(device) Notification(_("Bluetooth Network"), _("Trying to obtain an IP address on %s\nPlease wait…" % device), icon_name="network-workgroup").show() m = Mechanism() m.DhcpClient('(s)', device, result_handler=reply, error_handler=err, timeout=120 * 1000)
class NetUsage(AppletPlugin, GObject.GObject): __depends__ = ["Menu"] __icon__ = "network-wireless" __description__ = _( "Allows you to monitor your (mobile broadband) network traffic usage. Useful for limited data access plans. This plugin tracks every device seperately.") __author__ = "Walmis" __autoload__ = False __gsignals__ = { str('monitor-added'): (GObject.SignalFlags.NO_HOOKS, None, (GObject.TYPE_PYOBJECT,)), str('monitor-removed'): (GObject.SignalFlags.NO_HOOKS, None, (GObject.TYPE_PYOBJECT,)), #monitor, tx, rx str('stats'): ( GObject.SignalFlags.NO_HOOKS, None, (GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT,)), } _any_network = None def on_load(self, applet): GObject.GObject.__init__(self) self.monitors = [] self.devices = weakref.WeakValueDictionary() self.bus = dbus.SystemBus() self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_network_property_changed) item = create_menuitem(_("Network _Usage"), get_icon("network-wireless", 16)) item.props.tooltip_text = _("Shows network traffic usage") item.connect("activate", self.activate_ui) self.Applet.Plugins.Menu.Register(self, item, 84, True) self.bus.add_signal_receiver(self.on_nm_ppp_stats, "PppStats", "org.freedesktop.NetworkManager.Device.Serial", path_keyword="path") self.nm_paths = {} def on_nm_ppp_stats(self, down, up, path): if path not in self.nm_paths: props = self.bus.call_blocking("org.freedesktop.NetworkManager", path, "org.freedesktop.DBus.Properties", "GetAll", "s", ["org.freedesktop.NetworkManager.Device"]) if props["Driver"] == "bluetooth" and "rfcomm" in props["Interface"]: self.nm_paths[path] = True portid = int(props["Interface"].strip("rfcomm")) ls = rfcomm_list() for dev in ls: if dev["id"] == portid: adapter = self.Applet.Manager.get_adapter(dev["src"]) device = adapter.find_device(dev["dst"]) self.monitor_interface(NMMonitor, device, path) return else: self.nm_paths[path] = False def _on_network_property_changed(self, _network, key, value, path): if key == "Interface" and value != "": d = Device(path) self.monitor_interface(Monitor, d, value) def activate_ui(self, item): Dialog(self) def on_unload(self): self.bus.remove_signal_receiver(self.on_nm_ppp_stats, "PppStats", "org.freedesktop.NetworkManager.Device.Serial", path_keyword="path") del self._any_network self.Applet.Plugins.Menu.Unregister(self) def monitor_interface(self, montype, *args): m = montype(*args) self.monitors.append(m) m.connect("stats", self.on_stats) m.connect("disconnected", self.on_monitor_disconnected) self.emit("monitor-added", m) def on_ppp_connected(self, device, rfcomm, ppp_port): self.monitor_interface(Monitor, device, ppp_port) def on_monitor_disconnected(self, monitor): self.monitors.remove(monitor) self.emit("monitor-removed", monitor) def on_stats(self, monitor, tx, rx): self.emit("stats", monitor, tx, rx)
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 NetUsage(AppletPlugin, GObject.GObject): __depends__ = ["Menu"] __icon__ = "network-wireless" __description__ = _( "Allows you to monitor your (mobile broadband) network traffic usage. Useful for limited data access plans. This plugin tracks every device seperately.") __author__ = "Walmis" __autoload__ = False __gsignals__ = { 'monitor-added': (GObject.SignalFlags.NO_HOOKS, None, (GObject.TYPE_PYOBJECT,)), 'monitor-removed': (GObject.SignalFlags.NO_HOOKS, None, (GObject.TYPE_PYOBJECT,)), #monitor, tx, rx 'stats': (GObject.SignalFlags.NO_HOOKS, None, (GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT,)), } _any_network = None def on_load(self, applet): GObject.GObject.__init__(self) self.monitors = [] self.devices = weakref.WeakValueDictionary() self.bus = dbus.SystemBus() self._any_network = AnyNetwork() self._any_network.connect_signal('property-changed', self._on_network_property_changed) item = create_menuitem(_("Network _Usage"), "network-wireless") item.props.tooltip_text = _("Shows network traffic usage") item.connect("activate", self.activate_ui) self.Applet.Plugins.Menu.Register(self, item, 84, True) self.bus.add_signal_receiver(self.on_nm_ppp_stats, "PppStats", "org.freedesktop.NetworkManager.Device.Serial", path_keyword="path") self.nm_paths = {} def on_nm_ppp_stats(self, down, up, path): if path not in self.nm_paths: props = self.bus.call_blocking("org.freedesktop.NetworkManager", path, "org.freedesktop.DBus.Properties", "GetAll", "s", ["org.freedesktop.NetworkManager.Device"]) if props["Driver"] == "bluetooth" and "rfcomm" in props["Interface"]: self.nm_paths[path] = True portid = int(props["Interface"].strip("rfcomm")) ls = rfcomm_list() for dev in ls: if dev["id"] == portid: adapter = self.Applet.Manager.get_adapter(dev["src"]) device = self.Applet.Manager.find_device(dev["dst"], adapter.get_object_path()) self.monitor_interface(NMMonitor, device, path) return else: self.nm_paths[path] = False def _on_network_property_changed(self, _network, key, value, path): if key == "Interface" and value != "": d = Device(path) self.monitor_interface(Monitor, d, value) def activate_ui(self, item): Dialog(self) def on_unload(self): self.bus.remove_signal_receiver(self.on_nm_ppp_stats, "PppStats", "org.freedesktop.NetworkManager.Device.Serial", path_keyword="path") del self._any_network self.Applet.Plugins.Menu.Unregister(self) def monitor_interface(self, montype, *args): m = montype(*args) self.monitors.append(m) m.connect("stats", self.on_stats) m.connect("disconnected", self.on_monitor_disconnected) self.emit("monitor-added", m) def on_ppp_connected(self, device, rfcomm, ppp_port): self.monitor_interface(Monitor, device, ppp_port) def on_monitor_disconnected(self, monitor): self.monitors.remove(monitor) self.emit("monitor-removed", monitor) def on_stats(self, monitor, tx, rx): self.emit("stats", monitor, tx, rx)
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 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