hyprman/internal/hyprman.go

280 lines
5.7 KiB
Go

package hyprman
import (
"bufio"
"encoding/json"
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
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
}
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 openWorkspaces(monitors []Monitor) []int {
open := make([]int, len(monitors))
for i, m := range monitors {
open[i] = m.ActiveWorkspace.Id
}
return open
}
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(200 * time.Second)
if err := exec.Command("eww", cmd, "bar1").Run(); err != nil {
log.Fatal(err)
}
}
func notify(message string) {
cmd := exec.Command("notify-send", "hyprman", message)
cmd.Run()
}
func (hm *Hyprman) handleHyprEvent(line string) {
s := strings.Split(line, ">>")
event, _ := s[0], s[1]
switch event {
case "monitorremoved":
notify("Monitor removed closing bar1")
go ewwBar1("close")
hm.generateEwwClasses()
case "monitoradded":
notify("Monitor added opening bar1")
go ewwBar1("open")
hm.generateEwwClasses()
case "workspace",
"focusedmon",
"activewindow",
"createworkspace",
"destroyworkspace",
"moveworkspace",
"renameworkspace",
"openwindow",
"closewindow",
"movewindow",
"movewindowv2",
"changefloatingmode",
"windowtitle",
"togglegroup",
"moveintogroup",
"moveoutofgroup",
"configreloaded":
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)
}
}