2024-01-08 20:50:29 +01:00

571 lines
20 KiB
Python
Executable File

#!/usr/bin/env python
import sys
import os
# POLYWINS.py
# SETTINGS
name_style = None # options: upper, lower, None
separator = " " # What to separate workspaces with
windowlist_prefix = " " # prefix before listing windows, default is " "
show = "window_classname"
forbidden_classes = "Polybar Conky Gmrun Pavucontrol".casefold().split(" ")
hide_unpopulated_desktops = False
iconize = True
hide_name = True # Controls whether to hide window names when an icon is present
char_limit = 10
max_windows = 10
add_spaces = "true"
resize_increment = 16
resize_offset = resize_increment / 2
use_pywal = False
override_names = [
" %{F#b4befe} %{F-}",
" %{F#b4befe} %{F-}",
" %{F#b4befe} %{F-}"
] # Either a list containing the focused, populated and unfocused versions of workspace name, or False
underline = True
highlight_active_wps = True
if len(sys.argv) <= 2:
try:
if use_pywal is not True:
raise TypeError
with open(os.path.expanduser("~/.cache/wal/colors")) as colors:
colors = tuple(map(lambda x: x[:-1], colors.readlines()))
active_text_color = colors[1]
active_underline = colors[1] if underline is not False else None
inactive_text_color = colors[7]
inactive_underline = colors[7] if underline is not False else None
except (OSError, IndexError, TypeError):
active_text_color = "#cdd6f4"
active_underline = "#EB6572" if underline is not False else None
inactive_text_color = "#9399b2"
inactive_underline = "#C0CAF5" if underline is not False else None
# WINDOW LIST SETUP
active_left = "%{F" + active_text_color + "}"
active_right = "%{F-} "
inactive_left = "%{F" + inactive_text_color + "}"
inactive_right = "%{F-}"
# separator = "%{F" + inactive_text_color + "}" + separator + "%{F-}"
wps_active_left = (
"%{F" + (inactive_text_color if highlight_active_wps is False else active_text_color) + "}"
)
wps_active_right = "%{F-}"
wps_inactive_left = "%{F" + inactive_text_color + "}"
wps_inactive_right = "%{F-}"
if active_underline is not None:
active_left = active_left + "%{+u}%{u" + active_underline + "}"
active_right = "%{-u}" + active_right
wps_active_left = "%{F" + inactive_text_color + "}%{+u}%{u" + inactive_underline + "}"
wps_active_right = "%{-u}%{u}%{F-}"
if inactive_underline is not None:
inactive_left += "%{+u}%{u" + inactive_underline + "}"
inactive_right = "%{-u}" + inactive_right
on_click = " ".join(sys.argv[:2])
monitor = sys.argv[1]
printf = sys.stdout.write
superscript = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹")
class_icons = {
"alacritty": "",
"atom": "",
"vscode": "",
"code": "",
"code-oss": "",
"neovim": "",
"nvim": "",
"nvim-qt": "",
"neovide": "",
"banshee": "",
"blender": "",
"chromium": "",
"google-chrome": "",
"cura": "",
"darktable": "",
"discord": "",
"eclipse": "",
"emacs": "",
"eog": "",
"evince": "",
"evolution": "",
"feh": "",
"file-roller": "",
"filezilla": "",
"firefox": "",
"firefox-esr": "",
"firefoxdev": "",
"navigator": "",
"gimp": "",
"gimp-2.8": "",
"gnome-control-center": "",
"gnome-terminal-server": "",
"prusa-slicer": "",
"gpick": "",
"imv": "",
"insomnia": "",
"java": "",
"jetbrains-idea": "",
"jetbrains-studio": "",
"keepassxc": "",
"keybase": "",
"kicad": "",
"kitty": "",
"st-256color": "",
"st": "",
"libreoffice": "",
"lua5.1": "",
"mpv": "",
"mupdf": "",
"mysql-workbench-bin": "",
"nautilus": "",
"nemo": "",
"openscad": "",
"pavucontrol": "",
"postman": "",
"rhythmbox": "",
"robo3t": "",
"signal": "",
"slack": "",
"slic3r.pl": "",
"spotify": "",
"steam": "",
"subl": "",
"subl3": "",
"sublime_text": "",
"thunar": "",
"thunderbird": "",
"totem": "",
"urxvt": "",
"xfce4-terminal": "",
"xournal": "",
"yelp": "",
"zenity": "",
"zoom": "",
"telegram": "",
"kotatogram": "",
"lunarclient": "",
"viber": "",
}
def get_active_wid():
return os.popen("bspc query -N -n .focused").read()[:-1]
def ensure_len(ID, length=10):
while len(ID) < length - 1:
ID = "0x0" + ID[2:]
return ID
if hide_name:
def to_icon(name):
try:
return class_icons[name.lower()] + " "
except KeyError:
if not name.casefold().startswith("lunar client"):
return name[:char_limit]
else:
return ""
else:
def to_icon(name):
try:
return class_icons[name.lower()] + " " + name[:char_limit]
except KeyError:
if not name.casefold().startswith("lunar client"):
return name[:char_limit]
else:
return ""
def wid_to_name(wid, cache={}):
if not isinstance(wid, list):
if show == "window_class":
out = os.popen(f"xprop -id {wid} WM_CLASS 2> /dev/null").read().split('"')
if show == "window_classname":
out = os.popen(f"xprop -id {wid} WM_CLASS 2> /dev/null").read().split('"')[:-1][-1]
if show == "window_title":
out = os.popen(f"xprop -id {wid} _NET_WM_NAME 2> /dev/null").read().split('"')[1]
if name_style == "upper":
out = out.upper()
elif name_style == "lower":
out = out.lower()
return out
else:
out = {}
cached = cache.keys()
for id in wid:
if id not in cached:
if show == "window_class":
name = os.popen(f"xprop -id {id} WM_CLASS 2> /dev/null").read().split('"')[1]
if show == "window_classname":
name = os.popen(f"xprop -id {id} WM_CLASS 2> /dev/null").read().split('"')[-2]
if show == "window_title":
name = (
os.popen(f"xprop -id {id} _NET_WM_NAME 2> /dev/null").read().split('"')[1]
)
if name.lower() not in forbidden_classes:
if iconize:
name = to_icon(name)
try:
out[name].append(id)
except KeyError:
out[name] = [id]
cache[id] = name
else:
try:
out[cache[id]].append(id)
except KeyError:
out[cache[id]] = [id]
return out, cache
def generate(workspaces, focused_desk, order):
global classcache
focused = os.popen(f"bspc query -N -m {mon_id} -n .focused").read()[
:-1
] # ID of the currently focused window
for workspace_id in order:
if (
len(workspaces[workspace_id][0]) < hide_unpopulated_desktops
and workspace_id != focused_desk
):
continue
printf(
"%{A1:"
+ on_click
+ " switch_workspace "
+ workspace_id
+ ":}"
+ "%{A2:"
+ on_click
+ " swap_workspace "
+ workspace_id
+ ":}"
+ wps_inactive_left
+ (workspaces[workspace_id][1] if override_names is False else (override_names[1] if len(workspaces[workspace_id][0]) else override_names[0]))
if workspace_id != focused_desk
else wps_active_left
+ (workspaces[workspace_id][1] if override_names is False else override_names[2])
)
if len(workspaces[workspace_id][0]) == 0:
#printf(separator + wps_active_right + "%{A}%{A}")
printf( wps_active_right + "%{A}%{A}")
else:
printf(windowlist_prefix + "%{A}%{A}")
windows, classcache = wid_to_name(workspaces[workspace_id][0], classcache)
win_length = len(windows.keys())
for i, win_class in enumerate(windows.keys()):
if i == max_windows:
break
wid = " ".join(windows[win_class])
printf(
"%{A1:"
+ on_click
+ " focus "
+ wid
+ ":}%{A2:"
+ on_click
+ " close "
+ wid
+ ":}%{A3:"
+ on_click
+ " slop_resize "
+ wid
+ ":}%{A4:"
+ on_click
+ " increment_size "
+ wid
+ ":}%{A5:"
+ on_click
+ " decrement_size "
+ wid
+ ":}"
)
printf(active_left if focused in windows[win_class] else inactive_left)
# printf(separator if i == 0 else "")
printf(
win_class.upper()
if name_style == "upper"
else (win_class.lower() if name_style == "lower" else win_class)
)
printf(
#(separator if i < (win_length - 1) else "")
""
if len(windows[win_class]) <= 1
else str(len(windows[win_class])).translate(superscript)
)
printf(active_right + "%{A}%{A}%{A}%{A}%{A}")
printf(wps_active_right)
if len(windows.keys()) > max_windows:
printf(f"+{len(windows.keys())-max_windows}")
def main():
if len(sys.argv) <= 2:
focused = ""
command = os.popen(
"bspc subscribe desktop_focus desktop_add desktop_rename desktop_remove desktop_swap node_add node_remove node_swap node_transfer node_focus"
)
global mon_id
mon_id = os.popen(f"bspc query -M -m '{monitor}'").read()[:-1]
workspace_order = []
workspaces = {} # workspace ID and name pairs
for workspace in [
workspace[:-1] for workspace in os.popen(f"bspc query -D -m '{mon_id}'").readlines()
]:
workspace_order.append(workspace)
workspaces[workspace] = (
[
window[:-1]
for window in os.popen(f"bspc query -N -d {workspace} -n .window").readlines()
],
os.popen(f"bspc query -D -d {workspace} --names").read()[:-1],
)
focused_workspace = os.popen(f"bspc query -D -m {mon_id} -d .focused").read()[
:-1
] # ID of the currently focused workspace
global classcache
classcache = {}
try:
generate(
workspaces,
focused_workspace,
workspace_order,
)
printf("\n")
sys.stdout.flush()
except:
workspace_order = []
workspaces = {} # workspace ID and name pairs
for workspace in [
workspace[:-1] for workspace in os.popen(f"bspc query -D -m '{mon_id}'").readlines()
]:
workspace_order.append(workspace)
workspaces[workspace] = (
[
window[:-1]
for window in os.popen(
f"bspc query -N -d {workspace} -n .window"
).readlines()
],
os.popen(f"bspc query -D -d {workspace} --names").read()[:-1],
)
focused_workspace = os.popen(f"bspc query -D -m {mon_id} -d .focused").read()[
:-1
] # ID of the currently focused workspace
while True:
update = command.readline()[:-1]
if (
mon_id in update
or update.startswith("node_remove")
or update.startswith("node_focus")
or update.startswith("desktop_focus")
):
if update.startswith("node"):
update = update[5:].split(" ")
if update[0] == "focus":
# global focused
# focused = update[3]
pass
elif update[0] == "add":
try:
workspaces[update[2]][0].append(update[4])
except KeyError:
pass
elif update[0] == "remove":
classcache.pop(update[3], None)
try:
workspaces[update[2]][0].remove(update[3])
except KeyError:
continue
elif update[0] == "swap":
if update[1] == mon_id:
workspaces[update[2]][0].remove(update[3])
workspaces[update[2]][0].append(update[6])
if update[4] == mon_id:
workspaces[update[5]][0].remove(update[6])
workspaces[update[5]][0].append(update[3])
else:
if update[1] == mon_id:
workspaces[update[2]][0].remove(update[3])
if update[4] == mon_id:
workspaces[update[5]][0].append(update[3])
else:
update = update[8:].split(" ")
if update[0] == "focus":
focused_workspace = update[-1]
elif update[0] == "add":
workspaces[update[2]] = ([], update[-1])
elif update[0] == "rename":
workspaces[update[2]] = (
[
window[:-1]
for window in os.popen(f"bspc query -N -d {update[2]}").readlines()
],
update[-1],
)
elif update[0] == "remove":
workspaces.pop(update[-1], None)
else:
if update[1] == mon_id and update[3] == mon_id:
index = workspace_order.index(update[4])
workspace_order[workspace_order.index(update[2])] = update[4]
workspace_order[index] = update[2]
else:
workspace_order = []
workspaces = {} # workspace ID and name pairs
for workspace in [
workspace[:-1]
for workspace in os.popen(
f"bspc query -D -m '{mon_id}'"
).readlines()
]:
workspace_order.append(workspace)
workspaces[workspace] = (
[
window[:-1]
for window in os.popen(
f"bspc query -N -d {workspace} -n .window"
).readlines()
],
os.popen(f"bspc query -D -d {workspace} --names").read()[:-1],
)
focused_workspace = os.popen(
f"bspc query -D -m {mon_id} -d .focused"
).read()[
:-1
] # ID of the currently focused workspace
try:
generate(
workspaces,
focused_workspace,
workspace_order,
)
printf("\n")
sys.stdout.flush()
except:
workspace_order = []
workspaces = {} # workspace ID and name pairs
for workspace in [
workspace[:-1]
for workspace in os.popen(f"bspc query -D -m '{mon_id}'").readlines()
]:
workspace_order.append(workspace)
workspaces[workspace] = (
[
window[:-1]
for window in os.popen(
f"bspc query -N -d {workspace} -n .window"
).readlines()
],
os.popen(f"bspc query -D -d {workspace} --names").read()[:-1],
)
focused_workspace = os.popen(
f"bspc query -D -m {mon_id} -d .focused"
).read()[
:-1
] # ID of the currently focused workspace
command = os.popen(
"bspc subscribe desktop_focus desktop_add desktop_rename desktop_remove desktop_swap node_add node_remove node_swap node_transfer node_focus"
)
else:
exec(sys.argv[2] + "(" + "'" + " ".join(sys.argv[3:]) + "')")
def slop_resize(window):
window = sorted(window.split(" "))
try:
window = window[window.index(get_active_wid())]
except ValueError:
window = window[0]
os.system(
f"""bash -c 'bspc node "{window}" -g hidden=off &
bspc node "{window}" -g hidden=off &
xdo hide "{window}" &
pos="$(slop -b 2 -c 0.75,0.8,0.96.1 -f 0,%x,%y,%w,%h)"
xdo show "{window}"
bspc node "{window}" -t floating
bspc node "{window}" -d focused
wmctrl -ir "{window}" -e "$pos"
xdo activate "{window}"'"""
)
def close(window):
window = sorted(window.split(" "))
try:
window = window[window.index(get_active_wid())]
except ValueError:
window = window[0]
os.system("xdo close " + window)
def focus(window):
window = sorted(window.split(" "))
try:
window = window[(window.index(get_active_wid()) + 1) % len(window)]
os.system("bspc node " + window + " -g hidden=off")
# os.system("bspc node -s " + window)
os.system("bspc node -f " + window)
except ValueError:
window = window[0]
os.system("bspc node " + window + " -g hidden=off")
os.system("bspc node -f " + window)
os.system("wmctrl -ia " + window)
def increment_size(window):
window = sorted(window.split(" "))
try:
window = window[window.index(get_active_wid())]
except ValueError:
window = window[0]
os.system(f"xdo move -x -{resize_offset} -y -{resize_offset} {window}")
os.system(f"xdo resize -w +{resize_increment} -h +{resize_increment} {window}")
def decrement_size(window):
window = sorted(window.split(" "))
try:
window = window[window.index(get_active_wid())]
except ValueError:
window = window[0]
os.system(f"bspc node -t floating {window}")
os.system(f"xdo move -x +{resize_offset} -y +{resize_offset} {window}")
os.system(f"xdo resize -w -{resize_increment} -h -{resize_increment} {window}")
def switch_workspace(workspace):
os.system(f"bspc desktop -f {workspace}")
def swap_workspace(workspace):
os.system(f"bspc desktop -s {workspace}")
if __name__ == "__main__":
main()