diff --git a/client.go b/client.go index 37cc67f..32a059d 100644 --- a/client.go +++ b/client.go @@ -1,7 +1,11 @@ package sway import ( + "encoding/binary" + "encoding/json" "errors" + "fmt" + "io" "net" "os" "sync" @@ -41,3 +45,48 @@ func (c *Client) Close() error { 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 +} diff --git a/get_bar_config.go b/get_bar_config.go new file mode 100644 index 0000000..0656443 --- /dev/null +++ b/get_bar_config.go @@ -0,0 +1,13 @@ +package sway + +import ( + "context" +) + +func (c *Client) GetBarIDs(ctx context.Context) ([]string, error) { + return sendMessage[[]string](c, 6, "") +} + +func (c *Client) GetBarConfig(ctx context.Context, barID string) (*BarConfig, error) { + return sendMessage[*BarConfig](c, 6, barID) +} diff --git a/get_binding_modes.go b/get_binding_modes.go new file mode 100644 index 0000000..4ec7da9 --- /dev/null +++ b/get_binding_modes.go @@ -0,0 +1,9 @@ +package sway + +import ( + "context" +) + +func (c *Client) GetBindingModes(ctx context.Context) ([]string, error) { + return sendMessage[[]string](c, 8, "") +} diff --git a/get_config.go b/get_config.go new file mode 100644 index 0000000..2ed135e --- /dev/null +++ b/get_config.go @@ -0,0 +1,13 @@ +package sway + +import ( + "context" +) + +func (c *Client) GetConfig(ctx context.Context) (string, error) { + cfg, err := sendMessage[config](c, 9, "") + if err != nil { + return "", err + } + return cfg.Config, nil +} diff --git a/get_current_binding_mode.go b/get_current_binding_mode.go new file mode 100644 index 0000000..8ffeb6f --- /dev/null +++ b/get_current_binding_mode.go @@ -0,0 +1,13 @@ +package sway + +import ( + "context" +) + +func (c *Client) GetCurrentBindingMode(ctx context.Context) (string, error) { + mode, err := sendMessage[name](c, 12, "") + if err != nil { + return "", err + } + return mode.Name, nil +} diff --git a/get_marks.go b/get_marks.go new file mode 100644 index 0000000..53812b7 --- /dev/null +++ b/get_marks.go @@ -0,0 +1,9 @@ +package sway + +import ( + "context" +) + +func (c *Client) GetMarks(ctx context.Context) ([]string, error) { + return sendMessage[[]string](c, 5, "") +} diff --git a/get_outputs.go b/get_outputs.go index 5c69f92..f592ce3 100644 --- a/get_outputs.go +++ b/get_outputs.go @@ -2,46 +2,8 @@ package sway import ( "context" - "encoding/binary" - "encoding/json" - "fmt" - "io" ) func (c *Client) GetOutputs(ctx context.Context) ([]Output, error) { - c.Lock() - defer c.Unlock() - - if _, err := fmt.Fprint(c.conn, "i3-ipc"); err != nil { - return nil, err - } - - if err := binary.Write(c.conn, binary.LittleEndian, uint32(0)); err != nil { - return nil, err - } - - if err := binary.Write(c.conn, binary.LittleEndian, uint32(3)); err != nil { - return nil, err - } - - if _, err := c.conn.Read(make([]byte, 6)); err != nil { - return nil, err - } - - var length uint32 - if err := binary.Read(c.conn, binary.LittleEndian, &length); err != nil { - return nil, err - } - - var messageType uint32 - if err := binary.Read(c.conn, binary.LittleEndian, &messageType); err != nil { - return nil, err - } - - results := []Output{} - if err := json.NewDecoder(io.LimitReader(c.conn, int64(length))).Decode(&results); err != nil { - return nil, err - } - - return results, nil + return sendMessage[[]Output](c, 3, "") } diff --git a/get_tree.go b/get_tree.go new file mode 100644 index 0000000..f0dbf10 --- /dev/null +++ b/get_tree.go @@ -0,0 +1,9 @@ +package sway + +import ( + "context" +) + +func (c *Client) GetTree(ctx context.Context) (*Node, error) { + return sendMessage[*Node](c, 4, "") +} diff --git a/get_version.go b/get_version.go new file mode 100644 index 0000000..038af34 --- /dev/null +++ b/get_version.go @@ -0,0 +1,9 @@ +package sway + +import ( + "context" +) + +func (c *Client) GetVersion(ctx context.Context) (*Version, error) { + return sendMessage[*Version](c, 7, "") +} diff --git a/get_workspaces.go b/get_workspaces.go index 4e215ad..3a74ac0 100644 --- a/get_workspaces.go +++ b/get_workspaces.go @@ -2,46 +2,8 @@ package sway import ( "context" - "encoding/binary" - "encoding/json" - "fmt" - "io" ) func (c *Client) GetWorkspaces(ctx context.Context) ([]Workspace, error) { - c.Lock() - defer c.Unlock() - - if _, err := fmt.Fprint(c.conn, "i3-ipc"); err != nil { - return nil, err - } - - if err := binary.Write(c.conn, binary.LittleEndian, uint32(0)); err != nil { - return nil, err - } - - if err := binary.Write(c.conn, binary.LittleEndian, uint32(1)); err != nil { - return nil, err - } - - if _, err := c.conn.Read(make([]byte, 6)); err != nil { - return nil, err - } - - var length uint32 - if err := binary.Read(c.conn, binary.LittleEndian, &length); err != nil { - return nil, err - } - - var messageType uint32 - if err := binary.Read(c.conn, binary.LittleEndian, &messageType); err != nil { - return nil, err - } - - results := []Workspace{} - if err := json.NewDecoder(io.LimitReader(c.conn, int64(length))).Decode(&results); err != nil { - return nil, err - } - - return results, nil + return sendMessage[[]Workspace](c, 1, "") } diff --git a/run_command.go b/run_command.go index 1dd61ba..2e79f1d 100644 --- a/run_command.go +++ b/run_command.go @@ -2,50 +2,13 @@ package sway import ( "context" - "encoding/binary" - "encoding/json" - "fmt" - "io" "git.milar.in/milarin/slices" ) func (c *Client) RunCommand(ctx context.Context, cmd string) ([]error, error) { - c.Lock() - defer c.Unlock() - - if _, err := fmt.Fprint(c.conn, "i3-ipc"); err != nil { - return nil, err - } - - if err := binary.Write(c.conn, binary.LittleEndian, uint32(len(cmd))); err != nil { - return nil, err - } - - if err := binary.Write(c.conn, binary.LittleEndian, uint32(0)); err != nil { - return nil, err - } - - if _, err := fmt.Fprint(c.conn, cmd); err != nil { - return nil, err - } - - if _, err := c.conn.Read(make([]byte, 6)); err != nil { - return nil, err - } - - var length uint32 - if err := binary.Read(c.conn, binary.LittleEndian, &length); err != nil { - return nil, err - } - - var messageType uint32 - if err := binary.Read(c.conn, binary.LittleEndian, &messageType); err != nil { - return nil, err - } - - results := []commandResult{} - if err := json.NewDecoder(io.LimitReader(c.conn, int64(length))).Decode(&results); err != nil { + results, err := sendMessage[[]commandResult](c, 0, cmd) + if err != nil { return nil, err } diff --git a/send_tick.go b/send_tick.go new file mode 100644 index 0000000..0b4fd5d --- /dev/null +++ b/send_tick.go @@ -0,0 +1,18 @@ +package sway + +import ( + "context" +) + +func (c *Client) SendTick(ctx context.Context, payload string) error { + result, err := sendMessage[commandResult](c, 10, payload) + if err != nil { + return err + } + + if result.HasError() { + return result.GetError() + } + + return nil +} diff --git a/types.go b/types.go index 4b32122..0268679 100644 --- a/types.go +++ b/types.go @@ -56,6 +56,27 @@ func (o Output) String() string { return string(data) } +type BarConfig struct { + ID string `json:"id"` + Mode BarMode `json:"mode"` + Position BarPosition `json:"position"` + StatusCommand string `json:"status_command"` + Font string `json:"font"` + WorkspaceButtons bool `json:"workspace_buttons"` + WorkspaceMinWidth int `json:"workspace_min_width"` + BindingModeIndicator bool `json:"binding_mode_indicator"` + Colors BarColors `json:"colors"` + Gaps BarGaps `json:"gaps"` + BarHeight int `json:"bar_height"` + StatusPadding int `json:"status_padding"` + StatusEdgePadding int `json:"status_edge_padding"` +} + +func (bc BarConfig) String() string { + data, _ := json.MarshalIndent(bc, "", "\t") + return string(data) +} + type WindowProperties struct { Title string `json:"title"` Instance string `json:"instance"` @@ -84,9 +105,9 @@ type Node struct { Geometry Rectangle `json:"geometry"` Urgent bool `json:"urgent"` Sticky bool `json:"sticky"` - Marks []interface{} `json:"marks"` // TODO type + Marks []string `json:"marks"` Focused bool `json:"focused"` - Focus []Node `json:"focus"` + Focus []NodeID `json:"focus"` Nodes []Node `json:"nodes"` FloatingNodes []Node `json:"floating_nodes"` Representation string `json:"representation"` @@ -99,9 +120,10 @@ type Node struct { Window int `json:"window"` WindowProperties WindowProperties `json:"window_properties"` - // Undocumented fields. Use with caution! - Num int `json:"num"` - Output string `json:"output"` + // The following fields are undocumented. Use with caution! + // They are included in sway as well as in i3 for years though + Num int `json:"num"` // workspace number + Output string `json:"output"` // output port name } func (n Node) String() string { @@ -218,6 +240,21 @@ const ( TransformFlipped270 Transform = "flipped-270" ) +type BarMode = string + +const ( + BarModeDock BarMode = "dock" + BarModeHide BarMode = "hide" + barModeInvisible BarMode = "invisible" +) + +type BarPosition = string + +const ( + BarPositionTop BarPosition = "top" + BarPositionBottom BarPosition = "bottom" +) + type commandResult struct { Success bool `json:"success"` ParseError bool `json:"parse_error"` @@ -242,3 +279,55 @@ type Mode struct { Height int `json:"height"` Refresh int `json:"refresh"` } + +type BarColors struct { + Background string `json:"background"` + Statusline string `json:"statusline"` + Separator string `json:"separator"` + FocusedBackground string `json:"focused_background"` + FocusedStatusline string `json:"focused_statusline"` + FocusedSeparator string `json:"focused_separator"` + FocusedWorkspaceText string `json:"focused_workspace_text"` + FocusedWorkspaceBackground string `json:"focused_workspace_bg"` + FocusedWorkspaceBorder string `json:"focused_workspace_border"` + ActiveWorkspaceText string `json:"active_workspace_text"` + ActiveWorkspaceBackground string `json:"active_workspace_bg"` + ActiveWorkspaceBorder string `json:"active_workspace_border"` + InactiveWorkspaceText string `json:"inactive_workspace_text"` + InactiveWorkspaceBackground string `json:"inactive_workspace_bg"` + InactiveWorkspaceBorder string `json:"inactive_workspace_border"` + UrgentWorkspaceText string `json:"urgent_workspace_text"` + UrgentWorkspaceBackground string `json:"urgent_workspace_bg"` + UrgentWorkspaceBorder string `json:"urgent_workspace_border"` + BindingModeText string `json:"binding_mode_text"` + BindingModeBackground string `json:"binding_mode_bg"` + BindingModeBorder string `json:"binding_mode_border"` +} + +type BarGaps struct { + Top int `json:"top"` + Right int `json:"right"` + Bottom int `json:"bottom"` + Left int `json:"left"` +} + +type Version struct { + Major int `json:"major"` + Minor int `json:"minor"` + Patch int `json:"patch"` + HumanReadable string `json:"human_readable"` + LoadedConfigFileName string `json:"loaded_config_file_name"` +} + +func (v Version) String() string { + data, _ := json.MarshalIndent(v, "", "\t") + return string(data) +} + +type config struct { + Config string `json:"config"` +} + +type name struct { + Name string `json:"name"` +}