190 lines
5.6 KiB
Nim
190 lines
5.6 KiB
Nim
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..<getMonitors().len:
|
|
let (output, code) = execCmdEx("eww open bar" & $i)
|
|
if code != 0:
|
|
notify("eww failed:\n" & output)
|
|
|
|
proc maybeTriggerSwww(last: var DateTime) =
|
|
let current = now()
|
|
if (current - last).inSeconds > 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
|