sway/client.go

109 lines
2.6 KiB
Go

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
}