Compare commits

...

14 Commits
v0.1.3 ... main

9 changed files with 144 additions and 31 deletions

41
check_reachable.go Normal file
View File

@ -0,0 +1,41 @@
package hypr
import (
"context"
"strings"
"time"
)
// IsReachable pings the unix socket to check
// if Hyprland is ready to accept incoming requests
func (c *Client) IsReachable() bool {
str, err := readSocketString(c.SocketPath(), strings.NewReader("dispatch exec echo"))
if err != nil {
return false
}
return str == "ok"
}
// WaitUntilReachable periodically calls IsReachable
// and returns true as soon as IsReachable returns true.
// When ctx is closed before Hyprland is reachable, false is returned
func (c *Client) WaitUntilReachable(ctx context.Context) bool {
if c.IsReachable() {
return true
}
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if c.IsReachable() {
return true
}
case <-ctx.Done():
return false
}
}
}

View File

@ -6,12 +6,12 @@ import (
"strings"
)
func (i *Client) Dispatch(cmd string) (io.ReadCloser, error) {
return readSocketRaw(i.SocketPath(), strings.NewReader(fmt.Sprintf("dispatch %s", cmd)))
func (c *Client) Dispatch(cmd string) (io.ReadCloser, error) {
return readSocketRaw(c.SocketPath(), strings.NewReader(fmt.Sprintf("dispatch %s", cmd)))
}
func (i *Client) DispatchExpectOK(cmd string) error {
str, err := readSocketString(i.SocketPath(), strings.NewReader(fmt.Sprintf("dispatch %s", cmd)))
func (c *Client) DispatchExpectOK(cmd string) error {
str, err := readSocketString(c.SocketPath(), strings.NewReader(fmt.Sprintf("dispatch %s", cmd)))
if err != nil {
return err
}

View File

@ -1,6 +1,7 @@
package hypr
import (
"context"
"errors"
"fmt"
"io"
@ -13,7 +14,7 @@ import (
)
func GetClient(signature string) (*Client, error) {
lockFilePath := fmt.Sprintf("/tmp/hypr/%s.lock", signature)
lockFilePath := fmt.Sprintf("/run/user/1000/hypr/%s/hyprland.lock", signature)
file, err := os.Open(lockFilePath)
if err != nil {
@ -50,7 +51,7 @@ func GetDefaultClient() (*Client, error) {
}
func GetClients() ([]*Client, error) {
entries, err := os.ReadDir("/tmp/hypr")
entries, err := os.ReadDir("/run/user/1000/hypr")
if err != nil {
return nil, err
}
@ -68,3 +69,36 @@ func GetClients() ([]*Client, error) {
return clients, nil
}
func WaitForDefaultClient(ctx context.Context) (*Client, 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 WaitForClient(ctx, signature)
}
func WaitForClient(ctx context.Context, signature string) (*Client, error) {
lockFilePath := fmt.Sprintf("/run/user/1000/hypr/%s/hyprland.lock", signature)
lockFileExists := waitFor(ctx, func() bool {
_, err := os.Stat(lockFilePath)
return !errors.Is(err, os.ErrNotExist)
})
if !lockFileExists {
return nil, errors.New("hyprland lock file not found")
}
client, err := GetClient(signature)
if err != nil {
return nil, err
}
if !client.WaitUntilReachable(context.Background()) {
return nil, errors.New("hyprland not reachable")
}
return client, nil
}

2
go.mod
View File

@ -1,4 +1,4 @@
module git.milar.in/milarin/hypr
module git.milar.in/hyprland-tools/hypr
go 1.21.6

View File

@ -31,8 +31,10 @@ const (
EventTypeActiveWindowV2 EventType = "activewindowv2"
EventTypeKeyboardFocus EventType = "keyboardfocus"
EventTypeMoveWorkspace EventType = "moveworkspace"
EventTypeMoveWorkspaceV2 EventType = "moveworkspacev2"
EventTypeFocusedMon EventType = "focusedmon"
EventTypeMoveWindow EventType = "movewindow"
EventTypeMoveWindowV2 EventType = "movewindowv2"
EventTypeOpenLayer EventType = "openlayer"
EventTypeCloseLayer EventType = "closelayer"
EventTypeOpenWindow EventType = "openwindow"
@ -40,12 +42,16 @@ const (
EventTypeUrgent EventType = "urgent"
EventTypeMinimize EventType = "minimize"
EventTypeMonitorAdded EventType = "monitoradded"
EventTypeMonitorAddedV2 EventType = "monitoraddedv2"
EventTypeMonitorRemoved EventType = "monitorremoved"
EventTypeCreateWorkspace EventType = "createworkspace"
EventTypeCreateWorkspaceV2 EventType = "createworkspacev2"
EventTypeDestroyWorkspace EventType = "destroyworkspace"
EventTypeDestroyWorkspaceV2 EventType = "destroyworkspacev2"
EventTypeFullscreen EventType = "fullscreen"
EventTypeChangeFloatingMode EventType = "changefloatingmode"
EventTypeWorkspace EventType = "workspace"
EventTypeWorkspaceV2 EventType = "workspacev2"
EventTypeSubmap EventType = "submap"
EventTypeMouseMove EventType = "mousemove"
EventTypeMouseButton EventType = "mousebutton"

View File

@ -17,9 +17,9 @@ func (c Client) String() string {
}
func (c Client) SocketPath() string {
return fmt.Sprintf("/tmp/hypr/%s/.socket.sock", c.Signature)
return fmt.Sprintf("/run/user/1000/hypr/%s/.socket.sock", c.Signature)
}
func (c Client) EventSocketPath() string {
return fmt.Sprintf("/tmp/hypr/%s/.socket2.sock", c.Signature)
return fmt.Sprintf("/run/user/1000/hypr/%s/.socket2.sock", c.Signature)
}

View File

@ -18,9 +18,8 @@ type Window struct {
PID int `json:"pid"`
Xwayland bool `json:"xwayland"`
Pinned bool `json:"pinned"`
Fullscreen bool `json:"fullscreen"`
FullscreenMode int `json:"fullscreenMode"`
FakeFullscreen bool `json:"fakeFullscreen"`
Fullscreen FullscreenState `json:"fullscreen"`
FullscreenClient FullscreenState `json:"fullscreenClient"`
Grouped []interface{} `json:"grouped"` // TODO
Swallowing string `json:"swallowing"`
FocusHistoryID int `json:"focusHistoryID"`
@ -30,3 +29,13 @@ func (w Window) String() string {
data, _ := json.MarshalIndent(w, "", "\t")
return string(data)
}
type FullscreenState int
const (
FullscreenCurrent FullscreenState = -1
FullscreenNone FullscreenState = 0
FullscreenMaximize FullscreenState = 1
FullscreenFullscreen FullscreenState = 2
FullscreenMaximizeFullscreen FullscreenState = 3
)

View File

@ -16,6 +16,7 @@ func (i *Client) Subscribe(ctx context.Context, events ...EventType) (<-chan Eve
out := make(chan Event, 10)
eventMap := slices.ToStructMap(events)
allEvents := len(events) == 0
go func() {
defer r.Close()
@ -25,7 +26,7 @@ func (i *Client) Subscribe(ctx context.Context, events ...EventType) (<-chan Eve
for ctx.Err() == nil && sc.Scan() {
event := parseEvent(sc.Text())
if _, ok := eventMap[event.Type]; ok {
if _, ok := eventMap[event.Type]; allEvents || ok {
out <- event
}
}

View File

@ -1,9 +1,11 @@
package hypr
import (
"context"
"encoding/json"
"io"
"net"
"time"
)
func readSocketRaw(socket string, body io.Reader) (io.ReadCloser, error) {
@ -49,3 +51,23 @@ func readSocket[T any](socket string, body io.Reader) (T, error) {
return *value, nil
}
func waitFor(ctx context.Context, condition func() bool) bool {
if condition() {
return true
}
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if condition() {
return true
}
case <-ctx.Done():
return false
}
}
}