Esempio n. 1
0
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)
Esempio n. 2
0
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")
Esempio n. 3
0
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")
Esempio n. 4
0
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)
Esempio n. 5
0
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)
Esempio n. 6
0
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
Esempio n. 7
0
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")
Esempio n. 8
0
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
Esempio n. 9
0
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)