Compare commits
15 commits
Author | SHA1 | Date | |
---|---|---|---|
8a866a8082 | |||
952d79d368 | |||
7a06a2a28c | |||
f73bbbd0d3 | |||
f6c5fc3c48 | |||
2385e69aa5 | |||
45f63e3075 | |||
c93f726dd6 | |||
8fa101f0e2 | |||
f5b54f962f | |||
795e6941f8 | |||
33e47efe43 | |||
1561acdb5f | |||
26a3212dad | |||
babe900107 |
24 changed files with 504 additions and 617 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -29,3 +29,6 @@ go.work
|
||||||
# nix
|
# nix
|
||||||
result
|
result
|
||||||
hyprman
|
hyprman
|
||||||
|
nimbledeps
|
||||||
|
nimble.develop
|
||||||
|
nimble.paths
|
||||||
|
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# hyprman
|
17
cmd/eww.go
17
cmd/eww.go
|
@ -1,17 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ewwCmd = &cobra.Command{
|
|
||||||
Use: "eww",
|
|
||||||
Short: "eww integration",
|
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
hm.LoadConfig(configPath)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(ewwCmd)
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var startCmd = &cobra.Command{
|
|
||||||
Use: "start",
|
|
||||||
Short: "Launch eww",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
hm.LaunchEww()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ewwCmd.AddCommand(startCmd)
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var watchCmd = &cobra.Command{
|
|
||||||
Use: "watch",
|
|
||||||
Short: "Watch hyprland events and propagate to eww",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
hm.Watch()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ewwCmd.AddCommand(watchCmd)
|
|
||||||
}
|
|
26
cmd/mako.go
26
cmd/mako.go
|
@ -1,26 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
hyprman "git.dayl.in/daylin/hyprman/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
var makoCmd = &cobra.Command{
|
|
||||||
Use: "mako",
|
|
||||||
Short: "mako integration",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
hyprman.ListNotifications(number, json)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
number int
|
|
||||||
json bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(makoCmd)
|
|
||||||
makoCmd.Flags().IntVarP(&number, "number", "n", 10, "number of notifications")
|
|
||||||
makoCmd.Flags().BoolVar(&json, "json", false, "output data as json")
|
|
||||||
}
|
|
42
cmd/root.go
42
cmd/root.go
|
@ -1,42 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
cc "github.com/ivanpirog/coloredcobra"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
hyprman "git.dayl.in/daylin/hyprman/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Execute() {
|
|
||||||
cc.Init(&cc.Config{
|
|
||||||
RootCmd: rootCmd,
|
|
||||||
Headings: cc.HiMagenta + cc.Bold,
|
|
||||||
Commands: cc.Bold,
|
|
||||||
Example: cc.Italic,
|
|
||||||
ExecName: cc.Bold,
|
|
||||||
Flags: cc.Bold,
|
|
||||||
NoExtraNewlines: true,
|
|
||||||
NoBottomNewline: true,
|
|
||||||
})
|
|
||||||
err := rootCmd.Execute()
|
|
||||||
if err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
configPath string
|
|
||||||
hm = &hyprman.Hyprman{}
|
|
||||||
)
|
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
|
||||||
Use: "hyprman",
|
|
||||||
Short: "hyprland companion app",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.CompletionOptions.HiddenDefaultCmd = true
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", hyprman.DefaultConfigPath(), "path to config file")
|
|
||||||
}
|
|
16
config.nims
Normal file
16
config.nims
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
# useful while hwylterm is tracking HEAD
|
||||||
|
task update, "update deps":
|
||||||
|
rmFile "nimble.lock"
|
||||||
|
rmDir "nimbledeps"
|
||||||
|
exec "nimble lock -l"
|
||||||
|
exec "nimble setup -l"
|
||||||
|
|
||||||
|
task build, "build":
|
||||||
|
selfExec "c --outdir:bin src/hyprman.nim"
|
||||||
|
|
||||||
|
# begin Nimble config (version 2)
|
||||||
|
--noNimblePath
|
||||||
|
when withDir(thisDir(), system.fileExists("nimble.paths")):
|
||||||
|
include "nimble.paths"
|
||||||
|
# end Nimble config
|
27
flake.lock
27
flake.lock
|
@ -1,12 +1,32 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"nim2nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1725469705,
|
||||||
|
"narHash": "sha256-4/iSUwB3XKmybdt4R87VhBPiD3z4BL6RrD9LyrrkesM=",
|
||||||
|
"owner": "daylinmorgan",
|
||||||
|
"repo": "nim2nix",
|
||||||
|
"rev": "42af12ca45025c7146bc24e27f5d8b65b003d663",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "daylinmorgan",
|
||||||
|
"repo": "nim2nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717646450,
|
"lastModified": 1729265718,
|
||||||
"narHash": "sha256-KE+UmfSVk5PG8jdKdclPVcMrUB8yVZHbsjo7ZT1Bm3c=",
|
"narHash": "sha256-4HQI+6LsO3kpWTYuVGIzhJs1cetFcwT7quWCk/6rqeo=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "818dbe2f96df233d2041739d6079bb616d3e5597",
|
"rev": "ccc0c2126893dd20963580b6478d1a10a4512185",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -18,6 +38,7 @@
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"nim2nix": "nim2nix",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
38
flake.nix
38
flake.nix
|
@ -2,10 +2,13 @@
|
||||||
description = "hyprman";
|
description = "hyprman";
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||||
|
nim2nix.url = "github:daylinmorgan/nim2nix";
|
||||||
|
nim2nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
inputs@{
|
{
|
||||||
|
nim2nix,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
self,
|
self,
|
||||||
...
|
...
|
||||||
|
@ -18,30 +21,35 @@
|
||||||
"aarch64-linux"
|
"aarch64-linux"
|
||||||
"aarch64-darwin"
|
"aarch64-darwin"
|
||||||
];
|
];
|
||||||
forAllSystems = f: genAttrs supportedSystems (system: f nixpkgs.legacyPackages.${system});
|
forAllSystems =
|
||||||
in
|
f:
|
||||||
|
genAttrs supportedSystems (
|
||||||
|
system:
|
||||||
|
f (
|
||||||
|
import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ nim2nix.overlays.default ];
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
in
|
||||||
{
|
{
|
||||||
devShells = forAllSystems (pkgs: {
|
devShells = forAllSystems (pkgs: {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
packages = with pkgs; [go];
|
packages = with pkgs; [
|
||||||
};
|
nim
|
||||||
|
nimble
|
||||||
|
];
|
||||||
|
};
|
||||||
});
|
});
|
||||||
packages = forAllSystems (pkgs: {
|
packages = forAllSystems (pkgs: {
|
||||||
default = self.packages.${pkgs.system}.hyprman;
|
default = self.packages.${pkgs.system}.hyprman;
|
||||||
hyprman = pkgs.buildGoModule {
|
hyprman = pkgs.buildNimblePackage {
|
||||||
pname = "hyprman";
|
pname = "hyprman";
|
||||||
version = "${self.shortRev or "dirty"}";
|
version = "${self.shortRev or "dirty"}";
|
||||||
src = cleanSource ./.;
|
src = cleanSource ./.;
|
||||||
vendorHash = "sha256-hJwRLVIiWxLbX2tAPVVVLGFk4OaAy5qiFcICEqhVMJM=";
|
nimbleDepsHash = "sha256-72FYXiYIgEDX2j/bBADGvwX6+kd+7py0RHTz2WeyXO8=";
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [installShellFiles];
|
|
||||||
|
|
||||||
postInstall = ''
|
|
||||||
installShellCompletion --cmd hyprman \
|
|
||||||
--zsh <($out/bin/hyprman completion zsh)
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
formatter = forAllSystems (pkgs: pkgs.nixfmt-rfc-style);
|
formatter = forAllSystems (pkgs: pkgs.nixfmt-rfc-style);
|
||||||
};
|
};
|
||||||
|
|
28
go.mod
28
go.mod
|
@ -1,28 +0,0 @@
|
||||||
module git.dayl.in/daylin/hyprman
|
|
||||||
|
|
||||||
go 1.22.2
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/charmbracelet/lipgloss v0.11.0
|
|
||||||
github.com/goccy/go-yaml v1.11.3
|
|
||||||
github.com/ivanpirog/coloredcobra v1.0.1
|
|
||||||
github.com/spf13/cobra v1.8.0
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.1.2 // indirect
|
|
||||||
github.com/fatih/color v1.17.0 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
|
||||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
replace github.com/ivanpirog/coloredcobra => github.com/daylinmorgan/coloredcobra v0.0.0-20240527152736-9d3ce38297a6
|
|
60
go.sum
60
go.sum
|
@ -1,60 +0,0 @@
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
||||||
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
|
|
||||||
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
|
|
||||||
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
|
|
||||||
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/daylinmorgan/coloredcobra v0.0.0-20240527152736-9d3ce38297a6 h1:Zst7HlWvQj8LPJ3mhIWtGH2DxqUo6VEq0oekNyAho6M=
|
|
||||||
github.com/daylinmorgan/coloredcobra v0.0.0-20240527152736-9d3ce38297a6/go.mod h1:csDZxFD5oWCy1x8hxXVD4txpYQgo12WOcLS7JON5SVE=
|
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
|
||||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
|
||||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
|
||||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
|
||||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
|
||||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
|
||||||
github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I=
|
|
||||||
github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
|
||||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
|
||||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
|
||||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
17
hyprman.nimble
Normal file
17
hyprman.nimble
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Package
|
||||||
|
|
||||||
|
version = "0.1.0"
|
||||||
|
author = "Daylin Morgan"
|
||||||
|
description = "hyrpman "
|
||||||
|
license = "MIT"
|
||||||
|
srcDir = "src"
|
||||||
|
bin = @["hyprman"]
|
||||||
|
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
requires "nim >= 2.0.8"
|
||||||
|
requires "cligen"
|
||||||
|
requires "yaml"
|
||||||
|
requires "jsony"
|
||||||
|
requires "https://github.com/daylinmorgan/hwylterm#HEAD"
|
122
internal/eww.go
122
internal/eww.go
|
@ -1,122 +0,0 @@
|
||||||
package hyprman
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EwwWorkspace struct {
|
|
||||||
Icon string `json:"icon"`
|
|
||||||
Class string `json:"class"`
|
|
||||||
Id int `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hm *Hyprman) pickIcon(class string) string {
|
|
||||||
for k, v := range hm.Config.Classes {
|
|
||||||
if strings.Contains(k, class) {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hm.Config.DefaultIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hm *Hyprman) generateEwwClasses() {
|
|
||||||
monitors := getMonitors()
|
|
||||||
clients := getClients()
|
|
||||||
|
|
||||||
var ewwClasses [][]EwwWorkspace
|
|
||||||
|
|
||||||
monitor := make([]EwwWorkspace, 9)
|
|
||||||
|
|
||||||
for i := range 9 {
|
|
||||||
monitor[i] = EwwWorkspace{
|
|
||||||
hm.Config.NoClientIcon,
|
|
||||||
fmt.Sprintf("ws-button-%d", i+1),
|
|
||||||
i + 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, client := range clients {
|
|
||||||
id := client.Workspace.Id - 1
|
|
||||||
currentIcon := monitor[id].Icon
|
|
||||||
if currentIcon == hm.Config.NoClientIcon {
|
|
||||||
monitor[id].Icon = hm.pickIcon(client.Class)
|
|
||||||
} else {
|
|
||||||
monitor[id].Icon = fmt.Sprintf("%s%s", currentIcon, hm.pickIcon(client.Class))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, id := range openWorkspaces(monitors) {
|
|
||||||
// activeId := m.ActiveWorkspace.Id
|
|
||||||
monitor[id-1].Class = fmt.Sprintf("%s %s", monitor[id-1].Class, "ws-button-open")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, m := range monitors {
|
|
||||||
activeId := m.ActiveWorkspace.Id - 1
|
|
||||||
ewwClasses = append(ewwClasses, make([]EwwWorkspace, 9))
|
|
||||||
copy(ewwClasses[i], monitor)
|
|
||||||
ewwClasses[i][activeId].Class = fmt.Sprintf("%s %s-%d", monitor[activeId].Class, "ws-button-active", activeId+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := json.Marshal(ewwClasses)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ewwBar1(cmd string) {
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
output, err := exec.Command("eww", cmd, "bar1").CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
notify(fmt.Sprintf("failed to %s bar 1\n\n%s\n\n%v", cmd, output, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hm *Hyprman) handleHyprEvent(line string) {
|
|
||||||
s := strings.Split(line, ">>")
|
|
||||||
event, _ := s[0], s[1]
|
|
||||||
if event == "monitoradded" {
|
|
||||||
notify("Monitor added opening bar1")
|
|
||||||
go ewwBar1("open")
|
|
||||||
}
|
|
||||||
hm.generateEwwClasses()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hm *Hyprman) LaunchEww() {
|
|
||||||
for i := range getMonitors() {
|
|
||||||
if err := exec.Command("eww", "open", fmt.Sprintf("bar%d", i)).Run(); err != nil {
|
|
||||||
notify(fmt.Sprintf("Error lanching eww:\n%v", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hm *Hyprman) Watch() {
|
|
||||||
socketPath := hyprSocket2()
|
|
||||||
conn, err := net.Dial("unix", socketPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("Error connecting to hyprland IPC:", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
reader := bufio.NewReader(conn)
|
|
||||||
hm.generateEwwClasses()
|
|
||||||
for {
|
|
||||||
line, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
// log.Printf("reached EOF for %s\n", socketPath)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// log.Println("error reading line")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hm.handleHyprEvent(line)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
package hyprman
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
yaml "github.com/goccy/go-yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DefaultConfigPath() string {
|
|
||||||
configPath, exists := os.LookupEnv("XDG_CONFIG_HOME")
|
|
||||||
if !exists {
|
|
||||||
home, exists := os.LookupEnv("HOME")
|
|
||||||
if !exists {
|
|
||||||
log.Fatalln("failed to set default icons.json path is $HOME or $XDG_CONFIG_HOME set?")
|
|
||||||
}
|
|
||||||
configPath = filepath.Join(home, ".config")
|
|
||||||
}
|
|
||||||
return filepath.Join(configPath, "hyprman", "config.yml")
|
|
||||||
}
|
|
||||||
|
|
||||||
func hyprSocketBase() string {
|
|
||||||
runtimeDir, exists := os.LookupEnv("XDG_RUNTIME_DIR")
|
|
||||||
if !exists {
|
|
||||||
log.Fatalln("XDG_RUNTIME_DIR not set")
|
|
||||||
}
|
|
||||||
instanceSig, exists := os.LookupEnv("HYPRLAND_INSTANCE_SIGNATURE")
|
|
||||||
if !exists {
|
|
||||||
log.Fatalln("HYPRLAND_INSTANCE_SIGNATURE not set")
|
|
||||||
}
|
|
||||||
return filepath.Join(runtimeDir, "hypr", instanceSig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hyprSocket() string {
|
|
||||||
return filepath.Join(hyprSocketBase(), ".socket.sock")
|
|
||||||
}
|
|
||||||
|
|
||||||
func hyprSocket2() string {
|
|
||||||
return filepath.Join(hyprSocketBase(), ".socket2.sock")
|
|
||||||
}
|
|
||||||
|
|
||||||
type Workspace struct {
|
|
||||||
Name string
|
|
||||||
Id int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
Class string
|
|
||||||
Workspace Workspace
|
|
||||||
}
|
|
||||||
|
|
||||||
type Icons map[string]string
|
|
||||||
|
|
||||||
type Hyprman struct {
|
|
||||||
Config Config
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Classes Icons
|
|
||||||
NoClientIcon string `yaml:"no-client"`
|
|
||||||
DefaultIcon string `yaml:"default-icon"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hm *Hyprman) LoadConfig(path string) {
|
|
||||||
var config Config
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(fmt.Errorf("failed to read config at %s:\n%w", path, err))
|
|
||||||
}
|
|
||||||
if err := yaml.Unmarshal([]byte(data), &config); err != nil {
|
|
||||||
log.Fatal(fmt.Errorf("failed to read config at %s:\n%w", path, err))
|
|
||||||
}
|
|
||||||
hm.Config = config
|
|
||||||
}
|
|
||||||
|
|
||||||
func hyprctl(cmd string) []byte {
|
|
||||||
socketPath := hyprSocket()
|
|
||||||
conn, err := net.Dial("unix", socketPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("Error connecting to hyprland IPC:", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
_, err = conn.Write([]byte(cmd))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 1024*8) // Bigger?
|
|
||||||
n, err := conn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return buf[0:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActiveWorkspace struct {
|
|
||||||
Name string
|
|
||||||
Id int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Monitor struct {
|
|
||||||
ActiveWorkspace ActiveWorkspace
|
|
||||||
Id int
|
|
||||||
}
|
|
||||||
|
|
||||||
// generic or interface?
|
|
||||||
func getMonitors() []Monitor {
|
|
||||||
data := hyprctl("[-j]/monitors")
|
|
||||||
var monitors []Monitor
|
|
||||||
err := json.Unmarshal(data, &monitors)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
return monitors
|
|
||||||
}
|
|
||||||
|
|
||||||
func getClients() []Client {
|
|
||||||
data := hyprctl("[-j]/clients")
|
|
||||||
var clients []Client
|
|
||||||
err := json.Unmarshal(data, &clients)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
return clients
|
|
||||||
}
|
|
||||||
|
|
||||||
func openWorkspaces(monitors []Monitor) []int {
|
|
||||||
open := make([]int, len(monitors))
|
|
||||||
for i, m := range monitors {
|
|
||||||
open[i] = m.ActiveWorkspace.Id
|
|
||||||
}
|
|
||||||
return open
|
|
||||||
}
|
|
||||||
|
|
||||||
func notify(message string) {
|
|
||||||
cmd := exec.Command(
|
|
||||||
"notify-send",
|
|
||||||
"--app-name=hyprman",
|
|
||||||
"--transient",
|
|
||||||
message,
|
|
||||||
)
|
|
||||||
cmd.Run()
|
|
||||||
}
|
|
112
internal/mako.go
112
internal/mako.go
|
@ -1,112 +0,0 @@
|
||||||
package hyprman
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MakoHistory struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Data [][]MakoNotification `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MakoNotification struct {
|
|
||||||
AppName MakoNotificationData `json:"app-name"`
|
|
||||||
Summary MakoNotificationData
|
|
||||||
Body MakoNotificationData
|
|
||||||
// AppIcon MakoNotificationData `json:"app-icon"`
|
|
||||||
// Category MakoNotificationData
|
|
||||||
// DesktopEntry MakoNotificationData `json:"desktop-entry"`
|
|
||||||
// Id MakoNotificationData
|
|
||||||
// Urgency MakoNotificationData
|
|
||||||
// Actions MakoNotificationData
|
|
||||||
}
|
|
||||||
|
|
||||||
type MakoNotificationData struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Data string `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type History struct {
|
|
||||||
Notifications []Notifcation
|
|
||||||
}
|
|
||||||
|
|
||||||
type Notifcation struct {
|
|
||||||
AppName string
|
|
||||||
Summary string
|
|
||||||
Body string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mn MakoNotification) toNotification() Notifcation {
|
|
||||||
var n Notifcation
|
|
||||||
n.AppName = mn.AppName.Data
|
|
||||||
n.Summary = mn.Summary.Data
|
|
||||||
n.Body = mn.Body.Data
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mh MakoHistory) toHistory() History {
|
|
||||||
var h History
|
|
||||||
var notifications []Notifcation
|
|
||||||
for _, mn := range mh.Data[0] {
|
|
||||||
notifications = append(notifications, mn.toNotification())
|
|
||||||
}
|
|
||||||
h.Notifications = notifications
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHistory() History {
|
|
||||||
var makoHistory MakoHistory
|
|
||||||
makoOutput, err := exec.Command("makoctl", "history").CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
json.Unmarshal(makoOutput, &makoHistory)
|
|
||||||
return makoHistory.toHistory()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *History) truncate(number int) {
|
|
||||||
h.Notifications = h.Notifications[:min(len(h.Notifications), number)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListNotifications(number int, outputJson bool) {
|
|
||||||
history := getHistory()
|
|
||||||
history.truncate(number)
|
|
||||||
if !outputJson {
|
|
||||||
for _, n := range history.Notifications {
|
|
||||||
n.Render()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b, err := json.MarshalIndent(history.Notifications, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Notifcation) Render() {
|
|
||||||
appStyle := lipgloss.NewStyle().
|
|
||||||
Bold(true).
|
|
||||||
Foreground(lipgloss.Color("3")).
|
|
||||||
PaddingTop(1)
|
|
||||||
textStyle := lipgloss.NewStyle().Width(80).PaddingLeft(2).
|
|
||||||
BorderStyle(lipgloss.ThickBorder()).
|
|
||||||
BorderLeft(true)
|
|
||||||
|
|
||||||
fmt.Println(appStyle.Render(n.AppName))
|
|
||||||
if len(n.Summary) > 0 {
|
|
||||||
fmt.Println(textStyle.
|
|
||||||
BorderForeground(lipgloss.Color("14")).
|
|
||||||
Render(n.Summary))
|
|
||||||
}
|
|
||||||
if len(n.Body) > 0 {
|
|
||||||
fmt.Println(textStyle.
|
|
||||||
BorderForeground(lipgloss.Color("2")).
|
|
||||||
Render(n.Body))
|
|
||||||
}
|
|
||||||
}
|
|
7
main.go
7
main.go
|
@ -1,7 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import "git.dayl.in/daylin/hyprman/cmd"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cmd.Execute()
|
|
||||||
}
|
|
46
nimble.lock
Normal file
46
nimble.lock
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"packages": {
|
||||||
|
"cligen": {
|
||||||
|
"version": "1.7.6",
|
||||||
|
"vcsRevision": "54f1da5d63cf7e116625e2392e85ae54c2b70719",
|
||||||
|
"url": "https://github.com/c-blake/cligen.git",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"dependencies": [],
|
||||||
|
"checksums": {
|
||||||
|
"sha1": "853785ddace4ee4f3c6c21bdf7f5e9ff0358eb3f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hwylterm": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"vcsRevision": "5c71355b5f319a9c174ea88132c7c67a78d00030",
|
||||||
|
"url": "https://github.com/daylinmorgan/hwylterm",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"dependencies": [],
|
||||||
|
"checksums": {
|
||||||
|
"sha1": "30f8f61787c36b63d484f3d5e149995fad16c63c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jsony": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"vcsRevision": "ea811bec7fa50f5abd3088ba94cda74285e93f18",
|
||||||
|
"url": "https://github.com/treeform/jsony",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"dependencies": [],
|
||||||
|
"checksums": {
|
||||||
|
"sha1": "6aeb83e7481ca8686396a568096054bc668294df"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yaml": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"vcsRevision": "48a90e36e82bd97457dae87e86efe423dcd3bb40",
|
||||||
|
"url": "https://github.com/flyx/NimYAML",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"dependencies": [],
|
||||||
|
"checksums": {
|
||||||
|
"sha1": "302727fcd74c79d0697a4e909d26455d61a5b979"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tasks": {}
|
||||||
|
}
|
184
src/hyprland.nim
Normal file
184
src/hyprland.nim
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
import std/[
|
||||||
|
os, osproc, strformat, strutils,
|
||||||
|
streams, tables, net, sugar, times
|
||||||
|
]
|
||||||
|
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"
|
||||||
|
|
||||||
|
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
|
42
src/hyprman.nim
Normal file
42
src/hyprman.nim
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
## hyprman, the hyprland companion
|
||||||
|
|
||||||
|
import std/[osproc, strformat]
|
||||||
|
import hwylterm/cligen, cligen
|
||||||
|
import ./[
|
||||||
|
hyprland,
|
||||||
|
lib,
|
||||||
|
mako,
|
||||||
|
]
|
||||||
|
|
||||||
|
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 eww() =
|
||||||
|
## watch hyprland events for eww class changes
|
||||||
|
streamEwwClasses()
|
||||||
|
|
||||||
|
proc watch() =
|
||||||
|
## handle monitor changes on hyprland
|
||||||
|
watchHyprland()
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
const
|
||||||
|
config = //{"config": "path/to/config"}
|
||||||
|
makoHelp = config // {
|
||||||
|
"count" : "# of notifications",
|
||||||
|
"json" : "output as json",
|
||||||
|
"reverse": "swap notification order"
|
||||||
|
}
|
||||||
|
dispatchMulti(
|
||||||
|
[makoCmd, usage = clCfg.use, help = makoHelp, cmdName = "mako",],
|
||||||
|
[start , usage = clCfg.use],
|
||||||
|
[eww , usage = clCfg.use],
|
||||||
|
[watch , usage = clCfg.use],
|
||||||
|
)
|
39
src/lib.nim
Normal file
39
src/lib.nim
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import std/[
|
||||||
|
os, osproc, streams, strutils, tables
|
||||||
|
]
|
||||||
|
export tables
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
proc notify*(message: string) =
|
||||||
|
var cmd = "notify-send --app-name=hyprman --transient hyprman --expire-time 10000"
|
||||||
|
cmd.add "\""
|
||||||
|
cmd.add message
|
||||||
|
cmd.add "\""
|
||||||
|
discard execCmd(cmd)
|
||||||
|
|
||||||
|
type
|
||||||
|
Icons = Table[string, string]
|
||||||
|
Config = object # config vs icons?
|
||||||
|
timeout* {.defaultVal: 60.}: int
|
||||||
|
wallpapers* {.defaultVal: "".}: string
|
||||||
|
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)
|
||||||
|
result.wallpapers = expandTilde(result.wallpapers)
|
||||||
|
|
||||||
|
func pickIcon*(
|
||||||
|
c: Config,
|
||||||
|
class: string
|
||||||
|
): string =
|
||||||
|
for k, v in c.classes:
|
||||||
|
if class in k: result = v
|
||||||
|
if result == "":
|
||||||
|
result = c.`default-icon`
|
||||||
|
|
||||||
|
let config* = loadConfig()
|
56
src/mako.nim
Normal file
56
src/mako.nim
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import std/[algorithm, strutils, json, osproc, wordwrap, terminal]
|
||||||
|
import hwylterm
|
||||||
|
|
||||||
|
type
|
||||||
|
MakoNotificationData = object
|
||||||
|
`type`, data: string
|
||||||
|
MakoNotification = object
|
||||||
|
`app-name`, summary, body: MakoNotificationData
|
||||||
|
MakoHistory = object
|
||||||
|
`type`: string
|
||||||
|
data: seq[seq[MakoNotification]]
|
||||||
|
Notification = object
|
||||||
|
appName, summary, body: string
|
||||||
|
History = object
|
||||||
|
notifications: seq[Notification]
|
||||||
|
|
||||||
|
func toNotification(mn: MakoNotification): Notification =
|
||||||
|
result.appName = mn.`app-name`.data
|
||||||
|
result.summary = mn.summary.data
|
||||||
|
result.body = mn.body.data
|
||||||
|
|
||||||
|
func toHistory(mh: MakoHistory): History =
|
||||||
|
for mn in mh.data[0]:
|
||||||
|
result.notifications.add mn.toNotification()
|
||||||
|
|
||||||
|
func filter(h: History, reverse: bool, count: int): History =
|
||||||
|
result.notifications = h.notifications
|
||||||
|
if reverse:
|
||||||
|
result.notifications.reverse()
|
||||||
|
let high = min(count, h.notifications.len - 1)
|
||||||
|
result.notifications =
|
||||||
|
result.notifications[0..high]
|
||||||
|
|
||||||
|
proc getMakoHistory(): MakoHistory =
|
||||||
|
let (output, errCode) = execCmdEx("makoctl history")
|
||||||
|
if errCode != 0: quit output, errCode
|
||||||
|
result = parseJson(output).to(MakoHistory)
|
||||||
|
|
||||||
|
proc bb(n: Notification): BbString =
|
||||||
|
template border(style: string): untyped = "[" & style & "]" & "┃ [/]"
|
||||||
|
var raw: string
|
||||||
|
raw.add border("magenta") & "[yellow]" & n.appName & "[/]\n"
|
||||||
|
raw.add border("green") & "[b]" & n.summary & "[/]\n"
|
||||||
|
for line in n.body.wrapWords(maxLineWidth = terminalWidth()-2).splitLines():
|
||||||
|
raw.add border("default") & line & "\n"
|
||||||
|
result = bb(raw)
|
||||||
|
|
||||||
|
proc makoCmd*(config = "", count = 10, json = false, reverse = false) =
|
||||||
|
## interact with mako
|
||||||
|
let history = getMakoHistory().toHistory().filter(reverse, count)
|
||||||
|
if json:
|
||||||
|
echo $(%* history)
|
||||||
|
else:
|
||||||
|
for n in history.notifications:
|
||||||
|
echo $bb(n)
|
||||||
|
|
48
src/swww.nim
Normal file
48
src/swww.nim
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import std/[os, osproc, strformat, strutils, random]
|
||||||
|
import ./lib
|
||||||
|
|
||||||
|
randomize()
|
||||||
|
|
||||||
|
const oneMinute = 1000 * 60
|
||||||
|
|
||||||
|
proc loadGallery(): seq[string] =
|
||||||
|
for path in walkDirRec(
|
||||||
|
config.wallpapers, yieldFilter = {pcFile, pcLinkToFile}
|
||||||
|
):
|
||||||
|
let (_,_, ext) = splitFile(path)
|
||||||
|
if ext in [".png", ".jpeg"]:
|
||||||
|
result.add path
|
||||||
|
|
||||||
|
proc swwwMonitors(): seq[string] =
|
||||||
|
let (output, code) = execCmdEx("swww query")
|
||||||
|
if code != 0: notify "swww failed"
|
||||||
|
for line in output.strip().splitLines():
|
||||||
|
result.add line.split(':', 1)[0]
|
||||||
|
|
||||||
|
proc setImg(path: string, output: string) =
|
||||||
|
let code =
|
||||||
|
execCmd(fmt"swww img --transition-type fade --outputs {output} {path}")
|
||||||
|
if code != 0: notify "swww failed"
|
||||||
|
|
||||||
|
proc oneShotSwww*() =
|
||||||
|
if config.wallpapers == "": return
|
||||||
|
if not dirExists(config.wallpapers):
|
||||||
|
notify(fmt"{config.wallpapers} directory does not exist")
|
||||||
|
quit(1)
|
||||||
|
let gallery = loadGallery()
|
||||||
|
for monitor in swwwMonitors():
|
||||||
|
setImg(gallery.sample(), monitor)
|
||||||
|
|
||||||
|
|
||||||
|
proc persistentSwww*() =
|
||||||
|
if config.wallpapers == "": quit(0)
|
||||||
|
if not dirExists(config.wallpapers):
|
||||||
|
notify(fmt"{config.wallpapers} directory does not exist")
|
||||||
|
quit(1)
|
||||||
|
let gallery = loadGallery()
|
||||||
|
while true:
|
||||||
|
for monitor in swwwMonitors():
|
||||||
|
setImg(gallery.sample(), monitor)
|
||||||
|
sleep 5 * oneMinute
|
||||||
|
|
||||||
|
|
8
todo.md
8
todo.md
|
@ -1,8 +1,10 @@
|
||||||
# hyprman-go todo's
|
# hyprman todo's
|
||||||
|
|
||||||
- [ ] include in class the 'other' active monitor?
|
- [ ] include in class the 'other' active monitor?
|
||||||
- [ ] use goroutine with slightly delay to "open/close"
|
- [ ] switch to usu once parser is stable again
|
||||||
bar to give hyprland time to draw/setup monitor
|
- [x] make swww powered wallpaper cycler
|
||||||
|
- [ ] add support for monitor orientation?
|
||||||
|
- [ ] make `hyprman swww` support monitor changes by watching hyprland IPC
|
||||||
|
|
||||||
<!-- generated with <3 by daylinmorgan/todo -->
|
<!-- generated with <3 by daylinmorgan/todo -->
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue