Skip to content

Commit

Permalink
event: add initial support for events
Browse files Browse the repository at this point in the history
  • Loading branch information
thiagokokada committed Jul 22, 2024
1 parent 3c62338 commit ea275f4
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 53 deletions.
56 changes: 56 additions & 0 deletions event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package hyprland

import (
"fmt"
"net"
"strings"
)

const SEPARATOR = ">>"

func MustEventClient() *EventClient {
return must1(NewEventClient(mustSocket(".socket2.sock")))
}

// Initiate a new event client.
// Receive as parameters a socket that is generally localised in
// '$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock'.
func NewEventClient(socket string) (*EventClient, error) {
conn, err := net.Dial("unix", socket)
if err != nil {
return nil, fmt.Errorf("error while connecting to socket: %w", err)
}
return &EventClient{ conn: conn }, nil
}

// Low-level receive event method, should be avoided unless there is no
// alternative.
func (c *EventClient) Receive() ([]ReceivedData, error) {
buf := make([]byte, BUF_SIZE)
n, err := c.conn.Read(buf)
if err != nil {
return nil, err
}

buf = buf[:n]

var recv []ReceivedData
raw := strings.Split(string(buf), "\n")
for _, event := range raw {
if event == "" {
continue
}

split := strings.Split(event, SEPARATOR)
if split[0] == "" || split[1] == "" || split[1] == "," {
continue
}

recv = append(recv, ReceivedData{
Type: EventType(split[0]),
Data: RawData(split[1]),
})
}

return recv, nil
}
26 changes: 26 additions & 0 deletions event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package hyprland

import (
"fmt"
"os"
"testing"
)

var ec *EventClient

func init() {
if os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "" {
ec = MustEventClient()
}
}

func TestReceive(t *testing.T) {
if ec == nil {
t.Skip("HYPRLAND_INSTANCE_SIGNATURE not set, skipping test")
}
msg, err := ec.Receive()
if err != nil {
t.Error(err)
}
fmt.Println(msg)
}
76 changes: 31 additions & 45 deletions hyprland.go → request.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func prepareRequests(command string, params []string) (requests []RawRequest) {
return requests
}

func (c *IPCClient) validateResponse(params []string, response RawResponse) error {
func (c *RequestClient) validateResponse(params []string, response RawResponse) error {
if !c.Validate {
return nil
}
Expand Down Expand Up @@ -108,7 +108,7 @@ func unmarshalResponse(response RawResponse, v any) (err error) {
return nil
}

func (c *IPCClient) doRequest(command string, params ...string) (response RawResponse, err error) {
func (c *RequestClient) doRequest(command string, params ...string) (response RawResponse, err error) {
requests := prepareRequests(command, params)

var buf bytes.Buffer
Expand All @@ -123,13 +123,7 @@ func (c *IPCClient) doRequest(command string, params ...string) (response RawRes
return buf.Bytes(), nil
}

// 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
// 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 [NewClient] instead.
func MustClient() *IPCClient {
func mustSocket(socket string) string {
his := os.Getenv("HYPRLAND_INSTANCE_SIGNATURE")
if his == "" {
panic("HYPRLAND_INSTANCE_SIGNATURE is empty, are you using Hyprland?")
Expand All @@ -141,38 +135,30 @@ func MustClient() *IPCClient {
user := must1(user.Current()).Uid
runtimeDir = filepath.Join("/run/user", user)
}
return filepath.Join(runtimeDir, "hypr", his, socket)
}

return must1(
NewClient(
filepath.Join(runtimeDir, "hypr", his, ".socket.sock"),
filepath.Join(runtimeDir, "hypr", his, ".socket2.sock"),
),
)
// 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
// 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 [NewClient] instead.
func MustClient() *RequestClient {
return NewClient(mustSocket(".socket.sock"))
}

// Initiate a new client.
// Receive as parameters a requestSocket that is generally localised in
// '$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket.sock' and
// eventSocket that is generally localised in
// '$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock'.
func NewClient(requestSocket, eventSocket string) (*IPCClient, error) {
if requestSocket == "" || eventSocket == "" {
return nil, errors.New("empty request or event socket")
}

conn, err := net.Dial("unix", eventSocket)
if err != nil {
return nil, fmt.Errorf("error while connecting to socket: %w", err)
}

return &IPCClient{
// '$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket.sock'.
func NewClient(socket string) *RequestClient {
return &RequestClient{
Validate: true,
requestConn: &net.UnixAddr{
conn: &net.UnixAddr{
Net: "unix",
Name: requestSocket,
Name: socket,
},
eventConn: conn,
}, nil
}
}

// Low-level request method, should be avoided unless there is no alternative.
Expand All @@ -181,13 +167,13 @@ func NewClient(requestSocket, eventSocket string) (*IPCClient, error) {
// '[]byte("dispatch exec kitty")'.
// Keep in mind that there is no validation. In case of an invalid request, the
// response will generally be something different from "ok".
func (c *IPCClient) Request(request RawRequest) (response RawResponse, err error) {
func (c *RequestClient) Request(request RawRequest) (response RawResponse, err error) {
if len(request) == 0 {
return nil, errors.New("empty request")
}

// Connect to the request socket
conn, err := net.DialUnix("unix", nil, c.requestConn)
conn, err := net.DialUnix("unix", nil, c.conn)
defer conn.Close()
if err != nil {
return nil, fmt.Errorf("error while connecting to socket: %w", err)
Expand Down Expand Up @@ -223,7 +209,7 @@ func (c *IPCClient) Request(request RawRequest) (response RawResponse, err error

// Get option command, similar to 'hyprctl activewindow'.
// Returns a [Window] object.
func (c *IPCClient) ActiveWindow() (w Window, err error) {
func (c *RequestClient) ActiveWindow() (w Window, err error) {
response, err := c.doRequest("activewindow")
if err != nil {
return w, err
Expand All @@ -233,7 +219,7 @@ func (c *IPCClient) ActiveWindow() (w Window, err error) {

// Get option command, similar to 'hyprctl activeworkspace'.
// Returns a [Workspace] object.
func (c *IPCClient) ActiveWorkspace() (w Workspace, err error) {
func (c *RequestClient) ActiveWorkspace() (w Workspace, err error) {
response, err := c.doRequest("activeworkspace")
if err != nil {
return w, err
Expand All @@ -243,7 +229,7 @@ func (c *IPCClient) ActiveWorkspace() (w Workspace, err error) {

// Get option command, similar to 'hyprctl clients'.
// Returns a [Client] object.
func (c *IPCClient) Clients() (cl []Client, err error) {
func (c *RequestClient) Clients() (cl []Client, err error) {
response, err := c.doRequest("clients")
if err != nil {
return cl, err
Expand All @@ -253,7 +239,7 @@ func (c *IPCClient) Clients() (cl []Client, err error) {

// Get option command, similar to 'hyprctl cursorpos'.
// Returns a [CursorPos] object.
func (c *IPCClient) CursorPos() (cu CursorPos, err error) {
func (c *RequestClient) CursorPos() (cu CursorPos, err error) {
response, err := c.doRequest("cursorpos")
if err != nil {
return cu, err
Expand All @@ -264,7 +250,7 @@ func (c *IPCClient) CursorPos() (cu CursorPos, err error) {
// Dispatch commands, similar to 'hyprctl dispatch'.
// Accept multiple commands at the same time, in this case it will use batch
// mode, similar to 'hyprctl dispatch --batch'.
func (c *IPCClient) Dispatch(params ...string) error {
func (c *RequestClient) Dispatch(params ...string) error {
response, err := c.doRequest("dispatch", params...)
if err != nil {
return err
Expand All @@ -274,7 +260,7 @@ func (c *IPCClient) Dispatch(params ...string) error {

// Get option command, similar to 'hyprctl getoption'.
// Returns an [Option] object.
func (c *IPCClient) GetOption(name string) (o Option, err error) {
func (c *RequestClient) GetOption(name string) (o Option, err error) {
response, err := c.doRequest("getoption", name)
if err != nil {
return o, err
Expand All @@ -284,7 +270,7 @@ func (c *IPCClient) GetOption(name string) (o Option, err error) {

// Kill command, similar to 'hyprctl kill'.
// Will NOT wait for the user to click in the window.
func (c *IPCClient) Kill() error {
func (c *RequestClient) Kill() error {
response, err := c.doRequest("kill")
if err != nil {
return err
Expand All @@ -293,7 +279,7 @@ func (c *IPCClient) Kill() error {
}

// Reload command, similar to 'hyprctl reload'.
func (c *IPCClient) Reload() error {
func (c *RequestClient) Reload() error {
response, err := c.doRequest("reload")
if err != nil {
return err
Expand All @@ -303,7 +289,7 @@ func (c *IPCClient) Reload() error {

// Get option command, similar to 'hyprctl version'.
// Returns an [Version] object.
func (c *IPCClient) Version() (v Version, err error) {
func (c *RequestClient) Version() (v Version, err error) {
response, err := c.doRequest("version")
if err != nil {
return v, err
Expand All @@ -312,7 +298,7 @@ func (c *IPCClient) Version() (v Version, err error) {
}

// Get option command, similar to 'hyprctl splash'.
func (c *IPCClient) Splash() (s string, err error) {
func (c *RequestClient) Splash() (s string, err error) {
response, err := c.doRequest("splash")
if err != nil {
return "", err
Expand Down
6 changes: 3 additions & 3 deletions hyprland_test.go → request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
"testing"
)

var c *IPCClient
var c *RequestClient

type DummyClient struct {
IPCClient
RequestClient
}

func init() {
Expand Down Expand Up @@ -55,7 +55,7 @@ func testCommand1[T any](t *testing.T, command func() (T, error), v any) {
}
}

func TestMakeRequest(t *testing.T) {
func TestPrepareRequests(t *testing.T) {
// test params
tests := []struct {
command string
Expand Down
23 changes: 18 additions & 5 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,26 @@ type RawRequest []byte

type RawResponse []byte

// IPCClient is the main struct from hyprland-go.
type RawData string

type EventType string

type ReceivedData struct {
Type EventType
Data RawData
}

// RequestClient is the main struct from hyprland-go.
// You may want to set 'Validate' as false to avoid (possibly costly)
// validations, at the expense of not reporting some errors in the IPC.
type IPCClient struct {
Validate bool
requestConn *net.UnixAddr
eventConn net.Conn
type RequestClient struct {
Validate bool
conn *net.UnixAddr
}

// EventClient is the event struct from hyprland-go.
type EventClient struct {
conn net.Conn
}

// Try to keep struct fields in the same order as the output for `hyprctl` for
Expand Down

0 comments on commit ea275f4

Please sign in to comment.