From 05bac0140193b1c3a7ea6edc676a31d46a30d491 Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Wed, 8 Mar 2023 19:35:45 +0100 Subject: [PATCH 1/5] Executor: split using shell syntax --- nwg_panel/modules/executor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nwg_panel/modules/executor.py b/nwg_panel/modules/executor.py index c20066c9..ba42c56a 100644 --- a/nwg_panel/modules/executor.py +++ b/nwg_panel/modules/executor.py @@ -4,6 +4,8 @@ import threading import signal +from shlex import split + import gi from gi.repository import GLib @@ -138,7 +140,7 @@ def update_widget(self, output): def get_output(self): if "script" in self.settings and self.settings["script"]: try: - output = subprocess.check_output(self.settings["script"].split()).decode("utf-8").splitlines() + output = subprocess.check_output(split(self.settings["script"])).decode("utf-8").splitlines() GLib.idle_add(self.update_widget, output) except Exception as e: print(e) From ee8271c6ace4e51471f8ea8e45333d58919cd44c Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Wed, 8 Mar 2023 21:56:30 +0100 Subject: [PATCH 2/5] Implement continuous executor --- nwg_panel/modules/executor.py | 41 +++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/nwg_panel/modules/executor.py b/nwg_panel/modules/executor.py index ba42c56a..4fa1b476 100644 --- a/nwg_panel/modules/executor.py +++ b/nwg_panel/modules/executor.py @@ -28,9 +28,11 @@ def __init__(self, settings, icons_path, executor_name): self.image = Gtk.Image() self.label = Gtk.Label("") self.icon_path = None + self.process = None check_key(settings, "script", "") check_key(settings, "interval", 0) + check_key(settings, "icon", "view-refresh-symbolic") check_key(settings, "root-css-name", "root-executor") check_key(settings, "css-name", "") check_key(settings, "icon-placement", "left") @@ -44,6 +46,7 @@ def __init__(self, settings, icons_path, executor_name): check_key(settings, "angle", 0.0) check_key(settings, "sigrt", signal.SIGRTMIN) check_key(settings, "use-sigrt", False) + check_key(settings, "continuous", False) self.label.set_angle(settings["angle"]) @@ -54,7 +57,10 @@ def __init__(self, settings, icons_path, executor_name): if settings["angle"] != 0.0: self.box.set_orientation(Gtk.Orientation.VERTICAL) - update_image(self.image, "view-refresh-symbolic", self.settings["icon-size"], self.icons_path) + if self.settings["icon"] is None or len(self.settings["icon"]) == 0: + self.image.hide() + else: + update_image(self.image, self.settings["icon"], self.settings["icon-size"], self.icons_path) self.set_property("name", settings["root-css-name"]) @@ -85,6 +91,7 @@ def __init__(self, settings, icons_path, executor_name): def update_widget(self, output): if output: if len(output) == 1: + print("output1 {}".format(output)) if output[0].endswith(".svg") or output[0].endswith(".png"): new_path = output[0].strip() if new_path != self.icon_path: @@ -112,7 +119,10 @@ def update_widget(self, output): elif len(output) == 2: new_path = output[0].strip() - if "/" not in new_path and "." not in new_path: # name given instead of path + if new_path == "": + if self.image.get_visible(): + self.image.hide() + elif "/" not in new_path and "." not in new_path: # name given instead of path update_image(self.image, new_path, self.settings["icon-size"], self.icons_path) self.icon_path = new_path else: @@ -139,9 +149,32 @@ def update_widget(self, output): def get_output(self): if "script" in self.settings and self.settings["script"]: + script = split(self.settings["script"]) + continuous = self.settings["continuous"] try: - output = subprocess.check_output(split(self.settings["script"])).decode("utf-8").splitlines() - GLib.idle_add(self.update_widget, output) + if not continuous: + subprocess.check_output(script) + output = subprocess.check_output(split(self.settings["script"])).decode("utf-8").splitlines() + GLib.idle_add(self.update_widget, output) + return + + if self.process is not None and self.process.poll() is None: + # Last process has not yet finished + # Wait for it, possibly this is a continuous output + return + + self.process = subprocess.Popen(script, + stdout = subprocess.PIPE) + first_line = None + while True: + line = self.process.stdout.readline().decode('utf-8') + if line is None or len(line) == 0: break + + if first_line is None: + first_line = line + else: + GLib.idle_add(self.update_widget, [first_line, line]) + first_line = None except Exception as e: print(e) From 0f2006339a7c8c592bb3d560718ae7f0b325b1e8 Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Thu, 9 Mar 2023 16:13:46 +0100 Subject: [PATCH 3/5] Implement executor config throuh GUI --- nwg_panel/config.py | 13 ++++++++- nwg_panel/glade/config_executor.glade | 41 +++++++++++++++++++++++++-- nwg_panel/langs/en_US.json | 2 ++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/nwg_panel/config.py b/nwg_panel/config.py index d1240d99..14eca9f6 100644 --- a/nwg_panel/config.py +++ b/nwg_panel/config.py @@ -2598,7 +2598,9 @@ def edit_executor(self, item, name, new=False): "interval": 1, "angle": 0.0, "sigrt": signal.SIGRTMIN, - "use-sigrt": False + "use-sigrt": False, + "icon": "view-refresh-symbolic", + "continuous": False } for key in defaults: check_key(settings, key, defaults[key]) @@ -2660,6 +2662,9 @@ def edit_executor(self, item, name, new=False): self.executor_icon_placement = builder.get_object("icon-placement") self.executor_icon_placement.set_active_id(settings["icon-placement"]) + self.executor_icon = builder.get_object("icon") + self.executor_icon.set_text(settings["icon"]) + self.executor_icon_size = builder.get_object("icon-size") self.executor_icon_size.set_numeric(True) adj = Gtk.Adjustment(value=0, lower=8, upper=128, step_increment=1, page_increment=10, page_size=1) @@ -2689,6 +2694,10 @@ def edit_executor(self, item, name, new=False): self.executor_use_sigrt.set_label(voc["use-signal"]) self.executor_use_sigrt.set_active(settings["use-sigrt"]) + self.executor_continuous = builder.get_object("continuous") + self.executor_continuous.set_label(voc["continuous"]) + self.executor_continuous.set_active(settings["continuous"]) + self.executor_remove = builder.get_object("remove") self.executor_remove.set_label(voc["remove-executor"]) @@ -2736,6 +2745,7 @@ def update_executor(self): val = self.executor_icon_placement.get_active_id() if val: settings["icon-placement"] = val + settings["icon"] = self.executor_icon.get_text() settings["icon-size"] = int(self.executor_icon_size.get_value()) settings["interval"] = int(self.executor_interval.get_value()) @@ -2746,6 +2756,7 @@ def update_executor(self): settings["sigrt"] = int(self.executor_sigrt.get_value()) settings["use-sigrt"] = self.executor_use_sigrt.get_active() + settings["continuous"] = self.executor_continuous.get_active() self.panel[config_key] = settings else: diff --git a/nwg_panel/glade/config_executor.glade b/nwg_panel/glade/config_executor.glade index 0c16187c..b3818718 100644 --- a/nwg_panel/glade/config_executor.glade +++ b/nwg_panel/glade/config_executor.glade @@ -205,6 +205,20 @@ 2 + + + continuous + True + True + False + True + Updates the executor each time the script puts two lines of text (icon or empty line and text) + + + 2 + 2 + + True @@ -347,6 +361,29 @@ executor will create a new one. 11 + + + True + False + end + Icon: + + + 0 + 16 + + + + + True + True + 22 + + + 1 + 16 + + Save to database @@ -356,7 +393,7 @@ executor will create a new one. 1 - 16 + 17 @@ -369,7 +406,7 @@ executor will create a new one. 0 - 16 + 17 diff --git a/nwg_panel/langs/en_US.json b/nwg_panel/langs/en_US.json index e3ac33b3..eeb59d50 100644 --- a/nwg_panel/langs/en_US.json +++ b/nwg_panel/langs/en_US.json @@ -49,6 +49,7 @@ "command": "Command", "common": "Common", "common-panel-settings": "Common nwg-panel settings", + "continuous": "Continuous", "controls": "Controls", "controls-window-width-tooltip": "Controls window width in pixels; leave 0 for auto.", "cover-size": "Cover size", @@ -80,6 +81,7 @@ "gust": "gust", "header-icon-size": "Header icon size", "height": "Height", + "hide-empty": "Hide empty workspaces", "homogeneous": "Homogeneous", "homogeneous-tooltip": "Sets equal columns width be default if 'Modules center' not empty.", "horizontal-padding": "Horizontal padding", From e3fc32aa9764451c4091d8ddf70f364a8ccb39ec Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Thu, 9 Mar 2023 16:13:46 +0100 Subject: [PATCH 4/5] Implement Sway workspaces config though GUI --- nwg_panel/config.py | 5 +++++ nwg_panel/glade/config_sway_workspaces.glade | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/nwg_panel/config.py b/nwg_panel/config.py index 14eca9f6..420ba35d 100644 --- a/nwg_panel/config.py +++ b/nwg_panel/config.py @@ -1230,6 +1230,10 @@ def edit_sway_taskbar(self, *args): self.ckb_show_layout.set_label(voc["show-layout"]) self.ckb_show_layout.set_active(settings["show-layout"]) + self.ckb_hide_empty = builder.get_object("hide-empty") + self.ckb_hide_empty.set_label(voc["hide-empty"]) + self.ckb_hide_empty.set_active(settings["hide-empty"]) + self.workspace_buttons = builder.get_object("workspace-buttons") self.workspace_buttons.set_label(voc["workspaces-as-buttons"]) self.workspace_buttons.set_active(settings["workspace-buttons"]) @@ -1284,6 +1288,7 @@ def update_sway_taskbar(self): settings["show-app-icon"] = self.ckb_show_app_icon.get_active() settings["show-app-name"] = self.ckb_show_app_name.get_active() settings["show-layout"] = self.ckb_show_layout.get_active() + settings["hide-empty"] = self.ckb_hide_empty.get_active() settings["workspace-buttons"] = self.workspace_buttons.get_active() settings["all-workspaces"] = self.ckb_all_workspaces.get_active() settings["mark-autotiling"] = self.ckb_mark_autotiling.get_active() diff --git a/nwg_panel/glade/config_sway_workspaces.glade b/nwg_panel/glade/config_sway_workspaces.glade index 97fca26c..0923b8a7 100644 --- a/nwg_panel/glade/config_sway_workspaces.glade +++ b/nwg_panel/glade/config_sway_workspaces.glade @@ -154,6 +154,20 @@ 8 + + + Hide empty workspaces + True + True + False + start + True + + + 1 + 8 + + True From de94b6b4f279944b4c379f5419770ee49e40ce2a Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Mon, 13 Mar 2023 13:31:44 +0100 Subject: [PATCH 5/5] [sway-workspaces] Improvement of dynamic workspaces - sort workspaces number from sway to show them in correct order - fix configuration interface - handle workspaces labels in config - handle workspaces labels from sway if not defined in config --- nwg_panel/config.py | 30 +++-- nwg_panel/glade/config_sway_workspaces.glade | 41 ++++--- nwg_panel/langs/en_US.json | 1 + nwg_panel/main.py | 16 ++- nwg_panel/modules/sway_workspaces.py | 123 +++++++++++++------ 5 files changed, 147 insertions(+), 64 deletions(-) diff --git a/nwg_panel/config.py b/nwg_panel/config.py index 420ba35d..775bf613 100644 --- a/nwg_panel/config.py +++ b/nwg_panel/config.py @@ -1230,10 +1230,6 @@ def edit_sway_taskbar(self, *args): self.ckb_show_layout.set_label(voc["show-layout"]) self.ckb_show_layout.set_active(settings["show-layout"]) - self.ckb_hide_empty = builder.get_object("hide-empty") - self.ckb_hide_empty.set_label(voc["hide-empty"]) - self.ckb_hide_empty.set_active(settings["hide-empty"]) - self.workspace_buttons = builder.get_object("workspace-buttons") self.workspace_buttons.set_label(voc["workspaces-as-buttons"]) self.workspace_buttons.set_active(settings["workspace-buttons"]) @@ -1288,7 +1284,6 @@ def update_sway_taskbar(self): settings["show-app-icon"] = self.ckb_show_app_icon.get_active() settings["show-app-name"] = self.ckb_show_app_name.get_active() settings["show-layout"] = self.ckb_show_layout.get_active() - settings["hide-empty"] = self.ckb_hide_empty.get_active() settings["workspace-buttons"] = self.workspace_buttons.get_active() settings["all-workspaces"] = self.ckb_all_workspaces.get_active() settings["mark-autotiling"] = self.ckb_mark_autotiling.get_active() @@ -1752,7 +1747,9 @@ def edit_sway_workspaces(self, *args): "mark-autotiling": True, "mark-content": True, "show-layout": True, - "angle": 0.0 + "angle": 0.0, + "hide-empty": False, + "hide-other-outputs": False, } for key in defaults: check_key(settings, key, defaults[key]) @@ -1801,6 +1798,14 @@ def edit_sway_workspaces(self, *args): self.ws_show_name.set_label(voc["show-window-name"]) self.ws_show_name.set_active(settings["show-name"]) + self.ws_hide_empty = builder.get_object("hide-empty") + self.ws_hide_empty.set_label(voc["hide-empty"]) + self.ws_hide_empty.set_active(settings["hide-empty"]) + + self.ws_hide_other_outputs = builder.get_object("hide-other-outputs") + self.ws_hide_other_outputs.set_label(voc["hide-other-outputs"]) + self.ws_hide_other_outputs.set_active(settings["hide-other-outputs"]) + self.ws_image_size = builder.get_object("image-size") self.ws_image_size.set_numeric(True) adj = Gtk.Adjustment(value=0, lower=8, upper=129, step_increment=1, page_increment=10, page_size=1) @@ -1837,8 +1842,11 @@ def update_sway_workspaces(self): settings = self.panel["sway-workspaces"] val = self.eb_workspaces_menu.get_text() - if val: + if val is not None: + print('numbers', repr(settings["numbers"]), repr(val.split())) settings["numbers"] = val.split() + print('numbers', repr(settings["numbers"]), repr(val.split())) + val = self.ws_custom_labels.get_text() settings["custom-labels"] = val.split() @@ -1854,6 +1862,14 @@ def update_sway_workspaces(self): if val is not None: settings["show-name"] = val + val = self.ws_hide_other_outputs.get_active() + if val is not None: + settings["hide-other-outputs"] = val + + val = self.ws_hide_empty.get_active() + if val is not None: + settings["hide-empty"] = val + settings["image-size"] = int(self.ws_image_size.get_value()) settings["name-length"] = int(self.ws_name_length.get_value()) diff --git a/nwg_panel/glade/config_sway_workspaces.glade b/nwg_panel/glade/config_sway_workspaces.glade index 0923b8a7..9a9f29b4 100644 --- a/nwg_panel/glade/config_sway_workspaces.glade +++ b/nwg_panel/glade/config_sway_workspaces.glade @@ -154,20 +154,6 @@ 8 - - - Hide empty workspaces - True - True - False - start - True - - - 1 - 8 - - True @@ -240,7 +226,32 @@ - + + Hide empty workspaces + True + True + False + start + True + + + 0 + 10 + + + + + Hide workspaces in other outputs + True + True + False + start + True + + + 1 + 10 + diff --git a/nwg_panel/langs/en_US.json b/nwg_panel/langs/en_US.json index eeb59d50..9e947b49 100644 --- a/nwg_panel/langs/en_US.json +++ b/nwg_panel/langs/en_US.json @@ -82,6 +82,7 @@ "header-icon-size": "Header icon size", "height": "Height", "hide-empty": "Hide empty workspaces", + "hide-other-outputs": "Hide workspaces on other outputs", "homogeneous": "Homogeneous", "homogeneous-tooltip": "Sets equal columns width be default if 'Modules center' not empty.", "horizontal-padding": "Horizontal padding", diff --git a/nwg_panel/main.py b/nwg_panel/main.py index 33aa61d5..308b8a8f 100644 --- a/nwg_panel/main.py +++ b/nwg_panel/main.py @@ -207,12 +207,20 @@ def instantiate_content(panel, container, content_list, icons_path=""): if item == "sway-workspaces": if sway: - if "sway-workspaces" in panel: - workspaces = SwayWorkspaces(panel["sway-workspaces"], common.i3, icons_path=icons_path) + if "sway-workspaces" not in panel: + print("'sway-workspaces' not defined in this panel instance") + else: + output = None + if "output" in panel: + output = "{}".format(panel["output"]) + + workspaces = SwayWorkspaces(panel["sway-workspaces"], common.i3, + icons_path=icons_path, + output=output) + container.pack_start(workspaces, False, False, panel["items-padding"]) common.workspaces_list.append(workspaces) - else: - print("'sway-workspaces' not defined in this panel instance") + else: print("'sway-workspaces' ignored") diff --git a/nwg_panel/modules/sway_workspaces.py b/nwg_panel/modules/sway_workspaces.py index 42aa594f..af29d59f 100644 --- a/nwg_panel/modules/sway_workspaces.py +++ b/nwg_panel/modules/sway_workspaces.py @@ -7,10 +7,11 @@ class SwayWorkspaces(Gtk.Box): - def __init__(self, settings, i3, icons_path): + def __init__(self, settings, i3, icons_path, output): Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=0) self.settings = settings self.i3 = i3 + self.panel_output = output self.num_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) self.ws_num2box = {} self.ws_num2lbl = {} @@ -34,6 +35,7 @@ def build_box(self): 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, "hide-other-outputs", False) check_key(self.settings, "show-layout", True) check_key(self.settings, "angle", 0.0) if self.settings["angle"] != 0.0: @@ -42,27 +44,17 @@ def build_box(self): # 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 or len(self.settings["custom-labels"]) == len(self.settings["numbers"]): - self.settings["custom-labels"] *= len(self.settings["numbers"]) - else: - self.settings["custom-labels"] = [] - - if len(self.settings["focused-labels"]) == 1 or len(self.settings["focused-labels"]) == len(self.settings["numbers"]): - self.settings["focused-labels"] *= len(self.settings["numbers"]) - else: - self.settings["focused-labels"] = [] + ws_num, win_name, win_id, non_empty, win_layout, numbers, ws_defs = self.find_details() 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) + try: + int_num = int(num) + except: + int_num = 0 + + label = self.get_ws_label(int_num, focused = (str(num) == str(ws_num))) eb, lbl = self.build_number(num, label) self.num_box.pack_start(eb, False, False, 0) @@ -114,9 +106,47 @@ def build_number(self, num, label): return eb, lbl + def get_ws_label(self, num, idx = None, ws_defs = None, focused = False): + """ + Get the text to display for a workspace: + - num: the number of the workspace (as integer) + - idx: the index of the workspace in ws_defs (if defined) + - focused: if the workspace is currently focused + - ws_defs: as returned by find_details() + """ + + # config_idx is the index of the current workspace in the + # configuration. It is the index of the workspace in the + # numbers config field, or the an index based on the + # workspace number, first workspace being number 1 + config_idx = None + if num in self.settings["numbers"]: + config_idx = self.settings["numbers"].index(num) + elif len(self.settings["numbers"]) == 0: + config_idx = num - 1 + + if focused: + labels = self.settings["focused-labels"] + else: + labels = self.settings["custom-labels"] + + if labels and config_idx in range(len(labels)): + text = labels[config_idx] + elif labels and len(labels) == 1: + text = labels[0] + elif idx in range(len(ws_defs)): + text = ws_defs[idx]['name'] + else: + text = str(num) + + return text + def refresh(self): if self.i3.get_tree().find_focused(): - ws_num, win_name, win_id, non_empty, win_layout, numbers = self.find_details() + ws_num, win_name, win_id, non_empty, win_layout, numbers, ws_defs = self.find_details() + + custom_labels = self.settings["custom-labels"] + focused_labels = self.settings["focused-labels"] if len(self.settings["numbers"]) > 0: numbers = self.settings["numbers"] @@ -125,23 +155,15 @@ def refresh(self): 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) + for idx, num in enumerate(numbers): + focused = (str(num) == str(ws_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) + text = self.get_ws_label(int_num, idx, ws_defs, + focused = focused) if num not in self.ws_num2lbl: eb, lbl = self.build_number(num, text) @@ -150,7 +172,7 @@ def refresh(self): lbl = self.ws_num2lbl[num] - if not self.settings["hide-empty"] or int_num in non_empty or num == str(ws_num): + if not self.settings["hide-empty"] or int_num in non_empty or focused: lbl.show() else: lbl.hide() @@ -163,10 +185,10 @@ def refresh(self): else: if text.endswith("."): text = text[0:-1] - + lbl.set_text(text) - if num == str(ws_num): + if focused: self.ws_num2box[num].set_property("name", "task-box-focused") else: self.ws_num2box[num].set_property("name", "task-box") @@ -229,12 +251,30 @@ def find_details(self): win_id = "" # app_id if available, else window_class layout = None numbers = [] + ws_defs = [] for ws in workspaces: - numbers.append(str(ws.num)) + _, _, name = ws.name.partition(':') + if len(name) == 0: + name = str(ws.num) + + hide_other_outputs = self.settings["hide-other-outputs"] and self.panel_output is not None + if hide_other_outputs and ws.output != self.panel_output: + continue + + ws_defs.append({ + 'num': int(ws.num), + 'name': name + }) if ws.focused: ws_num = ws.num + # Sort ws_defs before constructing numbers and names to ensure + # dynamic workspaces always appear in sorted order + ws_defs.sort(key = lambda ws: ws['num']) + for _idx, ws in enumerate(ws_defs): + numbers.append(ws['num']) + non_empty = [] if self.settings["show-name"] or self.settings["show-icon"]: f = self.i3.get_tree().find_focused() @@ -273,16 +313,23 @@ def find_details(self): if not layout: layout = f.parent.layout - return ws_num, win_name, win_id, non_empty, layout, numbers + return ws_num, win_name, win_id, non_empty, layout, numbers, ws_defs 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): + hide_other_outputs = self.settings["hide-other-outputs"] and self.panel_output is not None if event.direction == Gdk.ScrollDirection.UP: - nwg_panel.common.i3.command("workspace prev") + if hide_other_outputs: + nwg_panel.common.i3.command("workspace prev_on_output") + else: + nwg_panel.common.i3.command("workspace prev") elif event.direction == Gdk.ScrollDirection.DOWN: - nwg_panel.common.i3.command("workspace next") + if hide_other_outputs: + nwg_panel.common.i3.command("workspace next_on_output") + else: + 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)