2024-10-21 18:48:36 -05:00
import std / [
os , osproc , strformat , strutils ,
2025-01-14 16:27:52 -06:00
streams , tables , net , sugar , times , unicode
2024-10-21 18:48:36 -05:00
]
2024-10-21 00:12:17 -05:00
import . / [ lib , swww ]
2024-10-20 07:46:02 -05:00
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
2024-10-20 09:36:46 -05:00
name : string
2024-10-20 07:46:02 -05:00
id * : int
2024-10-25 09:43:03 -05:00
transform : int
2024-10-20 07:46:02 -05:00
2024-10-25 09:43:03 -05:00
proc isRotated ( m : Monitor ) : bool =
# 1 -> 90 degrees
m . transform = = 1
2024-10-20 07:46:02 -05:00
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 "
2025-01-14 16:27:52 -06:00
# truncate icons at 3 windows
2025-01-14 16:19:38 -06:00
for ws in workspaces . mitems :
2025-01-14 16:27:52 -06:00
if ws . icon . runeLen > 3 :
ws . icon =
( $ ( ws . icon . toRunes ( ) [ 0 .. 2 ] ) & " + " )
2025-01-14 16:19:38 -06:00
2024-10-20 07:46:02 -05:00
var ewwClasses =
collect :
for m in monitors :
workspaces . setActive m
stdout . write ( ewwClasses . toJson ( ) & " \n " )
flushFile stdout
2024-10-20 15:45:05 -05:00
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
]
2024-10-21 18:48:36 -05:00
const monitorChangeEvents = [
" monitorremoved " , # emitted when a monitor is removed (disconnected) MONITORNAME
" monitoraddedv2 " , # emitted when a monitor is added (connected) MONITORID,MONITORNAME,MONITORDESCRIPTION
]
2024-10-20 07:46:02 -05:00
2024-10-21 18:48:36 -05:00
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 ( )
2024-10-20 07:46:02 -05:00
2024-10-21 18:48:36 -05:00
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 ( )
2024-10-21 19:16:09 -05:00
if ( current - last ) . inSeconds > config . timeout :
2024-10-21 18:48:36 -05:00
oneShotSwww ( )
last = current
2024-10-20 07:46:02 -05:00
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? "
)
2024-10-25 09:43:03 -05:00
oneShotSwww ( )
2024-10-21 18:48:36 -05:00
var lastSwww = now ( )
2024-10-20 07:46:02 -05:00
while true :
var line : string
socket . readLine line
2024-10-21 18:48:36 -05:00
let ( e , _ ) = parseEvent ( line )
handleHyprEvent e
maybeTriggerSwww lastSwww