Files
nwg-panel/nwg_panel/modules/sway_workspaces.py
2024-07-16 02:52:10 +02:00

311 lines
12 KiB
Python

#!/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)