add nim implementation

This commit is contained in:
Daylin Morgan 2024-10-20 07:46:02 -05:00
parent 26a3212dad
commit 1561acdb5f
Signed by: daylin
GPG key ID: 950D13E9719334AD
8 changed files with 202 additions and 8 deletions

View file

@ -48,7 +48,7 @@
pname = "hyprman"; pname = "hyprman";
version = "${self.shortRev or "dirty"}"; version = "${self.shortRev or "dirty"}";
src = cleanSource ./.; src = cleanSource ./.;
nimbleDepsHash = "sha256-EQ3gdxIluE+e3Qzqk9v0Q3mBa6nOKR1C1EWyjwSmQSQ="; nimbleDepsHash = "sha256-72FYXiYIgEDX2j/bBADGvwX6+kd+7py0RHTz2WeyXO8=";
}; };
}); });
formatter = forAllSystems (pkgs: pkgs.nixfmt-rfc-style); formatter = forAllSystems (pkgs: pkgs.nixfmt-rfc-style);

View file

@ -13,4 +13,5 @@ bin = @["hyprman"]
requires "nim >= 2.0.8" requires "nim >= 2.0.8"
requires "cligen" requires "cligen"
requires "yaml" requires "yaml"
requires "jsony"
requires "https://github.com/daylinmorgan/hwylterm#HEAD" requires "https://github.com/daylinmorgan/hwylterm#HEAD"

View file

@ -21,6 +21,16 @@
"sha1": "30f8f61787c36b63d484f3d5e149995fad16c63c" "sha1": "30f8f61787c36b63d484f3d5e149995fad16c63c"
} }
}, },
"jsony": {
"version": "1.1.5",
"vcsRevision": "ea811bec7fa50f5abd3088ba94cda74285e93f18",
"url": "https://github.com/treeform/jsony",
"downloadMethod": "git",
"dependencies": [],
"checksums": {
"sha1": "6aeb83e7481ca8686396a568096054bc668294df"
}
},
"yaml": { "yaml": {
"version": "2.1.1", "version": "2.1.1",
"vcsRevision": "48a90e36e82bd97457dae87e86efe423dcd3bb40", "vcsRevision": "48a90e36e82bd97457dae87e86efe423dcd3bb40",

135
src/hyprland.nim Normal file
View file

@ -0,0 +1,135 @@
import std/[os, strformat, strutils, streams, tables, net, sugar]
import ./lib
export tables
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
id*: int
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"
var ewwClasses =
collect:
for m in monitors:
workspaces.setActive m
stdout.write (ewwClasses.toJson() & "\n")
flushFile stdout
proc handleHyprEvent(event: string) =
let
s = event.split(">>", 1)
event = s[0]
# use enum?
case event:
of "monitoraddedv2":
# TODO: open as many bars as necessary depending on num monitors
# is it ok to just call open bar again?
notify("monitor added")
of "openwindow", "workspacev2":
writeEwwClasses()
else: discard
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?"
)
writeEwwClasses()
while true:
var line: string
socket.readLine line
handleHyprEvent line

View file

@ -1,10 +1,24 @@
## hyprman, the hyprland companion ## hyprman, the hyprland companion
import ./mako import std/[osproc, strformat]
import hwylterm/cligen, cligen
import ./[mako,hyprland, lib]# state]
hwylCli(clCfg)
proc start() =
## launch eww
notify("starting eww")
for i in 0..<getMonitors().len:
let code = execCmd fmt"eww open bar{i}"
if code != 0:
notify(fmt"failed to open eww bar{i}")
proc watch() =
## watch hyprland events for eww class changes
watchHyprland()
when isMainModule: when isMainModule:
import hwylterm/cligen, cligen
hwylCli(clCfg)
const const
config = //{"config": "path/to/config"} config = //{"config": "path/to/config"}
makoHelp = config // { makoHelp = config // {
@ -13,5 +27,7 @@ when isMainModule:
"reverse": "swap notification order" "reverse": "swap notification order"
} }
dispatchMulti( dispatchMulti(
[makoCmd, cmdName = "mako", usage = clCfg.use, help = makoHelp] [makoCmd, cmdName = "mako", usage = clCfg.use, help = makoHelp],
[start, usage = clCfg.use],
[watch, usage = clCfg.use]
) )

31
src/lib.nim Normal file
View file

@ -0,0 +1,31 @@
import std/[
os, osproc, streams, strformat, strutils, tables
]
export tables
import yaml
proc notify*(message: string) =
discard execCmd fmt"notify-send --app-name=hyprman --transient '{message}'"
type
Icons = Table[string, string]
Config = object # config vs icons?
classes*: Icons
`no-client`*: string
`default-icon`*: string
proc loadConfig*(): Config =
let configPath = getConfigDir() / "hyprman" / "config.yml"
if fileExists(configPath):
var s = newFileStream(configPath)
load(s, result)
func pickIcon*(
c: Config,
class: string
): string =
for k, v in c.classes:
if class in k:
result = v
let config* = loadConfig()

View file

@ -1,5 +1,6 @@
import std/[algorithm, strutils, json, osproc, wordwrap, terminal] import std/[algorithm, strutils, json, osproc, wordwrap, terminal]
import hwylterm import hwylterm
type type
MakoNotificationData = object MakoNotificationData = object
`type`, data: string `type`, data: string
@ -16,13 +17,12 @@ type
func toNotification(mn: MakoNotification): Notification = func toNotification(mn: MakoNotification): Notification =
result.appName = mn.`app-name`.data result.appName = mn.`app-name`.data
result.summary = mn.summary.data result.summary = mn.summary.data
result.body = mn.body.data result.body = mn.body.data
func toHistory(mh: MakoHistory): History = func toHistory(mh: MakoHistory): History =
for mn in mh.data[0]: for mn in mh.data[0]:
result.notifications.add mn.toNotification() result.notifications.add mn.toNotification()
func filter(h: History, reverse: bool, count: int): History = func filter(h: History, reverse: bool, count: int): History =
result.notifications = h.notifications result.notifications = h.notifications
if reverse: if reverse:
@ -36,7 +36,6 @@ proc getMakoHistory(): MakoHistory =
if errCode != 0: quit output, errCode if errCode != 0: quit output, errCode
result = parseJson(output).to(MakoHistory) result = parseJson(output).to(MakoHistory)
proc bb(n: Notification): BbString = proc bb(n: Notification): BbString =
template border(style: string): untyped = "[" & style & "]" & "┃ [/]" template border(style: string): untyped = "[" & style & "]" & "┃ [/]"
var raw: string var raw: string

View file

@ -1,6 +1,8 @@
# hyprman todo's # hyprman todo's
- [ ] include in class the 'other' active monitor? - [ ] include in class the 'other' active monitor?
- [ ] switch to usu once parser is stable again
- [ ] make swww powered wallpaper cycler
<!-- generated with <3 by daylinmorgan/todo --> <!-- generated with <3 by daylinmorgan/todo -->