From 8edaa9d713187e0c72939dedae8aa15a25e4dec6 Mon Sep 17 00:00:00 2001 From: Milarin Date: Fri, 9 Feb 2024 20:24:48 +0100 Subject: [PATCH] rewrite --- .gitignore | 1 + dispatch.go | 24 ++++++++++++++++ get_instances.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++ getters.go | 33 ++++++++++++++++++++++ go.mod | 7 +++++ go.sum | 4 +++ model_bind.go | 22 +++++++++++++++ model_instance.go | 25 +++++++++++++++++ model_misc.go | 13 +++++++++ model_monitor.go | 31 ++++++++++++++++++++ model_window.go | 32 +++++++++++++++++++++ model_workspace.go | 24 ++++++++++++++++ utils.go | 51 +++++++++++++++++++++++++++++++++ 13 files changed, 337 insertions(+) create mode 100644 .gitignore create mode 100644 dispatch.go create mode 100644 get_instances.go create mode 100644 getters.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 model_bind.go create mode 100644 model_instance.go create mode 100644 model_misc.go create mode 100644 model_monitor.go create mode 100644 model_window.go create mode 100644 model_workspace.go create mode 100644 utils.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de95680 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +hypr_test.go diff --git a/dispatch.go b/dispatch.go new file mode 100644 index 0000000..44d3f84 --- /dev/null +++ b/dispatch.go @@ -0,0 +1,24 @@ +package hypr + +import ( + "fmt" + "io" + "strings" +) + +func (i *Instance) Dispatch(cmd string) (io.ReadCloser, error) { + return readSocketRaw(i.SocketPath(), strings.NewReader(cmd)) +} + +func (i *Instance) DispatchExpectOK(cmd string) error { + str, err := readSocketString(i.SocketPath(), strings.NewReader(cmd)) + if err != nil { + return err + } + + if strings.ToLower(strings.TrimSpace(str)) != "ok" { + return fmt.Errorf("dispatcher '%s' returned an error: %s", cmd, str) + } + + return nil +} diff --git a/get_instances.go b/get_instances.go new file mode 100644 index 0000000..efb8116 --- /dev/null +++ b/get_instances.go @@ -0,0 +1,70 @@ +package hypr + +import ( + "errors" + "fmt" + "io" + "io/fs" + "os" + "strconv" + "strings" + + "git.milar.in/milarin/slices" +) + +func GetInstance(signature string) (*Instance, error) { + lockFilePath := fmt.Sprintf("/tmp/hypr/%s.lock", signature) + + file, err := os.Open(lockFilePath) + if err != nil { + return nil, err + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return nil, err + } + + lines := strings.Split(string(data), "\n") + + pid, err := strconv.Atoi(lines[0]) + if err != nil { + return nil, err + } + + return &Instance{ + Signature: signature, + PID: pid, + WaylandSocket: lines[1], + }, nil +} + +func GetDefaultInstance() (*Instance, error) { + signature, ok := os.LookupEnv("HYPRLAND_INSTANCE_SIGNATURE") + if !ok { + return nil, errors.New("default instance not found because HYPRLAND_INSTANCE_SIGNATURE is not set") + } + + return GetInstance(signature) +} + +func GetInstances() ([]*Instance, error) { + entries, err := os.ReadDir("/tmp/hypr") + if err != nil { + return nil, err + } + + entries = slices.Filter(entries, fs.DirEntry.IsDir) + instances := make([]*Instance, 0, len(entries)) + for _, entry := range entries { + instance, err := GetInstance(entry.Name()) + if err != nil { + fmt.Println(err) + continue + } + instances = append(instances, instance) + } + + return instances, nil +} diff --git a/getters.go b/getters.go new file mode 100644 index 0000000..82cf179 --- /dev/null +++ b/getters.go @@ -0,0 +1,33 @@ +package hypr + +import ( + "strings" +) + +func (i *Instance) GetActiveWindow() (*Window, error) { + return readSocket[*Window](i.SocketPath(), strings.NewReader("j/activewindow")) +} + +func (i *Instance) GetActiveWorkspace() (*Workspace, error) { + return readSocket[*Workspace](i.SocketPath(), strings.NewReader("j/activeworkspace")) +} + +func (i *Instance) GetBinds() ([]*Bind, error) { + return readSocket[[]*Bind](i.SocketPath(), strings.NewReader("j/binds")) +} + +func (i *Instance) GetWindows() ([]*Window, error) { + return readSocket[[]*Window](i.SocketPath(), strings.NewReader("j/clients")) +} + +func (i *Instance) GetCursorPos() (Point, error) { + return readSocket[Point](i.SocketPath(), strings.NewReader("j/cursorpos")) +} + +func (i *Instance) GetMonitors() ([]*Monitor, error) { + return readSocket[[]*Monitor](i.SocketPath(), strings.NewReader("j/monitors")) +} + +func (i *Instance) GetWorkspaces() ([]*Workspace, error) { + return readSocket[[]*Workspace](i.SocketPath(), strings.NewReader("j/workspaces")) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f8e9f42 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module git.milar.in/milarin/hypr + +go 1.21.6 + +require git.milar.in/milarin/slices v0.0.8 + +require git.milar.in/milarin/gmath v0.0.3 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3107e78 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +git.milar.in/milarin/gmath v0.0.3 h1:ii6rKNItS55O/wtIFhD1cTN2BMwDZjTBmiOocKURvxM= +git.milar.in/milarin/gmath v0.0.3/go.mod h1:HDLftG5RLpiNGKiIWh+O2G1PYkNzyLDADO8Cd/1abiE= +git.milar.in/milarin/slices v0.0.8 h1:qN9TE3tkArdTixMKSnwvNPcApwAjxpLVwA5a9k1rm2s= +git.milar.in/milarin/slices v0.0.8/go.mod h1:qMhdtMnfWswc1rHpwgNw33lB84aNEkdBn5BDiYA+G3k= diff --git a/model_bind.go b/model_bind.go new file mode 100644 index 0000000..0e59d70 --- /dev/null +++ b/model_bind.go @@ -0,0 +1,22 @@ +package hypr + +import "encoding/json" + +type Bind struct { + Locked bool `json:"locked"` + Mouse bool `json:"mouse"` + Release bool `json:"release"` + Repeat bool `json:"repeat"` + NonConsuming bool `json:"non_consuming"` + ModMask int `json:"modmask"` + Submap string `json:"submap"` + Key string `json:"key"` + KeyCode int `json:"keycode"` + Dispatcher string `json:"dispatcher"` + Arg string `json:"arg"` +} + +func (b Bind) String() string { + data, _ := json.MarshalIndent(b, "", "\t") + return string(data) +} diff --git a/model_instance.go b/model_instance.go new file mode 100644 index 0000000..6efa34b --- /dev/null +++ b/model_instance.go @@ -0,0 +1,25 @@ +package hypr + +import ( + "encoding/json" + "fmt" +) + +type Instance struct { + Signature string `json:"instance"` + PID int `json:"pid"` + WaylandSocket string `json:"wl_socket"` +} + +func (i Instance) String() string { + data, _ := json.MarshalIndent(i, "", "\t") + return string(data) +} + +func (i Instance) SocketPath() string { + return fmt.Sprintf("/tmp/hypr/%s/.socket.sock", i.Signature) +} + +func (i Instance) EventSocketPath() string { + return fmt.Sprintf("/tmp/hypr/%s/.socket2.sock", i.Signature) +} diff --git a/model_misc.go b/model_misc.go new file mode 100644 index 0000000..eae1bcc --- /dev/null +++ b/model_misc.go @@ -0,0 +1,13 @@ +package hypr + +import "encoding/json" + +type Point struct { + X int `json:"x"` + Y int `json:"y"` +} + +func (p Point) String() string { + data, _ := json.MarshalIndent(p, "", "\t") + return string(data) +} diff --git a/model_monitor.go b/model_monitor.go new file mode 100644 index 0000000..af4aacf --- /dev/null +++ b/model_monitor.go @@ -0,0 +1,31 @@ +package hypr + +import "encoding/json" + +type Monitor struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Make string `json:"make"` + Model string `json:"model"` + Serial string `json:"serial"` + Width int `json:"width"` + Height int `json:"height"` + RefreshRate float64 `json:"refreshRate"` + X int `json:"x"` + Y int `json:"y"` + ActiveWorkspace WorkspaceIdent `json:"activeWorkspace"` + SpecialWorkspace WorkspaceIdent `json:"specialWorkspace"` + Reserved [4]int `json:"reserved"` + Scale float64 `json:"scale"` + Transform int `json:"transform"` + Focused bool `json:"focused"` + DPMSStatus bool `json:"dpmsStatus"` + VRR bool `json:"vrr"` + ActivelyTearing bool `json:"activelyTearing"` +} + +func (m Monitor) String() string { + data, _ := json.MarshalIndent(m, "", "\t") + return string(data) +} diff --git a/model_window.go b/model_window.go new file mode 100644 index 0000000..53d8ce9 --- /dev/null +++ b/model_window.go @@ -0,0 +1,32 @@ +package hypr + +import "encoding/json" + +type Window struct { + Address string `json:"address"` + Mapped bool `json:"mapped"` + Hidden bool `json:"hidden"` + At [2]int `json:"at"` + Size [2]int `json:"size"` + Workspace WorkspaceIdent `json:"workspace"` + Floating bool `json:"floating"` + MonitorID int `json:"monitor"` + Class string `json:"class"` + Title string `json:"title"` + InitialClass string `json:"initialClass"` + InitialTitle string `json:"initialTitle"` + PID int `json:"pid"` + Xwayland bool `json:"xwayland"` + Pinned bool `json:"pinned"` + Fullscreen bool `json:"fullscreen"` + FullscreenMode int `json:"fullscreenMode"` + FakeFullscreen bool `json:"fakeFullscreen"` + Grouped []interface{} `json:"grouped"` // TODO + Swallowing string `json:"swallowing"` + FocusHistoryID int `json:"focusHistoryID"` +} + +func (w Window) String() string { + data, _ := json.MarshalIndent(w, "", "\t") + return string(data) +} diff --git a/model_workspace.go b/model_workspace.go new file mode 100644 index 0000000..1990478 --- /dev/null +++ b/model_workspace.go @@ -0,0 +1,24 @@ +package hypr + +import "encoding/json" + +type WorkspaceIdent struct { + ID int `json:"id"` + Name string `json:"name"` +} + +type Workspace struct { + ID int `json:"id"` + Name string `json:"name"` + Monitor string `json:"monitor"` + MonitorID int `json:"monitorID"` + Windows int `json:"windows"` + HasFullscreen bool `json:"hasfullscreen"` + LastWindow string `json:"lastwindow"` + LastWindowTitle string `json:"lastwindowtitle"` +} + +func (w Workspace) String() string { + data, _ := json.MarshalIndent(w, "", "\t") + return string(data) +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..8823d53 --- /dev/null +++ b/utils.go @@ -0,0 +1,51 @@ +package hypr + +import ( + "encoding/json" + "io" + "net" +) + +func readSocketRaw(socket string, body io.Reader) (io.ReadCloser, error) { + conn, err := net.Dial("unix", socket) + if err != nil { + return nil, err + } + + if _, err := io.Copy(conn, body); err != nil { + conn.Close() + return nil, err + } + + return conn, nil +} + +func readSocketString(socket string, body io.Reader) (string, error) { + r, err := readSocketRaw(socket, body) + if err != nil { + return "", err + } + defer r.Close() + + data, err := io.ReadAll(r) + if err != nil { + return "", err + } + + return string(data), nil +} + +func readSocket[T any](socket string, body io.Reader) (T, error) { + r, err := readSocketRaw(socket, body) + if err != nil { + return *new(T), err + } + defer r.Close() + + value := new(T) + if err := json.NewDecoder(r).Decode(value); err != nil { + return *new(T), err + } + + return *value, nil +}