Files
2026-07-11 16:41:17 +04:00

209 lines
7.9 KiB
Python
Executable File

#!/usr/bin/env python3
import asyncio
import json
import os
import sys
class UnifiedWorkspaceManager:
def __init__(self):
self.last_sent_json = ""
self.urgent_window_addresses = set()
self.script_directory = os.path.dirname(os.path.realpath(__file__))
self.clients_cache = {}
self.monitors_cache = {}
self.load_icon_configuration()
def load_icon_configuration(self):
config_path = os.path.join(self.script_directory, "workspaces_map.json")
try:
with open(config_path, 'r') as file:
self.icon_map = json.load(file)
except:
self.icon_map = {"default": "", "no-icon": ""}
async def monitors_refresh(self):
try:
proc = await asyncio.create_subprocess_exec(
"hyprctl", "-j", "monitors",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.DEVNULL
)
stdout, _ = await proc.communicate()
if proc.returncode == 0:
monitors = json.loads(stdout.decode())
self.monitors_cache = {
m['name']: {
"id": m['id'],
"active_workspace_id": m['activeWorkspace']['id'],
"focused": m.get('focused', False)
} for m in monitors
}
except:
self.monitors_cache = {}
async def clients_refresh(self):
try:
proc = await asyncio.create_subprocess_exec(
"hyprctl", "-j", "clients",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.DEVNULL
)
stdout, _ = await proc.communicate()
if proc.returncode == 0:
clients = json.loads(stdout.decode())
self.clients_cache = {
c['address'].strip().replace('0x', ''): {
"class": c['class'],
"workspace_id": c['workspace']['id'],
"monitor_id": c['monitor']
} for c in clients
}
except:
self.clients_cache = {}
def get_current_state_json(self):
try:
output = {}
for mon_name, mon_data in self.monitors_cache.items():
m_id = mon_data["id"]
active_id = mon_data["active_workspace_id"]
focused = mon_data["focused"]
workspaces = {
str(i): {"exists": False, "active": False, "focused": False, "urgent": False, "icons": []}
for i in range(1, 11)
}
for addr, client in self.clients_cache.items():
if client['monitor_id'] == m_id:
ws_id = str(client['workspace_id'])
if ws_id in workspaces:
slot = workspaces[ws_id]
slot["exists"] = True
if addr in self.urgent_window_addresses:
slot["urgent"] = True
icon = self.icon_map.get(client['class'], self.icon_map.get("default", ""))
if icon not in slot["icons"]:
slot["icons"].append(icon)
curr_ws = str(active_id)
if curr_ws in workspaces:
workspaces[curr_ws].update({"active": True, "focused": focused, "exists": True})
for ws in workspaces.values():
if ws["exists"] and not ws["icons"]:
ws["icons"] = [self.icon_map.get("no-icon", "")]
output[mon_name.replace("-", "_")] = workspaces
return json.dumps(output)
except:
return "{}"
def push_update(self):
current = self.get_current_state_json()
if current != self.last_sent_json:
self.last_sent_json = current
sys.stdout.write(current + '\n')
sys.stdout.flush()
async def update_client(self, window_address):
try:
clean_addr = window_address.replace('0x', '')
search_addr = f"0x{clean_addr}"
proc = await asyncio.create_subprocess_exec(
"hyprctl", "-j", "clients",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.DEVNULL
)
stdout, _ = await proc.communicate()
if proc.returncode == 0:
clients = json.loads(stdout.decode())
target = next((c for c in clients if c['address'] == search_addr), None)
if target:
self.clients_cache[clean_addr] = {
"class": target.get('class', 'unknown'),
"workspace_id": target['workspace']['id'],
"monitor_id": target['monitor']
}
return
self.clients_cache.pop(clean_addr, None)
except:
await self.clients_refresh()
async def run(self):
await self.clients_refresh()
await self.monitors_refresh()
self.push_update()
signature = os.environ.get('HYPRLAND_INSTANCE_SIGNATURE')
socket_path = f"/run/user/{os.getuid()}/hypr/{signature}/.socket2.sock"
while True:
try:
reader, writer = await asyncio.open_unix_connection(socket_path)
while True:
line_bytes = await reader.readline()
if not line_bytes:
break
line = line_bytes.decode('utf-8', errors='ignore').strip()
if ">>" not in line:
continue
event, args = line.split('>>')
trigger = False
if event in ["openwindow", "movewindow"]:
addr = args.split(',')[0].replace('0x', '')
await self.update_client(addr)
trigger = True
elif event == "closewindow":
addr = args.replace('0x', '')
self.clients_cache.pop(addr, None)
self.urgent_window_addresses.discard(addr)
trigger = True
elif event in ["workspace", "workspacev2", "monitoradded", "monitorremoved"]:
await self.monitors_refresh()
trigger = True
elif event in ["focusedmon", "focusedmonv2"]:
mon_name = args.split(',')[0]
for name in self.monitors_cache:
self.monitors_cache[name]["focused"] = (name == mon_name)
trigger = True
elif event == "urgent":
self.urgent_window_addresses.add(args.replace('0x', ''))
trigger = True
elif event in ["activewindow", "activewindowv2"]:
addr = args.split(',')[0].replace('0x', '')
if addr in self.urgent_window_addresses:
self.urgent_window_addresses.remove(addr)
trigger = True
if trigger:
self.push_update()
except (asyncio.CancelledError, KeyboardInterrupt):
break
except Exception as e:
await asyncio.sleep(2)
if __name__ == "__main__":
try:
asyncio.run(UnifiedWorkspaceManager().run())
except KeyboardInterrupt:
sys.exit(0)