From 2dc46dc69a3e9d285abbd8e1b55867c4a317cdd3 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Sat, 31 Aug 2024 12:25:53 +0100 Subject: [PATCH 01/10] LICENSE: add @labi-le as copyright holder A good portion of this repository is based on their work, so nothing more appropriate than giving proper credits. Also, since the original license is MIT, this seems to be a compliant way to be compatible with the original license too: https://opensource.stackexchange.com/a/5488. --- LICENSE | 1 + 1 file changed, 1 insertion(+) diff --git a/LICENSE b/LICENSE index e069067..d78b33c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2024 Thiago Kenji Okada +Copyright (c) 2024 labi-le Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From ca14584486aa7ce705cd0b48988f28e31a2709c9 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Sat, 31 Aug 2024 12:34:03 +0100 Subject: [PATCH 02/10] event: create a separate package --- event.go => event/event.go | 8 ++++++-- event_test.go => event/event_test.go | 0 event_types.go => event/event_types.go | 0 internal/helpers/helpers.go | 25 +++++++++++++++++++++++++ request.go | 22 ++-------------------- 5 files changed, 33 insertions(+), 22 deletions(-) rename event.go => event/event.go (90%) rename event_test.go => event/event_test.go (100%) rename event_types.go => event/event_types.go (100%) create mode 100644 internal/helpers/helpers.go diff --git a/event.go b/event/event.go similarity index 90% rename from event.go rename to event/event.go index bb356fc..84893fa 100644 --- a/event.go +++ b/event/event.go @@ -6,9 +6,13 @@ import ( "strings" "github.com/thiagokokada/hyprland-go/internal/assert" + "github.com/thiagokokada/hyprland-go/internal/helpers" ) -const sep = ">>" +const ( + bufSize = 8192 + sep = ">>" +) // Initiate a new client or panic. // This should be the preferred method for user scripts, since it will @@ -18,7 +22,7 @@ const sep = ">>" // will not panic on error, use [NewEventClient] instead. // Experimental: WIP func MustEventClient() *EventClient { - return assert.Must1(NewEventClient(mustSocket(".socket2.sock"))) + return assert.Must1(NewEventClient(helpers.MustSocket(".socket2.sock"))) } // Initiate a new event client. diff --git a/event_test.go b/event/event_test.go similarity index 100% rename from event_test.go rename to event/event_test.go diff --git a/event_types.go b/event/event_types.go similarity index 100% rename from event_types.go rename to event/event_types.go diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go new file mode 100644 index 0000000..5551485 --- /dev/null +++ b/internal/helpers/helpers.go @@ -0,0 +1,25 @@ +package helpers + +import ( + "os" + "os/user" + "path/filepath" + + "github.com/thiagokokada/hyprland-go/internal/assert" +) + +// Returns a Hyprland socket or panics. +func MustSocket(socket string) string { + his := os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") + if his == "" { + panic("HYPRLAND_INSTANCE_SIGNATURE is empty, are you using Hyprland?") + } + + // https://github.com/hyprwm/Hyprland/blob/83a5395eaa99fecef777827fff1de486c06b6180/hyprctl/main.cpp#L53-L62 + runtimeDir := os.Getenv("XDG_RUNTIME_DIR") + if runtimeDir == "" { + user := assert.Must1(user.Current()).Uid + runtimeDir = filepath.Join("/run/user", user) + } + return filepath.Join(runtimeDir, "hypr", his, socket) +} diff --git a/request.go b/request.go index e7726c4..bc48ea6 100644 --- a/request.go +++ b/request.go @@ -8,12 +8,9 @@ import ( "fmt" "io" "net" - "os" - "os/user" - "path/filepath" "strings" - "github.com/thiagokokada/hyprland-go/internal/assert" + "github.com/thiagokokada/hyprland-go/internal/helpers" ) const ( @@ -207,21 +204,6 @@ func (c *RequestClient) doRequest(command string, params ...string) (response Ra return buf.Bytes(), nil } -func mustSocket(socket string) string { - his := os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") - if his == "" { - panic("HYPRLAND_INSTANCE_SIGNATURE is empty, are you using Hyprland?") - } - - // https://github.com/hyprwm/Hyprland/blob/83a5395eaa99fecef777827fff1de486c06b6180/hyprctl/main.cpp#L53-L62 - runtimeDir := os.Getenv("XDG_RUNTIME_DIR") - if runtimeDir == "" { - user := assert.Must1(user.Current()).Uid - runtimeDir = filepath.Join("/run/user", user) - } - return filepath.Join(runtimeDir, "hypr", his, socket) -} - // Initiate a new client or panic. // This should be the preferred method for user scripts, since it will // automatically find the proper socket to connect and use the @@ -229,7 +211,7 @@ func mustSocket(socket string) string { // If you need to connect to arbitrary user instances or need a method that // will not panic on error, use [NewClient] instead. func MustClient() *RequestClient { - return NewClient(mustSocket(".socket.sock")) + return NewClient(helpers.MustSocket(".socket.sock")) } // Initiate a new client. From 5ef89fc673e066247aeb5249764fbeaff910fbfa Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Sat, 31 Aug 2024 12:47:17 +0100 Subject: [PATCH 03/10] event: import event code from labi-le/hyprland-ipc-client --- event/event.go | 118 +++++++++++++++++++++++++++++++++++- event/event_dummy.go | 21 +++++++ event/event_test.go | 115 ++++++++++++++++++++++++++++++++--- event/event_types.go | 141 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 379 insertions(+), 16 deletions(-) create mode 100644 event/event_dummy.go diff --git a/event/event.go b/event/event.go index 84893fa..b2c5738 100644 --- a/event/event.go +++ b/event/event.go @@ -1,4 +1,4 @@ -package hyprland +package event import ( "fmt" @@ -69,3 +69,119 @@ func (c *EventClient) Receive() ([]ReceivedData, error) { return recv, nil } + +func (c *EventClient) Subscribe(ev EventHandler, events ...EventType) error { + for { + msg, err := c.Receive() + if err != nil { + return err + } + + for _, data := range msg { + processEvent(ev, data, events) + } + } +} + +func processEvent(ev EventHandler, msg ReceivedData, events []EventType) { + for _, event := range events { + raw := strings.Split(string(msg.Data), ",") + if msg.Type == event { + switch event { + case EventWorkspace: + // e.g. "1" (workspace number) + ev.Workspace(WorkspaceName(raw[0])) + break + case EventFocusedMonitor: + // idk + ev.FocusedMonitor(FocusedMonitor{ + MonitorName: MonitorName(raw[0]), + WorkspaceName: WorkspaceName(raw[1]), + }) + break + case EventActiveWindow: + // e.g. jetbrains-goland,hyprland-ipc-ipc – main.go + ev.ActiveWindow(ActiveWindow{ + Name: raw[0], + Title: raw[1], + }) + + break + case EventFullscreen: + // e.g. "true" or "false" + ev.Fullscreen(raw[0] == "1") + break + case EventMonitorRemoved: + // e.g. idk + ev.MonitorRemoved(MonitorName(raw[0])) + break + case EventMonitorAdded: + // e.g. idk + ev.MonitorAdded(MonitorName(raw[0])) + break + case EventCreateWorkspace: + // e.g. "1" (workspace number) + ev.CreateWorkspace(WorkspaceName(raw[0])) + break + case EventDestroyWorkspace: + // e.g. "1" (workspace number) + ev.DestroyWorkspace(WorkspaceName(raw[0])) + break + case EventMoveWorkspace: + // e.g. idk + ev.MoveWorkspace(MoveWorkspace{ + WorkspaceName: WorkspaceName(raw[0]), + MonitorName: MonitorName(raw[1]), + }) + break + case EventActiveLayout: + // e.g. AT Translated Set 2 keyboard,Russian + ev.ActiveLayout(ActiveLayout{ + Type: raw[0], + Name: raw[1], + }) + break + case EventOpenWindow: + // e.g. 80864f60,1,Alacritty,Alacritty + ev.OpenWindow(OpenWindow{ + Address: raw[0], + WorkspaceName: WorkspaceName(raw[1]), + Class: raw[2], + Title: raw[3], + }) + break + case EventCloseWindow: + // e.g. 5 + ev.CloseWindow(CloseWindow{ + Address: raw[0], + }) + break + case EventMoveWindow: + // e.g. 5 + ev.MoveWindow(MoveWindow{ + Address: raw[0], + WorkspaceName: WorkspaceName(raw[1]), + }) + break + case EventOpenLayer: + // e.g. wofi + ev.OpenLayer(OpenLayer(raw[0])) + break + case EventCloseLayer: + // e.g. wofi + ev.CloseLayer(CloseLayer(raw[0])) + break + case EventSubMap: + // e.g. idk + ev.SubMap(SubMap(raw[0])) + break + case EventScreencast: + ev.Screencast(Screencast{ + Sharing: raw[0] == "1", + Owner: raw[1], + }) + break + } + } + } +} diff --git a/event/event_dummy.go b/event/event_dummy.go new file mode 100644 index 0000000..b5780c3 --- /dev/null +++ b/event/event_dummy.go @@ -0,0 +1,21 @@ +package event + +type DummyEvHandler struct{} + +func (e *DummyEvHandler) Workspace(WorkspaceName) {} +func (e *DummyEvHandler) FocusedMonitor(FocusedMonitor) {} +func (e *DummyEvHandler) ActiveWindow(ActiveWindow) {} +func (e *DummyEvHandler) Fullscreen(bool) {} +func (e *DummyEvHandler) MonitorRemoved(MonitorName) {} +func (e *DummyEvHandler) MonitorAdded(MonitorName) {} +func (e *DummyEvHandler) CreateWorkspace(WorkspaceName) {} +func (e *DummyEvHandler) DestroyWorkspace(WorkspaceName) {} +func (e *DummyEvHandler) MoveWorkspace(MoveWorkspace) {} +func (e *DummyEvHandler) ActiveLayout(ActiveLayout) {} +func (e *DummyEvHandler) OpenWindow(OpenWindow) {} +func (e *DummyEvHandler) CloseWindow(CloseWindow) {} +func (e *DummyEvHandler) MoveWindow(MoveWindow) {} +func (e *DummyEvHandler) OpenLayer(OpenLayer) {} +func (e *DummyEvHandler) CloseLayer(CloseLayer) {} +func (e *DummyEvHandler) SubMap(SubMap) {} +func (e *DummyEvHandler) Screencast(Screencast) {} diff --git a/event/event_test.go b/event/event_test.go index a182141..b3b56b3 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -1,24 +1,119 @@ -package hyprland +package event import ( - "os" "testing" ) var ec *EventClient -func init() { - if os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "" { - ec = MustEventClient() - } +var ( + h = &FakeEventHandler{} + c = &FakeEventClient{} +) + +type FakeEventClient struct { + EventClient } -func TestReceive(t *testing.T) { - t.Skip("temporary disabled") +type FakeEventHandler struct { + DummyEvHandler +} + +func (f *FakeEventClient) Receive() ([]ReceivedData, error) { + return []ReceivedData{ + { + Type: EventWorkspace, + Data: "1", + }, + { + Type: EventFocusedMonitor, + Data: "1,1", + // TODO I only have one monitor, so I didn't check this + }, + { + Type: EventActiveWindow, + Data: "jetbrains-goland,hyprland-ipc-ipc – ipc.go", + }, + { + Type: EventFullscreen, + Data: "1", + }, + { + Type: EventMonitorRemoved, + Data: "1", + // TODO I only have one monitor, so I didn't check this + }, + { + Type: EventMonitorAdded, + Data: "1", + // TODO I only have one monitor, so I didn't check this + }, + { + Type: EventCreateWorkspace, + Data: "1", + }, + { + Type: EventDestroyWorkspace, + Data: "1", + }, + + { + Type: EventMoveWorkspace, + Data: "1,1", + // TODO I only have one monitor, so I didn't check this + }, + { + Type: EventActiveLayout, + Data: "AT Translated Set 2 keyboard,Russian", + }, + { + Type: EventOpenWindow, + Data: "80e62df0,2,jetbrains-goland,win430", + }, + { + Type: EventCloseWindow, + Data: "80e62df0", + }, + { + Type: EventMoveWindow, + Data: "80e62df0,1", + }, + { + Type: EventOpenLayer, + Data: "wofi", + }, + { + Type: EventCloseLayer, + Data: "wofi", + }, + { + Type: EventSubMap, + Data: "1", + // idk + }, + { + Type: EventScreencast, + Data: "1,0", + }, + }, nil +} - msg, err := ec.Receive() +func TestSubscribe(t *testing.T) { + err := c.SubscribeWithoutLoop(h, GetAllEvents()...) if err != nil { t.Error(err) } - t.Log(msg) +} + +func (c *FakeEventClient) SubscribeWithoutLoop(ev EventHandler, events ...EventType) error { + msg, err := c.Receive() + if err != nil { + return err + } + + for _, data := range msg { + processEvent(ev, data, events) + } + + return nil } diff --git a/event/event_types.go b/event/event_types.go index 25c903f..08909a5 100644 --- a/event/event_types.go +++ b/event/event_types.go @@ -1,7 +1,13 @@ -package hyprland +package event import "net" +// EventClient is the event struct from hyprland-go. +// Experimental: WIP +type EventClient struct { + conn net.Conn +} + type RawData string type EventType string @@ -11,8 +17,133 @@ type ReceivedData struct { Data RawData } -// EventClient is the event struct from hyprland-go. -// Experimental: WIP -type EventClient struct { - conn net.Conn +// EventHandler Hyprland will write to each connected ipc live events like this: +type EventHandler interface { + // Workspace emitted on workspace change. Is emitted ONLY when a user requests a workspace change, and is not emitted on mouse movements (see activemon) + Workspace(w WorkspaceName) + // FocusedMonitor emitted on the active monitor being changed. + FocusedMonitor(m FocusedMonitor) + // ActiveWindow emitted on the active window being changed. + ActiveWindow(w ActiveWindow) + // Fullscreen emitted when a fullscreen status of a window changes. + Fullscreen(f bool) + // MonitorRemoved emitted when a monitor is removed (disconnected) + MonitorRemoved(m MonitorName) + // MonitorAdded emitted when a monitor is added (connected) + MonitorAdded(m MonitorName) + // CreateWorkspace emitted when a workspace is created + CreateWorkspace(w WorkspaceName) + // DestroyWorkspace emitted when a workspace is destroyed + DestroyWorkspace(w WorkspaceName) + // MoveWorkspace emitted when a workspace is moved to a different monitor + MoveWorkspace(w MoveWorkspace) + // ActiveLayout emitted on a layout change of the active keyboard + ActiveLayout(l ActiveLayout) + // OpenWindow emitted when a window is opened + OpenWindow(o OpenWindow) + // CloseWindow emitted when a window is closed + CloseWindow(c CloseWindow) + // MoveWindow emitted when a window is moved to a workspace + MoveWindow(m MoveWindow) + // OpenLayer emitted when a layerSurface is mapped + OpenLayer(l OpenLayer) + // CloseLayer emitted when a layerSurface is unmapped + CloseLayer(c CloseLayer) + // SubMap emitted when a keybind submap changes. Empty means default. + SubMap(s SubMap) + Screencast(s Screencast) +} + +const ( + EventWorkspace EventType = "workspace" + EventFocusedMonitor EventType = "focusedmon" + EventActiveWindow EventType = "activewindow" + EventFullscreen EventType = "fullscreen" + EventMonitorRemoved EventType = "monitorremoved" + EventMonitorAdded EventType = "monitoradded" + EventCreateWorkspace EventType = "createworkspace" + EventDestroyWorkspace EventType = "destroyworkspace" + EventMoveWorkspace EventType = "moveworkspace" + EventActiveLayout EventType = "activelayout" + EventOpenWindow EventType = "openwindow" + EventCloseWindow EventType = "closewindow" + EventMoveWindow EventType = "movewindow" + EventOpenLayer EventType = "openlayer" + EventCloseLayer EventType = "closelayer" + EventSubMap EventType = "submap" + EventScreencast EventType = "screencast" +) + +func GetAllEvents() []EventType { + return []EventType{ + EventWorkspace, + EventFocusedMonitor, + EventActiveWindow, + EventFullscreen, + EventMonitorRemoved, + EventMonitorAdded, + EventCreateWorkspace, + EventDestroyWorkspace, + EventMoveWorkspace, + EventActiveLayout, + EventOpenWindow, + EventCloseWindow, + EventMoveWindow, + EventOpenLayer, + EventCloseLayer, + EventSubMap, + EventScreencast, + } +} + +type MoveWorkspace struct { + WorkspaceName + MonitorName +} + +type MonitorName string + +type FocusedMonitor struct { + MonitorName + WorkspaceName +} + +type WorkspaceName string + +type SubMap string + +type CloseLayer string + +type OpenLayer string + +type MoveWindow struct { + Address string + WorkspaceName +} + +type CloseWindow struct { + Address string +} + +type OpenWindow struct { + Address, Class, Title string + WorkspaceName +} + +type ActiveLayout struct { + Type, Name string +} + +type ActiveWindow struct { + Name, Title string +} + +type ActiveWorkspace WorkspaceName + +type Screencast struct { + // True if a screen or window is being shared. + Sharing bool + + // "0" if monitor is shared, "1" if window is shared. + Owner string } From 6f039d671ff8c640a46ff0c085fd523fa98a3946 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Sat, 31 Aug 2024 12:59:42 +0100 Subject: [PATCH 04/10] event_noop: rename from event_dummy --- event/event_dummy.go | 21 --------------------- event/event_noop.go | 24 ++++++++++++++++++++++++ event/event_test.go | 2 +- 3 files changed, 25 insertions(+), 22 deletions(-) delete mode 100644 event/event_dummy.go create mode 100644 event/event_noop.go diff --git a/event/event_dummy.go b/event/event_dummy.go deleted file mode 100644 index b5780c3..0000000 --- a/event/event_dummy.go +++ /dev/null @@ -1,21 +0,0 @@ -package event - -type DummyEvHandler struct{} - -func (e *DummyEvHandler) Workspace(WorkspaceName) {} -func (e *DummyEvHandler) FocusedMonitor(FocusedMonitor) {} -func (e *DummyEvHandler) ActiveWindow(ActiveWindow) {} -func (e *DummyEvHandler) Fullscreen(bool) {} -func (e *DummyEvHandler) MonitorRemoved(MonitorName) {} -func (e *DummyEvHandler) MonitorAdded(MonitorName) {} -func (e *DummyEvHandler) CreateWorkspace(WorkspaceName) {} -func (e *DummyEvHandler) DestroyWorkspace(WorkspaceName) {} -func (e *DummyEvHandler) MoveWorkspace(MoveWorkspace) {} -func (e *DummyEvHandler) ActiveLayout(ActiveLayout) {} -func (e *DummyEvHandler) OpenWindow(OpenWindow) {} -func (e *DummyEvHandler) CloseWindow(CloseWindow) {} -func (e *DummyEvHandler) MoveWindow(MoveWindow) {} -func (e *DummyEvHandler) OpenLayer(OpenLayer) {} -func (e *DummyEvHandler) CloseLayer(CloseLayer) {} -func (e *DummyEvHandler) SubMap(SubMap) {} -func (e *DummyEvHandler) Screencast(Screencast) {} diff --git a/event/event_noop.go b/event/event_noop.go new file mode 100644 index 0000000..b8dd0cc --- /dev/null +++ b/event/event_noop.go @@ -0,0 +1,24 @@ +package event + +// NoopEventHandler is an implementation of [EventHandler] interface with all +// methods doing nothing. It is a good starting point to be embedded your own +// struct to be extended. +type NoopEventHandler struct{} + +func (e *NoopEventHandler) Workspace(WorkspaceName) {} +func (e *NoopEventHandler) FocusedMonitor(FocusedMonitor) {} +func (e *NoopEventHandler) ActiveWindow(ActiveWindow) {} +func (e *NoopEventHandler) Fullscreen(bool) {} +func (e *NoopEventHandler) MonitorRemoved(MonitorName) {} +func (e *NoopEventHandler) MonitorAdded(MonitorName) {} +func (e *NoopEventHandler) CreateWorkspace(WorkspaceName) {} +func (e *NoopEventHandler) DestroyWorkspace(WorkspaceName) {} +func (e *NoopEventHandler) MoveWorkspace(MoveWorkspace) {} +func (e *NoopEventHandler) ActiveLayout(ActiveLayout) {} +func (e *NoopEventHandler) OpenWindow(OpenWindow) {} +func (e *NoopEventHandler) CloseWindow(CloseWindow) {} +func (e *NoopEventHandler) MoveWindow(MoveWindow) {} +func (e *NoopEventHandler) OpenLayer(OpenLayer) {} +func (e *NoopEventHandler) CloseLayer(CloseLayer) {} +func (e *NoopEventHandler) SubMap(SubMap) {} +func (e *NoopEventHandler) Screencast(Screencast) {} diff --git a/event/event_test.go b/event/event_test.go index b3b56b3..7cc8da9 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -16,7 +16,7 @@ type FakeEventClient struct { } type FakeEventHandler struct { - DummyEvHandler + NoopEventHandler } func (f *FakeEventClient) Receive() ([]ReceivedData, error) { From 82603a48cb1bf5c70d3720919ee51fe5b32fe9f6 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Sat, 31 Aug 2024 13:02:10 +0100 Subject: [PATCH 05/10] event: remove WIP --- event/event.go | 6 +++--- event/event_types.go | 34 ++++++++++++++++++++-------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/event/event.go b/event/event.go index b2c5738..742e418 100644 --- a/event/event.go +++ b/event/event.go @@ -20,7 +20,6 @@ const ( // HYPRLAND_INSTANCE_SIGNATURE for the current user. // If you need to connect to arbitrary user instances or need a method that // will not panic on error, use [NewEventClient] instead. -// Experimental: WIP func MustEventClient() *EventClient { return assert.Must1(NewEventClient(helpers.MustSocket(".socket2.sock"))) } @@ -28,7 +27,6 @@ func MustEventClient() *EventClient { // Initiate a new event client. // Receive as parameters a socket that is generally localised in // '$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock'. -// Experimental: WIP func NewEventClient(socket string) (*EventClient, error) { conn, err := net.Dial("unix", socket) if err != nil { @@ -39,7 +37,6 @@ func NewEventClient(socket string) (*EventClient, error) { // Low-level receive event method, should be avoided unless there is no // alternative. -// Experimental: WIP func (c *EventClient) Receive() ([]ReceivedData, error) { buf := make([]byte, bufSize) n, err := c.conn.Read(buf) @@ -70,6 +67,9 @@ func (c *EventClient) Receive() ([]ReceivedData, error) { return recv, nil } +// Subscribe to events. +// You need to pass an implementation of [EventHandler] interface for each of +// the events you want to handle and all event types you want to handle. func (c *EventClient) Subscribe(ev EventHandler, events ...EventType) error { for { msg, err := c.Receive() diff --git a/event/event_types.go b/event/event_types.go index 08909a5..dcf8c89 100644 --- a/event/event_types.go +++ b/event/event_types.go @@ -3,7 +3,6 @@ package event import "net" // EventClient is the event struct from hyprland-go. -// Experimental: WIP type EventClient struct { conn net.Conn } @@ -17,9 +16,13 @@ type ReceivedData struct { Data RawData } -// EventHandler Hyprland will write to each connected ipc live events like this: +// EventHandler is the interface that defines all methods to handle each of +// events emitted by Hyprland. +// You can find move information about each event in the main Hyprland Wiki: +// https://wiki.hyprland.org/Plugins/Development/Event-list/. type EventHandler interface { - // Workspace emitted on workspace change. Is emitted ONLY when a user requests a workspace change, and is not emitted on mouse movements (see activemon) + // Workspace emitted on workspace change. Is emitted ONLY when a user + // requests a workspace change, and is not emitted on mouse movements. Workspace(w WorkspaceName) // FocusedMonitor emitted on the active monitor being changed. FocusedMonitor(m FocusedMonitor) @@ -27,30 +30,33 @@ type EventHandler interface { ActiveWindow(w ActiveWindow) // Fullscreen emitted when a fullscreen status of a window changes. Fullscreen(f bool) - // MonitorRemoved emitted when a monitor is removed (disconnected) + // MonitorRemoved emitted when a monitor is removed (disconnected). MonitorRemoved(m MonitorName) - // MonitorAdded emitted when a monitor is added (connected) + // MonitorAdded emitted when a monitor is added (connected). MonitorAdded(m MonitorName) - // CreateWorkspace emitted when a workspace is created + // CreateWorkspace emitted when a workspace is created. CreateWorkspace(w WorkspaceName) - // DestroyWorkspace emitted when a workspace is destroyed + // DestroyWorkspace emitted when a workspace is destroyed. DestroyWorkspace(w WorkspaceName) - // MoveWorkspace emitted when a workspace is moved to a different monitor + // MoveWorkspace emitted when a workspace is moved to a different + // monitor. MoveWorkspace(w MoveWorkspace) - // ActiveLayout emitted on a layout change of the active keyboard + // ActiveLayout emitted on a layout change of the active keyboard. ActiveLayout(l ActiveLayout) - // OpenWindow emitted when a window is opened + // OpenWindow emitted when a window is opened. OpenWindow(o OpenWindow) - // CloseWindow emitted when a window is closed + // CloseWindow emitted when a window is closed. CloseWindow(c CloseWindow) - // MoveWindow emitted when a window is moved to a workspace + // MoveWindow emitted when a window is moved to a workspace. MoveWindow(m MoveWindow) - // OpenLayer emitted when a layerSurface is mapped + // OpenLayer emitted when a layerSurface is mapped. OpenLayer(l OpenLayer) - // CloseLayer emitted when a layerSurface is unmapped + // CloseLayer emitted when a layerSurface is unmapped. CloseLayer(c CloseLayer) // SubMap emitted when a keybind submap changes. Empty means default. SubMap(s SubMap) + // Screencast is fired when the screencopy state of a client changes. + // Keep in mind there might be multiple separate clients. Screencast(s Screencast) } From 2911f0f191c78c5fa347e6dc135860518e1e7371 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Sat, 31 Aug 2024 13:13:14 +0100 Subject: [PATCH 06/10] event: make EventClient a parameter of Subscribe --- event/event.go | 2 +- event/event_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/event/event.go b/event/event.go index 742e418..b8cf95a 100644 --- a/event/event.go +++ b/event/event.go @@ -70,7 +70,7 @@ func (c *EventClient) Receive() ([]ReceivedData, error) { // Subscribe to events. // You need to pass an implementation of [EventHandler] interface for each of // the events you want to handle and all event types you want to handle. -func (c *EventClient) Subscribe(ev EventHandler, events ...EventType) error { +func Subscribe(c *EventClient, ev EventHandler, events ...EventType) error { for { msg, err := c.Receive() if err != nil { diff --git a/event/event_test.go b/event/event_test.go index 7cc8da9..fdaebbf 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -99,13 +99,13 @@ func (f *FakeEventClient) Receive() ([]ReceivedData, error) { } func TestSubscribe(t *testing.T) { - err := c.SubscribeWithoutLoop(h, GetAllEvents()...) + err := SubscribeWithoutLoop(*c, h, GetAllEvents()...) if err != nil { t.Error(err) } } -func (c *FakeEventClient) SubscribeWithoutLoop(ev EventHandler, events ...EventType) error { +func SubscribeWithoutLoop(c FakeEventClient, ev EventHandler, events ...EventType) error { msg, err := c.Receive() if err != nil { return err From 775c0f580b527d3b19af52e4fa512599c6c76fb2 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Sat, 31 Aug 2024 13:25:10 +0100 Subject: [PATCH 07/10] examples/events: init --- examples/events/events.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 examples/events/events.go diff --git a/examples/events/events.go b/examples/events/events.go new file mode 100644 index 0000000..c44e262 --- /dev/null +++ b/examples/events/events.go @@ -0,0 +1,30 @@ +// Basic example on how to handle events in hyprland-go. +package main + +import ( + "fmt" + + "github.com/thiagokokada/hyprland-go/event" +) + +type ev struct { + event.NoopEventHandler +} + +func (e *ev) Workspace(w event.WorkspaceName) { + fmt.Printf("Workspace: %+v\n", w) +} + +func (e *ev) ActiveWindow(w event.ActiveWindow) { + fmt.Printf("ActiveWindow: %+v\n", w) +} + +func main() { + c := event.MustEventClient() + event.Subscribe( + c, &ev{}, + event.EventWorkspace, + event.EventActiveWindow, + ) + +} From ba855586ae918377002c2a0381255b9d0fed69d2 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Sat, 31 Aug 2024 13:32:20 +0100 Subject: [PATCH 08/10] README.md: update to add events --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 23b37dd..d9ec96e 100644 --- a/README.md +++ b/README.md @@ -84,10 +84,9 @@ library. for general usage, sending commands directly to the IPC socket of Hyprland is supported for i.e.: performance, e.g.: `c.RawRequest("[[BATCH]] dispatch exec kitty, keyword general:border_size 1")` - -## Planned - -- [Events](https://wiki.hyprland.org/Plugins/Development/Event-list/) +- [Events:](https://wiki.hyprland.org/Plugins/Development/Event-list/) to + subscribe and handle Hyprland events, see + [events](./examples/events/events.go) for an example on how to use it. ## Development From e2b70b5aa15ed531c1d14f6e5b37d084c031fb6c Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Sat, 31 Aug 2024 13:57:43 +0100 Subject: [PATCH 09/10] event: use bufio.NewReader --- event/event.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/event/event.go b/event/event.go index b8cf95a..78b10f3 100644 --- a/event/event.go +++ b/event/event.go @@ -1,6 +1,7 @@ package event import ( + "bufio" "fmt" "net" "strings" @@ -39,7 +40,8 @@ func NewEventClient(socket string) (*EventClient, error) { // alternative. func (c *EventClient) Receive() ([]ReceivedData, error) { buf := make([]byte, bufSize) - n, err := c.conn.Read(buf) + reader := bufio.NewReader(c.conn) + n, err := reader.Read(buf) if err != nil { return nil, err } From 9d3c4e0a5eba8a5fc65003e9ae64a2f75775266f Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Sat, 31 Aug 2024 13:59:46 +0100 Subject: [PATCH 10/10] event: fix staticcheck warns --- event/event.go | 18 ------------------ event/event_test.go | 2 -- 2 files changed, 20 deletions(-) diff --git a/event/event.go b/event/event.go index 78b10f3..3ec8011 100644 --- a/event/event.go +++ b/event/event.go @@ -93,56 +93,45 @@ func processEvent(ev EventHandler, msg ReceivedData, events []EventType) { case EventWorkspace: // e.g. "1" (workspace number) ev.Workspace(WorkspaceName(raw[0])) - break case EventFocusedMonitor: // idk ev.FocusedMonitor(FocusedMonitor{ MonitorName: MonitorName(raw[0]), WorkspaceName: WorkspaceName(raw[1]), }) - break case EventActiveWindow: // e.g. jetbrains-goland,hyprland-ipc-ipc – main.go ev.ActiveWindow(ActiveWindow{ Name: raw[0], Title: raw[1], }) - - break case EventFullscreen: // e.g. "true" or "false" ev.Fullscreen(raw[0] == "1") - break case EventMonitorRemoved: // e.g. idk ev.MonitorRemoved(MonitorName(raw[0])) - break case EventMonitorAdded: // e.g. idk ev.MonitorAdded(MonitorName(raw[0])) - break case EventCreateWorkspace: // e.g. "1" (workspace number) ev.CreateWorkspace(WorkspaceName(raw[0])) - break case EventDestroyWorkspace: // e.g. "1" (workspace number) ev.DestroyWorkspace(WorkspaceName(raw[0])) - break case EventMoveWorkspace: // e.g. idk ev.MoveWorkspace(MoveWorkspace{ WorkspaceName: WorkspaceName(raw[0]), MonitorName: MonitorName(raw[1]), }) - break case EventActiveLayout: // e.g. AT Translated Set 2 keyboard,Russian ev.ActiveLayout(ActiveLayout{ Type: raw[0], Name: raw[1], }) - break case EventOpenWindow: // e.g. 80864f60,1,Alacritty,Alacritty ev.OpenWindow(OpenWindow{ @@ -151,38 +140,31 @@ func processEvent(ev EventHandler, msg ReceivedData, events []EventType) { Class: raw[2], Title: raw[3], }) - break case EventCloseWindow: // e.g. 5 ev.CloseWindow(CloseWindow{ Address: raw[0], }) - break case EventMoveWindow: // e.g. 5 ev.MoveWindow(MoveWindow{ Address: raw[0], WorkspaceName: WorkspaceName(raw[1]), }) - break case EventOpenLayer: // e.g. wofi ev.OpenLayer(OpenLayer(raw[0])) - break case EventCloseLayer: // e.g. wofi ev.CloseLayer(CloseLayer(raw[0])) - break case EventSubMap: // e.g. idk ev.SubMap(SubMap(raw[0])) - break case EventScreencast: ev.Screencast(Screencast{ Sharing: raw[0] == "1", Owner: raw[1], }) - break } } } diff --git a/event/event_test.go b/event/event_test.go index fdaebbf..a67a7d7 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -4,8 +4,6 @@ import ( "testing" ) -var ec *EventClient - var ( h = &FakeEventHandler{} c = &FakeEventClient{}