class ConnectionNotifier(AppletPlugin):
    __author__ = "cschramm"
    __icon__ = "bluetooth-symbolic"
    __description__ = _(
        "Shows desktop notifications when devices get connected or disconnected."
    )

    _sig = None
    _notifications: Dict[str, Union[_NotificationBubble,
                                    _NotificationDialog]] = {}

    def on_load(self) -> None:
        self._manager = Manager()
        self._sig = self._manager.connect_signal("battery-created",
                                                 self._on_battery_created)

    def on_unload(self) -> None:
        if self._sig is not None:
            self._manager.disconnect_signal(self._sig)

    def on_device_property_changed(self, path: str, key: str,
                                   value: Any) -> None:
        device = Device(obj_path=path)
        battery = Battery(obj_path=path)

        if key == "Connected":
            if value:
                self._notifications[path] = notification = Notification(
                    device["Alias"], _('Connected'), icon_name=device["Icon"])
                notification.show()

                sig = battery.connect_signal("property-changed",
                                             self._on_battery_property_changed)

                def disconnect_signal() -> bool:
                    battery.disconnect_signal(sig)
                    return False

                GLib.timeout_add_seconds(5, disconnect_signal)
            else:
                Notification(device["Alias"],
                             _('Disconnected'),
                             icon_name=device["Icon"]).show()

    def _on_battery_created(self, _manager: Manager, obj_path: str) -> None:
        battery = Battery(obj_path=obj_path)
        self._on_battery_property_changed(battery, "Percentage",
                                          battery["Percentage"], obj_path)

    def _on_battery_property_changed(self, _battery: Battery, key: str,
                                     value: Any, path: str) -> None:
        if key == "Percentage":
            notification = self._notifications[path]
            if notification:
                notification.set_message(f"{_('Connected')} {value}%")
                notification.set_notification_icon("battery")
示例#2
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)
示例#3
0
class BluemanAdapters(Gtk.Window):
    def __init__(self, selected_hci_dev, socket_id):
        super().__init__(title=_("Bluetooth Adapters"),
                         border_width=5,
                         resizable=False,
                         icon_name="blueman-device",
                         name="BluemanAdapters")
        self.connect("delete-event", self._on_close)

        self.notebook = Gtk.Notebook(visible=True)

        if socket_id:
            plug = Gtk.Plug.new(socket_id)
            plug.show()
            plug.connect('delete-event', self._on_close)
            plug.add(self.notebook)
        else:
            self.add(self.notebook)
            self.connect("delete-event", self._on_close)
            self.show()

        self.tabs: Dict[str, "Tab"] = {}
        self._adapters: Dict[str, Adapter] = {}

        setup_icon_path()
        Manager.watch_name_owner(self._on_dbus_name_appeared,
                                 self._on_dbus_name_vanished)
        self.manager = Manager()

        check_single_instance("blueman-adapters",
                              lambda time: self.present_with_time(time))

        check_bluetooth_status(
            _("Bluetooth needs to be turned on for the adapter manager to work"
              ), lambda: exit())

        adapters = self.manager.get_adapters()
        if not adapters:
            logging.error('No adapter(s) found')
            exit(1)

        self.manager.connect_signal('adapter-added', self.on_adapter_added)
        self.manager.connect_signal('adapter-removed', self.on_adapter_removed)
        for adapter in adapters:
            path = adapter.get_object_path()
            hci_dev = os.path.basename(path)
            self._adapters[hci_dev] = adapter
            self.on_adapter_added(self.manager, path)

        # activate a particular tab according to command line option
        if selected_hci_dev is not None:
            if selected_hci_dev in self.tabs:
                hci_dev_num = int(selected_hci_dev[3:])
                self.notebook.set_current_page(hci_dev_num)
            else:
                logging.error('Error: the selected adapter does not exist')
        self.notebook.show_all()

    def _on_close(self, *args, **kwargs):
        Gtk.main_quit()

    def on_property_changed(self, adapter, name, value, path):
        hci_dev = os.path.basename(path)
        if name == "Discoverable" and value == 0:
            self.tabs[hci_dev]["hidden_radio"].set_active(True)
        elif name == "Alias":
            self.tabs[hci_dev]["label"].set_text(value)

    def on_adapter_added(self, _manager, adapter_path):
        hci_dev = os.path.basename(adapter_path)
        if hci_dev not in self._adapters:
            self._adapters[hci_dev] = Adapter(obj_path=adapter_path)

        self._adapters[hci_dev].connect_signal("property-changed",
                                               self.on_property_changed)
        self.add_to_notebook(self._adapters[hci_dev])

    def on_adapter_removed(self, _manager, adapter_path):
        hci_dev = os.path.basename(adapter_path)
        self.remove_from_notebook(self._adapters[hci_dev])

    def _on_dbus_name_appeared(self, _connection, name, owner):
        logging.info("%s %s" % (name, owner))

    def _on_dbus_name_vanished(self, _connection, name):
        logging.info(name)
        # FIXME: show error dialog and exit

    def build_adapter_tab(self, adapter):
        def on_hidden_toggle(radio):
            if not radio.props.active:
                return
            adapter['DiscoverableTimeout'] = 0
            adapter['Discoverable'] = False
            hscale.set_sensitive(False)

        def on_always_toggle(radio):
            if not radio.props.active:
                return
            adapter['DiscoverableTimeout'] = 0
            adapter['Discoverable'] = True
            hscale.set_sensitive(False)

        def on_temporary_toggle(radio):
            if not radio.props.active:
                return
            adapter['Discoverable'] = True
            hscale.set_sensitive(True)
            hscale.set_value(3)

        def on_scale_format_value(scale, value):
            if value == 0:
                if adapter['Discoverable']:
                    return _("Always")
                else:
                    return _("Hidden")
            else:
                return gettext.ngettext("%d Minute", "%d Minutes",
                                        value) % value

        def on_scale_value_changed(scale):
            val = scale.get_value()
            logging.info('value: %s' % val)
            if val == 0 and adapter['Discoverable']:
                always_radio.props.active = True
            timeout = int(val * 60)
            adapter['DiscoverableTimeout'] = timeout

        def on_name_changed(entry):
            adapter['Alias'] = entry.get_text()

        ui = {}

        builder = Gtk.Builder()
        builder.set_translation_domain("blueman")
        builder.add_from_file(UI_PATH + "/adapters-tab.ui")

        hscale = builder.get_object("hscale")
        hscale.connect("format-value", on_scale_format_value)
        hscale.connect("value-changed", on_scale_value_changed)
        hscale.set_range(0, 30)
        hscale.set_increments(1, 1)

        hidden_radio = builder.get_object("hidden")
        always_radio = builder.get_object("always")
        temporary_radio = builder.get_object("temporary")

        if adapter['Discoverable'] and adapter['DiscoverableTimeout'] > 0:
            temporary_radio.set_active(True)
            hscale.set_value(adapter['DiscoverableTimeout'])
            hscale.set_sensitive(True)
        elif adapter['Discoverable'] and adapter['DiscoverableTimeout'] == 0:
            always_radio.set_active(True)
        else:
            hidden_radio.set_active(True)

        name_entry = builder.get_object("name_entry")
        name_entry.set_text(adapter.get_name())

        hidden_radio.connect("toggled", on_hidden_toggle)
        always_radio.connect("toggled", on_always_toggle)
        temporary_radio.connect("toggled", on_temporary_toggle)
        name_entry.connect("changed", on_name_changed)

        ui['grid'] = builder.get_object("grid")
        ui["hidden_radio"] = hidden_radio
        ui["always_radio"] = always_radio
        ui["temparary_radio"] = temporary_radio
        return ui

    def add_to_notebook(self, adapter):
        hci_dev = os.path.basename(adapter.get_object_path())
        hci_dev_num = int(hci_dev[3:])

        if hci_dev not in self.tabs:
            self.tabs[hci_dev] = self.build_adapter_tab(adapter)
        else:
            if self.tabs[hci_dev]['visible']:
                return
                # might need to update settings at this point
        ui = self.tabs[hci_dev]
        ui['visible'] = True
        name = adapter.get_name()
        if name == '':
            name = _('Adapter') + ' %d' % (hci_dev_num + 1)
        label = Gtk.Label(label=name)
        ui['label'] = label
        label.set_max_width_chars(20)
        label.props.hexpand = True
        label.set_ellipsize(Pango.EllipsizeMode.END)
        self.notebook.insert_page(ui['grid'], label, hci_dev_num)

    def remove_from_notebook(self, adapter):
        hci_dev = os.path.basename(adapter.get_object_path())
        hci_dev_num = int(hci_dev[3:])

        self.tabs[hci_dev]['visible'] = False
        self.notebook.remove_page(hci_dev_num)
示例#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)
示例#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)
示例#6
0
class BluemanAdapters(Gtk.Application):
    def __init__(self, selected_hci_dev: Optional[str],
                 socket_id: Optional[int]) -> None:
        super().__init__(application_id="org.blueman.Adapters")

        def do_quit(_: object) -> bool:
            self.quit()
            return False

        s = GLib.unix_signal_source_new(signal.SIGINT)
        s.set_callback(do_quit)
        s.attach()

        self.socket_id = socket_id
        self.selected_hci_dev = selected_hci_dev

        self.notebook = Gtk.Notebook(visible=True)

        self.window: Optional[Gtk.ApplicationWindow] = None

        self.tabs: Dict[str, "Tab"] = {}
        self._adapters: Dict[str, Adapter] = {}

        setup_icon_path()
        Manager.watch_name_owner(self._on_dbus_name_appeared,
                                 self._on_dbus_name_vanished)
        self.manager = Manager()

        check_bluetooth_status(
            _("Bluetooth needs to be turned on for the adapter manager to work"
              ), bmexit)

        adapters = self.manager.get_adapters()
        if not adapters:
            logging.error('No adapter(s) found')
            bmexit()

        self.manager.connect_signal('adapter-added', self.on_adapter_added)
        self.manager.connect_signal('adapter-removed', self.on_adapter_removed)
        for adapter in adapters:
            path = adapter.get_object_path()
            self.on_adapter_added(self.manager, path)

        # activate a particular tab according to command line option
        if selected_hci_dev is not None:
            if selected_hci_dev in self.tabs:
                hci_dev_num = int(selected_hci_dev[3:])
                self.notebook.set_current_page(hci_dev_num)
            else:
                logging.error('Error: the selected adapter does not exist')
        self.notebook.show_all()

    def do_activate(self) -> None:
        def app_release(_plug: Gtk.Plug, _event: Gdk.Event) -> bool:
            self.release()
            return False

        if self.socket_id:
            self.hold()
            plug = Gtk.Plug.new(self.socket_id)
            plug.show()
            plug.connect('delete-event', app_release)
            plug.add(self.notebook)
            return

        if not self.window:
            self.window = Gtk.ApplicationWindow(application=self,
                                                title=_("Bluetooth Adapters"),
                                                border_width=10,
                                                resizable=False,
                                                icon_name="blueman-device",
                                                name="BluemanAdapters")
            self.window.add(self.notebook)
            self.window.set_position(Gtk.WindowPosition.CENTER)

        self.window.present_with_time(Gtk.get_current_event_time())

    def on_property_changed(self, _adapter: Adapter, name: str, value: Any,
                            path: str) -> None:
        hci_dev = os.path.basename(path)
        if name == "Discoverable" and value == 0:
            self.tabs[hci_dev]["hidden_radio"].set_active(True)
        elif name == "Alias":
            self.tabs[hci_dev]["label"].set_text(value)

    def on_adapter_added(self, _manager: Manager, adapter_path: str) -> None:
        hci_dev = os.path.basename(adapter_path)
        if hci_dev not in self._adapters:
            self._adapters[hci_dev] = Adapter(obj_path=adapter_path)

        self._adapters[hci_dev].connect_signal("property-changed",
                                               self.on_property_changed)
        self.add_to_notebook(self._adapters[hci_dev])

    def on_adapter_removed(self, _manager: Manager, adapter_path: str) -> None:
        hci_dev = os.path.basename(adapter_path)
        self.remove_from_notebook(self._adapters[hci_dev])

    def _on_dbus_name_appeared(self, _connection: Gio.DBusConnection,
                               name: str, owner: str) -> None:
        logging.info(f"{name} {owner}")

    def _on_dbus_name_vanished(self, _connection: Gio.DBusConnection,
                               name: str) -> None:
        logging.info(name)
        # FIXME: show error dialog and exit

    def build_adapter_tab(self, adapter: Adapter) -> "Tab":
        def on_hidden_toggle(radio: Gtk.RadioButton) -> None:
            if not radio.props.active:
                return
            adapter['DiscoverableTimeout'] = 0
            adapter['Discoverable'] = False
            hscale.set_sensitive(False)

        def on_always_toggle(radio: Gtk.RadioButton) -> None:
            if not radio.props.active:
                return
            adapter['DiscoverableTimeout'] = 0
            adapter['Discoverable'] = True
            hscale.set_sensitive(False)

        def on_temporary_toggle(radio: Gtk.RadioButton) -> None:
            if not radio.props.active:
                return
            adapter['Discoverable'] = True
            hscale.set_sensitive(True)
            hscale.set_value(3)

        def on_scale_format_value(_scale: Gtk.Scale, value: float) -> str:
            if value == 0:
                if adapter['Discoverable']:
                    return _("Always")
                else:
                    return _("Hidden")
            else:
                return gettext.ngettext("%(minutes)d Minute",
                                        "%(minutes)d Minutes", int(value)) % {
                                            "minutes": value
                                        }

        def on_scale_value_changed(scale: Gtk.Scale) -> None:
            val = scale.get_value()
            logging.info(f"value: {val}")
            if val == 0 and adapter['Discoverable']:
                always_radio.props.active = True
            timeout = int(val * 60)
            adapter['DiscoverableTimeout'] = timeout

        def on_name_changed(entry: Gtk.Entry) -> None:
            adapter['Alias'] = entry.get_text()

        builder = Builder("adapters-tab.ui")

        hscale = builder.get_widget("hscale", Gtk.Scale)
        hscale.connect("format-value", on_scale_format_value)
        hscale.connect("value-changed", on_scale_value_changed)
        hscale.set_range(0, 30)
        hscale.set_increments(1, 1)

        hidden_radio = builder.get_widget("hidden", Gtk.RadioButton)
        always_radio = builder.get_widget("always", Gtk.RadioButton)
        temporary_radio = builder.get_widget("temporary", Gtk.RadioButton)

        if adapter['Discoverable'] and adapter['DiscoverableTimeout'] > 0:
            temporary_radio.set_active(True)
            hscale.set_value(adapter['DiscoverableTimeout'])
            hscale.set_sensitive(True)
        elif adapter['Discoverable'] and adapter['DiscoverableTimeout'] == 0:
            always_radio.set_active(True)
        else:
            hidden_radio.set_active(True)

        name_entry = builder.get_widget("name_entry", Gtk.Entry)
        name_entry.set_text(adapter.get_name())

        hidden_radio.connect("toggled", on_hidden_toggle)
        always_radio.connect("toggled", on_always_toggle)
        temporary_radio.connect("toggled", on_temporary_toggle)
        name_entry.connect("changed", on_name_changed)

        return {
            "grid": builder.get_widget("grid", Gtk.Grid),
            "hidden_radio": hidden_radio,
            "always_radio": always_radio,
            "temparary_radio": temporary_radio,
            "visible": False,
            "label": Gtk.Label()
        }

    def add_to_notebook(self, adapter: Adapter) -> None:
        hci_dev = os.path.basename(adapter.get_object_path())
        hci_dev_num = int(hci_dev[3:])

        if hci_dev not in self.tabs:
            self.tabs[hci_dev] = self.build_adapter_tab(adapter)
        else:
            if self.tabs[hci_dev]['visible']:
                return
                # might need to update settings at this point
        ui = self.tabs[hci_dev]
        ui['visible'] = True
        name = adapter.get_name()
        if name == '':
            name = _('Adapter') + ' %d' % (hci_dev_num + 1)
        label = Gtk.Label(label=name)
        ui['label'] = label
        label.set_max_width_chars(20)
        label.props.hexpand = True
        label.set_ellipsize(Pango.EllipsizeMode.END)
        self.notebook.insert_page(ui['grid'], label, hci_dev_num)

    def remove_from_notebook(self, adapter: Adapter) -> None:
        hci_dev = os.path.basename(adapter.get_object_path())
        hci_dev_num = int(hci_dev[3:])

        self.tabs[hci_dev]['visible'] = False
        self.notebook.remove_page(hci_dev_num)
示例#7
0
class ManagerMenu:
    def __init__(self, blueman):
        self.blueman = blueman
        self.Config = Config("org.blueman.general")

        self.adapter_items: Dict[str, Tuple[Gtk.RadioMenuItem, Adapter]] = {}
        self._adapters_group: List[Gtk.RadioMenuItem] = []
        self._insert_adapter_item_pos = 2
        self.Search = None

        self.item_adapter = self.blueman.Builder.get_object("item_adapter")
        self.item_device = self.blueman.Builder.get_object("item_device")

        self.item_view = self.blueman.Builder.get_object("item_view")
        self.item_help = self.blueman.Builder.get_object("item_help")

        help_menu = Gtk.Menu()

        self.item_help.set_submenu(help_menu)
        help_menu.show()

        report_item = create_menuitem(_("_Report a Problem"), "dialog-warning")
        report_item.show()
        help_menu.append(report_item)

        report_item.connect("activate",
                            lambda x: launch(f"xdg-open {WEBSITE}/issues"))

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        help_menu.append(sep)

        help_item = create_menuitem(_("_Help"), "help-about")
        help_item.show()
        help_menu.append(help_item)
        help_item.connect(
            "activate",
            lambda x: show_about_dialog('Blueman ' + _('Device Manager'),
                                        parent=self.blueman.get_toplevel()))

        view_menu = Gtk.Menu()
        self.item_view.set_submenu(view_menu)
        view_menu.show()

        item_toolbar = Gtk.CheckMenuItem.new_with_mnemonic(_("Show _Toolbar"))
        item_toolbar.show()
        view_menu.append(item_toolbar)
        self.blueman.Config.bind_to_widget("show-toolbar", item_toolbar,
                                           "active")

        item_statusbar = Gtk.CheckMenuItem.new_with_mnemonic(
            _("Show _Statusbar"))
        item_statusbar.show()
        view_menu.append(item_statusbar)
        self.blueman.Config.bind_to_widget("show-statusbar", item_statusbar,
                                           "active")

        item_services = Gtk.SeparatorMenuItem()
        view_menu.append(item_services)
        item_services.show()

        sorting_group: List[Gtk.RadioMenuItem] = []
        item_sort = Gtk.MenuItem.new_with_mnemonic(_("S_ort By"))
        view_menu.append(item_sort)
        item_sort.show()

        sorting_menu = Gtk.Menu()
        item_sort.set_submenu(sorting_menu)

        self._sort_alias_item = Gtk.RadioMenuItem.new_with_mnemonic(
            sorting_group, _("_Name"))
        self._sort_alias_item.show()
        sorting_group = self._sort_alias_item.get_group()
        sorting_menu.append(self._sort_alias_item)

        self._sort_timestamp_item = Gtk.RadioMenuItem.new_with_mnemonic(
            sorting_group, _("_Added"))
        self._sort_timestamp_item.show()
        sorting_menu.append(self._sort_timestamp_item)

        sort_config = self.Config['sort-by']
        if sort_config == "alias":
            self._sort_alias_item.props.active = True
        else:
            self._sort_timestamp_item.props.active = True

        sort_sep = Gtk.SeparatorMenuItem()
        sort_sep.show()
        sorting_menu.append(sort_sep)

        self._sort_type_item = Gtk.CheckMenuItem.new_with_mnemonic(
            _("_Descending"))
        self._sort_type_item.show()
        sorting_menu.append(self._sort_type_item)

        if self.Config['sort-order'] == "ascending":
            self._sort_type_item.props.active = False
        else:
            self._sort_type_item.props.active = True

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        view_menu.append(sep)

        item_plugins = create_menuitem(_("_Plugins"), 'blueman-plugin')
        item_plugins.show()
        view_menu.append(item_plugins)
        item_plugins.connect('activate', self._on_plugin_dialog_activate)

        item_services = create_menuitem(
            _("_Local Services") + "…", "preferences-desktop")
        item_services.connect(
            'activate', lambda *args: launch("blueman-services",
                                             name=_("Service Preferences")))
        view_menu.append(item_services)
        item_services.show()

        adapter_menu = Gtk.Menu()
        self.item_adapter.set_submenu(adapter_menu)
        self.item_adapter.props.sensitive = False

        search_item = create_menuitem(_("_Search"), "edit-find")
        search_item.connect("activate", lambda x: self.blueman.inquiry())
        search_item.show()
        adapter_menu.prepend(search_item)
        self.Search = search_item

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        adapter_menu.append(sep)

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        adapter_menu.append(sep)

        adapter_settings = create_menuitem(_("_Preferences"),
                                           "preferences-system")
        adapter_settings.connect("activate",
                                 lambda x: self.blueman.adapter_properties())
        adapter_settings.show()
        adapter_menu.append(adapter_settings)

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        adapter_menu.append(sep)

        exit_item = create_menuitem(_("_Exit"), "application-exit")
        exit_item.connect("activate", lambda x: Gtk.main_quit())
        exit_item.show()
        adapter_menu.append(exit_item)

        self.item_adapter.show()
        self.item_view.show()
        self.item_help.show()
        self.item_device.show()
        self.item_device.props.sensitive = False

        self._manager = Manager()
        self._manager.connect_signal("adapter-added", self.on_adapter_added)
        self._manager.connect_signal("adapter-removed",
                                     self.on_adapter_removed)

        blueman.List.connect("device-selected", self.on_device_selected)

        for adapter in self._manager.get_adapters():
            self.on_adapter_added(None, adapter.get_object_path())

        self.device_menu = None

        self.Config.connect("changed", self._on_settings_changed)
        self._sort_alias_item.connect("activate", self._on_sorting_changed,
                                      "alias")
        self._sort_timestamp_item.connect("activate", self._on_sorting_changed,
                                          "timestamp")
        self._sort_type_item.connect("activate", self._on_sorting_changed,
                                     "sort-type")

    def _on_sorting_changed(self, btn, sort_opt):
        if sort_opt == 'alias' and btn.props.active:
            self.Config['sort-by'] = "alias"
        elif sort_opt == "timestamp" and btn.props.active:
            self.Config['sort-by'] = "timestamp"
        elif sort_opt == 'sort-type':
            # FIXME bind widget to gsetting
            if btn.props.active:
                self.Config["sort-order"] = "descending"
            else:
                self.Config["sort-order"] = "ascending"

    def _on_settings_changed(self, settings, key):
        value = settings[key]
        if key == 'sort-by':
            if value == "alias":
                if not self._sort_alias_item.props.active:
                    self._sort_alias_item.props.active = True
            elif value == "timestamp":
                if not self._sort_timestamp_item.props.active:
                    self._sort_timestamp_item.props.active = True
        elif key == "sort-type":
            if value == "ascending":
                if not self._sort_type_item.props.active:
                    self._sort_type_item.props.active = True
            else:
                if not self._sort_type_item.props.active:
                    self._sort_type_item.props.active = False

    def on_device_selected(self, lst, device, tree_iter):
        if tree_iter and device:
            self.item_device.props.sensitive = True

            if self.device_menu is None:
                self.device_menu = ManagerDeviceMenu(self.blueman)
                self.item_device.set_submenu(self.device_menu)
            else:
                GLib.idle_add(self.device_menu.generate,
                              priority=GLib.PRIORITY_LOW)

        else:
            self.item_device.props.sensitive = False

    def on_adapter_property_changed(self, _adapter, name, value, path):
        if name == "Name" or name == "Alias":
            item = self.adapter_items[path][0]
            item.set_label(value)
        elif name == "Discovering":
            if self.Search:
                if value:
                    self.Search.props.sensitive = False
                else:
                    self.Search.props.sensitive = True

    def on_adapter_selected(self, menuitem, adapter_path):
        if menuitem.props.active:
            if adapter_path != self.blueman.List.Adapter.get_object_path():
                logging.info(f"selected {adapter_path}")
                self.blueman.Config["last-adapter"] = adapter_path_to_name(
                    adapter_path)
                self.blueman.List.set_adapter(adapter_path)

    def on_adapter_added(self, _manager, adapter_path):
        adapter = Adapter(obj_path=adapter_path)
        menu = self.item_adapter.get_submenu()
        object_path = adapter.get_object_path()

        item = Gtk.RadioMenuItem.new_with_label(self._adapters_group,
                                                adapter.get_name())
        item.show()
        self._adapters_group = item.get_group()

        self._itemhandler = item.connect("activate", self.on_adapter_selected,
                                         object_path)
        self._adapterhandler = adapter.connect_signal(
            "property-changed", self.on_adapter_property_changed)

        menu.insert(item, self._insert_adapter_item_pos)
        self._insert_adapter_item_pos += 1

        self.adapter_items[object_path] = (item, adapter)

        if adapter_path == self.blueman.List.Adapter.get_object_path():
            item.props.active = True

        if len(self.adapter_items) > 0:
            self.item_adapter.props.sensitive = True

    def on_adapter_removed(self, _manager, adapter_path):
        item, adapter = self.adapter_items.pop(adapter_path)
        menu = self.item_adapter.get_submenu()

        item.disconnect(self._itemhandler)
        adapter.disconnect(self._adapterhandler)

        menu.remove(item)
        self._insert_adapter_item_pos -= 1

        if len(self.adapter_items) == 0:
            self.item_adapter.props.sensitive = False

    def _on_plugin_dialog_activate(self, *args):
        def cb(*args):
            pass

        self.blueman.Applet.OpenPluginDialog(result_handler=cb)
示例#8
0
class ManagerMenu:
    def __init__(self, blueman: "Blueman"):
        self.blueman = blueman
        self.Config = Config("org.blueman.general")

        self.adapter_items: Dict[str, Tuple[Gtk.RadioMenuItem, Adapter]] = {}
        self._adapters_group: Sequence[Gtk.RadioMenuItem] = []
        self._insert_adapter_item_pos = 2
        self.Search = None

        self.item_adapter = self.blueman.builder.get_widget("item_adapter", Gtk.MenuItem)
        self.item_device = self.blueman.builder.get_widget("item_device", Gtk.MenuItem)

        self.item_view = self.blueman.builder.get_widget("item_view", Gtk.MenuItem)
        self.item_help = self.blueman.builder.get_widget("item_help", Gtk.MenuItem)

        help_menu = Gtk.Menu()

        self.item_help.set_submenu(help_menu)
        help_menu.show()

        report_item = create_menuitem(_("_Report a Problem"), "dialog-warning-symbolic")
        report_item.show()
        help_menu.append(report_item)

        report_item.connect("activate", lambda x: launch(f"xdg-open {WEBSITE}/issues"))

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        help_menu.append(sep)

        help_item = create_menuitem(_("_Help"), "help-about-symbolic")
        help_item.show()
        help_menu.append(help_item)
        assert self.blueman.window is not None
        widget = self.blueman.window.get_toplevel()
        assert isinstance(widget, Gtk.Window)
        window = widget
        help_item.connect("activate", lambda x: show_about_dialog('Blueman ' + _('Device Manager'), parent=window))

        view_menu = Gtk.Menu()
        self.item_view.set_submenu(view_menu)
        view_menu.show()

        item_toolbar = Gtk.CheckMenuItem.new_with_mnemonic(_("Show _Toolbar"))
        item_toolbar.show()
        view_menu.append(item_toolbar)
        self.blueman.Config.bind_to_widget("show-toolbar", item_toolbar, "active")

        item_statusbar = Gtk.CheckMenuItem.new_with_mnemonic(_("Show _Statusbar"))
        item_statusbar.show()
        view_menu.append(item_statusbar)
        self.blueman.Config.bind_to_widget("show-statusbar", item_statusbar, "active")

        item_unnamed = Gtk.CheckMenuItem.new_with_mnemonic(_("Hide _unnamed devices"))
        item_unnamed.show()
        view_menu.append(item_unnamed)
        self.blueman.Config.bind_to_widget("hide-unnamed", item_unnamed, "active")

        item_services: Gtk.MenuItem = Gtk.SeparatorMenuItem()
        view_menu.append(item_services)
        item_services.show()

        sorting_group: Sequence[Gtk.RadioMenuItem] = []
        item_sort = Gtk.MenuItem.new_with_mnemonic(_("S_ort By"))
        view_menu.append(item_sort)
        item_sort.show()

        sorting_menu = Gtk.Menu()
        item_sort.set_submenu(sorting_menu)

        self._sort_alias_item = Gtk.RadioMenuItem.new_with_mnemonic(sorting_group, _("_Name"))
        self._sort_alias_item.show()
        sorting_group = self._sort_alias_item.get_group()
        sorting_menu.append(self._sort_alias_item)

        self._sort_timestamp_item = Gtk.RadioMenuItem.new_with_mnemonic(sorting_group, _("_Added"))
        self._sort_timestamp_item.show()
        sorting_menu.append(self._sort_timestamp_item)

        sort_config = self.Config['sort-by']
        if sort_config == "alias":
            self._sort_alias_item.props.active = True
        else:
            self._sort_timestamp_item.props.active = True

        sort_sep = Gtk.SeparatorMenuItem()
        sort_sep.show()
        sorting_menu.append(sort_sep)

        self._sort_type_item = Gtk.CheckMenuItem.new_with_mnemonic(_("_Descending"))
        self._sort_type_item.show()
        sorting_menu.append(self._sort_type_item)

        if self.Config['sort-order'] == "ascending":
            self._sort_type_item.props.active = False
        else:
            self._sort_type_item.props.active = True

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        view_menu.append(sep)

        item_plugins = create_menuitem(_("_Plugins"), 'application-x-addon-symbolic')
        item_plugins.show()
        view_menu.append(item_plugins)
        item_plugins.connect('activate', self._on_plugin_dialog_activate)

        item_services = create_menuitem(_("_Local Services") + "…", "document-properties-symbolic")
        item_services.connect('activate', lambda *args: launch("blueman-services", name=_("Service Preferences")))
        view_menu.append(item_services)
        item_services.show()

        adapter_menu = Gtk.Menu()
        self.item_adapter.set_submenu(adapter_menu)
        self.item_adapter.props.sensitive = False

        search_item = create_menuitem(_("_Search"), "edit-find-symbolic")
        search_item.connect("activate", lambda x: self.blueman.inquiry())
        search_item.show()
        adapter_menu.prepend(search_item)
        self.Search = search_item

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        adapter_menu.append(sep)

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        adapter_menu.append(sep)

        adapter_settings = create_menuitem(_("_Preferences"), "document-properties-symbolic")
        adapter_settings.connect("activate", lambda x: self.blueman.adapter_properties())
        adapter_settings.show()
        adapter_menu.append(adapter_settings)

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        adapter_menu.append(sep)

        exit_item = create_menuitem(_("_Exit"), "application-exit-symbolic")
        exit_item.connect("activate", lambda x: self.blueman.quit())
        exit_item.show()
        adapter_menu.append(exit_item)

        self.item_adapter.show()
        self.item_view.show()
        self.item_help.show()
        self.item_device.show()
        self.item_device.props.sensitive = False

        self._manager = Manager()
        self._manager.connect_signal("adapter-added", self.on_adapter_added)
        self._manager.connect_signal("adapter-removed", self.on_adapter_removed)

        blueman.List.connect("device-selected", self.on_device_selected)

        for adapter in self._manager.get_adapters():
            self.on_adapter_added(None, adapter.get_object_path())

        self.device_menu: Optional[ManagerDeviceMenu] = None

        self.Config.connect("changed", self._on_settings_changed)
        self._sort_alias_item.connect("activate", self._on_sorting_changed, "alias")
        self._sort_timestamp_item.connect("activate", self._on_sorting_changed, "timestamp")
        self._sort_type_item.connect("activate", self._on_sorting_changed, "sort-type")

    def _on_sorting_changed(self, btn: Gtk.CheckMenuItem, sort_opt: str) -> None:
        if sort_opt == 'alias' and btn.props.active:
            self.Config['sort-by'] = "alias"
        elif sort_opt == "timestamp" and btn.props.active:
            self.Config['sort-by'] = "timestamp"
        elif sort_opt == 'sort-type':
            # FIXME bind widget to gsetting
            if btn.props.active:
                self.Config["sort-order"] = "descending"
            else:
                self.Config["sort-order"] = "ascending"

    def _on_settings_changed(self, settings: Config, key: str) -> None:
        value = settings[key]
        if key == 'sort-by':
            if value == "alias":
                if not self._sort_alias_item.props.active:
                    self._sort_alias_item.props.active = True
            elif value == "timestamp":
                if not self._sort_timestamp_item.props.active:
                    self._sort_timestamp_item.props.active = True
        elif key == "sort-type":
            if value == "ascending":
                if not self._sort_type_item.props.active:
                    self._sort_type_item.props.active = True
            else:
                if not self._sort_type_item.props.active:
                    self._sort_type_item.props.active = False
        elif key == "hide-unnamed":
            logging.debug("refilter")
            self.blueman.List.filter.refilter()

    def on_device_selected(self, _lst: ManagerDeviceList, device: Device, tree_iter: Gtk.TreeIter) -> None:
        if tree_iter and device:
            self.item_device.props.sensitive = True

            if self.device_menu is None:
                self.device_menu = ManagerDeviceMenu(self.blueman)
                self.item_device.set_submenu(self.device_menu)
            else:
                def idle() -> bool:
                    assert self.device_menu is not None  # https://github.com/python/mypy/issues/2608
                    self.device_menu.generate()
                    return False
                GLib.idle_add(idle, priority=GLib.PRIORITY_LOW)

        else:
            self.item_device.props.sensitive = False

    def on_adapter_property_changed(self, _adapter: Adapter, name: str, value: Any, path: str) -> None:
        if name == "Name" or name == "Alias":
            item = self.adapter_items[path][0]
            item.set_label(value)
        elif name == "Discovering":
            if self.Search:
                if value:
                    self.Search.props.sensitive = False
                else:
                    self.Search.props.sensitive = True

    def on_adapter_selected(self, menuitem: Gtk.CheckMenuItem, adapter_path: str) -> None:
        if menuitem.props.active:
            assert self.blueman.List.Adapter is not None
            if adapter_path != self.blueman.List.Adapter.get_object_path():
                logging.info(f"selected {adapter_path}")
                self.blueman.Config["last-adapter"] = adapter_path_to_name(adapter_path)
                self.blueman.List.set_adapter(adapter_path)

    def on_adapter_added(self, _manager: Optional[Manager], adapter_path: str) -> None:
        adapter = Adapter(obj_path=adapter_path)
        menu = self.item_adapter.get_submenu()
        assert isinstance(menu, Gtk.Menu)

        item = Gtk.RadioMenuItem.new_with_label(self._adapters_group, adapter.get_name())
        item.show()
        self._adapters_group = item.get_group()

        self._itemhandler = item.connect("activate", self.on_adapter_selected, adapter_path)
        self._adapterhandler = adapter.connect_signal("property-changed", self.on_adapter_property_changed)

        menu.insert(item, self._insert_adapter_item_pos)
        self._insert_adapter_item_pos += 1

        self.adapter_items[adapter_path] = (item, adapter)

        assert self.blueman.List.Adapter is not None
        if adapter_path == self.blueman.List.Adapter.get_object_path():
            item.props.active = True

        if len(self.adapter_items) > 0:
            self.item_adapter.props.sensitive = True

    def on_adapter_removed(self, _manager: Manager, adapter_path: str) -> None:
        item, adapter = self.adapter_items.pop(adapter_path)
        menu = self.item_adapter.get_submenu()
        assert isinstance(menu, Gtk.Menu)

        item.disconnect(self._itemhandler)
        adapter.disconnect(self._adapterhandler)

        menu.remove(item)
        self._insert_adapter_item_pos -= 1

        if len(self.adapter_items) == 0:
            self.item_adapter.props.sensitive = False

    def _on_plugin_dialog_activate(self, _item: Gtk.MenuItem) -> None:
        def cb(_proxy: Gio.DBusProxy, _res: Any, _userdata: Any) -> None:
            pass
        self.blueman.Applet.OpenPluginDialog(result_handler=cb)
示例#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)