package sway import ( "encoding/binary" "encoding/json" "errors" "fmt" "io" "net" "os" "sync" ) // Client is a single connection to a sway socket. // All requests are synchronized in order to be thread-safe. // That means they are processed in incoming order. // If you want to be truly concurrent, use multiple clients // connected to the same sway socket path. // Subscriptions are the exception: They use their own socket connection // so other requests are still possible. // Subscription connections get closed as soon as the provided context is done. // Use Client.Close after all requests are processed // to close the connection to the socket. // Client.Close does not handle subscriptions. type Client struct { sync.Mutex socket string conn net.Conn } // GetDefaultClient returns a sway client for the current seat. // It determines the current seat by the SWAYSOCK environment variable func GetDefaultClient() (*Client, error) { socket, ok := os.LookupEnv("SWAYSOCK") if !ok { return nil, errors.New("could not find sway socket. is $SWAYSOCK set properly?") } return GetClientBySocket(socket) } // GetClientBySocket returns a sway client for the provided socket. func GetClientBySocket(socket string) (*Client, error) { conn, err := net.Dial("unix", socket) if err != nil { return nil, err } return &Client{ socket: socket, conn: conn, }, nil } // Close closes the socket connection. // All requests will return errors after calling Close func (c *Client) Close() error { c.Lock() defer c.Unlock() return c.conn.Close() } func sendMessage[T any](client *Client, messageType uint32, payload string) (T, error) { client.Lock() defer client.Unlock() if _, err := fmt.Fprint(client.conn, "i3-ipc"); err != nil { return *new(T), err } if err := binary.Write(client.conn, binary.LittleEndian, uint32(len(payload))); err != nil { return *new(T), err } if err := binary.Write(client.conn, binary.LittleEndian, messageType); err != nil { return *new(T), err } if _, err := fmt.Fprint(client.conn, payload); err != nil { return *new(T), err } if _, err := client.conn.Read(make([]byte, 6)); err != nil { return *new(T), err } var length uint32 if err := binary.Read(client.conn, binary.LittleEndian, &length); err != nil { return *new(T), err } var responseType uint32 if err := binary.Read(client.conn, binary.LittleEndian, &responseType); err != nil { return *new(T), err } // io.Copy(os.Stdout, client.conn) // return *new(T), nil result := new(T) if err := json.NewDecoder(io.LimitReader(client.conn, int64(length))).Decode(result); err != nil { return *new(T), err } return *result, nil }