571 lines
20 KiB
Python
Executable File
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() |