Пример #1
0
    def on_event_clicked(self, _widget: Gtk.Widget, event: Gdk.Event) -> bool:
        if event.type not in (Gdk.EventType._2BUTTON_PRESS,
                              Gdk.EventType.BUTTON_PRESS):
            return False

        path = self.get_path_at_pos(int(cast(Gdk.EventButton, event).x),
                                    int(cast(Gdk.EventButton, event).y))
        if path is None:
            return False

        assert path[0] is not None
        row = self.get(path[0], "device", "connected")
        if not row:
            return False

        if self.Blueman is None:
            return False

        if self.menu is None:
            self.menu = ManagerDeviceMenu(self.Blueman)

        if event.type == Gdk.EventType._2BUTTON_PRESS and cast(
                Gdk.EventButton, event).button == 1:
            if self.menu.show_generic_connect_calc(row["device"]['UUIDs']):
                self.menu.generic_connect(None,
                                          device=row["device"],
                                          connect=not row["connected"])

        if event.type == Gdk.EventType.BUTTON_PRESS and cast(
                Gdk.EventButton, event).button == 3:
            self.menu.popup_at_pointer(event)

        return False
Пример #2
0
    def on_event_clicked(self, _widget: Gtk.Widget, event: Gdk.Event) -> bool:
        if event.type not in (Gdk.EventType._2BUTTON_PRESS, Gdk.EventType.BUTTON_PRESS):
            return False

        posdata = self.get_path_at_pos(int(cast(Gdk.EventButton, event).x), int(cast(Gdk.EventButton, event).y))
        if posdata is None:
            return False
        else:
            path = posdata[0]
            assert path is not None

        childpath = self.filter.convert_path_to_child_path(path)
        assert childpath is not None
        row = self.get(childpath, "device", "connected")
        if not row:
            return False

        if self.Blueman is None:
            return False

        if self.menu is None:
            self.menu = ManagerDeviceMenu(self.Blueman)

        if event.type == Gdk.EventType._2BUTTON_PRESS and cast(Gdk.EventButton, event).button == 1:
            if self.menu.show_generic_connect_calc(row["device"]['UUIDs']):
                if row["connected"]:
                    self.menu.disconnect_service(row["device"])
                else:
                    self.menu.connect_service(row["device"])

        if event.type == Gdk.EventType.BUTTON_PRESS and cast(Gdk.EventButton, event).button == 3:
            self.menu.popup_at_pointer(event)

        return False
Пример #3
0
    def on_event_clicked(self, widget, event):
        if event.type not in (Gdk.EventType._2BUTTON_PRESS,
                              Gdk.EventType.BUTTON_PRESS):
            return

        path = self.get_path_at_pos(int(event.x), int(event.y))
        if path is None:
            return

        row = self.get(path[0], "device", "connected")
        if not row:
            return

        if self.Blueman is None:
            return

        if self.menu is None:
            self.menu = ManagerDeviceMenu(self.Blueman)

        if event.type == Gdk.EventType._2BUTTON_PRESS and event.button == 1:
            if self.menu.show_generic_connect_calc(row["device"]['UUIDs']):
                self.menu.generic_connect(item=None,
                                          device=row["device"],
                                          connect=not row["connected"])

        if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
            self.menu.popup_at_pointer(event)
Пример #4
0
    def on_event_clicked(self, widget, event):

        if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
            path = self.get_path_at_pos(int(event.x), int(event.y))
            if path is not None:
                row = self.get(path[0], "device")

                if row:
                    if self.Blueman is not None:
                        if self.menu is None:
                            self.menu = ManagerDeviceMenu(self.Blueman)

                        self.menu.popup(None, None, None, None, event.button, event.time)
Пример #5
0
    def _on_popup_menu(self, _widget: Gtk.Widget) -> bool:
        if self.Blueman is None:
            return False

        if self.menu is None:
            self.menu = ManagerDeviceMenu(self.Blueman)

        window = self.get_window()
        assert window is not None
        selected = self.selected()
        assert selected is not None
        rect = self.get_cell_area(self.liststore.get_path(selected), self.get_column(1))
        self.menu.popup_at_rect(window, rect, Gdk.Gravity.CENTER, Gdk.Gravity.NORTH)

        return True
Пример #6
0
    def on_event_clicked(self, widget, event):

        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
            path = self.get_path_at_pos(int(event.x), int(event.y))
            if path != None:
                row = self.get(path[0][0], "device")

                if row:
                    device = row["device"]
                    if self.Blueman != None:
                        if self.menu == None:
                            self.menu = ManagerDeviceMenu(self.Blueman)

                        self.menu.popup(None, None, None, event.button,
                                        event.time)
Пример #7
0
    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
Пример #8
0
 def on_request_menu_items(self, manager_menu: ManagerDeviceMenu, device: Device) -> List[DeviceMenuItem]:
     item = create_menuitem(_("_Info"), "dialog-information")
     item.props.tooltip_text = _("Show device information")
     _window = manager_menu.get_toplevel()
     assert isinstance(_window, Gtk.Window)
     window = _window  # https://github.com/python/mypy/issues/2608
     item.connect('activate', lambda x: show_info(device, window))
     return [DeviceMenuItem(item, DeviceMenuItem.Group.ACTIONS, 400)]
Пример #9
0
 def on_request_menu_items(
         self, manager_menu: ManagerDeviceMenu,
         device: Device) -> List[Tuple[Gtk.MenuItem, int]]:
     item = create_menuitem(_("Send _note"), "dialog-information")
     item.props.tooltip_text = _("Send a text note")
     item.connect('activate',
                  lambda x: send_note(device, manager_menu.get_toplevel()))
     return [(item, 500)]
Пример #10
0
 def on_request_menu_items(
         self, manager_menu: ManagerDeviceMenu,
         device: Device) -> List[Tuple[Gtk.MenuItem, int]]:
     item = create_menuitem(_("Send _note"), "dialog-information")
     item.props.tooltip_text = _("Send a text note")
     _window = manager_menu.get_toplevel()
     assert isinstance(_window, Gtk.Window)
     window = _window  # https://github.com/python/mypy/issues/2608
     item.connect('activate', lambda x: send_note(device, window))
     return [(item, 500)]
Пример #11
0
    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
Пример #12
0
    def on_device_selected(self, List, device, iter):
        if iter and device:
            self.item_device.props.sensitive = True

            if self.device_menu == None:
                self.device_menu = ManagerDeviceMenu(self.blueman)
                self.item_device.set_submenu(self.device_menu)
            else:
                GObject.idle_add(self.device_menu.Generate, priority=GObject.PRIORITY_LOW)

        else:
            self.item_device.props.sensitive = False
Пример #13
0
    def on_event_clicked(self, widget, event):

        if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
            path = self.get_path_at_pos(int(event.x), int(event.y))
            if path is not None:
                row = self.get(path[0], "device")

                if row:
                    if self.Blueman is not None:
                        if self.menu is None:
                            self.menu = ManagerDeviceMenu(self.Blueman)

                        self.menu.popup(None, None, None, None, event.button, event.time)
Пример #14
0
	def on_event_clicked(self, widget, event):

		if event.type==gtk.gdk.BUTTON_PRESS and event.button==3:
			path = self.get_path_at_pos(int(event.x), int(event.y))
			if path != None:
				row = self.get(path[0][0], "device")
			
				if row:
					device = row["device"]
					if self.Blueman != None:
						if self.menu == None:
							self.menu = ManagerDeviceMenu(self.Blueman)
					
						self.menu.popup(None, None, None, event.button, event.time)
Пример #15
0
class ManagerDeviceList(DeviceList):
    def __init__(self, adapter=None, inst=None):
        cr = Gtk.CellRendererText()
        cr.props.ellipsize = Pango.EllipsizeMode.END
        tabledata = [
            # device picture
            {
                "id": "device_surface",
                "type": str,
                "renderer": Gtk.CellRendererPixbuf(),
                "render_attrs": {},
                "celldata_func": (self._set_device_cell_data, None)
            },
            # device caption
            {
                "id": "caption",
                "type": str,
                "renderer": cr,
                "render_attrs": {
                    "markup": 1
                },
                "view_props": {
                    "expand": True
                }
            },
            {
                "id": "rssi_pb",
                "type": GdkPixbuf.Pixbuf,
                "renderer": Gtk.CellRendererPixbuf(),
                "render_attrs": {
                    "pixbuf": 2
                },
                "view_props": {
                    "spacing": 0
                }
            },
            {
                "id": "lq_pb",
                "type": GdkPixbuf.Pixbuf,
                "renderer": Gtk.CellRendererPixbuf(),
                "render_attrs": {
                    "pixbuf": 3
                },
                "view_props": {
                    "spacing": 0
                }
            },
            {
                "id": "tpl_pb",
                "type": GdkPixbuf.Pixbuf,
                "renderer": Gtk.CellRendererPixbuf(),
                "render_attrs": {
                    "pixbuf": 4
                },
                "view_props": {
                    "spacing": 0
                }
            },
            {
                "id": "alias",
                "type": str
            },  # used for quick access instead of device.GetProperties
            {
                "id": "connected",
                "type": bool
            },  # used for quick access instead of device.GetProperties
            {
                "id": "paired",
                "type": bool
            },  # used for quick access instead of device.GetProperties
            {
                "id": "trusted",
                "type": bool
            },  # used for quick access instead of device.GetProperties
            {
                "id": "objpush",
                "type": bool
            },  # used to set Send File button
            {
                "id": "rssi",
                "type": float
            },
            {
                "id": "lq",
                "type": float
            },
            {
                "id": "tpl",
                "type": float
            },
            {
                "id": "icon_info",
                "type": Gtk.IconInfo
            },
            {
                "id": "cell_fader",
                "type": GObject.TYPE_PYOBJECT
            },
            {
                "id": "row_fader",
                "type": GObject.TYPE_PYOBJECT
            },
            {
                "id": "levels_visible",
                "type": bool
            },
            {
                "id": "initial_anim",
                "type": bool
            },
        ]
        super().__init__(adapter, tabledata)
        self.set_name("ManagerDeviceList")
        self.set_headers_visible(False)
        self.props.has_tooltip = True
        self.Blueman = inst

        self.Config = Config("org.blueman.general")
        self.Config.connect('changed', self._on_settings_changed)
        # Set the correct sorting
        self._on_settings_changed(self.Config, "sort-by")
        self._on_settings_changed(self.Config, "sort-type")

        self.connect("query-tooltip", self.tooltip_query)
        self.tooltip_row = None
        self.tooltip_col = None

        self.connect("button_press_event", self.on_event_clicked)
        self.connect("button_release_event", self.on_event_clicked)

        self.menu = None

        self.connect("drag_data_received", self.drag_recv)
        self.connect("drag-motion", self.drag_motion)

        Gtk.Widget.drag_dest_set(self, Gtk.DestDefaults.ALL, [],
                                 Gdk.DragAction.COPY | Gdk.DragAction.DEFAULT)
        Gtk.Widget.drag_dest_add_uri_targets(self)

        self.set_search_equal_func(self.search_func, None)

    def _on_settings_changed(self, settings, key):
        if key in ('sort-by', 'sort-order'):
            sort_by = settings['sort-by']
            sort_order = settings['sort-order']

            if sort_order == 'ascending':
                sort_type = Gtk.SortType.ASCENDING
            else:
                sort_type = Gtk.SortType.DESCENDING

            column_id = self.ids.setdefault(sort_by, None)

            if column_id:
                self.liststore.set_sort_column_id(column_id, sort_type)

    def on_icon_theme_changed(self, widget):
        for row in self.liststore:
            device = self.get(row.iter, "device")["device"]
            self.row_setup_event(row.iter, device)

    def do_device_found(self, device):
        tree_iter = self.find_device(device)
        if tree_iter:
            anim = TreeRowColorFade(self, self.props.model.get_path(tree_iter),
                                    Gdk.RGBA(0, 0, 1, 1))
            anim.animate(start=0.8, end=1.0)

    def search_func(self, model, column, key, tree_iter):
        row = self.get(tree_iter, "caption")
        if key.lower() in row["caption"].lower():
            return False
        logging.info("%s %s %s %s" % (model, column, key, tree_iter))
        return True

    def drag_recv(self, widget, context, x, y, selection, target_type, time):

        uris = list(selection.get_uris())

        context.finish(True, False, time)

        path = self.get_path_at_pos(x, y)
        if path:
            tree_iter = self.get_iter(path[0])
            device = self.get(tree_iter, "device")["device"]
            command = "blueman-sendto --device=%s" % device['Address']

            launch(command, uris, False, "blueman", _("File Sender"))
            context.finish(True, False, time)
        else:
            context.finish(False, False, time)

        return True

    def drag_motion(self, widget, drag_context, x, y, timestamp):
        result = self.get_path_at_pos(x, y)
        if result is not None:
            path = result[0]
            if not self.selection.path_is_selected(path):
                tree_iter = self.get_iter(path)
                has_obj_push = self._has_objpush(
                    self.get(tree_iter, "device")["device"])
                if has_obj_push:
                    Gdk.drag_status(drag_context, Gdk.DragAction.COPY,
                                    timestamp)
                    self.set_cursor(path)
                    return True
                else:
                    Gdk.drag_status(drag_context, Gdk.DragAction.DEFAULT,
                                    timestamp)
                    return False
        else:
            Gdk.drag_status(drag_context, Gdk.DragAction.DEFAULT, timestamp)
            return False

    def on_event_clicked(self, widget, event):

        if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
            path = self.get_path_at_pos(int(event.x), int(event.y))
            if path is not None:
                row = self.get(path[0], "device")

                if row:
                    if self.Blueman is not None:
                        if self.menu is None:
                            self.menu = ManagerDeviceMenu(self.Blueman)

                        self.menu.popup(None, None, None, None, event.button,
                                        event.time)

    def get_icon_info(self, icon_name, size=48, fallback=True):
        if icon_name is None and not fallback:
            return None
        elif icon_name is None and fallback:
            icon_name = "image-missing"

        icon_info = self.icon_theme.lookup_icon_for_scale(
            icon_name, size, self.get_scale_factor(),
            Gtk.IconLookupFlags.FORCE_SIZE)

        return icon_info

    def make_device_icon(self, icon_info, is_paired=False, is_trusted=False):
        window = self.get_window()
        scale = self.get_scale_factor()
        target = icon_info.load_surface(window)
        ctx = cairo.Context(target)

        if is_paired:
            icon_info = self.get_icon_info("dialog-password", 16, False)
            paired_surface = icon_info.load_surface(window)
            ctx.set_source_surface(paired_surface, 1 / scale, 1 / scale)
            ctx.paint_with_alpha(0.8)

        if is_trusted:
            icon_info = self.get_icon_info("blueman-trust", 16, False)
            trusted_surface = icon_info.load_surface(window)
            height = target.get_height()
            mini_height = trusted_surface.get_height()
            y = height / scale - mini_height / scale - 1 / scale

            ctx.set_source_surface(trusted_surface, 1 / scale, y)
            ctx.paint_with_alpha(0.8)

        return target

    def device_remove_event(self, device):
        tree_iter = self.find_device(device)

        row_fader = self.get(tree_iter, "row_fader")["row_fader"]
        row_fader.connect("animation-finished", self.__on_fader_finished,
                          device, tree_iter)
        row_fader.thaw()
        self.emit("device-selected", None, None)
        row_fader.animate(start=row_fader.get_state(), end=0.0, duration=400)

    def __on_fader_finished(self, fader, device, tree_iter):
        fader.disconnect_by_func(self.__on_fader_finished)
        fader.freeze()
        super().device_remove_event(device)

    def device_add_event(self, device):
        self.add_device(device)

    def make_caption(self, name, klass, address):
        return "<span size='x-large'>%(0)s</span>\n<span size='small'>%(1)s</span>\n<i>%(2)s</i>" \
               % {"0": html.escape(name), "1": klass.capitalize(), "2": address}

    def get_device_class(self, device):
        klass = get_minor_class(device['Class'])
        if klass != "uncategorized":
            return get_minor_class(device['Class'], True)
        else:
            return get_major_class(device['Class'])

    def row_setup_event(self, tree_iter, device):
        if not self.get(tree_iter, "initial_anim")["initial_anim"]:
            cell_fader = CellFade(self, self.props.model.get_path(tree_iter),
                                  [2, 3, 4])
            row_fader = TreeRowFade(self, self.props.model.get_path(tree_iter))

            has_objpush = self._has_objpush(device)

            self.set(tree_iter,
                     row_fader=row_fader,
                     cell_fader=cell_fader,
                     levels_visible=False,
                     objpush=has_objpush)

            cell_fader.freeze()

            def on_finished(fader):
                fader.disconnect_by_func(on_finished)
                fader.freeze()

            row_fader.connect("animation-finished", on_finished)
            row_fader.set_state(0.0)
            row_fader.animate(start=0.0, end=1.0, duration=500)

            self.set(tree_iter, initial_anim=True)

        klass = get_minor_class(device['Class'])
        # Bluetooth >= 4 devices use Appearance property
        appearance = device["Appearance"]
        if klass != "uncategorized" and klass != "unknown":
            # get translated version
            description = get_minor_class(device['Class'], True).capitalize()
        elif klass == "unknown" and appearance:
            description = gatt_appearance_to_name(appearance)
        else:
            description = get_major_class(device['Class']).capitalize()

        icon_info = self.get_icon_info(device["Icon"], 48, False)
        caption = self.make_caption(device['Alias'], description,
                                    device['Address'])

        self.set(tree_iter,
                 caption=caption,
                 icon_info=icon_info,
                 alias=device['Alias'])

        try:
            self.row_update_event(tree_iter, "Trusted", device['Trusted'])
        except Exception as e:
            logging.exception(e)
        try:
            self.row_update_event(tree_iter, "Paired", device['Paired'])
        except Exception as e:
            logging.exception(e)
        try:
            self.row_update_event(tree_iter, "Connected", device["Connected"])
        except Exception as e:
            logging.exception(e)

    def row_update_event(self, tree_iter, key, value):
        logging.info("%s %s" % (key, value))

        if key == "Trusted":
            if value:
                self.set(tree_iter, trusted=True)
            else:
                self.set(tree_iter, trusted=False)

        elif key == "Paired":
            if value:
                self.set(tree_iter, paired=True)
            else:
                self.set(tree_iter, paired=False)

        elif key == "Alias":
            device = self.get(tree_iter, "device")["device"]
            c = self.make_caption(value, self.get_device_class(device),
                                  device['Address'])
            self.set(tree_iter, caption=c, alias=value)

        elif key == "UUIDs":
            device = self.get(tree_iter, "device")["device"]
            has_objpush = self._has_objpush(device)
            self.set(tree_iter, objpush=has_objpush)

        elif key == "Connected":
            self.set(tree_iter, connected=value)

    def level_setup_event(self, row_ref, device, cinfo):
        if not row_ref.valid():
            return

        tree_iter = self.get_iter(row_ref.get_path())
        row = self.get(tree_iter, "levels_visible", "cell_fader", "rssi", "lq",
                       "tpl")
        if cinfo is not None:
            # cinfo init may fail for bluetooth devices version 4 and up
            # FIXME Workaround is horrible and we should show something better
            if cinfo.failed:
                rssi_perc = tpl_perc = lq_perc = 100
            else:
                try:
                    rssi = float(cinfo.get_rssi())
                except ConnInfoReadError:
                    rssi = 0
                try:
                    lq = float(cinfo.get_lq())
                except ConnInfoReadError:
                    lq = 0

                try:
                    tpl = float(cinfo.get_tpl())
                except ConnInfoReadError:
                    tpl = 0

                rssi_perc = 50 + (rssi / 127 / 2 * 100)
                tpl_perc = 50 + (tpl / 127 / 2 * 100)
                lq_perc = lq / 255 * 100

                if lq_perc < 10:
                    lq_perc = 10
                if rssi_perc < 10:
                    rssi_perc = 10
                if tpl_perc < 10:
                    tpl_perc = 10

            if not row["levels_visible"]:
                logging.info("animating up")
                self.set(tree_iter, levels_visible=True)
                fader = row["cell_fader"]
                fader.thaw()
                fader.set_state(0.0)
                fader.animate(start=0.0, end=1.0, duration=400)

                def on_finished(fader):
                    fader.freeze()
                    fader.disconnect_by_func(on_finished)

                fader.connect("animation-finished", on_finished)

            to_store = {}
            if round(row["rssi"], -1) != round(rssi_perc, -1):
                icon_name = "blueman-rssi-%d.png" % round(rssi_perc, -1)
                icon = GdkPixbuf.Pixbuf.new_from_file(
                    os.path.join(PIXMAP_PATH, icon_name))
                to_store.update({"rssi": rssi_perc, "rssi_pb": icon})

            if round(row["lq"], -1) != round(lq_perc, -1):
                icon_name = "blueman-lq-%d.png" % round(lq_perc, -1)
                icon = GdkPixbuf.Pixbuf.new_from_file(
                    os.path.join(PIXMAP_PATH, icon_name))
                to_store.update({"lq": lq_perc, "lq_pb": icon})

            if round(row["tpl"], -1) != round(tpl_perc, -1):
                icon_name = "blueman-tpl-%d.png" % round(tpl_perc, -1)
                icon = GdkPixbuf.Pixbuf.new_from_file(
                    os.path.join(PIXMAP_PATH, icon_name))
                to_store.update({"tpl": tpl_perc, "tpl_pb": icon})

            if to_store:
                self.set(tree_iter, **to_store)

        else:

            if row["levels_visible"]:
                logging.info("animating down")
                self.set(tree_iter,
                         levels_visible=False,
                         rssi=-1,
                         lq=-1,
                         tpl=-1)
                fader = row["cell_fader"]
                fader.thaw()
                fader.set_state(1.0)
                fader.animate(start=fader.get_state(), end=0.0, duration=400)

                def on_finished(fader):
                    fader.disconnect_by_func(on_finished)
                    fader.freeze()
                    if row_ref.valid():
                        self.set(tree_iter,
                                 rssi_pb=None,
                                 lq_pb=None,
                                 tpl_pb=None)

                fader.connect("animation-finished", on_finished)

    def tooltip_query(self, tw, x, y, kb, tooltip):
        path = self.get_path_at_pos(x, y)

        if path is not None:
            if path[0] != self.tooltip_row or path[1] != self.tooltip_col:
                self.tooltip_row = path[0]
                self.tooltip_col = path[1]
                return False

            if path[1] == self.columns["device_surface"]:
                tree_iter = self.get_iter(path[0])

                row = self.get(tree_iter, "trusted", "paired")
                trusted = row["trusted"]
                paired = row["paired"]
                if trusted and paired:
                    tooltip.set_markup(_("<b>Trusted and Paired</b>"))
                elif paired:
                    tooltip.set_markup(_("<b>Paired</b>"))
                elif trusted:
                    tooltip.set_markup(_("<b>Trusted</b>"))
                else:
                    return False

                self.tooltip_row = path[0]
                self.tooltip_col = path[1]
                return True

            if path[1] == self.columns["tpl_pb"] \
                    or path[1] == self.columns["lq_pb"] \
                    or path[1] == self.columns["rssi_pb"]:
                tree_iter = self.get_iter(path[0])

                dt = self.get(tree_iter, "connected")["connected"]
                if dt:
                    rssi = self.get(tree_iter, "rssi")["rssi"]
                    lq = self.get(tree_iter, "lq")["lq"]
                    tpl = self.get(tree_iter, "tpl")["tpl"]

                    if rssi < 30:
                        rssi_state = _("Poor")
                    elif rssi < 40:
                        rssi_state = _("Sub-optimal")
                    elif rssi < 60:
                        rssi_state = _("Optimal")
                    elif rssi < 70:
                        rssi_state = _("Much")
                    else:
                        rssi_state = _("Too much")

                    if tpl < 30:
                        tpl_state = _("Low")
                    elif tpl < 40:
                        tpl_state = _("Sub-optimal")
                    elif tpl < 60:
                        tpl_state = _("Optimal")
                    elif tpl < 70:
                        tpl_state = _("High")
                    else:
                        tpl_state = _("Very High")

                    tooltip_template = None
                    if path[1] == self.columns["tpl_pb"]:
                        tooltip_template = \
                            "<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\n" \
                            "Link Quality: %(lq)u%%\n<b>Transmit Power Level: %(tpl)u%%</b> <i>(%(tpl_state)s)</i>"
                    elif path[1] == self.columns["lq_pb"]:
                        tooltip_template = \
                            "<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\n" \
                            "<b>Link Quality: %(lq)u%%</b>\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>"
                    elif path[1] == self.columns["rssi_pb"]:
                        tooltip_template = \
                            "<b>Connected</b>\n<b>Received Signal Strength: %(rssi)u%%</b> <i>(%(rssi_state)s)</i>\n" \
                            "Link Quality: %(lq)u%%\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>"

                    state_dict = {
                        "rssi_state": rssi_state,
                        "rssi": rssi,
                        "lq": lq,
                        "tpl": tpl,
                        "tpl_state": tpl_state
                    }
                    tooltip.set_markup(tooltip_template % state_dict)
                    self.tooltip_row = path[0]
                    self.tooltip_col = path[1]
                    return True
        return False

    def _has_objpush(self, device):
        if device is None:
            return False

        for uuid in device["UUIDs"]:
            if ServiceUUID(uuid).short_uuid == OBEX_OBJPUSH_SVCLASS_ID:
                return True
        return False

    def _set_device_cell_data(self, col, cell, model, tree_iter, data):
        row = self.get(tree_iter, "icon_info", "trusted", "paired")
        surface = self.make_device_icon(row["icon_info"], row["paired"],
                                        row["trusted"])
        cell.set_property("surface", surface)
Пример #16
0
class ManagerDeviceList(DeviceList):
    def __init__(self, adapter: Optional[str] = None, inst: Optional["Blueman"] = None) -> None:
        cr = Gtk.CellRendererText()
        cr.props.ellipsize = Pango.EllipsizeMode.END
        tabledata: List[ListDataDict] = [
            # device picture
            {"id": "device_surface", "type": str, "renderer": Gtk.CellRendererPixbuf(),
             "render_attrs": {}, "celldata_func": (self._set_cell_data, None)},
            # device caption
            {"id": "caption", "type": str, "renderer": cr,
             "render_attrs": {"markup": 1}, "view_props": {"expand": True}},
            {"id": "battery_pb", "type": GdkPixbuf.Pixbuf, "renderer": Gtk.CellRendererPixbuf(),
             "render_attrs": {}, "view_props": {"spacing": 0},
             "celldata_func": (self._set_cell_data, "battery")},
            {"id": "rssi_pb", "type": GdkPixbuf.Pixbuf, "renderer": Gtk.CellRendererPixbuf(),
             "render_attrs": {}, "view_props": {"spacing": 0},
             "celldata_func": (self._set_cell_data, "rssi")},
            {"id": "lq_pb", "type": GdkPixbuf.Pixbuf, "renderer": Gtk.CellRendererPixbuf(),
             "render_attrs": {}, "view_props": {"spacing": 0},
             "celldata_func": (self._set_cell_data, "lq")},
            {"id": "tpl_pb", "type": GdkPixbuf.Pixbuf, "renderer": Gtk.CellRendererPixbuf(),
             "render_attrs": {}, "view_props": {"spacing": 0},
             "celldata_func": (self._set_cell_data, "tpl")},
            {"id": "alias", "type": str},  # used for quick access instead of device.GetProperties
            {"id": "connected", "type": bool},  # used for quick access instead of device.GetProperties
            {"id": "paired", "type": bool},  # used for quick access instead of device.GetProperties
            {"id": "trusted", "type": bool},  # used for quick access instead of device.GetProperties
            {"id": "objpush", "type": bool},  # used to set Send File button
            {"id": "battery", "type": float},
            {"id": "rssi", "type": float},
            {"id": "lq", "type": float},
            {"id": "tpl", "type": float},
            {"id": "icon_info", "type": Gtk.IconInfo},
            {"id": "cell_fader", "type": CellFade},
            {"id": "row_fader", "type": TreeRowFade},
            {"id": "initial_anim", "type": bool},
            {"id": "blocked", "type": bool}
        ]
        super().__init__(adapter, tabledata)
        self.set_name("ManagerDeviceList")
        self.set_headers_visible(False)
        self.props.has_tooltip = True
        self.Blueman = inst

        self._monitored_devices: Set[str] = set()

        self.manager.connect_signal("battery-created", self.on_battery_created)
        self.manager.connect_signal("battery-removed", self.on_battery_removed)
        self._batteries: Dict[str, Battery] = {}

        self.Config = Config("org.blueman.general")
        self.Config.connect('changed', self._on_settings_changed)
        # Set the correct sorting
        self._on_settings_changed(self.Config, "sort-by")
        self._on_settings_changed(self.Config, "sort-type")

        self.connect("query-tooltip", self.tooltip_query)
        self.tooltip_row: Optional[Gtk.TreePath] = None
        self.tooltip_col: Optional[Gtk.TreeViewColumn] = None

        self.connect("popup-menu", self._on_popup_menu)
        self.connect("button_press_event", self.on_event_clicked)
        self.connect("button_release_event", self.on_event_clicked)

        self.menu: Optional[ManagerDeviceMenu] = None

        self.connect("drag_data_received", self.drag_recv)
        self.connect("drag-motion", self.drag_motion)

        Gtk.Widget.drag_dest_set(self, Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY | Gdk.DragAction.DEFAULT)
        Gtk.Widget.drag_dest_add_uri_targets(self)

        self.set_search_equal_func(self.search_func)
        self.filter.set_visible_func(self.filter_func)

    def _on_settings_changed(self, settings: Config, key: str) -> None:
        if key in ('sort-by', 'sort-order'):
            sort_by = settings['sort-by']
            sort_order = settings['sort-order']

            if sort_order == 'ascending':
                sort_type = Gtk.SortType.ASCENDING
            else:
                sort_type = Gtk.SortType.DESCENDING

            column_id = self.ids.get(sort_by)

            if column_id:
                self.liststore.set_sort_column_id(column_id, sort_type)

    def on_icon_theme_changed(self, _icon_them: Gtk.IconTheme) -> None:
        for row in self.liststore:
            device = self.get(row.iter, "device")["device"]
            self.row_setup_event(row.iter, device)

    def on_battery_created(self, _manager: Manager, obj_path: str) -> None:
        if obj_path not in self._batteries:
            battery_proxy = Battery(obj_path=obj_path)
            self._batteries[obj_path] = battery_proxy
            logging.debug(f"{obj_path} {battery_proxy['Percentage']}")

    def on_battery_removed(self, _manager: Manager, obj_path: str) -> None:
        if obj_path in self._batteries:
            battery = self._batteries.pop(obj_path)
            battery.destroy()

    def search_func(self, model: Gtk.TreeModel, column: int, key: str, tree_iter: Gtk.TreeIter) -> bool:
        row = self.get(tree_iter, "caption")
        if key.lower() in row["caption"].lower():
            return False
        logging.info(f"{model} {column} {key} {tree_iter}")
        return True

    def filter_func(self, _model: Gtk.TreeModel, tree_iter: Gtk.TreeIter, _data: Any) -> bool:
        no_name = self.get(tree_iter, "no_name")["no_name"]
        if no_name and self.Config["hide-unnamed"]:
            logging.debug("Hiding unnamed device")
            return False
        else:
            return True

    def drag_recv(self, _widget: Gtk.Widget, context: Gdk.DragContext, x: int, y: int, selection: Gtk.SelectionData,
                  _info: int, time: int) -> None:

        uris = list(selection.get_uris())

        context.finish(True, False, time)

        path = self.get_path_at_pos(x, y)
        if path:
            tree_iter = self.get_iter(path[0])
            assert tree_iter is not None
            device = self.get(tree_iter, "device")["device"]
            command = f"blueman-sendto --device={device['Address']}"

            launch(command, paths=uris, name=_("File Sender"))
            context.finish(True, False, time)
        else:
            context.finish(False, False, time)

    def drag_motion(self, _widget: Gtk.Widget, drag_context: Gdk.DragContext, x: int, y: int, timestamp: int) -> bool:
        result = self.get_path_at_pos(x, y)
        if result is not None:
            path = result[0]
            assert path is not None
            path = self.filter.convert_path_to_child_path(path)
            if path is None:
                return False

            if not self.selection.path_is_selected(path):
                tree_iter = self.get_iter(path)
                assert tree_iter is not None
                has_obj_push = self._has_objpush(self.get(tree_iter, "device")["device"])
                if has_obj_push:
                    Gdk.drag_status(drag_context, Gdk.DragAction.COPY, timestamp)
                    self.set_cursor(path)
                    return True
                else:
                    Gdk.drag_status(drag_context, Gdk.DragAction.DEFAULT, timestamp)
                    return False
            return False
        else:
            Gdk.drag_status(drag_context, Gdk.DragAction.DEFAULT, timestamp)
            return False

    def _on_popup_menu(self, _widget: Gtk.Widget) -> bool:
        if self.Blueman is None:
            return False

        if self.menu is None:
            self.menu = ManagerDeviceMenu(self.Blueman)

        window = self.get_window()
        assert window is not None
        selected = self.selected()
        assert selected is not None
        rect = self.get_cell_area(self.liststore.get_path(selected), self.get_column(1))
        self.menu.popup_at_rect(window, rect, Gdk.Gravity.CENTER, Gdk.Gravity.NORTH)

        return True

    def on_event_clicked(self, _widget: Gtk.Widget, event: Gdk.Event) -> bool:
        if event.type not in (Gdk.EventType._2BUTTON_PRESS, Gdk.EventType.BUTTON_PRESS):
            return False

        posdata = self.get_path_at_pos(int(cast(Gdk.EventButton, event).x), int(cast(Gdk.EventButton, event).y))
        if posdata is None:
            return False
        else:
            path = posdata[0]
            assert path is not None

        childpath = self.filter.convert_path_to_child_path(path)
        assert childpath is not None
        row = self.get(childpath, "device", "connected")
        if not row:
            return False

        if self.Blueman is None:
            return False

        if self.menu is None:
            self.menu = ManagerDeviceMenu(self.Blueman)

        if event.type == Gdk.EventType._2BUTTON_PRESS and cast(Gdk.EventButton, event).button == 1:
            if self.menu.show_generic_connect_calc(row["device"]['UUIDs']):
                if row["connected"]:
                    self.menu.disconnect_service(row["device"])
                else:
                    self.menu.connect_service(row["device"])

        if event.type == Gdk.EventType.BUTTON_PRESS and cast(Gdk.EventButton, event).button == 3:
            self.menu.popup_at_pointer(event)

        return False

    def get_icon_info(self, icon_name: str, size: int = 48, fallback: bool = True) -> Optional[Gtk.IconInfo]:
        if icon_name is None and not fallback:
            return None
        elif icon_name is None and fallback:
            icon_name = "image-missing"

        icon_info = self.icon_theme.lookup_icon_for_scale(icon_name, size, self.get_scale_factor(),
                                                          Gtk.IconLookupFlags.FORCE_SIZE)

        return icon_info

    def make_device_icon(self, icon_info: Gtk.IconInfo, is_paired: bool = False, is_trusted: bool = False,
                         is_blocked: bool = False) -> cairo.Surface:
        window = self.get_window()
        scale = self.get_scale_factor()
        target = icon_info.load_surface(window)
        ctx = cairo.Context(target)

        if is_paired:
            _icon_info = self.get_icon_info("blueman-paired-emblem", 16, False)
            assert _icon_info is not None
            paired_surface = _icon_info.load_surface(window)
            ctx.set_source_surface(paired_surface, 1 / scale, 1 / scale)
            ctx.paint_with_alpha(0.8)

        if is_trusted:
            _icon_info = self.get_icon_info("blueman-trusted-emblem", 16, False)
            assert _icon_info is not None
            trusted_surface = _icon_info.load_surface(window)
            assert isinstance(target, cairo.ImageSurface)
            assert isinstance(trusted_surface, cairo.ImageSurface)
            height = target.get_height()
            mini_height = trusted_surface.get_height()
            y = height / scale - mini_height / scale - 1 / scale

            ctx.set_source_surface(trusted_surface, 1 / scale, y)
            ctx.paint_with_alpha(0.8)

        if is_blocked:
            _icon_info = self.get_icon_info("blueman-blocked-emblem", 16, False)
            assert _icon_info is not None
            blocked_surface = _icon_info.load_surface(window)
            assert isinstance(target, cairo.ImageSurface)
            assert isinstance(blocked_surface, cairo.ImageSurface)
            width = target.get_width()
            mini_width = blocked_surface.get_width()
            ctx.set_source_surface(blocked_surface, (width - mini_width - 1) / scale, 1 / scale)
            ctx.paint_with_alpha(0.8)

        return target

    def device_remove_event(self, device: Device) -> None:
        tree_iter = self.find_device(device)
        assert tree_iter is not None

        iter_set, _child_tree_iter = self.filter.convert_child_iter_to_iter(tree_iter)
        if iter_set:
            row_fader = self.get(tree_iter, "row_fader")["row_fader"]
            self._prepare_fader(row_fader, lambda: self.__fader_finished(device))
            row_fader.animate(start=row_fader.get_state(), end=0.0, duration=400)

    def __fader_finished(self, device: Device) -> None:
        super().device_remove_event(device)
        self.emit("device-selected", None, None)

    def device_add_event(self, device: Device) -> None:
        self.add_device(device)

    @staticmethod
    def make_caption(name: str, klass: str, address: str) -> str:
        return "<span size='x-large'>%(0)s</span>\n<span size='small'>%(1)s</span>\n<i>%(2)s</i>" \
               % {"0": html.escape(name), "1": klass, "2": address}

    @staticmethod
    def get_device_class(device: Device) -> str:
        klass = get_minor_class(device['Class'])
        if klass != _("Uncategorized"):
            return klass
        else:
            return get_major_class(device['Class'])

    def row_setup_event(self, tree_iter: Gtk.TreeIter, device: Device) -> None:
        if not self.get(tree_iter, "initial_anim")["initial_anim"]:
            assert self.liststore is not None
            child_path = self.liststore.get_path(tree_iter)
            result = self.filter.convert_child_path_to_path(child_path)

            if child_path is not None:
                cell_fader = CellFade(self, child_path, [2, 3, 4, 5])
                row_fader = TreeRowFade(self, child_path)

                self.set(tree_iter, row_fader=row_fader, cell_fader=cell_fader)

                cell_fader.freeze()

                if result is not None:
                    self._prepare_fader(row_fader).animate(start=0.0, end=1.0, duration=500)
                    self.set(tree_iter, initial_anim=True)
                else:
                    self.set(tree_iter, initial_anim=False)

        has_objpush = self._has_objpush(device)
        klass = get_minor_class(device['Class'])
        # Bluetooth >= 4 devices use Appearance property
        appearance = device["Appearance"]
        if klass != _("Uncategorized") and klass != _("Unknown"):
            description = klass
        elif klass == _("Unknown") and appearance:
            description = gatt_appearance_to_name(appearance)
        else:
            description = get_major_class(device['Class'])

        icon_info = self.get_icon_info(device["Icon"], 48, False)
        caption = self.make_caption(device['Alias'], description, device['Address'])

        self.set(tree_iter, caption=caption, icon_info=icon_info, alias=device['Alias'], objpush=has_objpush)

        try:
            self.row_update_event(tree_iter, "Trusted", device['Trusted'])
        except Exception as e:
            logging.exception(e)
        try:
            self.row_update_event(tree_iter, "Paired", device['Paired'])
        except Exception as e:
            logging.exception(e)
        try:
            self.row_update_event(tree_iter, "Connected", device["Connected"])
        except Exception as e:
            logging.exception(e)
        try:
            self.row_update_event(tree_iter, "Blocked", device["Blocked"])
        except Exception as e:
            logging.exception(e)

        if device["Connected"]:
            self._monitor_power_levels(tree_iter, device)

    def _monitor_power_levels(self, tree_iter: Gtk.TreeIter, device: Device) -> None:
        if device["Address"] in self._monitored_devices:
            return

        assert self.Adapter is not None
        cinfo = conn_info(device["Address"], os.path.basename(self.Adapter.get_object_path()))
        try:
            cinfo.init()
        except ConnInfoReadError:
            logging.warning("Failed to get power levels, probably a LE device.")

        model = self.liststore
        assert isinstance(model, Gtk.TreeModel)
        r = Gtk.TreeRowReference.new(model, model.get_path(tree_iter))
        self._update_power_levels(tree_iter, device, cinfo)
        GLib.timeout_add(1000, self._check_power_levels, r, cinfo, device["Address"])
        self._monitored_devices.add(device["Address"])

    def _check_power_levels(self, row_ref: Gtk.TreeRowReference, cinfo: conn_info, address: str) -> bool:
        if not row_ref.valid():
            logging.warning("stopping monitor (row does not exist)")
            cinfo.deinit()
            self._monitored_devices.remove(address)
            return False

        tree_iter = self.get_iter(row_ref.get_path())
        assert tree_iter is not None

        device = self.get(tree_iter, "device")["device"]

        if device["Connected"]:
            self._update_power_levels(tree_iter, device, cinfo)
            return True
        else:
            cinfo.deinit()
            self._disable_power_levels(tree_iter)
            self._monitored_devices.remove(address)
            return False

    def row_update_event(self, tree_iter: Gtk.TreeIter, key: str, value: Any) -> None:
        logging.info(f"{key} {value}")

        if key == "Trusted":
            if value:
                self.set(tree_iter, trusted=True)
            else:
                self.set(tree_iter, trusted=False)

        elif key == "Paired":
            if value:
                self.set(tree_iter, paired=True)
            else:
                self.set(tree_iter, paired=False)

        elif key == "Alias":
            device = self.get(tree_iter, "device")["device"]
            c = self.make_caption(value, self.get_device_class(device), device['Address'])
            self.set(tree_iter, caption=c, alias=value)

        elif key == "UUIDs":
            device = self.get(tree_iter, "device")["device"]
            has_objpush = self._has_objpush(device)
            self.set(tree_iter, objpush=has_objpush)

        elif key == "Connected":
            self.set(tree_iter, connected=value)

            if value:
                self._monitor_power_levels(tree_iter, self.get(tree_iter, "device")["device"])
            else:
                self._disable_power_levels(tree_iter)
        elif key == "Name":
            self.set(tree_iter, no_name=False)
            self.filter.refilter()

        elif key == "Blocked":
            self.set(tree_iter, blocked=value)

    def _update_power_levels(self, tree_iter: Gtk.TreeIter, device: Device, cinfo: conn_info) -> None:
        row = self.get(tree_iter, "cell_fader", "battery", "rssi", "lq", "tpl")

        bars = {}

        obj_path = device.get_object_path()
        if obj_path in self._batteries:
            bars["battery"] = self._batteries[obj_path]["Percentage"]

        # cinfo init may fail for bluetooth devices version 4 and up
        # FIXME Workaround is horrible and we should show something better
        if cinfo.failed:
            bars.update({"rssi": 100.0, "tpl": 100.0, "lq": 100.0})
        else:
            try:
                bars["rssi"] = max(50 + float(cinfo.get_rssi()) / 127 * 50, 10)
            except ConnInfoReadError:
                bars["rssi"] = 50
            try:
                bars["lq"] = max(float(cinfo.get_lq()) / 255 * 100, 10)
            except ConnInfoReadError:
                bars["lq"] = 10
            try:
                bars["tpl"] = max(50 + float(cinfo.get_tpl()) / 127 * 50, 10)
            except ConnInfoReadError:
                bars["tpl"] = 50

        if row["battery"] == row["rssi"] == row["tpl"] == row["lq"] == 0:
            self._prepare_fader(row["cell_fader"]).animate(start=0.0, end=1.0, duration=400)

        w = 14 * self.get_scale_factor()
        h = 48 * self.get_scale_factor()

        for (name, perc) in bars.items():
            if round(row[name], -1) != round(perc, -1):
                icon_name = f"blueman-{name}-{int(round(perc, -1))}.png"
                icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(os.path.join(PIXMAP_PATH, icon_name), w, h, True)
                self.set(tree_iter, **{name: perc, f"{name}_pb": icon})

    def _disable_power_levels(self, tree_iter: Gtk.TreeIter) -> None:
        row = self.get(tree_iter, "cell_fader", "battery", "rssi", "lq", "tpl")
        if row["battery"] == row["rssi"] == row["tpl"] == row["lq"] == 0:
            return

        self.set(tree_iter, battery=0, rssi=0, lq=0, tpl=0)
        self._prepare_fader(row["cell_fader"], lambda: self.set(tree_iter, battery_pb=None, rssi_pb=None, lq_pb=None,
                                                                tpl_pb=None)).animate(start=1.0, end=0.0, duration=400)

    def _prepare_fader(self, fader: AnimBase, callback: Optional[Callable[[], None]] = None) -> AnimBase:
        def on_finished(finished_fader: AnimBase) -> None:
            finished_fader.disconnect(handler)
            finished_fader.freeze()
            if callback:
                callback()

        fader.thaw()
        handler = fader.connect("animation-finished", on_finished)
        return fader

    def tooltip_query(self, _tw: Gtk.Widget, x: int, y: int, _kb: bool, tooltip: Gtk.Tooltip) -> bool:
        path = self.get_path_at_pos(x, y)
        if path is None:
            return False

        if path[0] != self.tooltip_row or path[1] != self.tooltip_col:
            self.tooltip_row = path[0]
            self.tooltip_col = path[1]
            return False

        if path[1] == self.columns["device_surface"]:
            tree_iter = self.get_iter(path[0])
            assert tree_iter is not None

            row = self.get(tree_iter, "trusted", "paired", "blocked")
            trusted = row["trusted"]
            paired = row["paired"]
            blocked = row["blocked"]
            str_list = []
            if trusted:
                str_list.append(_("Trusted"))
            if paired:
                str_list.append(_("Paired"))
            if blocked:
                str_list.append(_("Blocked"))

            text = ", ".join(str_list)
            if text:
                tooltip.set_markup(f"<b>{text}</b>")
            else:
                return False

            self.tooltip_row = path[0]
            self.tooltip_col = path[1]
            return True

        elif path[1] == self.columns["battery_pb"] \
                or path[1] == self.columns["tpl_pb"] \
                or path[1] == self.columns["lq_pb"] \
                or path[1] == self.columns["rssi_pb"]:
            tree_iter = self.get_iter(path[0])
            assert tree_iter is not None

            dt = self.get(tree_iter, "connected")["connected"]
            if not dt:
                return False

            lines = [_("<b>Connected</b>")]

            battery = self.get(tree_iter, "battery")["battery"]
            rssi = self.get(tree_iter, "rssi")["rssi"]
            lq = self.get(tree_iter, "lq")["lq"]
            tpl = self.get(tree_iter, "tpl")["tpl"]

            if battery != 0:
                if path[1] == self.columns["battery_pb"]:
                    lines.append(f"<b>Battery: {int(battery)}%</b>")
                else:
                    lines.append(f"Battery: {int(battery)}%")

            if rssi != 0:
                if rssi < 30:
                    rssi_state = _("Poor")
                elif rssi < 40:
                    rssi_state = _("Sub-optimal")
                elif rssi < 60:
                    rssi_state = _("Optimal")
                elif rssi < 70:
                    rssi_state = _("Much")
                else:
                    rssi_state = _("Too much")

                if path[1] == self.columns["rssi_pb"]:
                    lines.append(_("<b>Received Signal Strength: %(rssi)u%%</b> <i>(%(rssi_state)s)</i>") %
                                 {"rssi": rssi, "rssi_state": rssi_state})
                else:
                    lines.append(_("Received Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>") %
                                 {"rssi": rssi, "rssi_state": rssi_state})

            if lq != 0:
                if path[1] == self.columns["lq_pb"]:
                    lines.append(_("<b>Link Quality: %(lq)u%%</b>") % {"lq": lq})
                else:
                    lines.append(_("Link Quality: %(lq)u%%") % {"lq": lq})

            if tpl != 0:
                if tpl < 30:
                    tpl_state = _("Low")
                elif tpl < 40:
                    tpl_state = _("Sub-optimal")
                elif tpl < 60:
                    tpl_state = _("Optimal")
                elif tpl < 70:
                    tpl_state = _("High")
                else:
                    tpl_state = _("Very High")

                if path[1] == self.columns["tpl_pb"]:
                    lines.append(_("<b>Transmit Power Level: %(tpl)u%%</b> <i>(%(tpl_state)s)</i>") %
                                 {"tpl": tpl, "tpl_state": tpl_state})
                else:
                    lines.append(_("Transmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>") %
                                 {"tpl": tpl, "tpl_state": tpl_state})

            tooltip.set_markup("\n".join(lines))
            self.tooltip_row = path[0]
            self.tooltip_col = path[1]
            return True
        return False

    def _has_objpush(self, device: Device) -> bool:
        if device is None:
            return False

        for uuid in device["UUIDs"]:
            if ServiceUUID(uuid).short_uuid == OBEX_OBJPUSH_SVCLASS_ID:
                return True
        return False

    def _set_cell_data(self, _col: Gtk.TreeViewColumn, cell: Gtk.CellRenderer, model: Gtk.TreeModelFilter,
                       tree_iter: Gtk.TreeIter, data: Optional[str]) -> None:
        tree_iter = model.convert_iter_to_child_iter(tree_iter)
        if data is None:
            row = self.get(tree_iter, "icon_info", "trusted", "paired", "blocked")
            surface = self.make_device_icon(row["icon_info"], row["paired"], row["trusted"], row["blocked"])
            cell.set_property("surface", surface)
        else:
            window = self.get_window()
            scale = self.get_scale_factor()
            pb = self.get(tree_iter, data + "_pb")[data + "_pb"]
            if pb:
                surface = Gdk.cairo_surface_create_from_pixbuf(pb, scale, window)
                cell.set_property("surface", surface)
            else:
                cell.set_property("surface", None)
Пример #17
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")
        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)
        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"), '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: 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":
            self.blueman.List.display_known_devices()

    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)
        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)

        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() -> None:
            pass
        self.blueman.Applet.OpenPluginDialog(result_handler=cb)
Пример #18
0
class ManagerDeviceList(DeviceList):
    def __init__(self, adapter=None, inst=None):
        cr = Gtk.CellRendererText()
        cr.props.ellipsize = Pango.EllipsizeMode.END
        tabledata = [
            # device picture
            {"id": "device_surface", "type": str, "renderer": Gtk.CellRendererPixbuf(),
             "render_attrs": {}, "celldata_func": (self._set_device_cell_data, None)},
            # device caption
            {"id": "caption", "type": str, "renderer": cr,
             "render_attrs": {"markup": 1}, "view_props": {"expand": True}},
            {"id": "rssi_pb", "type": GdkPixbuf.Pixbuf, "renderer": Gtk.CellRendererPixbuf(),
             "render_attrs": {"pixbuf": 2}, "view_props": {"spacing": 0}},
            {"id": "lq_pb", "type": GdkPixbuf.Pixbuf, "renderer": Gtk.CellRendererPixbuf(),
             "render_attrs": {"pixbuf": 3}, "view_props": {"spacing": 0}},
            {"id": "tpl_pb", "type": GdkPixbuf.Pixbuf, "renderer": Gtk.CellRendererPixbuf(),
             "render_attrs": {"pixbuf": 4}, "view_props": {"spacing": 0}},
            {"id": "alias", "type": str},  # used for quick access instead of device.GetProperties
            {"id": "connected", "type": bool},  # used for quick access instead of device.GetProperties
            {"id": "paired", "type": bool},  # used for quick access instead of device.GetProperties
            {"id": "trusted", "type": bool},  # used for quick access instead of device.GetProperties
            {"id": "objpush", "type": bool},  # used to set Send File button
            {"id": "rssi", "type": float},
            {"id": "lq", "type": float},
            {"id": "tpl", "type": float},
            {"id": "icon_info", "type": Gtk.IconInfo},
            {"id": "cell_fader", "type": GObject.TYPE_PYOBJECT},
            {"id": "row_fader", "type": GObject.TYPE_PYOBJECT},
            {"id": "levels_visible", "type": bool},
            {"id": "initial_anim", "type": bool},
        ]
        super(ManagerDeviceList, self).__init__(adapter, tabledata)
        self.set_name("ManagerDeviceList")
        self.set_headers_visible(False)
        self.props.has_tooltip = True
        self.Blueman = inst

        self.Config = Config("org.blueman.general")
        self.Config.connect('changed', self._on_settings_changed)
        # Set the correct sorting
        self._on_settings_changed(self.Config, "sort-by")
        self._on_settings_changed(self.Config, "sort-type")

        self.connect("query-tooltip", self.tooltip_query)
        self.tooltip_row = None
        self.tooltip_col = None

        self.connect("button_press_event", self.on_event_clicked)
        self.connect("button_release_event", self.on_event_clicked)

        self.menu = None

        self.connect("drag_data_received", self.drag_recv)
        self.connect("drag-motion", self.drag_motion)

        Gtk.Widget.drag_dest_set(self, Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY | Gdk.DragAction.DEFAULT)
        Gtk.Widget.drag_dest_add_uri_targets(self)

        self.set_search_equal_func(self.search_func, None)

    def _on_settings_changed(self, settings, key):
        if key in ('sort-by', 'sort-order'):
            sort_by = settings['sort-by']
            sort_order = settings['sort-order']

            if sort_order == 'ascending':
                sort_type = Gtk.SortType.ASCENDING
            else:
                sort_type = Gtk.SortType.DESCENDING

            column_id = self.ids.setdefault(sort_by, None)

            if column_id:
                self.liststore.set_sort_column_id(column_id, sort_type)

    def on_icon_theme_changed(self, widget):
        for row in self.liststore:
            device = self.get(row.iter, "device")["device"]
            self.row_setup_event(row.iter, device)

    def do_device_found(self, device):
        tree_iter = self.find_device(device)
        if tree_iter:
            anim = TreeRowColorFade(self, self.props.model.get_path(tree_iter), Gdk.RGBA(0, 0, 1, 1))
            anim.animate(start=0.8, end=1.0)

    def search_func(self, model, column, key, tree_iter):
        row = self.get(tree_iter, "caption")
        if key.lower() in row["caption"].lower():
            return False
        logging.info("%s %s %s %s" % (model, column, key, tree_iter))
        return True

    def drag_recv(self, widget, context, x, y, selection, target_type, time):

        uris = list(selection.get_uris())

        context.finish(True, False, time)

        path = self.get_path_at_pos(x, y)
        if path:
            tree_iter = self.get_iter(path[0])
            device = self.get(tree_iter, "device")["device"]
            command = "blueman-sendto --device=%s" % device['Address']

            launch(command, uris, False, "blueman", _("File Sender"))
            context.finish(True, False, time)
        else:
            context.finish(False, False, time)

        return True

    def drag_motion(self, widget, drag_context, x, y, timestamp):
        result = self.get_path_at_pos(x, y)
        if result is not None:
            path = result[0]
            if not self.selection.path_is_selected(path):
                tree_iter = self.get_iter(path)
                has_obj_push = self._has_objpush(self.get(tree_iter, "device")["device"])
                if has_obj_push:
                    Gdk.drag_status(drag_context, Gdk.DragAction.COPY, timestamp)
                    self.set_cursor(path)
                    return True
                else:
                    Gdk.drag_status(drag_context, Gdk.DragAction.DEFAULT, timestamp)
                    return False
        else:
            Gdk.drag_status(drag_context, Gdk.DragAction.DEFAULT, timestamp)
            return False

    def on_event_clicked(self, widget, event):

        if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
            path = self.get_path_at_pos(int(event.x), int(event.y))
            if path is not None:
                row = self.get(path[0], "device")

                if row:
                    if self.Blueman is not None:
                        if self.menu is None:
                            self.menu = ManagerDeviceMenu(self.Blueman)

                        self.menu.popup(None, None, None, None, event.button, event.time)

    def get_icon_info(self, icon_name, size=48, fallback=True):
        if icon_name is None and not fallback:
            return None
        elif icon_name is None and fallback:
            icon_name = "image-missing"

        icon_info = self.icon_theme.lookup_icon_for_scale(icon_name, size, self.get_scale_factor(),
                                                          Gtk.IconLookupFlags.FORCE_SIZE)

        return icon_info

    def make_device_icon(self, icon_info, is_paired=False, is_trusted=False):
        window = self.get_window()
        scale = self.get_scale_factor()
        target = icon_info.load_surface(window)
        ctx = cairo.Context(target)

        if is_paired:
            icon_info = self.get_icon_info("dialog-password", 16, False)
            paired_surface = icon_info.load_surface(window)
            ctx.set_source_surface(paired_surface, 1 / scale, 1 / scale)
            ctx.paint_with_alpha(0.8)

        if is_trusted:
            icon_info = self.get_icon_info("blueman-trust", 16, False)
            trusted_surface = icon_info.load_surface(window)
            height = target.get_height()
            mini_height = trusted_surface.get_height()
            y = height / scale - mini_height / scale - 1 / scale

            ctx.set_source_surface(trusted_surface, 1 / scale, y)
            ctx.paint_with_alpha(0.8)

        return target

    def device_remove_event(self, device, tree_iter):
        row_fader = self.get(tree_iter, "row_fader")["row_fader"]

        def on_finished(fader):

            fader.disconnect(signal)
            fader.freeze()
            super(ManagerDeviceList, self).device_remove_event(device, tree_iter)

        signal = row_fader.connect("animation-finished", on_finished)
        row_fader.thaw()
        self.emit("device-selected", None, None)
        row_fader.animate(start=row_fader.get_state(), end=0.0, duration=400)

    def device_add_event(self, device):
        self.add_device(device)

    def make_caption(self, name, klass, address):
        return "<span size='x-large'>%(0)s</span>\n<span size='small'>%(1)s</span>\n<i>%(2)s</i>" \
               % {"0": html.escape(name), "1": klass.capitalize(), "2": address}

    def get_device_class(self, device):
        klass = get_minor_class(device['Class'])
        if klass != "uncategorized":
            return get_minor_class(device['Class'], True)
        else:
            return get_major_class(device['Class'])

    def row_setup_event(self, tree_iter, device):
        if not self.get(tree_iter, "initial_anim")["initial_anim"]:
            cell_fader = CellFade(self, self.props.model.get_path(tree_iter), [2, 3, 4])
            row_fader = TreeRowFade(self, self.props.model.get_path(tree_iter))

            has_objpush = self._has_objpush(device)

            self.set(tree_iter, row_fader=row_fader, cell_fader=cell_fader, levels_visible=False, objpush=has_objpush)

            cell_fader.freeze()

            def on_finished(fader):
                fader.disconnect(signal)
                fader.freeze()

            signal = row_fader.connect("animation-finished", on_finished)
            row_fader.set_state(0.0)
            row_fader.animate(start=0.0, end=1.0, duration=500)

            self.set(tree_iter, initial_anim=True)

        klass = get_minor_class(device['Class'])
        # Bluetooth >= 4 devices use Appearance property
        appearance = device["Appearance"]
        if klass != "uncategorized" and klass != "unknown":
            # get translated version
            description = get_minor_class(device['Class'], True).capitalize()
        elif klass == "unknown" and appearance:
            description = gatt_appearance_to_name(appearance)
        else:
            description = get_major_class(device['Class']).capitalize()

        icon_info = self.get_icon_info(device["Icon"], 48, False)
        caption = self.make_caption(device['Alias'], description, device['Address'])

        self.set(tree_iter, caption=caption, icon_info=icon_info, alias=device['Alias'])

        try:
            self.row_update_event(tree_iter, "Trusted", device['Trusted'])
        except Exception as e:
            logging.exception(e)
        try:
            self.row_update_event(tree_iter, "Paired", device['Paired'])
        except Exception as e:
            logging.exception(e)
        try:
            self.row_update_event(tree_iter, "Connected", device["Connected"])
        except Exception as e:
            logging.exception(e)

    def row_update_event(self, tree_iter, key, value):
        logging.info("%s %s" % (key, value))

        if key == "Trusted":
            if value:
                self.set(tree_iter, trusted=True)
            else:
                self.set(tree_iter, trusted=False)

        elif key == "Paired":
            if value:
                self.set(tree_iter, paired=True)
            else:
                self.set(tree_iter, paired=False)

        elif key == "Alias":
            device = self.get(tree_iter, "device")["device"]
            c = self.make_caption(value, self.get_device_class(device), device['Address'])
            self.set(tree_iter, caption=c, alias=value)

        elif key == "UUIDs":
            device = self.get(tree_iter, "device")["device"]
            has_objpush = self._has_objpush(device)
            self.set(tree_iter, objpush=has_objpush)

        elif key == "Connected":
            self.set(tree_iter, connected=value)

    def level_setup_event(self, row_ref, device, cinfo):
        if not row_ref.valid():
            return

        tree_iter = self.get_iter(row_ref.get_path())
        row = self.get(tree_iter, "levels_visible", "cell_fader", "rssi", "lq", "tpl")
        if cinfo is not None:
            # cinfo init may fail for bluetooth devices version 4 and up
            # FIXME Workaround is horrible and we should show something better
            if cinfo.failed:
                rssi_perc = tpl_perc = lq_perc = 100
            else:
                try:
                    rssi = float(cinfo.get_rssi())
                except ConnInfoReadError:
                    rssi = 0
                try:
                    lq = float(cinfo.get_lq())
                except ConnInfoReadError:
                    lq = 0

                try:
                    tpl = float(cinfo.get_tpl())
                except ConnInfoReadError:
                    tpl = 0

                rssi_perc = 50 + (rssi / 127 / 2 * 100)
                tpl_perc = 50 + (tpl / 127 / 2 * 100)
                lq_perc = lq / 255 * 100

                if lq_perc < 10:
                    lq_perc = 10
                if rssi_perc < 10:
                    rssi_perc = 10
                if tpl_perc < 10:
                    tpl_perc = 10

            if not row["levels_visible"]:
                logging.info("animating up")
                self.set(tree_iter, levels_visible=True)
                fader = row["cell_fader"]
                fader.thaw()
                fader.set_state(0.0)
                fader.animate(start=0.0, end=1.0, duration=400)

                def on_finished(fader):
                    fader.freeze()
                    fader.disconnect(signal)

                signal = fader.connect("animation-finished", on_finished)

            to_store = {}
            if round(row["rssi"], -1) != round(rssi_perc, -1):
                icon_name = "blueman-rssi-%d.png" % round(rssi_perc, -1)
                icon = GdkPixbuf.Pixbuf.new_from_file(os.path.join(PIXMAP_PATH, icon_name))
                to_store.update({"rssi": rssi_perc, "rssi_pb": icon})

            if round(row["lq"], -1) != round(lq_perc, -1):
                icon_name = "blueman-lq-%d.png" % round(lq_perc, -1)
                icon = GdkPixbuf.Pixbuf.new_from_file(os.path.join(PIXMAP_PATH, icon_name))
                to_store.update({"lq": lq_perc, "lq_pb": icon})

            if round(row["tpl"], -1) != round(tpl_perc, -1):
                icon_name = "blueman-tpl-%d.png" % round(tpl_perc, -1)
                icon = GdkPixbuf.Pixbuf.new_from_file(os.path.join(PIXMAP_PATH, icon_name))
                to_store.update({"tpl": tpl_perc, "tpl_pb": icon})

            if to_store:
                self.set(tree_iter, **to_store)

        else:

            if row["levels_visible"]:
                logging.info("animating down")
                self.set(tree_iter, levels_visible=False,
                         rssi=-1,
                         lq=-1,
                         tpl=-1)
                fader = row["cell_fader"]
                fader.thaw()
                fader.set_state(1.0)
                fader.animate(start=fader.get_state(), end=0.0, duration=400)

                def on_finished(fader):
                    fader.disconnect(signal)
                    fader.freeze()
                    if row_ref.valid():
                        self.set(tree_iter, rssi_pb=None, lq_pb=None, tpl_pb=None)

                signal = fader.connect("animation-finished", on_finished)

    def tooltip_query(self, tw, x, y, kb, tooltip):
        path = self.get_path_at_pos(x, y)

        if path is not None:
            if path[0] != self.tooltip_row or path[1] != self.tooltip_col:
                self.tooltip_row = path[0]
                self.tooltip_col = path[1]
                return False

            if path[1] == self.columns["device_surface"]:
                tree_iter = self.get_iter(path[0])

                row = self.get(tree_iter, "trusted", "paired")
                trusted = row["trusted"]
                paired = row["paired"]
                if trusted and paired:
                    tooltip.set_markup(_("<b>Trusted and Paired</b>"))
                elif paired:
                    tooltip.set_markup(_("<b>Paired</b>"))
                elif trusted:
                    tooltip.set_markup(_("<b>Trusted</b>"))
                else:
                    return False

                self.tooltip_row = path[0]
                self.tooltip_col = path[1]
                return True

            if path[1] == self.columns["tpl_pb"] \
                    or path[1] == self.columns["lq_pb"] \
                    or path[1] == self.columns["rssi_pb"]:
                tree_iter = self.get_iter(path[0])

                dt = self.get(tree_iter, "connected")["connected"]
                if dt:
                    rssi = self.get(tree_iter, "rssi")["rssi"]
                    lq = self.get(tree_iter, "lq")["lq"]
                    tpl = self.get(tree_iter, "tpl")["tpl"]

                    if rssi < 30:
                        rssi_state = _("Poor")
                    elif rssi < 40:
                        rssi_state = _("Sub-optimal")
                    elif rssi < 60:
                        rssi_state = _("Optimal")
                    elif rssi < 70:
                        rssi_state = _("Much")
                    else:
                        rssi_state = _("Too much")

                    if tpl < 30:
                        tpl_state = _("Low")
                    elif tpl < 40:
                        tpl_state = _("Sub-optimal")
                    elif tpl < 60:
                        tpl_state = _("Optimal")
                    elif tpl < 70:
                        tpl_state = _("High")
                    else:
                        tpl_state = _("Very High")

                    tooltip_template = None
                    if path[1] == self.columns["tpl_pb"]:
                        tooltip_template = \
                            "<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\n" \
                            "Link Quality: %(lq)u%%\n<b>Transmit Power Level: %(tpl)u%%</b> <i>(%(tpl_state)s)</i>"
                    elif path[1] == self.columns["lq_pb"]:
                        tooltip_template = \
                            "<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\n" \
                            "<b>Link Quality: %(lq)u%%</b>\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>"
                    elif path[1] == self.columns["rssi_pb"]:
                        tooltip_template = \
                            "<b>Connected</b>\n<b>Received Signal Strength: %(rssi)u%%</b> <i>(%(rssi_state)s)</i>\n" \
                            "Link Quality: %(lq)u%%\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>"

                    state_dict = {"rssi_state": rssi_state, "rssi": rssi, "lq": lq, "tpl": tpl, "tpl_state": tpl_state}
                    tooltip.set_markup(tooltip_template % state_dict)
                    self.tooltip_row = path[0]
                    self.tooltip_col = path[1]
                    return True
        return False

    def _has_objpush(self, device):
        if device is None:
            return False

        for uuid in device["UUIDs"]:
            if ServiceUUID(uuid).short_uuid == OBEX_OBJPUSH_SVCLASS_ID:
                return True
        return False

    def _set_device_cell_data(self, col, cell, model, tree_iter, data):
        row = self.get(tree_iter, "icon_info", "trusted", "paired")
        surface = self.make_device_icon(row["icon_info"], row["paired"], row["trusted"])
        cell.set_property("surface", surface)
Пример #19
0
class ManagerDeviceList(DeviceList):
    def __init__(self,
                 adapter: Optional[str] = None,
                 inst: Optional["Blueman"] = None) -> None:
        cr = Gtk.CellRendererText()
        cr.props.ellipsize = Pango.EllipsizeMode.END
        tabledata: List[ListDataDict] = [
            # device picture
            {
                "id": "device_surface",
                "type": str,
                "renderer": Gtk.CellRendererPixbuf(),
                "render_attrs": {},
                "celldata_func": (self._set_cell_data, None)
            },
            # device caption
            {
                "id": "caption",
                "type": str,
                "renderer": cr,
                "render_attrs": {
                    "markup": 1
                },
                "view_props": {
                    "expand": True
                }
            },
            {
                "id": "rssi_pb",
                "type": GdkPixbuf.Pixbuf,
                "renderer": Gtk.CellRendererPixbuf(),
                "render_attrs": {},
                "view_props": {
                    "spacing": 0
                },
                "celldata_func": (self._set_cell_data, "rssi")
            },
            {
                "id": "lq_pb",
                "type": GdkPixbuf.Pixbuf,
                "renderer": Gtk.CellRendererPixbuf(),
                "render_attrs": {},
                "view_props": {
                    "spacing": 0
                },
                "celldata_func": (self._set_cell_data, "lq")
            },
            {
                "id": "tpl_pb",
                "type": GdkPixbuf.Pixbuf,
                "renderer": Gtk.CellRendererPixbuf(),
                "render_attrs": {},
                "view_props": {
                    "spacing": 0
                },
                "celldata_func": (self._set_cell_data, "tpl")
            },
            {
                "id": "alias",
                "type": str
            },  # used for quick access instead of device.GetProperties
            {
                "id": "connected",
                "type": bool
            },  # used for quick access instead of device.GetProperties
            {
                "id": "paired",
                "type": bool
            },  # used for quick access instead of device.GetProperties
            {
                "id": "trusted",
                "type": bool
            },  # used for quick access instead of device.GetProperties
            {
                "id": "objpush",
                "type": bool
            },  # used to set Send File button
            {
                "id": "rssi",
                "type": float
            },
            {
                "id": "lq",
                "type": float
            },
            {
                "id": "tpl",
                "type": float
            },
            {
                "id": "icon_info",
                "type": Gtk.IconInfo
            },
            {
                "id": "cell_fader",
                "type": CellFade
            },
            {
                "id": "row_fader",
                "type": TreeRowFade
            },
            {
                "id": "levels_visible",
                "type": bool
            },
            {
                "id": "initial_anim",
                "type": bool
            },
        ]
        super().__init__(adapter, tabledata)
        self.set_name("ManagerDeviceList")
        self.set_headers_visible(False)
        self.props.has_tooltip = True
        self.Blueman = inst

        self.Config = Config("org.blueman.general")
        self.Config.connect('changed', self._on_settings_changed)
        # Set the correct sorting
        self._on_settings_changed(self.Config, "sort-by")
        self._on_settings_changed(self.Config, "sort-type")

        self.connect("query-tooltip", self.tooltip_query)
        self.tooltip_row: Optional[Gtk.TreePath] = None
        self.tooltip_col: Optional[Gtk.TreeViewColumn] = None

        self.connect("popup-menu", self._on_popup_menu)
        self.connect("button_press_event", self.on_event_clicked)
        self.connect("button_release_event", self.on_event_clicked)

        self.menu: Optional[ManagerDeviceMenu] = None

        self.connect("drag_data_received", self.drag_recv)
        self.connect("drag-motion", self.drag_motion)

        Gtk.Widget.drag_dest_set(self, Gtk.DestDefaults.ALL, [],
                                 Gdk.DragAction.COPY | Gdk.DragAction.DEFAULT)
        Gtk.Widget.drag_dest_add_uri_targets(self)

        self.set_search_equal_func(self.search_func)

        self._faderhandlers: Dict[str, int] = {}

    def _on_settings_changed(self, settings: Config, key: str) -> None:
        if key in ('sort-by', 'sort-order'):
            sort_by = settings['sort-by']
            sort_order = settings['sort-order']

            if sort_order == 'ascending':
                sort_type = Gtk.SortType.ASCENDING
            else:
                sort_type = Gtk.SortType.DESCENDING

            column_id = self.ids.get(sort_by)

            if column_id:
                self.liststore.set_sort_column_id(column_id, sort_type)

    def on_icon_theme_changed(self, _icon_them: Gtk.IconTheme) -> None:
        for row in self.liststore:
            device = self.get(row.iter, "device")["device"]
            self.row_setup_event(row.iter, device)

    def search_func(self, model: Gtk.TreeModel, column: int, key: str,
                    tree_iter: Gtk.TreeIter) -> bool:
        row = self.get(tree_iter, "caption")
        if key.lower() in row["caption"].lower():
            return False
        logging.info(f"{model} {column} {key} {tree_iter}")
        return True

    def drag_recv(self, _widget: Gtk.Widget, context: Gdk.DragContext, x: int,
                  y: int, selection: Gtk.SelectionData, _info: int,
                  time: int) -> None:

        uris = list(selection.get_uris())

        context.finish(True, False, time)

        path = self.get_path_at_pos(x, y)
        if path:
            tree_iter = self.get_iter(path[0])
            assert tree_iter is not None
            device = self.get(tree_iter, "device")["device"]
            command = f"blueman-sendto --device={device['Address']}"

            launch(command, paths=uris, name=_("File Sender"))
            context.finish(True, False, time)
        else:
            context.finish(False, False, time)

    def drag_motion(self, _widget: Gtk.Widget, drag_context: Gdk.DragContext,
                    x: int, y: int, timestamp: int) -> bool:
        result = self.get_path_at_pos(x, y)
        if result is not None:
            path = result[0]
            assert path is not None
            if not self.selection.path_is_selected(path):
                tree_iter = self.get_iter(path)
                assert tree_iter is not None
                has_obj_push = self._has_objpush(
                    self.get(tree_iter, "device")["device"])
                if has_obj_push:
                    Gdk.drag_status(drag_context, Gdk.DragAction.COPY,
                                    timestamp)
                    self.set_cursor(path)
                    return True
                else:
                    Gdk.drag_status(drag_context, Gdk.DragAction.DEFAULT,
                                    timestamp)
                    return False
            return False
        else:
            Gdk.drag_status(drag_context, Gdk.DragAction.DEFAULT, timestamp)
            return False

    def _on_popup_menu(self, _widget: Gtk.Widget) -> bool:
        if self.Blueman is None:
            return False

        if self.menu is None:
            self.menu = ManagerDeviceMenu(self.Blueman)

        window = self.get_window()
        assert window is not None
        selected = self.selected()
        assert selected is not None
        rect = self.get_cell_area(self.liststore.get_path(selected),
                                  self.get_column(1))
        self.menu.popup_at_rect(window, rect, Gdk.Gravity.CENTER,
                                Gdk.Gravity.NORTH)

        return True

    def on_event_clicked(self, _widget: Gtk.Widget, event: Gdk.Event) -> bool:
        if event.type not in (Gdk.EventType._2BUTTON_PRESS,
                              Gdk.EventType.BUTTON_PRESS):
            return False

        path = self.get_path_at_pos(int(cast(Gdk.EventButton, event).x),
                                    int(cast(Gdk.EventButton, event).y))
        if path is None:
            return False

        assert path[0] is not None
        row = self.get(path[0], "device", "connected")
        if not row:
            return False

        if self.Blueman is None:
            return False

        if self.menu is None:
            self.menu = ManagerDeviceMenu(self.Blueman)

        if event.type == Gdk.EventType._2BUTTON_PRESS and cast(
                Gdk.EventButton, event).button == 1:
            if self.menu.show_generic_connect_calc(row["device"]['UUIDs']):
                self.menu.generic_connect(None,
                                          device=row["device"],
                                          connect=not row["connected"])

        if event.type == Gdk.EventType.BUTTON_PRESS and cast(
                Gdk.EventButton, event).button == 3:
            self.menu.popup_at_pointer(event)

        return False

    def get_icon_info(self,
                      icon_name: str,
                      size: int = 48,
                      fallback: bool = True) -> Optional[Gtk.IconInfo]:
        if icon_name is None and not fallback:
            return None
        elif icon_name is None and fallback:
            icon_name = "image-missing"

        icon_info = self.icon_theme.lookup_icon_for_scale(
            icon_name, size, self.get_scale_factor(),
            Gtk.IconLookupFlags.FORCE_SIZE)

        return icon_info

    def make_device_icon(self,
                         icon_info: Gtk.IconInfo,
                         is_paired: bool = False,
                         is_trusted: bool = False) -> cairo.Surface:
        window = self.get_window()
        scale = self.get_scale_factor()
        target = icon_info.load_surface(window)
        ctx = cairo.Context(target)

        if is_paired:
            _icon_info = self.get_icon_info("dialog-password", 16, False)
            assert _icon_info is not None
            paired_surface = _icon_info.load_surface(window)
            ctx.set_source_surface(paired_surface, 1 / scale, 1 / scale)
            ctx.paint_with_alpha(0.8)

        if is_trusted:
            _icon_info = self.get_icon_info("blueman-trust", 16, False)
            assert _icon_info is not None
            trusted_surface = _icon_info.load_surface(window)
            assert isinstance(target, cairo.ImageSurface)
            assert isinstance(trusted_surface, cairo.ImageSurface)
            height = target.get_height()
            mini_height = trusted_surface.get_height()
            y = height / scale - mini_height / scale - 1 / scale

            ctx.set_source_surface(trusted_surface, 1 / scale, y)
            ctx.paint_with_alpha(0.8)

        return target

    def device_remove_event(self, device: Device) -> None:
        tree_iter = self.find_device(device)
        assert tree_iter is not None

        row_fader = self.get(tree_iter, "row_fader")["row_fader"]
        super().device_remove_event(device)
        self._faderhandlers.update({
            device.get_object_path():
            row_fader.connect("animation-finished", self.__on_fader_finished,
                              device)
        })

        row_fader.thaw()
        self.emit("device-selected", None, None)
        row_fader.animate(start=row_fader.get_state(), end=0.0, duration=400)

    def __on_fader_finished(self, fader: TreeRowFade, device: Device) -> None:
        fader.disconnect(self._faderhandlers.pop(device.get_object_path()))
        fader.freeze()

    def device_add_event(self, device: Device) -> None:
        self.add_device(device)

    @staticmethod
    def make_caption(name: str, klass: str, address: str) -> str:
        return "<span size='x-large'>%(0)s</span>\n<span size='small'>%(1)s</span>\n<i>%(2)s</i>" \
               % {"0": html.escape(name), "1": klass, "2": address}

    @staticmethod
    def get_device_class(device: Device) -> str:
        klass = get_minor_class(device['Class'])
        if klass != _("Uncategorized"):
            return klass
        else:
            return get_major_class(device['Class'])

    def row_setup_event(self, tree_iter: Gtk.TreeIter, device: Device) -> None:
        if not self.get(tree_iter, "initial_anim")["initial_anim"]:
            model = self.props.model
            assert model is not None
            cell_fader = CellFade(self, model.get_path(tree_iter), [2, 3, 4])
            row_fader = TreeRowFade(self, model.get_path(tree_iter))

            has_objpush = self._has_objpush(device)

            self.set(tree_iter,
                     row_fader=row_fader,
                     cell_fader=cell_fader,
                     levels_visible=False,
                     objpush=has_objpush)

            cell_fader.freeze()

            def on_finished(fader: TreeRowFade) -> None:
                fader.disconnect(faderhandler)
                fader.freeze()

            faderhandler = row_fader.connect("animation-finished", on_finished)
            row_fader.set_state(0.0)
            row_fader.animate(start=0.0, end=1.0, duration=500)

            self.set(tree_iter, initial_anim=True)

        klass = get_minor_class(device['Class'])
        # Bluetooth >= 4 devices use Appearance property
        appearance = device["Appearance"]
        if klass != _("Uncategorized") and klass != _("Unknown"):
            description = klass
        elif klass == _("Unknown") and appearance:
            description = gatt_appearance_to_name(appearance)
        else:
            description = get_major_class(device['Class'])

        icon_info = self.get_icon_info(device["Icon"], 48, False)
        caption = self.make_caption(device['Alias'], description,
                                    device['Address'])

        self.set(tree_iter,
                 caption=caption,
                 icon_info=icon_info,
                 alias=device['Alias'])

        try:
            self.row_update_event(tree_iter, "Trusted", device['Trusted'])
        except Exception as e:
            logging.exception(e)
        try:
            self.row_update_event(tree_iter, "Paired", device['Paired'])
        except Exception as e:
            logging.exception(e)
        try:
            self.row_update_event(tree_iter, "Connected", device["Connected"])
        except Exception as e:
            logging.exception(e)

    def row_update_event(self, tree_iter: Gtk.TreeIter, key: str,
                         value: Any) -> None:
        logging.info(f"{key} {value}")

        if key == "Trusted":
            if value:
                self.set(tree_iter, trusted=True)
            else:
                self.set(tree_iter, trusted=False)

        elif key == "Paired":
            if value:
                self.set(tree_iter, paired=True)
            else:
                self.set(tree_iter, paired=False)

        elif key == "Alias":
            device = self.get(tree_iter, "device")["device"]
            c = self.make_caption(value, self.get_device_class(device),
                                  device['Address'])
            self.set(tree_iter, caption=c, alias=value)

        elif key == "UUIDs":
            device = self.get(tree_iter, "device")["device"]
            has_objpush = self._has_objpush(device)
            self.set(tree_iter, objpush=has_objpush)

        elif key == "Connected":
            self.set(tree_iter, connected=value)

    def level_setup_event(self, row_ref: Gtk.TreeRowReference, device: Device,
                          cinfo: Optional[conn_info]) -> None:
        if not row_ref.valid():
            return

        tree_iter = self.get_iter(row_ref.get_path())
        assert tree_iter is not None
        row = self.get(tree_iter, "levels_visible", "cell_fader", "rssi", "lq",
                       "tpl")
        if cinfo is not None:
            # cinfo init may fail for bluetooth devices version 4 and up
            # FIXME Workaround is horrible and we should show something better
            if cinfo.failed:
                rssi_perc = tpl_perc = lq_perc = 100.0
            else:
                try:
                    rssi = float(cinfo.get_rssi())
                except ConnInfoReadError:
                    rssi = 0
                try:
                    lq = float(cinfo.get_lq())
                except ConnInfoReadError:
                    lq = 0

                try:
                    tpl = float(cinfo.get_tpl())
                except ConnInfoReadError:
                    tpl = 0

                rssi_perc = 50 + (rssi / 127 / 2 * 100)
                tpl_perc = 50 + (tpl / 127 / 2 * 100)
                lq_perc = lq / 255 * 100

                if lq_perc < 10:
                    lq_perc = 10
                if rssi_perc < 10:
                    rssi_perc = 10
                if tpl_perc < 10:
                    tpl_perc = 10

            if not row["levels_visible"]:
                logging.info("animating up")
                self.set(tree_iter, levels_visible=True)
                fader = row["cell_fader"]
                fader.thaw()
                fader.set_state(0.0)
                fader.animate(start=0.0, end=1.0, duration=400)

                def on_finished(fader: CellFade) -> None:
                    fader.freeze()
                    fader.disconnect(faderhandler)

                faderhandler = fader.connect("animation-finished", on_finished)

            w = 14 * self.get_scale_factor()
            h = 48 * self.get_scale_factor()

            to_store = {}
            if round(row["rssi"], -1) != round(rssi_perc, -1):
                icon_name = "blueman-rssi-%d.png" % round(rssi_perc, -1)
                icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                    os.path.join(PIXMAP_PATH, icon_name), w, h, True)
                to_store.update({"rssi": rssi_perc, "rssi_pb": icon})

            if round(row["lq"], -1) != round(lq_perc, -1):
                icon_name = "blueman-lq-%d.png" % round(lq_perc, -1)
                icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                    os.path.join(PIXMAP_PATH, icon_name), w, h, True)
                to_store.update({"lq": lq_perc, "lq_pb": icon})

            if round(row["tpl"], -1) != round(tpl_perc, -1):
                icon_name = "blueman-tpl-%d.png" % round(tpl_perc, -1)
                icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                    os.path.join(PIXMAP_PATH, icon_name), w, h, True)
                to_store.update({"tpl": tpl_perc, "tpl_pb": icon})

            if to_store:
                self.set(tree_iter, **to_store)

        else:

            if row["levels_visible"]:
                logging.info("animating down")
                self.set(tree_iter,
                         levels_visible=False,
                         rssi=-1,
                         lq=-1,
                         tpl=-1)
                fader = row["cell_fader"]
                fader.thaw()
                fader.set_state(1.0)
                fader.animate(start=fader.get_state(), end=0.0, duration=400)

                def on_finished(fader: CellFade) -> None:
                    fader.disconnect(faderhandler)
                    fader.freeze()
                    if row_ref.valid():
                        assert tree_iter is not None  # https://github.com/python/mypy/issues/2608
                        self.set(tree_iter,
                                 rssi_pb=None,
                                 lq_pb=None,
                                 tpl_pb=None)

                faderhandler = fader.connect("animation-finished", on_finished)

    def tooltip_query(self, _tw: Gtk.Widget, x: int, y: int, _kb: bool,
                      tooltip: Gtk.Tooltip) -> bool:
        path = self.get_path_at_pos(x, y)

        if path is not None:
            if path[0] != self.tooltip_row or path[1] != self.tooltip_col:
                self.tooltip_row = path[0]
                self.tooltip_col = path[1]
                return False

            if path[1] == self.columns["device_surface"]:
                tree_iter = self.get_iter(path[0])
                assert tree_iter is not None

                row = self.get(tree_iter, "trusted", "paired")
                trusted = row["trusted"]
                paired = row["paired"]
                if trusted and paired:
                    tooltip.set_markup(_("<b>Trusted and Paired</b>"))
                elif paired:
                    tooltip.set_markup(_("<b>Paired</b>"))
                elif trusted:
                    tooltip.set_markup(_("<b>Trusted</b>"))
                else:
                    return False

                self.tooltip_row = path[0]
                self.tooltip_col = path[1]
                return True

            if path[1] == self.columns["tpl_pb"] \
                    or path[1] == self.columns["lq_pb"] \
                    or path[1] == self.columns["rssi_pb"]:
                tree_iter = self.get_iter(path[0])
                assert tree_iter is not None

                dt = self.get(tree_iter, "connected")["connected"]
                if dt:
                    rssi = self.get(tree_iter, "rssi")["rssi"]
                    lq = self.get(tree_iter, "lq")["lq"]
                    tpl = self.get(tree_iter, "tpl")["tpl"]

                    if rssi < 30:
                        rssi_state = _("Poor")
                    elif rssi < 40:
                        rssi_state = _("Sub-optimal")
                    elif rssi < 60:
                        rssi_state = _("Optimal")
                    elif rssi < 70:
                        rssi_state = _("Much")
                    else:
                        rssi_state = _("Too much")

                    if tpl < 30:
                        tpl_state = _("Low")
                    elif tpl < 40:
                        tpl_state = _("Sub-optimal")
                    elif tpl < 60:
                        tpl_state = _("Optimal")
                    elif tpl < 70:
                        tpl_state = _("High")
                    else:
                        tpl_state = _("Very High")

                    tooltip_template: str = ""
                    if path[1] == self.columns["tpl_pb"]:
                        tooltip_template = \
                            "<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\n" \
                            "Link Quality: %(lq)u%%\n<b>Transmit Power Level: %(tpl)u%%</b> <i>(%(tpl_state)s)</i>"
                    elif path[1] == self.columns["lq_pb"]:
                        tooltip_template = \
                            "<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\n" \
                            "<b>Link Quality: %(lq)u%%</b>\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>"
                    elif path[1] == self.columns["rssi_pb"]:
                        tooltip_template = \
                            "<b>Connected</b>\n<b>Received Signal Strength: %(rssi)u%%</b> <i>(%(rssi_state)s)</i>\n" \
                            "Link Quality: %(lq)u%%\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>"

                    state_dict = {
                        "rssi_state": rssi_state,
                        "rssi": rssi,
                        "lq": lq,
                        "tpl": tpl,
                        "tpl_state": tpl_state
                    }
                    tooltip.set_markup(tooltip_template % state_dict)
                    self.tooltip_row = path[0]
                    self.tooltip_col = path[1]
                    return True
        return False

    def _has_objpush(self, device: Device) -> bool:
        if device is None:
            return False

        for uuid in device["UUIDs"]:
            if ServiceUUID(uuid).short_uuid == OBEX_OBJPUSH_SVCLASS_ID:
                return True
        return False

    def _set_cell_data(self, _col: Gtk.TreeViewColumn, cell: Gtk.CellRenderer,
                       _model: Gtk.TreeModel, tree_iter: Gtk.TreeIter,
                       data: Optional[str]) -> None:
        if data is None:
            row = self.get(tree_iter, "icon_info", "trusted", "paired")
            surface = self.make_device_icon(row["icon_info"], row["paired"],
                                            row["trusted"])
            cell.set_property("surface", surface)
        elif data in ("rssi", "lq", "tpl"):
            window = self.get_window()
            scale = self.get_scale_factor()
            pb = self.get(tree_iter, data + "_pb")[data + "_pb"]
            if pb:
                surface = Gdk.cairo_surface_create_from_pixbuf(
                    pb, scale, window)
                cell.set_property("surface", surface)
            else:
                cell.set_property("surface", None)

    def add_device(self, device: Device) -> None:
        if "Name" not in device and self.Config["hide-unnamed"]:
            logging.info(f"Hiding unnamed device: {device['Address']}")
            return

        super().add_device(device)
Пример #20
0
class ManagerDeviceList(DeviceList):
	
	def __init__(self, adapter=None, inst=None):
		cr = gtk.CellRendererText()
		cr.props.ellipsize = pango.ELLIPSIZE_END
		data = [
			#device picture
			["device_pb", 'GdkPixbuf', gtk.CellRendererPixbuf(), {"pixbuf":0}, None],
			
			#device caption
			["caption", str, cr, {"markup":1}, None, {"expand": True}],

			
			["rssi_pb", 'GdkPixbuf', gtk.CellRendererPixbuf(), {"pixbuf":2}, None, {"spacing": 0, "sizing": gtk.TREE_VIEW_COLUMN_AUTOSIZE}],
			["lq_pb", 'GdkPixbuf', gtk.CellRendererPixbuf(), {"pixbuf":3}, None, {"spacing": 0, "sizing": gtk.TREE_VIEW_COLUMN_AUTOSIZE}],
			["tpl_pb", 'GdkPixbuf', gtk.CellRendererPixbuf(), {"pixbuf":4}, None, {"spacing": 0, "sizing": gtk.TREE_VIEW_COLUMN_AUTOSIZE}],
			
			#trusted/bonded icons
			#["tb_icons", 'PyObject', CellRendererPixbufTable(), {"pixbuffs":5}, None],
			
			["connected", bool], #used for quick access instead of device.GetProperties
			["bonded", bool], #used for quick access instead of device.GetProperties
			["trusted", bool], #used for quick access instead of device.GetProperties	
			["fake", bool], #used for quick access instead of device.GetProperties, 
					#fake determines whether device is "discovered" or a real bluez device
			
			["rssi", float],
			["lq", float],
			["tpl", float],
			["orig_icon", 'GdkPixbuf'],
			["cell_fader", gobject.TYPE_PYOBJECT],
			["row_fader", gobject.TYPE_PYOBJECT],
			["levels_visible", bool],
			["initial_anim", bool],
		]
		DeviceList.__init__(self, adapter, data)
		self.set_headers_visible(False)
		self.props.has_tooltip = True
		self.Blueman = inst
		
		self.connect("query-tooltip", self.tooltip_query)		
		self.tooltip_row = -1
		self.tooltip_col = None
		
		self.connect("button_press_event", self.on_event_clicked)
		self.connect("button_release_event", self.on_event_clicked)
		
		self.menu = None
		
		self.connect("drag_data_received", self.drag_recv)
		self.connect("drag-motion", self.drag_motion)

		self.drag_dest_set(gtk.DEST_DEFAULT_ALL, [], gtk.gdk.ACTION_COPY|gtk.gdk.ACTION_DEFAULT)
		self.drag_dest_add_uri_targets()
		
		self.set_search_equal_func(self.search_func)
		
	def do_device_found(self, device):
		iter = self.find_device(device)
		if iter:
			anim = TreeRowColorFade(self, self.props.model.get_path(iter), gtk.gdk.color_parse("blue"))
			anim.animate(start=0.8, end=1.0)
		
		
	def search_func(self, model, column, key, iter):
		row = self.get(iter, "caption")
		if key.lower() in row["caption"].lower():
			return False
		print model, column, key, iter
		return True	
	
	def drag_recv(self, widget, context, x, y, selection, target_type, time):

		uris = selection.get_uris()
		uris = list(uris)
		
		context.finish(True, False, time)
		
		path = self.get_path_at_pos(x, y)
		if path:
			iter = self.get_iter(path[0][0])
			device = self.get(iter, "device")["device"]
			uris.insert(0, "blueman-sendto")
			uris.insert(1, "--device=%s" % device.Address)
			
			spawn(uris)
			context.finish(True, False, time)
		else:
			context.finish(False, False, time)
		
		return True

	
	def drag_motion(self, widget, drag_context, x, y, timestamp):
		path = self.get_path_at_pos(x, y)
		if path != None:
			if path[0][0] != self.selected():
				iter = self.get_iter(path[0][0])
				device = self.get(iter, "device")["device"]
				if not device.Fake:	
					found = False
					for uuid in device.UUIDs:
						uuid16 = uuid128_to_uuid16(uuid)
						if uuid16 == OBEX_OBJPUSH_SVCLASS_ID:
							found = True
							break
					if found:
						drag_context.drag_status(gtk.gdk.ACTION_COPY, timestamp)
						self.set_cursor(path[0])
						return True
					else:
						drag_context.drag_status(gtk.gdk.ACTION_DEFAULT, timestamp)
						return False
				else:
					drag_context.drag_status(gtk.gdk.ACTION_COPY, timestamp)
					self.set_cursor(path[0])
					return True
		else:
			drag_context.drag_status(gtk.gdk.ACTION_DEFAULT, timestamp)
			return False			

	
	def on_event_clicked(self, widget, event):

		if event.type==gtk.gdk.BUTTON_PRESS and event.button==3:
			path = self.get_path_at_pos(int(event.x), int(event.y))
			if path != None:
				row = self.get(path[0][0], "device")
			
				if row:
					device = row["device"]
					if self.Blueman != None:
						if self.menu == None:
							self.menu = ManagerDeviceMenu(self.Blueman)
					
						self.menu.popup(None, None, None, event.button, event.time)
	
	
	
	def get_device_icon(self, klass):
		return get_icon("blueman-"+klass.replace(" ", "-").lower(), 48, "blueman")
		
	


	def make_device_icon(self, target, is_bonded=False, is_trusted=False, is_discovered=False, opacity=255):
		if opacity != 255:
			target = opacify_pixbuf(target, opacity)
		
		sources = []
		if is_bonded:
			sources.append((get_icon("gtk-dialog-authentication", 16), 0, 0, 200))
		
		if is_trusted:
			sources.append((get_icon("blueman-trust", 16), 0, 32, 200))
	
		if is_discovered:
			sources.append((get_icon("gtk-find", 24), 24, 0, 255))

		return composite_icon(target, sources)
	
	
	def device_remove_event(self, device, iter):
		if device.Temp:
			DeviceList.device_remove_event(self, device, iter)
		else:
			row_fader = self.get(iter, "row_fader")["row_fader"]
		
			def on_finished(fader):
			
				fader.disconnect(signal)
				fader.freeze()
				DeviceList.device_remove_event(self, device, iter)	
		
			signal = row_fader.connect("animation-finished", on_finished)
			row_fader.thaw()
			self.emit("device-selected", None, None)
			row_fader.animate(start=row_fader.get_state(), end=0.0, duration=400)
	
	def device_add_event(self, device):
		if device.Fake:
			self.PrependDevice(device)
			gobject.idle_add(self.props.vadjustment.set_value , 0)
			return

		if self.Blueman.Config.props.latest_last:
			self.AppendDevice(device)
		else:
			self.PrependDevice(device)
		
	def make_caption(self, name, klass, address):
		return "<span size='x-large'>%(0)s</span>\n<span size='small'>%(1)s</span>\n<i>%(2)s</i>" % {"0":cgi.escape(name), "1":klass.capitalize(), "2":address}
		
	
	def row_setup_event(self, iter, device):
		if not self.get(iter, "initial_anim")["initial_anim"]:
			
			cell_fader = CellFade(self, self.props.model.get_path(iter), [2,3,4])
			row_fader = TreeRowFade(self, self.props.model.get_path(iter))
		
		
			self.set(iter, row_fader=row_fader, cell_fader=cell_fader, levels_visible=False)
		
			cell_fader.freeze()		
			def on_finished(fader):
				fader.disconnect(signal)
				fader.freeze()
		
			signal = row_fader.connect("animation-finished", on_finished)
			row_fader.set_state(0.0)
			row_fader.animate(start=0.0, end=1.0, duration=500)
			
			self.set(iter, initial_anim=True)
		

		klass = get_minor_class(device.Class)
		icon = self.get_device_icon(klass)


		#get translated version
		klass = get_minor_class(device.Class, True)

		name = device.Alias
		address = device.Address
		
		caption = self.make_caption(name, klass, address)
		
		#caption = "<span size='x-large'>%(0)s</span>\n<span size='small'>%(1)s</span>\n<i>%(2)s</i>" % {"0":name, "1":klass.capitalize(), "2":address}
		self.set(iter, caption=caption, orig_icon=icon)
		
		self.row_update_event(iter, "Fake", device.Fake)
		
		try:
			self.row_update_event(iter, "Trusted", device.Trusted)
		except:
			pass
		try:
			self.row_update_event(iter, "Paired", device.Paired)
		except:
			pass
			
				
	def row_update_event(self, iter, key, value):
		dprint("row update event", key, value)
		
		#this property is only emitted when device is fake
		if key == "RSSI":
			row = self.get(iter, "orig_icon")
			
			#minimum opacity 90
			#maximum opacity 255
			#rssi at minimum opacity -100
			#rssi at maximum opacity -45
			# y = kx + b
			#solve linear system
			#{ 90 = k * -100 + b
			#{ 255 = k * -45 + b
			# k = 3
			# b = 390
			#and we have a formula for opacity based on rssi :)
			opacity = int(3 * value + 390)
			if opacity > 255:
				opacity = 255
			if opacity < 90:
				opacity = 90
			print "opacity", opacity
			icon = self.make_device_icon(row["orig_icon"], is_discovered=True, opacity=opacity) 
			self.set(iter, device_pb=icon)
		
		elif key == "Trusted":
			row = self.get(iter, "bonded", "orig_icon")
			if value:
				icon = self.make_device_icon(row["orig_icon"], row["bonded"], True, False) 
				self.set(iter, device_pb=icon, trusted=True)
			else:
				icon = self.make_device_icon(row["orig_icon"], row["bonded"], False, False) 
				self.set(iter, device_pb=icon, trusted=False)
			
		
		elif key == "Paired":
			row = self.get(iter, "trusted", "orig_icon")
			if value:
				icon = self.make_device_icon(row["orig_icon"], True, row["trusted"], False) 
				self.set(iter, device_pb=icon, bonded=True)
			else:
				icon = self.make_device_icon(row["orig_icon"], False, row["trusted"], False) 
				self.set(iter, device_pb=icon, bonded=False)
				
		elif key == "Fake":
			row = self.get(iter, "bonded", "trusted", "orig_icon")
			if value:
				icon = self.make_device_icon(row["orig_icon"], False, False, True) 
				self.set(iter, device_pb=icon, fake=True)
			else:
				icon = self.make_device_icon(row["orig_icon"], row["bonded"], row["trusted"], False) 
				self.set(iter, device_pb=icon, fake=False)
				
		elif key == "Alias" or key == "Class":
			device = self.get(iter, "device")["device"]
			c = self.make_caption(value, get_minor_class(device.Class, True), device.Address)
			self.set(iter, caption=c)
				
	
	def level_setup_event(self, row_ref, device, cinfo):
		def rnd(value):
			return int(round(value,-1))
		if not row_ref.valid():	
			return
			
		iter = self.get_iter(row_ref.get_path())
		if True:
			if cinfo != None:
				try:
					rssi = float(cinfo.get_rssi())
				except:
					rssi = 0
				try:
					lq = float(cinfo.get_lq())
				except:
					lq = 0
				
				try:
					tpl = float(cinfo.get_tpl())
				except:
					tpl = 0

				rssi_perc = 50 + (rssi / 127 / 2 * 100)
				tpl_perc = 50 + (tpl / 127 / 2 * 100)
				lq_perc = lq / 255 * 100
				
				if lq_perc < 10:
					lq_perc = 10
				if rssi_perc < 10:
					rssi_perc = 10
				if tpl_perc < 10:
					tpl_perc = 10

				
				row = self.get(iter, "levels_visible", "cell_fader", "rssi", "lq", "tpl")
				if not row["levels_visible"]:
					dprint("animating up")
					self.set(iter, levels_visible=True)
					fader = row["cell_fader"]
					fader.thaw()
					fader.set_state(0.0)
					fader.animate(start=0.0, end=1.0, duration=400)
					
					def on_finished(fader):
						fader.freeze()
						fader.disconnect(signal)
						
					signal = fader.connect("animation-finished", on_finished )
					
				if rnd(row["rssi"]) != rnd(rssi_perc):
					self.set(iter, rssi_pb=get_icon("blueman-rssi-%s" % rnd(rssi_perc), 48))
				
				if rnd(row["lq"]) != rnd(lq_perc):
					self.set(iter, lq_pb=get_icon("blueman-lq-%s" % rnd(lq_perc), 48))
				
				if rnd(row["tpl"]) != rnd(tpl_perc):
					self.set(iter, tpl_pb=get_icon("blueman-tpl-%s" % rnd(tpl_perc), 48))
					
					
				self.set(iter,
						rssi=rssi_perc,
						lq=lq_perc,
						tpl=tpl_perc,
						connected=True)
			else:
				
				row = self.get(iter, "levels_visible", "cell_fader")
				if row["levels_visible"]:
					dprint("animating down")
					self.set(iter, levels_visible=False,
						rssi=-1,
						lq=-1,
						tpl=-1)
					fader = row["cell_fader"]
					fader.thaw()
					fader.set_state(1.0)
					fader.animate(start=fader.get_state(), end=0.0, duration=400)
					def on_finished(fader):
						fader.disconnect(signal)
						fader.freeze()
						if row_ref.valid():
							self.set(iter, rssi_pb=None, lq_pb=None, tpl_pb=None, connected=False)
					
					signal = fader.connect("animation-finished", on_finished)

				
		else:
			dprint("invisible")
		
	
	def tooltip_query(self, tw, x, y, kb, tooltip):

		#print args
		#args[4].set_text("test"+str(args[1]))

		path = self.get_path_at_pos(x, y)


		if path:
			if path[0][0] != self.tooltip_row or path[1] != self.tooltip_col:
				self.tooltip_row = path[0][0]
				self.tooltip_col = path[1]
				return False
			
			if path[1] == self.columns["device_pb"]:
				iter = self.get_iter(path[0][0])
				
				row = self.get(iter, "trusted", "bonded")
				trusted = row["trusted"]
				bonded = row["bonded"]
				if trusted and bonded:
					tooltip.set_markup(_("<b>Trusted and Bonded</b>"))
				elif bonded:
					tooltip.set_markup(_("<b>Bonded</b>"))
				elif trusted:
					tooltip.set_markup(_("<b>Trusted</b>"))
				else:
					return False
				
					
				self.tooltip_row = path[0][0]
				self.tooltip_col = path[1]
				return True

			
			if path[1] == self.columns["tpl_pb"] or path[1] == self.columns["lq_pb"] or path[1] == self.columns["rssi_pb"]:
				iter = self.get_iter(path[0][0])

				dt = self.get(iter, "connected")["connected"]
				#print dt
				if dt:
					rssi = self.get(iter, "rssi")["rssi"]
					lq = self.get(iter, "lq")["lq"]
					tpl = self.get(iter, "tpl")["tpl"]
					
					if rssi < 30:
						rssi_state = _("Poor")
					
					if rssi < 40 and rssi > 30:
						rssi_state = _("Sub-optimal")

					elif rssi > 40 and rssi < 60:
						rssi_state = _("Optimal")
					
					elif rssi > 60:
						rssi_state = _("Much")
					
					elif rssi > 70:
						rssi_state = _("Too much")
						
					
					if tpl < 30:
						tpl_state = _("Low")
					
					if tpl < 40 and tpl > 30:
						tpl_state = _("Sub-optimal")

					elif tpl > 40 and rssi < 60:
						tpl_state = _("Optimal")
					
					elif tpl > 60:
						tpl_state = _("High")
					
					elif tpl > 70:
						tpl_state = _("Very High")
						
					if path[1] == self.columns["tpl_pb"]:
						tooltip.set_markup(_("<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\nLink Quality: %(lq)u%%\n<b>Transmit Power Level: %(tpl)u%%</b> <i>(%(tpl_state)s)</i>") % {"rssi_state": rssi_state, "rssi":rssi, "lq":lq, "tpl":tpl, "tpl_state": tpl_state})
					elif path[1] == self.columns["lq_pb"]:
						tooltip.set_markup(_("<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\n<b>Link Quality: %(lq)u%%</b>\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>") % {"rssi_state": rssi_state, "rssi":rssi, "lq":lq, "tpl":tpl, "tpl_state": tpl_state})
					elif path[1] == self.columns["rssi_pb"]:
						tooltip.set_markup(_("<b>Connected</b>\n<b>Received Signal Strength: %(rssi)u%%</b> <i>(%(rssi_state)s)</i>\nLink Quality: %(lq)u%%\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>") % {"rssi_state": rssi_state, "rssi":rssi, "lq":lq, "tpl":tpl, "tpl_state": tpl_state})
					
					self.tooltip_row = path[0][0]
					self.tooltip_col = path[1]
					return True	
		return False
Пример #21
0
class ManagerDeviceList(DeviceList):
    def __init__(self, adapter=None, inst=None):
        cr = Gtk.CellRendererText()
        cr.props.ellipsize = Pango.EllipsizeMode.END
        data = [
            # device picture
            ["device_pb", GdkPixbuf.Pixbuf, Gtk.CellRendererPixbuf(), {"pixbuf": 0}, None],
            # device caption
            ["caption", str, cr, {"markup": 1}, None, {"expand": True}],


            ["rssi_pb", GdkPixbuf.Pixbuf, Gtk.CellRendererPixbuf(), {"pixbuf": 2}, None, {"spacing": 0}],
            ["lq_pb", GdkPixbuf.Pixbuf, Gtk.CellRendererPixbuf(), {"pixbuf": 3}, None, {"spacing": 0}],
            ["tpl_pb", GdkPixbuf.Pixbuf, Gtk.CellRendererPixbuf(), {"pixbuf": 4}, None, {"spacing": 0}],
            # trusted/bonded icons
            # ["tb_icons", 'PyObject', CellRendererPixbufTable(), {"pixbuffs":5}, None],

            ["alias", str], # used for quick access instead of device.GetProperties
            ["connected", bool], # used for quick access instead of device.GetProperties
            ["bonded", bool], # used for quick access instead of device.GetProperties
            ["trusted", bool], # used for quick access instead of device.GetProperties
            ["fake", bool], # used for quick access instead of device.GetProperties,
            # fake determines whether device is "discovered" or a real bluez device
            ["objpush", bool], # used to set Send File button

            ["rssi", float],
            ["lq", float],
            ["tpl", float],
            ["orig_icon", GdkPixbuf.Pixbuf],
            ["cell_fader", GObject.TYPE_PYOBJECT],
            ["row_fader", GObject.TYPE_PYOBJECT],
            ["levels_visible", bool],
            ["initial_anim", bool],
        ]
        super(ManagerDeviceList, self).__init__(adapter, data)
        self.set_name("ManagerDeviceList")
        self.set_headers_visible(False)
        self.props.has_tooltip = True
        self.Blueman = inst

        self.connect("query-tooltip", self.tooltip_query)
        self.tooltip_row = None
        self.tooltip_col = None

        self.connect("button_press_event", self.on_event_clicked)
        self.connect("button_release_event", self.on_event_clicked)

        self.menu = None

        self.connect("drag_data_received", self.drag_recv)
        self.connect("drag-motion", self.drag_motion)

        Gtk.Widget.drag_dest_set(self, Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY | Gdk.DragAction.DEFAULT)
        Gtk.Widget.drag_dest_add_uri_targets(self)

        self.set_search_equal_func(self.search_func, None)

    def do_device_found(self, device):
        tree_iter = self.find_device(device)
        if tree_iter:
            anim = TreeRowColorFade(self, self.props.model.get_path(tree_iter), Gdk.RGBA(0, 0, 1, 1))
            anim.animate(start=0.8, end=1.0)

    def search_func(self, model, column, key, tree_iter):
        row = self.get(tree_iter, "caption")
        if key.lower() in row["caption"].lower():
            return False
        print(model, column, key, tree_iter)
        return True

    def drag_recv(self, widget, context, x, y, selection, target_type, time):

        uris = list(selection.get_uris())

        context.finish(True, False, time)

        path = self.get_path_at_pos(x, y)
        if path:
            tree_iter = self.get_iter(path[0])
            device = self.get(tree_iter, "device")["device"]
            command = "blueman-sendto --device=%s" % device['Address']

            launch(command, uris, False, "blueman", _("File Sender"))
            context.finish(True, False, time)
        else:
            context.finish(False, False, time)

        return True

    def drag_motion(self, widget, drag_context, x, y, timestamp):
        path = self.get_path_at_pos(x, y)
        if path is not None:
            if path[0] != self.selected():
                tree_iter = self.get_iter(path[0])
                device = self.get(tree_iter, "device")["device"]
                found = False
                for uuid in device['UUIDs']:
                    uuid16 = uuid128_to_uuid16(uuid)
                    if uuid16 == OBEX_OBJPUSH_SVCLASS_ID:
                        found = True
                        break
                if found:
                    drag_context.drag_status(Gdk.DragAction.COPY, timestamp)
                    self.set_cursor(path[0])
                    return True
                else:
                    drag_context.drag_status(Gdk.DragAction.DEFAULT, timestamp)
                    return False
        else:
            drag_context.drag_status(Gdk.DragAction.DEFAULT, timestamp)
            return False

    def on_event_clicked(self, widget, event):

        if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
            path = self.get_path_at_pos(int(event.x), int(event.y))
            if path is not None:
                row = self.get(path[0], "device")

                if row:
                    device = row["device"]
                    if self.Blueman is not None:
                        if self.menu is None:
                            self.menu = ManagerDeviceMenu(self.Blueman)

                        self.menu.popup(None, None, None, None, event.button, event.time)

    def get_device_icon(self, klass):
        return get_icon("blueman-" + klass.replace(" ", "-").lower(), 48, "blueman")

    def make_device_icon(self, target, is_bonded=False, is_trusted=False, is_discovered=False, opacity=255):
        if opacity != 255:
            target = opacify_pixbuf(target, opacity)

        sources = []
        if is_bonded:
            sources.append((get_icon("dialog-password", 16), 0, 0, 200))

        if is_trusted:
            sources.append((get_icon("blueman-trust", 16), 0, 32, 200))

        if is_discovered:
            sources.append((get_icon("edit-find", 24), 24, 0, 255))

        return composite_icon(target, sources)

    def device_remove_event(self, device, tree_iter):
        row_fader = self.get(tree_iter, "row_fader")["row_fader"]

        def on_finished(fader):

            fader.disconnect(signal)
            fader.freeze()
            DeviceList.device_remove_event(self, device, tree_iter)

        signal = row_fader.connect("animation-finished", on_finished)
        row_fader.thaw()
        self.emit("device-selected", None, None)
        row_fader.animate(start=row_fader.get_state(), end=0.0, duration=400)

    def device_add_event(self, device):
        if self.Blueman.Config["latest-last"]:
            self.add_device(device, append=True)
        else:
            self.add_device(device, append=False)

    def make_caption(self, name, klass, address):
        return "<span size='x-large'>%(0)s</span>\n<span size='small'>%(1)s</span>\n<i>%(2)s</i>" % {"0": cgi.escape(name), "1": klass.capitalize(), "2": address}

    def get_device_class(self, device):
        klass = get_minor_class(device['Class'])
        if klass != "uncategorized":
            return get_minor_class(device['Class'], True)
        else:
            return get_major_class(device['Class'])

    def row_setup_event(self, tree_iter, device):
        if not self.get(tree_iter, "initial_anim")["initial_anim"]:
            cell_fader = CellFade(self, self.props.model.get_path(tree_iter), [2, 3, 4])
            row_fader = TreeRowFade(self, self.props.model.get_path(tree_iter))

            has_objpush = self._has_objpush(device)

            self.set(tree_iter, row_fader=row_fader, cell_fader=cell_fader, levels_visible=False, objpush=has_objpush)

            cell_fader.freeze()

            def on_finished(fader):
                fader.disconnect(signal)
                fader.freeze()

            signal = row_fader.connect("animation-finished", on_finished)
            row_fader.set_state(0.0)
            row_fader.animate(start=0.0, end=1.0, duration=500)

            self.set(tree_iter, initial_anim=True)

        klass = get_minor_class(device['Class'])
        if klass != "uncategorized":
            icon = self.get_device_icon(klass)
            # get translated version
            klass = get_minor_class(device['Class'], True)
        else:
            icon = get_icon(device['Icon'], 48, "blueman")
            klass = get_major_class(device['Class'])

        name = device['Alias']
        address = device['Address']

        caption = self.make_caption(name, klass, address)

        # caption = "<span size='x-large'>%(0)s</span>\n<span size='small'>%(1)s</span>\n<i>%(2)s</i>" % {"0":name, "1":klass.capitalize(), "2":address}
        self.set(tree_iter, caption=caption, orig_icon=icon, alias=name)

        try:
            self.row_update_event(tree_iter, "Trusted", device['Trusted'])
        except:
            pass
        try:
            self.row_update_event(tree_iter, "Paired", device['Paired'])
        except:
            pass

    def row_update_event(self, tree_iter, key, value):
        dprint("row update event", key, value)

        # this property is only emitted when device is fake
        if key == "RSSI":
            row = self.get(tree_iter, "orig_icon")

            #minimum opacity 90
            #maximum opacity 255
            #rssi at minimum opacity -100
            #rssi at maximum opacity -45
            # y = kx + b
            #solve linear system
            #{ 90 = k * -100 + b
            #{ 255 = k * -45 + b
            # k = 3
            # b = 390
            #and we have a formula for opacity based on rssi :)
            opacity = int(3 * value + 390)
            if opacity > 255:
                opacity = 255
            if opacity < 90:
                opacity = 90
            print("opacity", opacity)
            icon = self.make_device_icon(row["orig_icon"], is_discovered=True, opacity=opacity)
            self.set(tree_iter, device_pb=icon)

        elif key == "Trusted":
            row = self.get(tree_iter, "bonded", "orig_icon")
            if value:
                icon = self.make_device_icon(row["orig_icon"], row["bonded"], True, False)
                self.set(tree_iter, device_pb=icon, trusted=True)
            else:
                icon = self.make_device_icon(row["orig_icon"], row["bonded"], False, False)
                self.set(tree_iter, device_pb=icon, trusted=False)

        elif key == "Paired":
            row = self.get(tree_iter, "trusted", "orig_icon")
            if value:
                icon = self.make_device_icon(row["orig_icon"], True, row["trusted"], False)
                self.set(tree_iter, device_pb=icon, bonded=True)
            else:
                icon = self.make_device_icon(row["orig_icon"], False, row["trusted"], False)
                self.set(tree_iter, device_pb=icon, bonded=False)

        elif key == "Alias":
            device = self.get(tree_iter, "device")["device"]
            c = self.make_caption(value, self.get_device_class(device), device['Address'])
            self.set(tree_iter, caption=c, alias=value)

        elif key == "UUIDs":
            device = self.get(tree_iter, "device")["device"]
            has_objpush = self._has_objpush(device)
            self.set(tree_iter, objpush=has_objpush)

    def level_setup_event(self, row_ref, device, cinfo):
        def rnd(value):
            return int(round(value, -1))

        if not row_ref.valid():
            return

        tree_iter = self.get_iter(row_ref.get_path())
        if True:
            if cinfo is not None:
                try:
                    rssi = float(cinfo.get_rssi())
                except:
                    rssi = 0
                try:
                    lq = float(cinfo.get_lq())
                except:
                    lq = 0

                try:
                    tpl = float(cinfo.get_tpl())
                except:
                    tpl = 0

                rssi_perc = 50 + (rssi / 127 / 2 * 100)
                tpl_perc = 50 + (tpl / 127 / 2 * 100)
                lq_perc = lq / 255 * 100

                if lq_perc < 10:
                    lq_perc = 10
                if rssi_perc < 10:
                    rssi_perc = 10
                if tpl_perc < 10:
                    tpl_perc = 10

                row = self.get(tree_iter, "levels_visible", "cell_fader", "rssi", "lq", "tpl")
                if not row["levels_visible"]:
                    dprint("animating up")
                    self.set(tree_iter, levels_visible=True)
                    fader = row["cell_fader"]
                    fader.thaw()
                    fader.set_state(0.0)
                    fader.animate(start=0.0, end=1.0, duration=400)

                    def on_finished(fader):
                        fader.freeze()
                        fader.disconnect(signal)

                    signal = fader.connect("animation-finished", on_finished)

                if rnd(row["rssi"]) != rnd(rssi_perc):
                    icon = GdkPixbuf.Pixbuf.new_from_file(PIXMAP_PATH + "/blueman-rssi-" + str(rnd(rssi_perc)) + ".png")
                    self.set(tree_iter, rssi_pb=icon)

                if rnd(row["lq"]) != rnd(lq_perc):
                    icon = GdkPixbuf.Pixbuf.new_from_file(PIXMAP_PATH + "/blueman-lq-" + str(rnd(lq_perc)) + ".png")
                    self.set(tree_iter, lq_pb=icon)

                if rnd(row["tpl"]) != rnd(tpl_perc):
                    icon = GdkPixbuf.Pixbuf.new_from_file(PIXMAP_PATH + "/blueman-tpl-" + str(rnd(tpl_perc)) + ".png")
                    self.set(tree_iter, tpl_pb=icon)

                self.set(tree_iter,
                         rssi=rssi_perc,
                         lq=lq_perc,
                         tpl=tpl_perc,
                         connected=True)
            else:

                row = self.get(tree_iter, "levels_visible", "cell_fader")
                if row["levels_visible"]:
                    dprint("animating down")
                    self.set(tree_iter, levels_visible=False,
                             rssi=-1,
                             lq=-1,
                             tpl=-1)
                    fader = row["cell_fader"]
                    fader.thaw()
                    fader.set_state(1.0)
                    fader.animate(start=fader.get_state(), end=0.0, duration=400)

                    def on_finished(fader):
                        fader.disconnect(signal)
                        fader.freeze()
                        if row_ref.valid():
                            self.set(tree_iter, rssi_pb=None, lq_pb=None, tpl_pb=None, connected=False)

                    signal = fader.connect("animation-finished", on_finished)

        else:
            dprint("invisible")

    def tooltip_query(self, tw, x, y, kb, tooltip):

        # print args
        #args[4].set_text("test"+str(args[1]))

        path = self.get_path_at_pos(x, y)

        if path is not None:
            if path[0] != self.tooltip_row or path[1] != self.tooltip_col:
                self.tooltip_row = path[0]
                self.tooltip_col = path[1]
                return False

            if path[1] == self.columns["device_pb"]:
                tree_iter = self.get_iter(path[0])

                row = self.get(tree_iter, "trusted", "bonded")
                trusted = row["trusted"]
                bonded = row["bonded"]
                if trusted and bonded:
                    tooltip.set_markup(_("<b>Trusted and Bonded</b>"))
                elif bonded:
                    tooltip.set_markup(_("<b>Bonded</b>"))
                elif trusted:
                    tooltip.set_markup(_("<b>Trusted</b>"))
                else:
                    return False

                self.tooltip_row = path[0]
                self.tooltip_col = path[1]
                return True

            if path[1] == self.columns["tpl_pb"] or path[1] == self.columns["lq_pb"] or path[1] == self.columns["rssi_pb"]:
                tree_iter = self.get_iter(path[0])

                dt = self.get(tree_iter, "connected")["connected"]
                #print dt
                if dt:
                    rssi = self.get(tree_iter, "rssi")["rssi"]
                    lq = self.get(tree_iter, "lq")["lq"]
                    tpl = self.get(tree_iter, "tpl")["tpl"]

                    if rssi < 30:
                        rssi_state = _("Poor")

                    if 40 > rssi > 30:
                        rssi_state = _("Sub-optimal")

                    elif 40 < rssi < 60:
                        rssi_state = _("Optimal")

                    elif rssi > 60:
                        rssi_state = _("Much")

                    elif rssi > 70:
                        rssi_state = _("Too much")

                    if tpl < 30:
                        tpl_state = _("Low")

                    if 40 > tpl > 30:
                        tpl_state = _("Sub-optimal")

                    elif tpl > 40 and rssi < 60:
                        tpl_state = _("Optimal")

                    elif tpl > 60:
                        tpl_state = _("High")

                    elif tpl > 70:
                        tpl_state = _("Very High")

                    if path[1] == self.columns["tpl_pb"]:
                        tooltip.set_markup(_("<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\nLink Quality: %(lq)u%%\n<b>Transmit Power Level: %(tpl)u%%</b> <i>(%(tpl_state)s)</i>") % {"rssi_state": rssi_state, "rssi": rssi, "lq": lq, "tpl": tpl, "tpl_state": tpl_state})
                    elif path[1] == self.columns["lq_pb"]:
                        tooltip.set_markup(_("<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\n<b>Link Quality: %(lq)u%%</b>\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>") % {"rssi_state": rssi_state, "rssi": rssi, "lq": lq, "tpl": tpl, "tpl_state": tpl_state})
                    elif path[1] == self.columns["rssi_pb"]:
                        tooltip.set_markup(_("<b>Connected</b>\n<b>Received Signal Strength: %(rssi)u%%</b> <i>(%(rssi_state)s)</i>\nLink Quality: %(lq)u%%\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>") % {"rssi_state": rssi_state, "rssi": rssi, "lq": lq, "tpl": tpl, "tpl_state": tpl_state})

                    self.tooltip_row = path[0]
                    self.tooltip_col = path[1]
                    return True
        return False

    def _has_objpush(self, device):
        if device is None:
            return False

        for uuid in device["UUIDs"]:
            uuid16 = uuid128_to_uuid16(uuid)
            if uuid16 == OBEX_OBJPUSH_SVCLASS_ID:
                return True
        return False
Пример #22
0
class ManagerDeviceList(DeviceList):
    def __init__(self, adapter=None, inst=None):
        cr = Gtk.CellRendererText()
        cr.props.ellipsize = Pango.EllipsizeMode.END
        data = [
            # device picture
            [
                "device_pb", GdkPixbuf.Pixbuf,
                Gtk.CellRendererPixbuf(), {
                    "pixbuf": 0
                }, None
            ],
            # device caption
            ["caption", str, cr, {
                "markup": 1
            }, None, {
                "expand": True
            }],
            [
                "rssi_pb", GdkPixbuf.Pixbuf,
                Gtk.CellRendererPixbuf(), {
                    "pixbuf": 2
                }, None, {
                    "spacing": 0
                }
            ],
            [
                "lq_pb", GdkPixbuf.Pixbuf,
                Gtk.CellRendererPixbuf(), {
                    "pixbuf": 3
                }, None, {
                    "spacing": 0
                }
            ],
            [
                "tpl_pb", GdkPixbuf.Pixbuf,
                Gtk.CellRendererPixbuf(), {
                    "pixbuf": 4
                }, None, {
                    "spacing": 0
                }
            ],
            # trusted/bonded icons
            # ["tb_icons", 'PyObject', CellRendererPixbufTable(), {"pixbuffs":5}, None],
            ["connected",
             bool],  # used for quick access instead of device.GetProperties
            ["bonded",
             bool],  # used for quick access instead of device.GetProperties
            ["trusted",
             bool],  # used for quick access instead of device.GetProperties
            ["fake",
             bool],  # used for quick access instead of device.GetProperties,
            # fake determines whether device is "discovered" or a real bluez device
            ["rssi", float],
            ["lq", float],
            ["tpl", float],
            ["orig_icon", GdkPixbuf.Pixbuf],
            ["cell_fader", GObject.TYPE_PYOBJECT],
            ["row_fader", GObject.TYPE_PYOBJECT],
            ["levels_visible", bool],
            ["initial_anim", bool],
        ]
        DeviceList.__init__(self, adapter, data)
        self.set_headers_visible(False)
        self.props.has_tooltip = True
        self.Blueman = inst

        self.connect("query-tooltip", self.tooltip_query)
        self.tooltip_row = None
        self.tooltip_col = None

        self.connect("button_press_event", self.on_event_clicked)
        self.connect("button_release_event", self.on_event_clicked)

        self.menu = None

        self.connect("drag_data_received", self.drag_recv)
        self.connect("drag-motion", self.drag_motion)

        Gtk.Widget.drag_dest_set(self, Gtk.DestDefaults.ALL, [],
                                 Gdk.DragAction.COPY | Gdk.DragAction.DEFAULT)
        Gtk.Widget.drag_dest_add_uri_targets(self)

        self.set_search_equal_func(self.search_func, None)

    def do_device_found(self, device):
        iter = self.find_device(device)
        if iter:
            anim = TreeRowColorFade(self, self.props.model.get_path(iter),
                                    Gdk.RGBA(0, 0, 1, 1))
            anim.animate(start=0.8, end=1.0)

    def search_func(self, model, column, key, iter):
        row = self.get(iter, "caption")
        if key.lower() in row["caption"].lower():
            return False
        print(model, column, key, iter)
        return True

    def drag_recv(self, widget, context, x, y, selection, target_type, time):

        uris = list(selection.get_uris())

        context.finish(True, False, time)

        path = self.get_path_at_pos(x, y)
        if path:
            iter = self.get_iter(path[0])
            device = self.get(iter, "device")["device"]
            command = "blueman-sendto --device=%s" % device.Address

            launch(command, uris, False, "blueman", _("File Sender"))
            context.finish(True, False, time)
        else:
            context.finish(False, False, time)

        return True

    def drag_motion(self, widget, drag_context, x, y, timestamp):
        path = self.get_path_at_pos(x, y)
        if path != None:
            if path[0] != self.selected():
                iter = self.get_iter(path[0])
                device = self.get(iter, "device")["device"]
                if not device.Fake:
                    found = False
                    for uuid in device.UUIDs:
                        uuid16 = uuid128_to_uuid16(uuid)
                        if uuid16 == OBEX_OBJPUSH_SVCLASS_ID:
                            found = True
                            break
                    if found:
                        drag_context.drag_status(Gdk.DragAction.COPY,
                                                 timestamp)
                        self.set_cursor(path[0])
                        return True
                    else:
                        drag_context.drag_status(Gdk.DragAction.DEFAULT,
                                                 timestamp)
                        return False
                else:
                    drag_context.drag_status(Gdk.DragAction.COPY, timestamp)
                    self.set_cursor(path[0])
                    return True
        else:
            drag_context.drag_status(Gdk.DragAction.DEFAULT, timestamp)
            return False

    def on_event_clicked(self, widget, event):

        if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
            path = self.get_path_at_pos(int(event.x), int(event.y))
            if path != None:
                row = self.get(path[0], "device")

                if row:
                    device = row["device"]
                    if self.Blueman != None:
                        if self.menu == None:
                            self.menu = ManagerDeviceMenu(self.Blueman)

                        self.menu.popup(None, None, None, None, event.button,
                                        event.time)

    def get_device_icon(self, klass):
        return get_icon("blueman-" + klass.replace(" ", "-").lower(), 48,
                        "blueman")

    def make_device_icon(self,
                         target,
                         is_bonded=False,
                         is_trusted=False,
                         is_discovered=False,
                         opacity=255):
        if opacity != 255:
            target = opacify_pixbuf(target, opacity)

        sources = []
        if is_bonded:
            sources.append((get_icon("dialog-password", 16), 0, 0, 200))

        if is_trusted:
            sources.append((get_icon("blueman-trust", 16), 0, 32, 200))

        if is_discovered:
            sources.append((get_icon("edit-find", 24), 24, 0, 255))

        return composite_icon(target, sources)

    def device_remove_event(self, device, iter):
        if device.Temp:
            DeviceList.device_remove_event(self, device, iter)
        else:
            row_fader = self.get(iter, "row_fader")["row_fader"]

            def on_finished(fader):

                fader.disconnect(signal)
                fader.freeze()
                DeviceList.device_remove_event(self, device, iter)

            signal = row_fader.connect("animation-finished", on_finished)
            row_fader.thaw()
            self.emit("device-selected", None, None)
            row_fader.animate(start=row_fader.get_state(),
                              end=0.0,
                              duration=400)

    def device_add_event(self, device):
        if device.Fake:
            self.PrependDevice(device)
            GObject.idle_add(self.props.vadjustment.set_value, 0)
            return

        if self.Blueman.Config["latest-last"]:
            self.AppendDevice(device)
        else:
            self.PrependDevice(device)

    def make_caption(self, name, klass, address):
        return "<span size='x-large'>%(0)s</span>\n<span size='small'>%(1)s</span>\n<i>%(2)s</i>" % {
            "0": cgi.escape(name),
            "1": klass.capitalize(),
            "2": address
        }

    def get_device_class(self, device):
        klass = get_minor_class(device.Class)
        if klass != "uncategorized":
            return get_minor_class(device.Class, True)
        else:
            return get_major_class(device.Class)

    def row_setup_event(self, iter, device):
        if not self.get(iter, "initial_anim")["initial_anim"]:
            cell_fader = CellFade(self, self.props.model.get_path(iter),
                                  [2, 3, 4])
            row_fader = TreeRowFade(self, self.props.model.get_path(iter))

            self.set(iter,
                     row_fader=row_fader,
                     cell_fader=cell_fader,
                     levels_visible=False)

            cell_fader.freeze()

            def on_finished(fader):
                fader.disconnect(signal)
                fader.freeze()

            signal = row_fader.connect("animation-finished", on_finished)
            row_fader.set_state(0.0)
            row_fader.animate(start=0.0, end=1.0, duration=500)

            self.set(iter, initial_anim=True)

        klass = get_minor_class(device.Class)
        if klass != "uncategorized":
            icon = self.get_device_icon(klass)
            # get translated version
            klass = get_minor_class(device.Class, True)
        else:
            icon = get_icon(device.Icon, 48, "blueman")
            klass = get_major_class(device.Class)

        name = device.Alias
        address = device.Address

        caption = self.make_caption(name, klass, address)

        # caption = "<span size='x-large'>%(0)s</span>\n<span size='small'>%(1)s</span>\n<i>%(2)s</i>" % {"0":name, "1":klass.capitalize(), "2":address}
        self.set(iter, caption=caption, orig_icon=icon)

        self.row_update_event(iter, "Fake", device.Fake)

        try:
            self.row_update_event(iter, "Trusted", device.Trusted)
        except:
            pass
        try:
            self.row_update_event(iter, "Paired", device.Paired)
        except:
            pass

    def row_update_event(self, iter, key, value):
        dprint("row update event", key, value)

        # this property is only emitted when device is fake
        if key == "RSSI":
            row = self.get(iter, "orig_icon")

            #minimum opacity 90
            #maximum opacity 255
            #rssi at minimum opacity -100
            #rssi at maximum opacity -45
            # y = kx + b
            #solve linear system
            #{ 90 = k * -100 + b
            #{ 255 = k * -45 + b
            # k = 3
            # b = 390
            #and we have a formula for opacity based on rssi :)
            opacity = int(3 * value + 390)
            if opacity > 255:
                opacity = 255
            if opacity < 90:
                opacity = 90
            print("opacity", opacity)
            icon = self.make_device_icon(row["orig_icon"],
                                         is_discovered=True,
                                         opacity=opacity)
            self.set(iter, device_pb=icon)

        elif key == "Trusted":
            row = self.get(iter, "bonded", "orig_icon")
            if value:
                icon = self.make_device_icon(row["orig_icon"], row["bonded"],
                                             True, False)
                self.set(iter, device_pb=icon, trusted=True)
            else:
                icon = self.make_device_icon(row["orig_icon"], row["bonded"],
                                             False, False)
                self.set(iter, device_pb=icon, trusted=False)

        elif key == "Paired":
            row = self.get(iter, "trusted", "orig_icon")
            if value:
                icon = self.make_device_icon(row["orig_icon"], True,
                                             row["trusted"], False)
                self.set(iter, device_pb=icon, bonded=True)
            else:
                icon = self.make_device_icon(row["orig_icon"], False,
                                             row["trusted"], False)
                self.set(iter, device_pb=icon, bonded=False)

        elif key == "Fake":
            row = self.get(iter, "bonded", "trusted", "orig_icon")
            if value:
                icon = self.make_device_icon(row["orig_icon"], False, False,
                                             True)
                self.set(iter, device_pb=icon, fake=True)
            else:
                icon = self.make_device_icon(row["orig_icon"], row["bonded"],
                                             row["trusted"], False)
                self.set(iter, device_pb=icon, fake=False)

        elif key == "Alias":
            device = self.get(iter, "device")["device"]
            c = self.make_caption(value, self.get_device_class(device),
                                  device.Address)
            self.set(iter, caption=c)

    def level_setup_event(self, row_ref, device, cinfo):
        def rnd(value):
            return int(round(value, -1))

        if not row_ref.valid():
            return

        iter = self.get_iter(row_ref.get_path())
        if True:
            if cinfo != None:
                try:
                    rssi = float(cinfo.get_rssi())
                except:
                    rssi = 0
                try:
                    lq = float(cinfo.get_lq())
                except:
                    lq = 0

                try:
                    tpl = float(cinfo.get_tpl())
                except:
                    tpl = 0

                rssi_perc = 50 + (rssi / 127 / 2 * 100)
                tpl_perc = 50 + (tpl / 127 / 2 * 100)
                lq_perc = lq / 255 * 100

                if lq_perc < 10:
                    lq_perc = 10
                if rssi_perc < 10:
                    rssi_perc = 10
                if tpl_perc < 10:
                    tpl_perc = 10

                row = self.get(iter, "levels_visible", "cell_fader", "rssi",
                               "lq", "tpl")
                if not row["levels_visible"]:
                    dprint("animating up")
                    self.set(iter, levels_visible=True)
                    fader = row["cell_fader"]
                    fader.thaw()
                    fader.set_state(0.0)
                    fader.animate(start=0.0, end=1.0, duration=400)

                    def on_finished(fader):
                        fader.freeze()
                        fader.disconnect(signal)

                    signal = fader.connect("animation-finished", on_finished)

                if rnd(row["rssi"]) != rnd(rssi_perc):
                    icon = GdkPixbuf.Pixbuf.new_from_file(PIXMAP_PATH +
                                                          "/blueman-rssi-" +
                                                          str(rnd(rssi_perc)) +
                                                          ".png")
                    self.set(iter, rssi_pb=icon)

                if rnd(row["lq"]) != rnd(lq_perc):
                    icon = GdkPixbuf.Pixbuf.new_from_file(PIXMAP_PATH +
                                                          "/blueman-lq-" +
                                                          str(rnd(lq_perc)) +
                                                          ".png")
                    self.set(iter, lq_pb=icon)

                if rnd(row["tpl"]) != rnd(tpl_perc):
                    icon = GdkPixbuf.Pixbuf.new_from_file(PIXMAP_PATH +
                                                          "/blueman-tpl-" +
                                                          str(rnd(tpl_perc)) +
                                                          ".png")
                    self.set(iter, tpl_pb=icon)

                self.set(iter,
                         rssi=rssi_perc,
                         lq=lq_perc,
                         tpl=tpl_perc,
                         connected=True)
            else:

                row = self.get(iter, "levels_visible", "cell_fader")
                if row["levels_visible"]:
                    dprint("animating down")
                    self.set(iter,
                             levels_visible=False,
                             rssi=-1,
                             lq=-1,
                             tpl=-1)
                    fader = row["cell_fader"]
                    fader.thaw()
                    fader.set_state(1.0)
                    fader.animate(start=fader.get_state(),
                                  end=0.0,
                                  duration=400)

                    def on_finished(fader):
                        fader.disconnect(signal)
                        fader.freeze()
                        if row_ref.valid():
                            self.set(iter,
                                     rssi_pb=None,
                                     lq_pb=None,
                                     tpl_pb=None,
                                     connected=False)

                    signal = fader.connect("animation-finished", on_finished)

        else:
            dprint("invisible")

    def tooltip_query(self, tw, x, y, kb, tooltip):

        # print args
        #args[4].set_text("test"+str(args[1]))

        path = self.get_path_at_pos(x, y)

        if path is not None:
            if path[0] != self.tooltip_row or path[1] != self.tooltip_col:
                self.tooltip_row = path[0]
                self.tooltip_col = path[1]
                return False

            if path[1] == self.columns["device_pb"]:
                iter = self.get_iter(path[0])

                row = self.get(iter, "trusted", "bonded")
                trusted = row["trusted"]
                bonded = row["bonded"]
                if trusted and bonded:
                    tooltip.set_markup(_("<b>Trusted and Bonded</b>"))
                elif bonded:
                    tooltip.set_markup(_("<b>Bonded</b>"))
                elif trusted:
                    tooltip.set_markup(_("<b>Trusted</b>"))
                else:
                    return False

                self.tooltip_row = path[0]
                self.tooltip_col = path[1]
                return True

            if path[1] == self.columns["tpl_pb"] or path[1] == self.columns[
                    "lq_pb"] or path[1] == self.columns["rssi_pb"]:
                iter = self.get_iter(path[0])

                dt = self.get(iter, "connected")["connected"]
                #print dt
                if dt:
                    rssi = self.get(iter, "rssi")["rssi"]
                    lq = self.get(iter, "lq")["lq"]
                    tpl = self.get(iter, "tpl")["tpl"]

                    if rssi < 30:
                        rssi_state = _("Poor")

                    if rssi < 40 and rssi > 30:
                        rssi_state = _("Sub-optimal")

                    elif rssi > 40 and rssi < 60:
                        rssi_state = _("Optimal")

                    elif rssi > 60:
                        rssi_state = _("Much")

                    elif rssi > 70:
                        rssi_state = _("Too much")

                    if tpl < 30:
                        tpl_state = _("Low")

                    if tpl < 40 and tpl > 30:
                        tpl_state = _("Sub-optimal")

                    elif tpl > 40 and rssi < 60:
                        tpl_state = _("Optimal")

                    elif tpl > 60:
                        tpl_state = _("High")

                    elif tpl > 70:
                        tpl_state = _("Very High")

                    if path[1] == self.columns["tpl_pb"]:
                        tooltip.set_markup(
                            _("<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\nLink Quality: %(lq)u%%\n<b>Transmit Power Level: %(tpl)u%%</b> <i>(%(tpl_state)s)</i>"
                              ) % {
                                  "rssi_state": rssi_state,
                                  "rssi": rssi,
                                  "lq": lq,
                                  "tpl": tpl,
                                  "tpl_state": tpl_state
                              })
                    elif path[1] == self.columns["lq_pb"]:
                        tooltip.set_markup(
                            _("<b>Connected</b>\nReceived Signal Strength: %(rssi)u%% <i>(%(rssi_state)s)</i>\n<b>Link Quality: %(lq)u%%</b>\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>"
                              ) % {
                                  "rssi_state": rssi_state,
                                  "rssi": rssi,
                                  "lq": lq,
                                  "tpl": tpl,
                                  "tpl_state": tpl_state
                              })
                    elif path[1] == self.columns["rssi_pb"]:
                        tooltip.set_markup(
                            _("<b>Connected</b>\n<b>Received Signal Strength: %(rssi)u%%</b> <i>(%(rssi_state)s)</i>\nLink Quality: %(lq)u%%\nTransmit Power Level: %(tpl)u%% <i>(%(tpl_state)s)</i>"
                              ) % {
                                  "rssi_state": rssi_state,
                                  "rssi": rssi,
                                  "lq": lq,
                                  "tpl": tpl,
                                  "tpl_state": tpl_state
                              })

                    self.tooltip_row = path[0]
                    self.tooltip_col = path[1]
                    return True
        return False
Пример #23
0
    def on_request_menu_items(self, manager_menu: ManagerDeviceMenu,
                              device: Device) -> List[DeviceMenuItem]:
        items: List[DeviceMenuItem] = []
        appl = AppletService()

        services = get_services(device)

        connectable_services = [
            service for service in services if service.connectable
        ]
        for service in connectable_services:
            item: Gtk.MenuItem = create_menuitem(service.name, service.icon)
            if service.description:
                item.props.tooltip_text = service.description
            item.connect(
                "activate", lambda _item: manager_menu.connect_service(
                    service.device, service.uuid))
            items.append(
                DeviceMenuItem(item, DeviceMenuItem.Group.CONNECT,
                               service.priority))
            item.props.sensitive = service.available
            item.show()

        connected_services = [
            service for service in services if service.connected_instances
        ]
        for service in connected_services:
            for instance in service.connected_instances:
                surface = self._make_x_icon(service.icon, 16)
                item = create_menuitem(instance.name, surface=surface)
                item.connect(
                    "activate", lambda _item: manager_menu.disconnect_service(
                        service.device, service.uuid, instance.port))
                items.append(
                    DeviceMenuItem(item, DeviceMenuItem.Group.DISCONNECT,
                                   service.priority + 100))
                item.show()

        if services:
            config = AutoConnectConfig()
            autoconnect_services = set(config["services"])
            for service in services:
                if service.connected_instances or (
                        device.get_object_path(),
                        service.uuid) in autoconnect_services:
                    item = Gtk.CheckMenuItem(label=service.name)
                    config.bind_to_menuitem(item, device, service.uuid)
                    item.show()
                    items.append(
                        DeviceMenuItem(item, DeviceMenuItem.Group.AUTOCONNECT,
                                       service.priority))

        for action, priority in set((action, service.priority)
                                    for service in services
                                    for action in service.common_actions
                                    if any(plugin in appl.QueryPlugins()
                                           for plugin in action.plugins)):
            item = create_menuitem(action.title, action.icon)
            items.append(
                DeviceMenuItem(item, DeviceMenuItem.Group.ACTIONS,
                               priority + 200))
            item.show()
            item.connect("activate",
                         self._get_activation_handler(action.callback))

        return items