60 Commits

Author SHA1 Message Date
piotr
f3d3ef512c common namespace for all panels 2024-10-09 02:59:03 +02:00
piotr
b119542490 set gtk-layer-shell namespace 2024-10-09 01:22:22 +02:00
piotr
142628158e mark running sink #324 2024-10-07 01:36:55 +02:00
piotr
d89a37b267 bump to 0.9.41 2024-10-05 01:09:00 +02:00
Piotr Miller
3394161648 Merge pull request #323 from nwg-piotr/watcher
improve hypr_watcher()
2024-10-05 01:08:38 +02:00
piotr
1345106637 comments, messages 2024-10-05 01:00:07 +02:00
piotr
4c8fe7dcb0 rework hypr_watcher #321 2024-10-04 03:23:13 +02:00
piotr
d4598c8843 refresh workspaces on createworkspace event #321 2024-10-03 17:22:51 +02:00
piotr
4852c8fa5e bump to 0.9.40 2024-10-02 03:01:45 +02:00
piotr
38c587fa9d handle NewToolTip signal #320 2024-10-02 02:58:51 +02:00
Piotr Miller
1673def218 Merge pull request #318 from nwg-piotr/fix-kb-layout
Workaround for hyprctl empty devices lists
2024-09-19 02:28:39 +02:00
piotr
572b195d5d bump to 0.9.39 2024-09-19 02:14:37 +02:00
piotr
5f036ce0d8 add error message 2024-09-19 01:52:12 +02:00
piotr
7de7034dfd add error message 2024-09-19 01:49:07 +02:00
piotr
ee913ce926 skip UI creation if hyprctl -j devices failed 2024-09-19 01:45:04 +02:00
piotr
c4cedee4ec fix package data 2024-08-25 16:42:59 +02:00
piotr
b79d259942 bum to 0.9.38 2024-08-25 16:24:24 +02:00
Piotr Miller
bc3b1db1d8 Merge pull request #317 from progandy/fix/sni-without-introspection
fix tray icons for electron (#224)
2024-08-25 16:19:23 +02:00
ProgAndy
56055db8f3 fix tray icons for electron (#224)
Electron does not offer introspection data.
Add the interface specification manually to make it usable anyways.
2024-08-25 15:45:48 +02:00
Piotr Miller
434d365f0a Merge pull request #316 from nwg-piotr/fix315
Hyprland data initialization moved to the `instantiate_content()` function loop #315
2024-07-28 01:11:00 +02:00
piotr
3a524a278f move Hyprland data initialization to the loop #315 2024-07-28 01:04:55 +02:00
piotr
5ae4d9738b bump to 0.9.37 2024-07-25 01:02:04 +02:00
Piotr Miller
0bb9b9d545 Merge pull request #314 from Marc-Pierre-Barbier/master
Fix crashes on wsl-randr if a monitor is disabled
2024-07-25 01:01:40 +02:00
Marc
94b4aafe99 cleaning up the monitor affectation loop 2024-07-24 20:43:24 +02:00
Marc
ec29c67c5f Fix crashes on wsl-randr if a monitor is disabled 2024-07-24 20:27:33 +02:00
piotr
65ffb8276c bump to 0.9.36 2024-07-18 01:09:07 +02:00
piotr
fb5e74b52a fix hyprland taskbar refresh arguments 2024-07-18 01:03:44 +02:00
Piotr Miller
d53e500aeb Merge pull request #313 from nwg-piotr/activeworkspace
Activeworkspace
2024-07-16 03:05:59 +02:00
piotr
0fd5456fd6 mark #workspace-occupied 2024-07-16 02:52:10 +02:00
piotr
3831b46604 add #workspace-occupied 2024-07-16 02:33:03 +02:00
piotr
fa20711bbe unbind marking color and dot 2024-07-16 02:30:59 +02:00
piotr
a702829190 debug 2024-07-16 02:07:46 +02:00
piotr
35251fe880 debug 2024-07-16 02:01:05 +02:00
piotr
6e9c3e12b1 debug 2024-07-16 01:58:01 +02:00
piotr
3d070d4f09 debug 2024-07-16 01:55:02 +02:00
piotr
2c63f64ccf debug 2024-07-16 01:53:44 +02:00
piotr
c007f8e428 debug 2024-07-16 01:52:10 +02:00
piotr
caeb1747ef debug 2024-07-16 01:49:25 +02:00
piotr
8a5923153f debug 2024-07-16 01:47:27 +02:00
piotr
dadebe681f debug 2024-07-16 01:45:30 +02:00
piotr
82d6c397e5 debug 2024-07-16 01:42:18 +02:00
piotr
503b66b077 debug 2024-07-16 01:40:47 +02:00
piotr
076fa222d4 debug 2024-07-16 01:39:07 +02:00
piotr
728cd1411b debug 2024-07-16 01:37:18 +02:00
piotr
a3ef3ee8fd debug 2024-07-16 01:35:05 +02:00
piotr
5156ca2d51 debug 2024-07-16 01:31:57 +02:00
piotr
0304c94e8f debug 2024-07-16 01:28:11 +02:00
piotr
01b1dcb85d debug 2024-07-16 01:20:32 +02:00
piotr
466348aef2 debug 2024-07-16 01:19:17 +02:00
piotr
770f0bd393 debug 2024-07-16 01:12:50 +02:00
piotr
e096a75dd5 debug 2024-07-16 01:01:37 +02:00
piotr
3fd934b0bd debug 2024-07-16 00:51:51 +02:00
piotr
fc7787f34a debug 2024-07-16 00:47:41 +02:00
piotr
35dbb036c9 debug 2024-07-16 00:46:17 +02:00
piotr
f36a80fa5d debug 2024-07-16 00:42:25 +02:00
piotr
4aa4b1d51e debug 2024-07-16 00:18:01 +02:00
piotr
2cd25c0e14 add & use h_get_active_workspace() #310 2024-07-15 23:49:53 +02:00
piotr
2c13954dda bump to 0.9.35 2024-07-15 02:12:34 +02:00
piotr
6bd10471cf [sway-workspaces] add 'workspace-occupied' css ID #311 2024-07-15 02:09:11 +02:00
piotr
277a83f35a add 'workspace-occupied' css ID #311 2024-07-15 01:57:51 +02:00
13 changed files with 256 additions and 140 deletions

View File

@@ -215,7 +215,8 @@
<property name="halign">start</property> <property name="halign">start</property>
<property name="label" translatable="yes">&lt;span size="small"&gt;&lt;b&gt;CSS IDs&lt;/b&gt;: <property name="label" translatable="yes">&lt;span size="small"&gt;&lt;b&gt;CSS IDs&lt;/b&gt;:
#hyprland-workspaces, #hyprland-workspaces-item, #hyprland-workspaces, #hyprland-workspaces-item,
#hyprland-workspaces-icon, #hyprland-workspaces-name&lt;/span&gt;</property> #hyprland-workspaces-icon, #hyprland-workspaces-name
#workspace-occupied&lt;/span&gt;</property>
<property name="use-markup">True</property> <property name="use-markup">True</property>
<property name="wrap">True</property> <property name="wrap">True</property>
</object> </object>

View File

@@ -266,7 +266,8 @@
<property name="halign">start</property> <property name="halign">start</property>
<property name="label" translatable="yes">&lt;span size="small"&gt;&lt;b&gt;CSS IDs&lt;/b&gt;: <property name="label" translatable="yes">&lt;span size="small"&gt;&lt;b&gt;CSS IDs&lt;/b&gt;:
#sway-workspaces, #sway-workspaces-item, #sway-workspaces, #sway-workspaces-item,
#sway-workspaces-icon, #sway-workspaces-name&lt;/span&gt;</property> #sway-workspaces-icon, #sway-workspaces-name
#workspace-occupied&lt;/span&gt;</property>
<property name="use-markup">True</property> <property name="use-markup">True</property>
<property name="wrap">True</property> <property name="wrap">True</property>
</object> </object>

View File

@@ -87,8 +87,6 @@ his = os.getenv('HYPRLAND_INSTANCE_SIGNATURE')
if his: if his:
from nwg_panel.modules.hyprland_taskbar import HyprlandTaskbar from nwg_panel.modules.hyprland_taskbar import HyprlandTaskbar
from nwg_panel.modules.hyprland_workspaces import HyprlandWorkspaces from nwg_panel.modules.hyprland_workspaces import HyprlandWorkspaces
last_client_addr = ""
last_client_title = ""
common_settings = {} common_settings = {}
restart_cmd = "" restart_cmd = ""
@@ -154,7 +152,6 @@ def restart():
subprocess.Popen(restart_cmd, shell=True) subprocess.Popen(restart_cmd, shell=True)
# read from Hyprland socket2 on async thread
def hypr_watcher(): def hypr_watcher():
import socket import socket
@@ -165,74 +162,48 @@ def hypr_watcher():
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect(f"{hypr_dir}/{his}/.socket2.sock") client.connect(f"{hypr_dir}/{his}/.socket2.sock")
just_refreshed = False
global last_client_addr, last_client_title
client_addr, client_title = None, None
while True: while True:
datagram = client.recv(2048) datagram = client.recv(2048)
e_full_string = datagram.decode('utf-8').strip() e_full_string = datagram.decode('utf-8').strip()
# eprint("Event: {}".format(e_full_string)) lines = e_full_string.splitlines()
# remember client address & title (string) for further event filtering event_names = []
if e_full_string.startswith("activewindow"): for line in lines:
lines = e_full_string.splitlines() event_names.append(line.split(">>")[0])
for line in lines: # print(f"events: {event_names}")
if line.startswith("activewindowv2"):
client_addr = e_full_string.split(">>")[1].strip()
elif line.startswith("activewindow>>"):
client_title = line.split(">>")[1].strip()
event_name = e_full_string.split(">>")[0] for event_name in event_names:
if event_name in ["activespecial",
"activewindow",
"activewindowv2",
"changefloatingmode",
"closewindow",
"createworkspace",
"destroyworkspace",
"focusedmon",
"monitoradded",
"movewindow",
"openwindow",
"windowtitle",
"workspace"]:
if event_name in ["monitoradded", "openwindow", "movewindow"]: if "activewindow" in event_name and just_refreshed:
monitors, workspaces, clients, activewindow = h_modules_get_all() just_refreshed = False
for item in common.h_taskbars_list: break
GLib.timeout_add(0, item.refresh, monitors, workspaces, clients, activewindow)
last_client_title = client_title
last_client_addr = client_addr
continue
if event_name == "focusedmon": # print(f">>> refreshing on {event_name}")
monitors, workspaces, clients, activewindow = h_modules_get_all() monitors, workspaces, clients, activewindow, activeworkspace = h_modules_get_all()
for item in common.h_workspaces_list: for item in common.h_taskbars_list:
GLib.timeout_add(0, item.refresh, monitors, workspaces, clients, activewindow) GLib.timeout_add(0, item.refresh, monitors, workspaces, clients, activewindow)
last_client_title = client_title
last_client_addr = client_addr
continue
if event_name == "activewindow" and client_title != last_client_title: for item in common.h_workspaces_list:
monitors, workspaces, clients, activewindow = h_modules_get_all() GLib.timeout_add(0, item.refresh, monitors, workspaces, clients, activewindow, activeworkspace)
for item in common.h_taskbars_list:
GLib.timeout_add(0, item.refresh, monitors, workspaces, clients, activewindow)
for item in common.h_workspaces_list: if event_name in ["createworkspace", "destroyworkspace", "focusedmon", "workspace"]:
GLib.timeout_add(0, item.refresh, monitors, workspaces, clients, activewindow) just_refreshed = True
break
last_client_title = client_title
continue
if event_name == "activewindowv2" and client_addr != last_client_addr:
monitors, workspaces, clients, activewindow = h_modules_get_all()
for item in common.h_taskbars_list:
GLib.timeout_add(0, item.refresh, monitors, workspaces, clients, activewindow)
for item in common.h_workspaces_list:
GLib.timeout_add(0, item.refresh, monitors, workspaces, clients, activewindow)
last_client_addr = client_addr
continue
if event_name in ["changefloatingmode", "closewindow"]:
monitors, workspaces, clients, activewindow = h_modules_get_all()
for item in common.h_taskbars_list:
GLib.timeout_add(0, item.refresh, monitors, workspaces, clients, activewindow)
for item in common.h_workspaces_list:
GLib.timeout_add(0, item.refresh, monitors, workspaces, clients, activewindow)
last_client_addr = ""
last_client_title = ""
def on_i3ipc_event(i3conn, event): def on_i3ipc_event(i3conn, event):
@@ -267,14 +238,14 @@ def instantiate_content(panel, container, content_list, icons_path=""):
check_key(panel, "position", "top") check_key(panel, "position", "top")
check_key(panel, "items-padding", 0) check_key(panel, "items-padding", 0)
# list initial data for Hyprland modules
if his:
if "hyprland-workspaces" in content_list or "hyprland-taskbar" in content_list:
monitors, workspaces, clients, activewindow = h_modules_get_all()
else:
monitors, workspaces, clients, activewindow = {}, {}, {}, {}
for item in content_list: for item in content_list:
# list initial data for Hyprland modules
if his:
if "hyprland-workspaces" in content_list or "hyprland-taskbar" in content_list:
monitors, workspaces, clients, activewindow, activeworkspace = h_modules_get_all()
else:
monitors, workspaces, clients, activewindow, activeworkspace = {}, {}, {}, {}, {}
if item == "sway-taskbar": if item == "sway-taskbar":
if "sway-taskbar" in panel: if "sway-taskbar" in panel:
if sway: if sway:
@@ -348,7 +319,7 @@ def instantiate_content(panel, container, content_list, icons_path=""):
if his: if his:
if "hyprland-workspaces" in panel: if "hyprland-workspaces" in panel:
workspaces = HyprlandWorkspaces(panel["hyprland-workspaces"], monitors, workspaces, clients, workspaces = HyprlandWorkspaces(panel["hyprland-workspaces"], monitors, workspaces, clients,
activewindow, icons_path=icons_path) activewindow, activeworkspace, icons_path=icons_path)
container.pack_start(workspaces, False, False, panel["items-padding"]) container.pack_start(workspaces, False, False, panel["items-padding"])
common.h_workspaces_list.append(workspaces) common.h_workspaces_list.append(workspaces)
else: else:
@@ -761,12 +732,14 @@ def main():
left_box.pack_start(ms, False, False, 0) left_box.pack_start(ms, False, False, 0)
instantiate_content(panel, left_box, panel["modules-left"], icons_path=icons_path) instantiate_content(panel, left_box, panel["modules-left"], icons_path=icons_path)
print("left box created")
center_box = Gtk.Box(orientation=o, spacing=panel["spacing"]) center_box = Gtk.Box(orientation=o, spacing=panel["spacing"])
center_box.set_property("name", "center-box") center_box.set_property("name", "center-box")
inner_box.pack_start(center_box, True, False, 0) inner_box.pack_start(center_box, True, False, 0)
check_key(panel, "modules-center", []) check_key(panel, "modules-center", [])
instantiate_content(panel, center_box, panel["modules-center"], icons_path=icons_path) instantiate_content(panel, center_box, panel["modules-center"], icons_path=icons_path)
print("center box created")
right_box = Gtk.Box(orientation=o, spacing=panel["spacing"]) right_box = Gtk.Box(orientation=o, spacing=panel["spacing"])
right_box.set_property("name", "right-box") right_box.set_property("name", "right-box")
@@ -776,6 +749,7 @@ def main():
inner_box.pack_start(helper_box, False, True, 0) inner_box.pack_start(helper_box, False, True, 0)
check_key(panel, "modules-right", []) check_key(panel, "modules-right", [])
instantiate_content(panel, right_box, panel["modules-right"], icons_path=icons_path) instantiate_content(panel, right_box, panel["modules-right"], icons_path=icons_path)
print("right box created")
if panel["menu-start"] == "right": if panel["menu-start"] == "right":
ms = MenuStart(panel["menu-start-settings"], icons_path=icons_path) ms = MenuStart(panel["menu-start-settings"], icons_path=icons_path)
@@ -803,6 +777,7 @@ def main():
window.add(vbox) window.add(vbox)
GtkLayerShell.init_for_window(window) GtkLayerShell.init_for_window(window)
GtkLayerShell.set_namespace(window, f"nwg-panel")
monitor = None monitor = None
try: try:
@@ -878,6 +853,7 @@ def main():
if his: if his:
if len(common.h_taskbars_list) > 0 or len(common.h_workspaces_list) > 0: if len(common.h_taskbars_list) > 0 or len(common.h_workspaces_list) > 0:
print("his: '{}', starting hypr_watcher".format(his)) print("his: '{}', starting hypr_watcher".format(his))
# read from Hyprland socket2 on another thread
thread = threading.Thread(target=hypr_watcher) thread = threading.Thread(target=hypr_watcher)
thread.daemon = True thread.daemon = True
thread.start() thread.start()

View File

@@ -837,6 +837,8 @@ class SinkBox(Gtk.Box):
desc = sink["desc"] desc = sink["desc"]
if len(desc) > 26: if len(desc) > 26:
desc = "{}\u2026".format(desc[:26]) desc = "{}\u2026".format(desc[:26])
if sink["running"]:
desc = f"{desc}"
label = Gtk.Label(desc) label = Gtk.Label(desc)
hbox.pack_start(label, True, True, 0) hbox.pack_start(label, True, True, 0)
eb.add(vbox) eb.add(vbox)

View File

@@ -6,7 +6,7 @@ from nwg_panel.tools import check_key, update_image_fallback_desktop, hyprctl
class HyprlandWorkspaces(Gtk.Box): class HyprlandWorkspaces(Gtk.Box):
def __init__(self, settings, monitors, workspaces, clients, activewindow, icons_path): def __init__(self, settings, monitors, workspaces, clients, activewindow, activeworkspace, icons_path):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=0) Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
self.settings = settings self.settings = settings
self.num_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) self.num_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
@@ -22,7 +22,7 @@ class HyprlandWorkspaces(Gtk.Box):
self.ws_nums = [] self.ws_nums = []
self.build_box() self.build_box()
self.refresh(monitors, workspaces, clients, activewindow) self.refresh(monitors, workspaces, clients, activewindow, activeworkspace)
def build_box(self): def build_box(self):
check_key(self.settings, "num-ws", 10) check_key(self.settings, "num-ws", 10)
@@ -79,6 +79,8 @@ class HyprlandWorkspaces(Gtk.Box):
name = "{} {}".format(num, self.ws_id2name[num]) name = "{} {}".format(num, self.ws_id2name[num])
lbl = Gtk.Label.new("{}".format(name)) if not add_dot else Gtk.Label.new("{}.".format(name)) lbl = Gtk.Label.new("{}".format(name)) if not add_dot else Gtk.Label.new("{}.".format(name))
# if add_dot:
# lbl.set_property("name", "workspace-occupied")
lbl.set_use_markup(True) lbl.set_use_markup(True)
if self.settings["angle"] != 0.0: if self.settings["angle"] != 0.0:
lbl.set_angle(self.settings["angle"]) lbl.set_angle(self.settings["angle"])
@@ -88,7 +90,7 @@ class HyprlandWorkspaces(Gtk.Box):
return eb, lbl return eb, lbl
def refresh(self, monitors, workspaces, clients, activewindow): def refresh(self, monitors, workspaces, clients, activewindow, activeworkspace):
occupied_workspaces = [] occupied_workspaces = []
self.ws_id2name = {} self.ws_id2name = {}
@@ -111,21 +113,22 @@ class HyprlandWorkspaces(Gtk.Box):
client_title = "X|{}".format(client_title) client_title = "X|{}".format(client_title)
floating = activewindow["floating"] floating = activewindow["floating"]
pinned = activewindow["pinned"] pinned = activewindow["pinned"]
active_ws = activewindow["workspace"]["id"]
else: else:
client_class = "" client_class = ""
client_title = "" client_title = ""
floating = False floating = False
pinned = False pinned = False
for m in monitors:
if m["focused"]: # fix #310
active_ws = m["activeWorkspace"]["id"] active_ws = activeworkspace["id"]
break
for num in self.ws_nums: for num in self.ws_nums:
if num in occupied_workspaces or self.settings["show-empty"]: if num in occupied_workspaces or self.settings["show-empty"]:
occ = num in occupied_workspaces
dot = num in occupied_workspaces and self.settings["show-empty"] and self.settings["mark-content"] dot = num in occupied_workspaces and self.settings["show-empty"] and self.settings["mark-content"]
eb, lbl = self.build_number(num, add_dot=dot, active_win_ws=active_ws) eb, lbl = self.build_number(num, add_dot=dot, active_win_ws=active_ws)
if occ:
lbl.set_property("name", "workspace-occupied")
self.num_box.pack_start(eb, False, False, 0) self.num_box.pack_start(eb, False, False, 0)
self.num_box.show_all() self.num_box.show_all()

View File

@@ -45,54 +45,57 @@ class KeyboardLayout(Gtk.EventBox):
if self.compositor: if self.compositor:
self.keyboards = self.list_keyboards() self.keyboards = self.list_keyboards()
self.keyboard_names = [] if self.keyboards:
for k in self.keyboards: self.keyboard_names = []
if self.compositor == "Hyprland": for k in self.keyboards:
self.keyboard_names.append(k["name"]) if self.compositor == "Hyprland":
# On sway some devices may be listed twice, let's add them just once self.keyboard_names.append(k["name"])
elif k.identifier not in self.keyboard_names: # On sway some devices may be listed twice, let's add them just once
self.keyboard_names.append(k.identifier) elif k.identifier not in self.keyboard_names:
# print(f"keyboard_names = {self.keyboard_names}") self.keyboard_names.append(k.identifier)
self.kb_layouts = self.get_kb_layouts() self.kb_layouts = self.get_kb_layouts()
# print(f"kb_layouts = {self.kb_layouts}")
check_key(settings, "keyboard-device-sway", "") check_key(settings, "keyboard-device-sway", "")
check_key(settings, "keyboard-device-hyprland", "") check_key(settings, "keyboard-device-hyprland", "")
self.device_name = settings["keyboard-device-sway"] if self.compositor == "sway" else settings[ self.device_name = settings["keyboard-device-sway"] if self.compositor == "sway" else settings[
"keyboard-device-hyprland"] "keyboard-device-hyprland"]
check_key(settings, "root-css-name", "root-executor") check_key(settings, "root-css-name", "root-executor")
check_key(settings, "css-name", "") check_key(settings, "css-name", "")
check_key(settings, "icon-placement", "left") check_key(settings, "icon-placement", "left")
check_key(settings, "icon-size", 16) check_key(settings, "icon-size", 16)
check_key(settings, "show-icon", True) check_key(settings, "show-icon", True)
check_key(settings, "tooltip-text", "LMB: Next layout, RMB: Menu") check_key(settings, "tooltip-text", "LMB: Next layout, RMB: Menu")
check_key(settings, "angle", 0.0) check_key(settings, "angle", 0.0)
self.label.set_angle(settings["angle"]) self.label.set_angle(settings["angle"])
if settings["angle"] != 0.0: if settings["angle"] != 0.0:
self.box.set_orientation(Gtk.Orientation.VERTICAL) self.box.set_orientation(Gtk.Orientation.VERTICAL)
update_image(self.image, "input-keyboard", self.settings["icon-size"], self.icons_path) update_image(self.image, "input-keyboard", self.settings["icon-size"], self.icons_path)
self.set_property("name", settings["root-css-name"]) self.set_property("name", settings["root-css-name"])
if settings["css-name"]: if settings["css-name"]:
self.label.set_property("name", settings["css-name"]) self.label.set_property("name", settings["css-name"])
else:
self.label.set_property("name", "executor-label")
if settings["tooltip-text"]:
self.set_tooltip_text(settings["tooltip-text"])
self.connect('button-release-event', self.on_button_release)
self.connect('enter-notify-event', on_enter_notify_event)
self.connect('leave-notify-event', on_leave_notify_event)
self.build_box()
label = self.get_current_layout()
if label:
self.label.set_text(label)
self.show_all()
else: else:
self.label.set_property("name", "executor-label") print("KeyboardLayout module: failed listing devices, won't create UI, sorry.")
if settings["tooltip-text"]:
self.set_tooltip_text(settings["tooltip-text"])
self.connect('button-release-event', self.on_button_release)
self.connect('enter-notify-event', on_enter_notify_event)
self.connect('leave-notify-event', on_leave_notify_event)
self.build_box()
self.refresh()
self.show_all()
def list_keyboards(self): def list_keyboards(self):
if self.compositor == "Hyprland": if self.compositor == "Hyprland":

View File

@@ -211,6 +211,15 @@ class Playerctl(Gtk.EventBox):
if self.settings["angle"] != 0.0: if self.settings["angle"] != 0.0:
button_box.set_orientation(Gtk.Orientation.VERTICAL) button_box.set_orientation(Gtk.Orientation.VERTICAL)
img = Gtk.Image()
update_image(img, "media-skip-backward-symbolic", self.settings["icon-size"], icons_path=self.icons_path)
btn = Gtk.Button()
btn.set_image(img)
if self.settings["button-css-name"]:
btn.set_property("name", self.settings["button-css-name"])
btn.connect("clicked", self.launch, self.PlayerOps.PREVIOUS)
button_box.pack_start(btn, False, False, 1)
self.play_pause_btn = Gtk.Button() self.play_pause_btn = Gtk.Button()
if self.settings["button-css-name"]: if self.settings["button-css-name"]:
self.play_pause_btn.set_property("name", self.settings["button-css-name"]) self.play_pause_btn.set_property("name", self.settings["button-css-name"])
@@ -220,6 +229,15 @@ class Playerctl(Gtk.EventBox):
self.play_pause_btn.connect("clicked", self.launch, self.PlayerOps.PLAY_PAUSE) self.play_pause_btn.connect("clicked", self.launch, self.PlayerOps.PLAY_PAUSE)
button_box.pack_start(self.play_pause_btn, False, False, 1) button_box.pack_start(self.play_pause_btn, False, False, 1)
img = Gtk.Image()
update_image(img, "media-skip-forward-symbolic", self.settings["icon-size"], icons_path=self.icons_path)
btn = Gtk.Button()
btn.set_image(img)
if self.settings["button-css-name"]:
btn.set_property("name", self.settings["button-css-name"])
btn.connect("clicked", self.launch, self.PlayerOps.NEXT)
button_box.pack_start(btn, False, False, 1)
self.num_players_lbl = Gtk.Label.new("") self.num_players_lbl = Gtk.Label.new("")
if self.settings["label-css-name"]: if self.settings["label-css-name"]:
self.num_players_lbl.set_property("name", self.settings["label-css-name"]) self.num_players_lbl.set_property("name", self.settings["label-css-name"])
@@ -239,9 +257,13 @@ class Playerctl(Gtk.EventBox):
self.box.pack_start(button_box, False, False, 2) self.box.pack_start(button_box, False, False, 2)
if self.settings["show-cover"]: if self.settings["show-cover"]:
self.box.pack_start(self.cover_img, False, False, 0) self.box.pack_start(self.cover_img, False, False, 0)
self.box.pack_start(self.num_players_lbl, False, False, 0)
self.box.pack_start(self.label, False, False, 5)
else: else:
if self.settings["show-cover"]: if self.settings["show-cover"]:
self.box.pack_start(self.cover_img, False, False, 2) self.box.pack_start(self.cover_img, False, False, 2)
self.box.pack_start(self.num_players_lbl, False, False, 0)
self.box.pack_start(self.label, False, False, 2)
self.box.pack_start(button_box, False, False, 10) self.box.pack_start(button_box, False, False, 10)
def launch(self, button, op): def launch(self, button, op):

View File

@@ -1,10 +1,13 @@
from gi.repository import Gdk from gi.repository import Gdk
from dasbus.connection import SessionMessageBus from dasbus.connection import SessionMessageBus
from dasbus.specification import DBusSpecificationParser
from dasbus.client.observer import DBusObserver from dasbus.client.observer import DBusObserver
from dasbus.client.proxy import disconnect_proxy from dasbus.client.proxy import disconnect_proxy
from dasbus.error import DBusError from dasbus.error import DBusError
from nwg_panel.tools import load_resource
PROPERTIES = [ PROPERTIES = [
"Id", "Id",
"Category", "Category",
@@ -57,6 +60,13 @@ class StatusNotifierItem(object):
def item_available_handler(self, _observer): def item_available_handler(self, _observer):
self.item_proxy = self.session_bus.get_proxy(self.service_name, self.object_path) self.item_proxy = self.session_bus.get_proxy(self.service_name, self.object_path)
try:
spec = self.item_proxy._handler.specification
if spec is not None:
if not any("StatusNotifierItem" in ifname for ifname in spec.interfaces):
DBusSpecificationParser._parse_xml(spec, load_resource(__package__,"org.kde.StatusNotifierItem.xml"))
except:
pass
if hasattr(self.item_proxy, "PropertiesChanged"): if hasattr(self.item_proxy, "PropertiesChanged"):
self.item_proxy.PropertiesChanged.connect( self.item_proxy.PropertiesChanged.connect(
lambda _if, changed, invalid: self.change_handler(list(changed), invalid) lambda _if, changed, invalid: self.change_handler(list(changed), invalid)
@@ -65,6 +75,10 @@ class StatusNotifierItem(object):
self.item_proxy.NewTitle.connect( self.item_proxy.NewTitle.connect(
lambda: self.change_handler(["Title"]) lambda: self.change_handler(["Title"])
) )
if hasattr(self.item_proxy, 'NewToolTip'):
self.item_proxy.NewToolTip.connect(
lambda: self.change_handler(["ToolTip"])
)
if hasattr(self.item_proxy, 'NewIcon'): if hasattr(self.item_proxy, 'NewIcon'):
self.item_proxy.NewIcon.connect( self.item_proxy.NewIcon.connect(
lambda: self.change_handler(["IconName", "IconPixmap"]) lambda: self.change_handler(["IconName", "IconPixmap"])

View File

@@ -0,0 +1,49 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.kde.StatusNotifierItem'>
<annotation name="org.gtk.GDBus.C.Name" value="Item" />
<method name='ContextMenu'>
<arg type='i' direction='in' name='x'/>
<arg type='i' direction='in' name='y'/>
</method>
<method name='Activate'>
<arg type='i' direction='in' name='x'/>
<arg type='i' direction='in' name='y'/>
</method>
<method name='SecondaryActivate'>
<arg type='i' direction='in' name='x'/>
<arg type='i' direction='in' name='y'/>
</method>
<method name='Scroll'>
<arg type='i' direction='in' name='delta'/>
<arg type='s' direction='in' name='orientation'/>
</method>
<signal name='NewTitle'/>
<signal name='NewIcon'/>
<signal name='NewAttentionIcon'/>
<signal name='NewOverlayIcon'/>
<signal name='NewToolTip'/>
<signal name='NewStatus'>
<arg type='s' name='status'/>
</signal>
<property name='Category' type='s' access='read'/>
<property name='Id' type='s' access='read'/>
<property name='Title' type='s' access='read'/>
<property name='Status' type='s' access='read'/>
<!-- See discussion on pull #536
<property name='WindowId' type='u' access='read'/>
-->
<property name='IconThemePath' type='s' access='read'/>
<property name='IconName' type='s' access='read'/>
<property name='IconPixmap' type='a(iiay)' access='read'/>
<property name='OverlayIconName' type='s' access='read'/>
<property name='OverlayIconPixmap' type='a(iiay)' access='read'/>
<property name='AttentionIconName' type='s' access='read'/>
<property name='AttentionIconPixmap' type='a(iiay)' access='read'/>
<property name='AttentionMovieName' type='s' access='read'/>
<property name='ToolTip' type='(sa(iiay)ss)' access='read'/>
<property name='Menu' type='o' access='read'/>
<property name='ItemIsMenu' type='b' access='read'/>
</interface>
</node>

View File

@@ -76,7 +76,7 @@ def update_icon_from_pixmap(image, item, icon_size):
def update_tooltip(image, item): def update_tooltip(image, item):
icon_name, icon_data, title, description = item.properties["Tooltip"] icon_name, icon_data, title, description = item.properties["ToolTip"] if "ToolTip" in item.properties else item.properties["Tooltip"]
tooltip = title tooltip = title
if description: if description:
tooltip = "<b>{}</b>\n{}".format(title, description) tooltip = "<b>{}</b>\n{}".format(title, description)
@@ -131,7 +131,7 @@ class Tray(Gtk.EventBox):
elif "IconPixmap" in item.properties and len(item.properties["IconPixmap"]) != 0: elif "IconPixmap" in item.properties and len(item.properties["IconPixmap"]) != 0:
update_icon_from_pixmap(image, item, self.icon_size) update_icon_from_pixmap(image, item, self.icon_size)
if "Tooltip" in item.properties: if "Tooltip" in item.properties or "ToolTip" in item.properties:
update_tooltip(image, item) update_tooltip(image, item)
elif "Title" in item.properties: elif "Title" in item.properties:
image.set_tooltip_markup(item.properties["Title"]) image.set_tooltip_markup(item.properties["Title"])
@@ -168,7 +168,7 @@ class Tray(Gtk.EventBox):
update_icon_from_pixmap(image, item, self.icon_size) update_icon_from_pixmap(image, item, self.icon_size)
pass pass
if "Tooltip" in changed_properties: if "Tooltip" in changed_properties or "ToolTip" in changed_properties:
update_tooltip(image, item) update_tooltip(image, item)
elif "Title" in changed_properties: elif "Title" in changed_properties:
image.set_tooltip_markup(item.properties["Title"]) image.set_tooltip_markup(item.properties["Title"])

View File

@@ -171,6 +171,10 @@ class SwayWorkspaces(Gtk.Box):
else: else:
lbl.hide() 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 # mark non-empty WS with a dot
if self.settings["mark-content"]: if self.settings["mark-content"]:
if int_num in non_empty: if int_num in non_empty:
@@ -262,13 +266,13 @@ class SwayWorkspaces(Gtk.Box):
for item in tree.descendants(): for item in tree.descendants():
if item.type == "workspace": if item.type == "workspace":
# find non-empty workspaces # find non-empty workspaces
if self.settings["mark-content"] or self.settings["hide-empty"]: # if self.settings["mark-content"] or self.settings["hide-empty"]:
tasks_num = 0 tasks_num = 0
for d in item.descendants(): for d in item.descendants():
if d.type == "con" and d.name: if d.type == "con" and d.name:
tasks_num += 1 tasks_num += 1
if tasks_num > 0: if tasks_num > 0:
non_empty.append(item.num) non_empty.append(item.num)
for node in item.floating_nodes: for node in item.floating_nodes:
if str(node.workspace().num) in self.settings["numbers"]: if str(node.workspace().num) in self.settings["numbers"]:

View File

@@ -358,6 +358,10 @@ def list_outputs(sway=False, tree=None, silent=False):
'transform': transform, 'transform': transform,
'scale': scale, 'scale': scale,
'monitor': None} 'monitor': None}
#Each monitor only have a single transform this avoid parsing multiple times the same monitor
#Disabled monitors don't have transforms.
# Gdk doesn't report disabled monitor, not filtering them would cause crashes
transform = None
else: else:
print("'wlr-randr' command not found, terminating") print("'wlr-randr' command not found, terminating")
sys.exit(1) sys.exit(1)
@@ -371,10 +375,8 @@ def list_outputs(sway=False, tree=None, silent=False):
monitor = display.get_monitor(i) monitor = display.get_monitor(i)
monitors.append(monitor) monitors.append(monitor)
idx = 0 for key, monitor in zip(outputs_dict.keys(), monitors):
for key in outputs_dict: outputs_dict[key]["monitor"] = monitor
outputs_dict[key]["monitor"] = monitors[idx]
idx += 1
return outputs_dict return outputs_dict
@@ -534,11 +536,14 @@ def list_sinks():
for line in lines: for line in lines:
details = line.split() details = line.split()
name = details[1][1:-1] name = details[1][1:-1]
desc = " ".join(details[2:])[1:-1] desc = " ".join(details[3:])[1:-1]
sinks.append({"name": name, "desc": desc}) sink = {"name": name, "desc": desc, "running": True if "Running" in line else False}
sinks.append(sink)
except Exception as e: except Exception as e:
eprint(e) eprint(e)
if nwg_panel.common.commands["pactl"]:
elif nwg_panel.common.commands["pactl"]:
try: try:
output = cmd2string("pactl list sinks") output = cmd2string("pactl list sinks")
if output: if output:
@@ -555,6 +560,8 @@ def list_sinks():
sink.update({"name": line.split(": ")[1]}) sink.update({"name": line.split(": ")[1]})
elif line.lower().startswith("description"): elif line.lower().startswith("description"):
sink.update({"desc": line.split(": ")[1]}) sink.update({"desc": line.split(": ")[1]})
elif line.lower().startswith("state"):
sink.update({"running": True if "RUNNING" in line else False})
if sink: if sink:
sinks.append(sink) sinks.append(sink)
except Exception as e: except Exception as e:
@@ -786,7 +793,6 @@ def get_cache_dir():
else: else:
return None return None
def file_age(path): def file_age(path):
return time.time() - os.stat(path)[stat.ST_MTIME] return time.time() - os.stat(path)[stat.ST_MTIME]
@@ -885,8 +891,17 @@ def h_get_activewindow():
return {} return {}
def h_get_active_workspace():
reply = hyprctl("j/activeworkspace")
try:
return json.loads(reply)
except Exception as e:
eprint(e)
return {}
def h_modules_get_all(): def h_modules_get_all():
return h_list_monitors(), h_list_workspaces(), h_list_clients(), h_get_activewindow() return h_list_monitors(), h_list_workspaces(), h_list_clients(), h_get_activewindow(), h_get_active_workspace()
def cmd_through_compositor(cmd): def cmd_through_compositor(cmd):
@@ -898,3 +913,28 @@ def cmd_through_compositor(cmd):
elif os.getenv("HYPRLAND_INSTANCE_SIGNATURE"): elif os.getenv("HYPRLAND_INSTANCE_SIGNATURE"):
cmd = f"hyprctl dispatch exec '{cmd}'" cmd = f"hyprctl dispatch exec '{cmd}'"
return cmd return cmd
def load_resource(package, resource_name):
try:
import importlib.resources as resources
with resources.open_binary(package, resource_name) as resource_file:
return resource_file.read()
except:
pass
try:
import importlib.util
spec = importlib.util.find_spec(package)
if spec is not None and spec.loader is not None and hasattr(spec.loader, 'get_data'):
return spec.loader.get_data(resource_name)
except Exception:
pass
try:
import pkgutil
data = pkgutil.get_data(package, resource_name)
if data is None:
raise FileNotFoundError(f"Resource {resource_name} not found in package {package}.")
return data
except ImportError as e:
raise ImportError("Failed to load the resource using any available method.") from e

View File

@@ -8,12 +8,13 @@ def read(f_name):
setup( setup(
name='nwg-panel', name='nwg-panel',
version='0.9.34', version='0.9.41',
description='GTK3-based panel for sway and Hyprland Wayland compositors', description='GTK3-based panel for sway and Hyprland Wayland compositors',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
package_data={ package_data={
"": ["config/*", "icons_dark/*", "icons_light/*", "icons_color/*", "langs/*", "executors/*", "local/*"] "": ["config/*", "icons_dark/*", "icons_light/*", "icons_color/*", "langs/*", "executors/*", "local/*",
"modules/sni_system_tray/org.kde.StatusNotifierItem.xml"]
}, },
url='https://github.com/nwg-piotr/nwg-panel', url='https://github.com/nwg-piotr/nwg-panel',
license='MIT', license='MIT',