import std/[ os, osproc, strformat, strutils, streams, tables, net, sugar, times, unicode ] import ./[lib, swww] import yaml, jsony type # Remove effects? HyprlandDefect* = Defect Workspace = object name*: string id*: int Client = object class*: string workspace*: Workspace ActiveWorkspace = object name*: string id*: int Monitor = object activeWorkspace*: ActiveWorkspace name: string id*: int transform: int proc isRotated(m: Monitor): bool = # 1 -> 90 degrees m.transform == 1 proc hyprSocketPath(): string = let runtimeDir = getEnv("XDG_RUNTIME_DIR") let instancSig = getEnv("HYPRLAND_INSTANCE_SIGNATURE") result = runtimeDir / "hypr" / instancSig let hyprSocket = hyprSocketPath() / ".socket.sock" hyprSocket2 = hyprSocketPath() / ".socket2.sock" # TODO: revamp? proc getData[T](msg: string, to: typedesc[T]): T = let socket = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP) try: socket.connectUnix(hyprSocket) except OSError: raise newException( HyprlandDefect, "Could not connect to Hyprland IPC UNIX path; is Hyprland running?" ) socket.send msg var recvData: string while true: let response = socket.recv(4096) if response == "": break recvData.add response socket.close() # is this necessary? result = recvData.fromJson(T) proc getMonitors*(): seq[Monitor] = result = getData("[-j]/monitors", seq[Monitor]) proc getClients(): seq[Client] = result = getData("[-j]/clients", seq[Client]) type EwwWorkspace = object icon*, class*: string id*: int proc add(ws: var EwwWorkspace, client: Client) = let clientIcon = config.pickIcon(client.class) if ws.icon == config.`no-client`: ws.icon = clientIcon else: ws.icon.add clientIcon func openWorkspaces(monitors: seq[Monitor]): seq[int] = for m in monitors: result.add m.activeWorkspace.id func setActive(workspaces: seq[EwwWorkspace], m: Monitor): seq[EwwWorkspace] = let id = m.activeWorkspace.id result = workspaces result[id - 1].class.add " ws-button-active-" & $(id) proc writeEwwClasses*() = let monitors = getMonitors() clients = getClients() var workspaces= collect: for i in 0..<9: EwwWorkspace( icon: config.`no-client`, class: fmt"ws-button-{i+1}", id: i+1 ) for client in clients: let id = client.workspace.id - 1 workspaces[id].add client for id in openWorkspaces(monitors): workspaces[id - 1].class.add " ws-button-open" # truncate icons at 3 windows for ws in workspaces.mitems: if ws.icon.runeLen > 3: ws.icon = ($(ws.icon.toRunes()[0..2]) & "+") var ewwClasses = collect: for m in monitors: workspaces.setActive m stdout.write (ewwClasses.toJson() & "\n") flushFile stdout const redrawEvents = [ "workspacev2", # emitted on workspace change. Is emitted ONLY when a user requests a workspace change, and is not emitted on mouse movements (see activemon) WORKSPACEID,WORKSPACENAME "focusedmon", # emitted on the active monitor being changed. MONNAME,WORKSPACENAME "activewindowv2", # emitted on the active window being changed. WINDOWADDRESS "fullscreen", # emitted when a fullscreen status of a window changes. 0/1 (exit fullscreen / enter fullscreen) "monitorremoved", # emitted when a monitor is removed (disconnected) MONITORNAME "monitoraddedv2", # emitted when a monitor is added (connected) MONITORID,MONITORNAME,MONITORDESCRIPTION "createworkspacev2", # emitted when a workspace is created WORKSPACEID,WORKSPACENAME "destroyworkspacev", # emitted when a workspace is destroyed WORKSPACEID,WORKSPACENAME "moveworkspacev2", # emitted when a workspace is moved to a different monitor WORKSPACEID,WORKSPACENAME,MONNAME "openwindow", # emitted when a window is opened WINDOWADDRESS,WORKSPACENAME,WINDOWCLASS,WINDOWTITLE "closewindow", # emitted when a window is closed WINDOWADDRESS "movewindowv2", # emitted when a window is moved to a workspace WINDOWADDRESS,WORKSPACEID,WORKSPACENAME ] const monitorChangeEvents = [ "monitorremoved", # emitted when a monitor is removed (disconnected) MONITORNAME "monitoraddedv2", # emitted when a monitor is added (connected) MONITORID,MONITORNAME,MONITORDESCRIPTION ] proc parseEvent(line: string): (string, string) = let s = line.split(">>", 1) result = (s[0], s[1]) proc streamEwwClasses*() = let socket = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP) try: socket.connectUnix(hyprSocket2) except OSError: raise newException( HyprlandDefect, "Could not connect to Hyprland IPC UNIX path; is Hyprland running?" ) writeEwwClasses() while true: var line: string socket.readLine line let (e, _) = parseEvent(line) if e in redrawEvents: writeEwwClasses() proc handleHyprEvent(e: string) = if e in monitorChangeEvents: notify("detected monitor change") oneShotSwww() for i in 0.. config.timeout: oneShotSwww() last = current proc watchHyprland*() = let socket = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP) try: socket.connectUnix(hyprSocket2) except OSError: raise newException( HyprlandDefect, "Could not connect to Hyprland IPC UNIX path; is Hyprland running?" ) oneShotSwww() var lastSwww= now() while true: var line: string socket.readLine line let (e, _) = parseEvent(line) handleHyprEvent e maybeTriggerSwww lastSwww