#!/usr/bin/env python3 from gi.repository import Gtk, Gdk, GLib from i3ipc import Event import nwg_panel.common from nwg_panel.tools import check_key, get_icon_name, update_image, update_image_fallback_desktop, load_autotiling class SwayWorkspaces(Gtk.Box): def __init__(self, settings, i3, icons_path): Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=0) self.settings = settings self.i3 = i3 self.num_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) self.num_box.set_property("name", "sway-workspaces") self.ws_num2box = {} self.ws_num2lbl = {} self.name_label = Gtk.Label() self.name_label.set_property("name", "sway-workspaces-name") self.win_id = "" self.win_pid = None self.icon = Gtk.Image() self.icon.set_property("name", "sway-workspaces-icon") self.layout_icon = Gtk.Image() self.icons_path = icons_path self.autotiling = load_autotiling() self.build_box() self.refresh() self.subscribe() def subscribe(self): self.i3.on(Event.WINDOW, self.on_i3ipc_event) self.i3.on(Event.WORKSPACE, self.on_i3ipc_event) def build_box(self): check_key(self.settings, "numbers", []) check_key(self.settings, "custom-labels", []) check_key(self.settings, "focused-labels", []) check_key(self.settings, "show-icon", True) check_key(self.settings, "image-size", 16) check_key(self.settings, "show-name", True) check_key(self.settings, "name-length", 40) check_key(self.settings, "mark-autotiling", True) check_key(self.settings, "mark-content", True) check_key(self.settings, "hide-empty", False) check_key(self.settings, "show-layout", True) check_key(self.settings, "angle", 0.0) if self.settings["angle"] != 0.0: self.set_orientation(Gtk.Orientation.VERTICAL) self.num_box.set_orientation(Gtk.Orientation.VERTICAL) # prevent from #142 ws_num = -1 if self.i3.get_tree().find_focused(): ws_num, win_name, win_id, non_empty, win_layout, numbers = self.find_details() if len(self.settings["custom-labels"]) == 1: self.settings["custom-labels"] *= len(self.settings["numbers"]) elif len(self.settings["custom-labels"]) != len(self.settings["numbers"]): self.settings["custom-labels"] = [] if len(self.settings["focused-labels"]) == 1: self.settings["focused-labels"] *= len(self.settings["numbers"]) elif len(self.settings["focused-labels"]) != len(self.settings["numbers"]): self.settings["focused-labels"] = [] self.pack_start(self.num_box, False, False, 0) for idx, num in enumerate(self.settings["numbers"]): if num == str(ws_num) and self.settings["focused-labels"]: label = self.settings["focused-labels"][idx] elif self.settings["custom-labels"]: label = self.settings["custom-labels"][idx] else: label = str(num) eb, lbl = self.build_number(num, label) self.num_box.pack_start(eb, False, False, 0) if num == str(ws_num): eb.set_property("name", "task-box-focused") else: eb.set_property("name", "task-box") if self.settings["show-icon"]: self.pack_start(self.icon, False, False, 6) if self.settings["show-name"]: self.pack_start(self.name_label, False, False, 0) if self.settings["show-layout"]: self.pack_start(self.layout_icon, False, False, 6) def build_number(self, num, label): eb = Gtk.EventBox() eb.connect("enter_notify_event", self.on_enter_notify_event) eb.connect("leave_notify_event", self.on_leave_notify_event) eb.connect("button-release-event", self.on_click, num) eb.add_events(Gdk.EventMask.SCROLL_MASK) eb.connect('scroll-event', self.on_scroll) box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) box.set_property("name", "sway-workspaces-item") if self.settings["angle"] != 0.0: box.set_orientation(Gtk.Orientation.VERTICAL) eb.add(box) if self.settings["mark-autotiling"]: try: at = int(num) in self.autotiling except: at = False autotiling = "a" if at in self.autotiling else "" lbl = Gtk.Label("{}{}".format(autotiling, label)) else: lbl = Gtk.Label("{}".format(label)) lbl.set_use_markup(True) if self.settings["angle"] != 0.0: lbl.set_angle(self.settings["angle"]) self.name_label.set_angle(self.settings["angle"]) self.ws_num2box[num] = eb self.ws_num2lbl[num] = lbl box.pack_start(lbl, False, False, 6) return eb, lbl def on_i3ipc_event(self, i3conn, event): GLib.idle_add(self.refresh, priority=GLib.PRIORITY_HIGH) def refresh(self): if self.i3.get_tree().find_focused(): ws_num, win_name, win_id, non_empty, win_layout, numbers = self.find_details() if len(self.settings["numbers"]) > 0: numbers = self.settings["numbers"] if ws_num > 0: for num in self.ws_num2lbl: self.ws_num2lbl[num].hide() for _idx, num in enumerate(numbers): idx = None if num in self.settings["numbers"]: idx = self.settings["numbers"].index(num) try: int_num = int(num) except: int_num = 0 if idx is None: text = str(num) elif num == str(ws_num) and self.settings["focused-labels"]: text = self.settings["focused-labels"][idx] elif self.settings["custom-labels"]: text = self.settings["custom-labels"][idx] else: text = str(num) if num not in self.ws_num2lbl: eb, lbl = self.build_number(num, text) self.num_box.pack_start(eb, False, False, 0) eb.show_all() lbl = self.ws_num2lbl[num] if not self.settings["hide-empty"] or int_num in non_empty or num == str(ws_num): lbl.show() else: lbl.hide() # mark non-empty WS with CSS ID if int_num in non_empty: lbl.set_property("name", "workspace-occupied") # mark non-empty WS with a dot if self.settings["mark-content"]: if int_num in non_empty: if not text.endswith("."): text += "." else: if text.endswith("."): text = text[0:-1] lbl.set_markup(text) if num == str(ws_num): self.ws_num2box[num].set_property("name", "task-box-focused") else: self.ws_num2box[num].set_property("name", "task-box") if self.settings["show-icon"] and win_id != self.win_id: self.update_icon(win_id, win_name) self.win_id = win_id if self.settings["show-name"]: self.name_label.set_text(win_name) if self.settings["show-layout"]: if win_name: if win_layout == "splith": update_image(self.layout_icon, "go-next-symbolic", self.settings["image-size"], self.icons_path) elif win_layout == "splitv": update_image(self.layout_icon, "go-down-symbolic", self.settings["image-size"], self.icons_path) elif win_layout == "tabbed": update_image(self.layout_icon, "view-dual-symbolic", self.settings["image-size"], self.icons_path) elif win_layout == "stacked": update_image(self.layout_icon, "view-paged-symbolic", self.settings["image-size"], self.icons_path) else: update_image(self.layout_icon, "window-pop-out-symbolic", self.settings["image-size"], self.icons_path) if not self.layout_icon.get_visible(): self.layout_icon.show() else: if self.layout_icon.get_visible(): self.layout_icon.hide() def update_icon(self, win_id, win_name): loaded_icon = False if win_id and win_name: try: update_image_fallback_desktop(self.icon, win_id, self.settings["image-size"], self.icons_path, fallback=False) loaded_icon = True if not self.icon.get_visible(): self.icon.show() except: pass if not loaded_icon and self.icon.get_visible(): self.icon.hide() def find_details(self): tree = self.i3.get_tree() workspaces = self.i3.get_workspaces() ws_num = -1 win_name = "" win_id = "" # app_id if available, else window_class layout = None numbers = [] for ws in workspaces: numbers.append(str(ws.num)) if ws.focused: ws_num = ws.num non_empty = [] if self.settings["show-name"] or self.settings["show-icon"]: f = self.i3.get_tree().find_focused() if f.type == "con" and f.name and str(f.parent.workspace().num) in self.settings["numbers"]: win_name = f.name[:self.settings["name-length"]] if f.app_id: win_id = f.app_id elif f.window_class: win_id = f.window_class for item in tree.descendants(): if item.type == "workspace": # find non-empty workspaces # if self.settings["mark-content"] or self.settings["hide-empty"]: tasks_num = 0 for d in item.descendants(): if d.type == "con" and d.name: tasks_num += 1 if tasks_num > 0: non_empty.append(item.num) for node in item.floating_nodes: if str(node.workspace().num) in self.settings["numbers"]: if node.focused and node.name: win_name = node.name[:self.settings["name-length"] - 1] if node.app_id: win_id = node.app_id elif node.window_class: win_id = node.window_class layout = "floating" non_empty.append(node.workspace().num) if not layout: layout = f.parent.layout return ws_num, win_name, win_id, non_empty, layout, numbers def on_click(self, event_box, event_button, num): nwg_panel.common.i3.command("workspace number {}".format(num)) def on_scroll(self, event_box, event): if event.direction == Gdk.ScrollDirection.UP: nwg_panel.common.i3.command("workspace prev") elif event.direction == Gdk.ScrollDirection.DOWN: nwg_panel.common.i3.command("workspace next") def on_enter_notify_event(self, widget, event): widget.set_state_flags(Gtk.StateFlags.DROP_ACTIVE, clear=False) widget.set_state_flags(Gtk.StateFlags.SELECTED, clear=False) def on_leave_notify_event(self, widget, event): widget.unset_state_flags(Gtk.StateFlags.DROP_ACTIVE) widget.unset_state_flags(Gtk.StateFlags.SELECTED)