mirror of
https://codeberg.org/scip/swayipc.git
synced 2025-12-18 04:51:04 +01:00
migrate to codeberg (#1)
This commit is contained in:
98
v1/bar.go
Normal file
98
v1/bar.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Container gaps
|
||||
type Gaps struct {
|
||||
Top int `json:"top"`
|
||||
Right int `json:"right"`
|
||||
Bottom int `json:"bottom"`
|
||||
Left int `json:"left"`
|
||||
}
|
||||
|
||||
// Color definition, used primarily by bars
|
||||
type Colors 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"`
|
||||
FocusedWorkspaceBorder string `json:"focused_workspace_border"`
|
||||
FocusedWorkspaceBg string `json:"focused_workspace_bg"`
|
||||
FocusedWorkspaceText string `json:"focused_workspace_text"`
|
||||
InactiveWorkspaceBorder string `json:"inactive_workspace_border"`
|
||||
InactiveWorkspaceBg string `json:"inactive_workspace_bg"`
|
||||
InactiveWorkspaceText string `json:"inactive_workspace_text"`
|
||||
Active_workspaceBorder string `json:"active_workspace_border"`
|
||||
Active_workspaceBg string `json:"active_workspace_bg"`
|
||||
Active_workspaceText string `json:"active_workspace_text"`
|
||||
Urgent_workspaceBorder string `json:"urgent_workspace_border"`
|
||||
Urgent_workspaceBg string `json:"urgent_workspace_bg"`
|
||||
Urgent_workspaceText string `json:"urgent_workspace_text"`
|
||||
BindingModeBorder string `json:"binding_mode_border"`
|
||||
BindingModeBg string `json:"binding_mode_bg"`
|
||||
BindingModeText string `json:"binding_mode_text"`
|
||||
}
|
||||
|
||||
// A bar such as a swaybar(5)
|
||||
type Bar struct {
|
||||
Id string `json:"id"`
|
||||
Mode string `json:"mode"`
|
||||
Position string `json:"position"`
|
||||
Status_command string `json:"status_command"`
|
||||
Font string `json:"font"`
|
||||
Gaps *Gaps `json:"gaps"`
|
||||
Height int `json:"bar_height"`
|
||||
StatusPadding int `json:"status_padding"`
|
||||
StatusEdgePadding int `json:"status_edge_padding"`
|
||||
WorkspaceButtons bool `json:"workspace_buttons"`
|
||||
WorkspaceMinWidth int `json:"workspace_min_width"`
|
||||
BindingModeIndicator bool `json:"binding_mode_indicator"`
|
||||
Verbose bool `json:"verbose"`
|
||||
PangoMarkup bool `json:"pango_markup"`
|
||||
Colors *Colors `json:"colors"`
|
||||
}
|
||||
|
||||
// Get a list of currently visible and active bar names
|
||||
func (ipc *SwayIPC) GetBars() ([]string, error) {
|
||||
payload, err := ipc.get(GET_BAR_CONFIG)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bars := []string{}
|
||||
if err := json.Unmarshal(payload.Payload, &bars); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return bars, nil
|
||||
}
|
||||
|
||||
// Get the bar object of the bar specified by the string 'id'
|
||||
func (ipc *SwayIPC) GetBar(id string) (*Bar, error) {
|
||||
err := ipc.sendHeader(GET_BAR_CONFIG, uint32(len(id)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ipc.sendPayload([]byte(id))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send get_bar_config payload: %w", err)
|
||||
}
|
||||
|
||||
payload, err := ipc.readResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bar := &Bar{}
|
||||
if err := json.Unmarshal(payload.Payload, &bar); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return bar, nil
|
||||
}
|
||||
76
v1/command.go
Normal file
76
v1/command.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Execute the specified global command[s] (one or more commands can be
|
||||
// given) and returns a response list.
|
||||
//
|
||||
// Possible commands are all non-specific commands, see sway(5)
|
||||
func (ipc *SwayIPC) RunGlobalCommand(command ...string) ([]Response, error) {
|
||||
return ipc.RunCommand(0, "", command...)
|
||||
}
|
||||
|
||||
// Execute the specified container command[s] (one or more commands can be
|
||||
// given) and returns a response list.
|
||||
//
|
||||
// Possible commands are all container-specific commands, see sway(5)
|
||||
func (ipc *SwayIPC) RunContainerCommand(id int, command ...string) ([]Response, error) {
|
||||
return ipc.RunCommand(id, "con", command...)
|
||||
}
|
||||
|
||||
// Execute the specified (target) command[s] (one or more commands can be
|
||||
// given) and returns a response list.
|
||||
//
|
||||
// Possible commands are all container-specific commands, see sway(5).
|
||||
//
|
||||
// Target can be one of con, workspace, output, input, etc. see sway-ipc(7).
|
||||
func (ipc *SwayIPC) RunCommand(id int, target string, command ...string) ([]Response, error) {
|
||||
if len(command) == 0 {
|
||||
return nil, errors.New("empty command arg")
|
||||
}
|
||||
|
||||
commands := strings.Join(command, ",")
|
||||
|
||||
if id > 0 {
|
||||
// a type specific command, otherwise global
|
||||
commands = fmt.Sprintf("[%s_id=%d] %s", target, id, commands)
|
||||
}
|
||||
|
||||
err := ipc.sendHeader(RUN_COMMAND, uint32(len(commands)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send run_command to IPC %w", err)
|
||||
}
|
||||
|
||||
err = ipc.sendPayload([]byte(commands))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send switch focus command: %w", err)
|
||||
}
|
||||
|
||||
payload, err := ipc.readResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responses := []Response{}
|
||||
|
||||
if err := json.Unmarshal(payload.Payload, &responses); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json response: %w", err)
|
||||
}
|
||||
|
||||
if len(responses) == 0 {
|
||||
return nil, errors.New("got zero IPC response")
|
||||
}
|
||||
|
||||
for _, response := range responses {
|
||||
if !response.Success {
|
||||
return responses, errors.New("one or more commands failed")
|
||||
}
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
19
v1/doc.go
Normal file
19
v1/doc.go
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
Package swayipc can be used to control sway, swayfx and possibly
|
||||
i3wmwindow managers via a unix domain socket.
|
||||
|
||||
swaywm's interprocess communication (or ipc) is the interface sway,
|
||||
swayfx and i3wm use to receive commands from client applications such
|
||||
as sway-msg. It also features a publish/subscribe mechanism for
|
||||
notifying interested parties of window manager events.
|
||||
|
||||
swayipc is a go module for controlling the window manager. This project
|
||||
is intended to be useful for general scripting, and for applications
|
||||
that interact with the window manager like status line generators,
|
||||
notification daemons, and window pagers. It is primarily designed to
|
||||
work with sway and swayfx, but may also work with i3wm, although I
|
||||
haven't tested it on i3wm.
|
||||
|
||||
The module uses the i3-IPC proctocol as outlined in sway-ipc(7).
|
||||
*/
|
||||
package swayipc
|
||||
183
v1/event.go
Normal file
183
v1/event.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Event types.
|
||||
const (
|
||||
EV_Workspace int = 0x80000000
|
||||
EV_Output int = 0x80000001
|
||||
EV_Mode int = 0x80000002
|
||||
EV_Window int = 0x80000003
|
||||
EV_BarconfigUpdate int = 0x80000004
|
||||
EV_Binding int = 0x80000005
|
||||
EV_Shutdown int = 0x80000006
|
||||
EV_Tick int = 0x80000007
|
||||
EV_BarStateUpdate int = 0x80000014
|
||||
EV_Input int = 0x80000015
|
||||
)
|
||||
|
||||
// Subscriber struct, use this to tell swayipc which events you want to
|
||||
// subscribe.
|
||||
type Event struct {
|
||||
Workspace bool
|
||||
Output bool
|
||||
Mode bool
|
||||
Window bool
|
||||
BarconfigUpdate bool
|
||||
Binding bool
|
||||
Shutdown bool
|
||||
Tick bool
|
||||
BarStateUpdate bool
|
||||
Input bool
|
||||
}
|
||||
|
||||
// Workspace event response
|
||||
type EventWorkspace struct {
|
||||
Change string `json:"change"`
|
||||
Current *Node `json:"workspace"`
|
||||
Old *Node `json:"old"`
|
||||
}
|
||||
|
||||
// Output event response
|
||||
type EventOutput struct {
|
||||
Change string `json:"change"`
|
||||
}
|
||||
|
||||
// Mode event response
|
||||
type EventMode struct {
|
||||
Change string `json:"change"`
|
||||
PangoMarkup *Node `json:"pango_markup"`
|
||||
}
|
||||
|
||||
// Window event response
|
||||
type EventWindow struct {
|
||||
Change string `json:"change"`
|
||||
Container *Node `json:"container"`
|
||||
}
|
||||
|
||||
// BarConfig event response
|
||||
type EventBarConfig struct {
|
||||
Change string `json:"change"`
|
||||
Binding *Binding `json:"binding"`
|
||||
}
|
||||
|
||||
// Shutdown event response
|
||||
type EventShutdown struct {
|
||||
Change string `json:"change"`
|
||||
}
|
||||
|
||||
// Tick event response
|
||||
type EventTick struct {
|
||||
First bool `json:"first"`
|
||||
Payload string `json:"payload"`
|
||||
}
|
||||
|
||||
// BarState event response
|
||||
type EventBarState struct {
|
||||
Id string `json:"id"`
|
||||
VisibleByModifier bool `json:"visible_by_modifier"`
|
||||
}
|
||||
|
||||
// Input event response
|
||||
type EventInput struct {
|
||||
Change string `json:"change"`
|
||||
Input *Input `json:"input"`
|
||||
}
|
||||
|
||||
// Subscribe to one or more events. Fill the swayipc.Event object
|
||||
// accordingly.
|
||||
//
|
||||
// Returns a response list containing a response for every subscribed
|
||||
// event.
|
||||
func (ipc *SwayIPC) Subscribe(sub *Event) ([]*Response, error) {
|
||||
events := []string{}
|
||||
|
||||
// looks ugly but makes it much more comfortable for the user
|
||||
if sub.Workspace {
|
||||
events = append(events, "workspace")
|
||||
}
|
||||
if sub.Output {
|
||||
events = append(events, "output")
|
||||
}
|
||||
if sub.Mode {
|
||||
events = append(events, "mode")
|
||||
}
|
||||
if sub.Window {
|
||||
events = append(events, "window")
|
||||
}
|
||||
if sub.BarconfigUpdate {
|
||||
events = append(events, "barconfig_update")
|
||||
}
|
||||
if sub.Binding {
|
||||
events = append(events, "binding")
|
||||
}
|
||||
if sub.Shutdown {
|
||||
events = append(events, "shutdown")
|
||||
}
|
||||
if sub.Tick {
|
||||
events = append(events, "tick")
|
||||
}
|
||||
if sub.BarStateUpdate {
|
||||
events = append(events, "bar_state_update")
|
||||
}
|
||||
if sub.Input {
|
||||
events = append(events, "input")
|
||||
}
|
||||
|
||||
jsonpayload, err := json.Marshal(events)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to json marshal event list: %w", err)
|
||||
}
|
||||
|
||||
err = ipc.sendHeader(SUBSCRIBE, uint32(len(jsonpayload)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ipc.sendPayload(jsonpayload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload, err := ipc.readResponse()
|
||||
if err != nil {
|
||||
responses := []*Response{}
|
||||
if err := json.Unmarshal(payload.Payload, &responses); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json response: %w", err)
|
||||
}
|
||||
|
||||
return responses, err
|
||||
}
|
||||
|
||||
// register the subscribed events
|
||||
ipc.Events = sub
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Event loop: Once you have subscribed to an event, you need to enter
|
||||
// an event loop over the same running socket connection. Sway will
|
||||
// send event payloads for every subscribed event whenever it happens.
|
||||
//
|
||||
// You supply the loop a generic callback function, which will be
|
||||
// called every time an event occurs. The function will receive the
|
||||
// swayipc.RawResponse object for the event. You need to Unmarshall the
|
||||
// Payload field yourself.
|
||||
//
|
||||
// If your callback function returns an error, the event loop returns
|
||||
// with this error and finishes thus.
|
||||
func (ipc *SwayIPC) EventLoop(callback func(event *RawResponse) error) error {
|
||||
for {
|
||||
payload, err := ipc.readResponse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := callback(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
5
v1/go.mod
Normal file
5
v1/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module codeberg.org/scip/swayipc/v1
|
||||
|
||||
go 1.23
|
||||
|
||||
require github.com/alecthomas/repr v0.5.1 // indirect
|
||||
2
v1/go.sum
Normal file
2
v1/go.sum
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
66
v1/input.go
Normal file
66
v1/input.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// An input (keyboard, mouse, whatever)
|
||||
type Input struct {
|
||||
Identifier string `json:"identifier"`
|
||||
Name string `json:"name"`
|
||||
Vendor int `json:"vendor"`
|
||||
Product int `json:"product"`
|
||||
Type string `json:"type"`
|
||||
XkbActiveLayoutName string `json:"xkb_active_layout_name"`
|
||||
XkbLayoutNames []string `json:"xkb_layout_names"`
|
||||
XkbActiveLayoutIndex int `json:"xkb_active_layout_index"`
|
||||
ScrollFactor float32 `json:"scroll_factor"`
|
||||
Libinput *LibInput `json:"libinput"`
|
||||
}
|
||||
|
||||
// Holds the data associated with libinput
|
||||
type LibInput struct {
|
||||
SendEvents string `json:"send_events"`
|
||||
Tap string `json:"tap"`
|
||||
TapButtonMap string `json:"tap_button_map"`
|
||||
TapDrag string `json:"tap_drag"`
|
||||
TapDragLock string `json:"tap_drag_lock"`
|
||||
AccelSpeed float32 `json:"accel_speed"`
|
||||
AccelProfile string `json:"accel_profile"`
|
||||
NaturalScroll string `json:"natural_scroll"`
|
||||
LeftHanded string `json:"left_handed"`
|
||||
ClickMethod string `json:"click_method"`
|
||||
ClickButtonMap string `json:"click_button_map"`
|
||||
MiddleEmulation string `json:"middle_emulation"`
|
||||
ScrollMethod string `json:"scroll_method"`
|
||||
ScrollButton int `json:"scroll_button"`
|
||||
ScrollButtonLock string `json:"scroll_button_lock"`
|
||||
Dwt string `json:"dwt"`
|
||||
Dwtp string `json:"dwtp"`
|
||||
CalibrationMatrix []float32 `json:"calibration_matrix"`
|
||||
}
|
||||
|
||||
// A key binding
|
||||
type Binding struct {
|
||||
Command string `json:"command"`
|
||||
EventStateMask []string `json:"event_state_mask"`
|
||||
InputCode int `json:"input_code"`
|
||||
Symbol string `json:"symbol"`
|
||||
InputType string `json:"input_type"`
|
||||
}
|
||||
|
||||
// Get a list of all currently supported inputs
|
||||
func (ipc *SwayIPC) GetInputs() ([]*Input, error) {
|
||||
payload, err := ipc.get(GET_INPUTS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inputs := []*Input{}
|
||||
if err := json.Unmarshal(payload.Payload, &inputs); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return inputs, nil
|
||||
}
|
||||
13
v1/io.go
Normal file
13
v1/io.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package swayipc
|
||||
|
||||
import "os"
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return !info.IsDir()
|
||||
}
|
||||
96
v1/net.go
Normal file
96
v1/net.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Contains a raw json response, not marshalled yet.
|
||||
type RawResponse struct {
|
||||
PayloadType int
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// Connect to unix domain ipc socket.
|
||||
func (ipc *SwayIPC) Connect() error {
|
||||
if !fileExists(ipc.SocketFile) {
|
||||
ipc.SocketFile = os.Getenv(ipc.SocketFile)
|
||||
if ipc.SocketFile == "" {
|
||||
return fmt.Errorf("socket file %s doesn't exist", ipc.SocketFile)
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", ipc.SocketFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ipc.socket = conn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the socket.
|
||||
func (ipc *SwayIPC) Close() error {
|
||||
return ipc.socket.Close()
|
||||
}
|
||||
|
||||
func (ipc *SwayIPC) sendHeader(messageType uint32, len uint32) error {
|
||||
sendPayload := make([]byte, IPC_HEADER_SIZE)
|
||||
|
||||
copy(sendPayload, []byte(IPC_MAGIC))
|
||||
binary.LittleEndian.PutUint32(sendPayload[IPC_MAGIC_LEN:], len)
|
||||
binary.LittleEndian.PutUint32(sendPayload[IPC_MAGIC_LEN+4:], messageType)
|
||||
|
||||
_, err := ipc.socket.Write(sendPayload)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send header to IPC socket %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipc *SwayIPC) sendPayload(payload []byte) error {
|
||||
_, err := ipc.socket.Write(payload)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send payload to IPC socket %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipc *SwayIPC) readResponse() (*RawResponse, error) {
|
||||
// read header
|
||||
buf := make([]byte, IPC_HEADER_SIZE)
|
||||
|
||||
_, err := ipc.socket.Read(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read header from ipc socket: %s", err)
|
||||
}
|
||||
|
||||
if string(buf[:6]) != IPC_MAGIC {
|
||||
return nil, errors.New("got invalid response from IPC socket")
|
||||
}
|
||||
|
||||
payloadLen := binary.LittleEndian.Uint32(buf[6:10])
|
||||
if payloadLen == 0 {
|
||||
return nil, errors.New("got empty payload response from IPC socket")
|
||||
}
|
||||
|
||||
payloadType := binary.LittleEndian.Uint32(buf[10:])
|
||||
|
||||
// read payload
|
||||
payload := make([]byte, payloadLen)
|
||||
|
||||
_, err = ipc.socket.Read(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read payload from IPC socket: %s", err)
|
||||
}
|
||||
|
||||
return &RawResponse{PayloadType: int(payloadType), Payload: payload}, nil
|
||||
}
|
||||
108
v1/node.go
Normal file
108
v1/node.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A node can be an output, a workspace, a container or a container
|
||||
// containing a window.
|
||||
type Node struct {
|
||||
Id int `json:"id"`
|
||||
Type string `json:"type"` // output, workspace or container
|
||||
Name string `json:"name"` // workspace number or app name
|
||||
Nodes []*Node `json:"nodes"`
|
||||
FloatingNodes []*Node `json:"floating_nodes"`
|
||||
Focused bool `json:"focused"`
|
||||
Urgent bool `json:"urgent"`
|
||||
Sticky bool `json:"sticky"`
|
||||
Border string `json:"border"`
|
||||
Layout string `json:"layout"`
|
||||
Orientation string `json:"orientation"`
|
||||
CurrentBorderWidth int `json:"current_border_width"`
|
||||
Percent float32 `json:"percent"`
|
||||
Focus []int `json:"focus"`
|
||||
Window int `json:"window"` // wayland native
|
||||
X11Window string `json:"app_id"` // x11 compat
|
||||
Current_workspace string `json:"current_workspace"`
|
||||
Rect Rect `json:"rect"`
|
||||
WindowRect Rect `json:"window_rect"`
|
||||
DecoRect Rect `json:"deco_rect"`
|
||||
Geometry Rect `json:"geometry"`
|
||||
}
|
||||
|
||||
var __focused *Node
|
||||
var __currentworkspace string
|
||||
|
||||
// Get the whole information tree, contains everything from output to
|
||||
// containers as a tree of nodes. Each node has a field 'Nodes' which
|
||||
// points to a list subnodes. Some nodes also have a field
|
||||
// 'FloatingNodes' which points to a list of floating containers.
|
||||
//
|
||||
// The top level node is the "root" node.
|
||||
//
|
||||
// Use the returned node oject to further investigate the wm setup.
|
||||
func (ipc *SwayIPC) GetTree() (*Node, error) {
|
||||
err := ipc.sendHeader(GET_TREE, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload, err := ipc.readResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node := &Node{}
|
||||
if err := json.Unmarshal(payload.Payload, &node); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// Usually called on the root node, returns the container which has
|
||||
// currently the focus.
|
||||
func (node *Node) FindFocused() *Node {
|
||||
searchFocused(node.Nodes)
|
||||
if __focused == nil {
|
||||
searchFocused(node.FloatingNodes)
|
||||
}
|
||||
|
||||
return __focused
|
||||
}
|
||||
|
||||
// internal recursive focus node searcher
|
||||
func searchFocused(nodes []*Node) {
|
||||
for _, node := range nodes {
|
||||
if node.Focused {
|
||||
__focused = node
|
||||
return
|
||||
} else {
|
||||
searchFocused(node.Nodes)
|
||||
if __focused == nil {
|
||||
searchFocused(node.FloatingNodes)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Usually called on the root node, returns the current active
|
||||
// workspace name.
|
||||
func (node *Node) FindCurrentWorkspace() string {
|
||||
searchCurrentWorkspace(node.Nodes)
|
||||
return __currentworkspace
|
||||
}
|
||||
|
||||
// internal recursive workspace node searcher
|
||||
func searchCurrentWorkspace(nodes []*Node) {
|
||||
for _, node := range nodes {
|
||||
if node.Current_workspace != "" {
|
||||
__currentworkspace = node.Current_workspace
|
||||
return
|
||||
} else {
|
||||
searchCurrentWorkspace(node.Nodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
47
v1/output.go
Normal file
47
v1/output.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Store an output mode.
|
||||
type Mode struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Refresh int `json:"refresh"`
|
||||
}
|
||||
|
||||
// An output object (i.e. a physical monitor)
|
||||
type Output struct {
|
||||
Name string `json:"name"`
|
||||
Make string `json:"make"`
|
||||
Serial string `json:"serial"`
|
||||
Active bool `json:"active"`
|
||||
Primary bool `json:"primary"`
|
||||
SubpixelHinting string `json:"subpixel_hinting"`
|
||||
Transform string `json:"transform"`
|
||||
Current_workspace string `json:"current_workspace"`
|
||||
Modes []*Mode `json:"modes"`
|
||||
CurrentMode *Mode `json:"current_mode"`
|
||||
}
|
||||
|
||||
// Get a list of currently available and usable outputs.
|
||||
func (ipc *SwayIPC) GetOutputs() ([]*Output, error) {
|
||||
err := ipc.sendHeader(GET_OUTPUTS, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload, err := ipc.readResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workspaces := []*Output{}
|
||||
if err := json.Unmarshal(payload.Payload, &workspaces); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return workspaces, nil
|
||||
}
|
||||
29
v1/seat.go
Normal file
29
v1/seat.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Information about a seat containing input devices
|
||||
type Seat struct {
|
||||
Name string `json:"name"`
|
||||
Capabilities int `json:"capabilities"`
|
||||
Focus int `json:"focus"`
|
||||
Devices []*Input `json:"devices"`
|
||||
}
|
||||
|
||||
// Get input seats
|
||||
func (ipc *SwayIPC) GetSeats() ([]*Seat, error) {
|
||||
payload, err := ipc.get(GET_SEATS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
seats := []*Seat{}
|
||||
if err := json.Unmarshal(payload.Payload, &seats); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return seats, nil
|
||||
}
|
||||
90
v1/simpletons.go
Normal file
90
v1/simpletons.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (ipc *SwayIPC) GetWorkspaces() ([]*Node, error) {
|
||||
payload, err := ipc.get(GET_WORKSPACES)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodes := []*Node{}
|
||||
if err := json.Unmarshal(payload.Payload, &nodes); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (ipc *SwayIPC) GetMarks() ([]string, error) {
|
||||
payload, err := ipc.get(GET_MARKS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
marks := []string{}
|
||||
if err := json.Unmarshal(payload.Payload, &marks); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return marks, nil
|
||||
}
|
||||
|
||||
func (ipc *SwayIPC) GetBindingModes() ([]string, error) {
|
||||
payload, err := ipc.get(GET_BINDING_MODES)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modes := []string{}
|
||||
if err := json.Unmarshal(payload.Payload, &modes); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return modes, nil
|
||||
}
|
||||
|
||||
func (ipc *SwayIPC) GetBindingState() (*State, error) {
|
||||
payload, err := ipc.get(GET_BINDING_STATE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
state := &State{}
|
||||
if err := json.Unmarshal(payload.Payload, &state); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (ipc *SwayIPC) GetConfig() (string, error) {
|
||||
payload, err := ipc.get(GET_CONFIG)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config := &Config{}
|
||||
if err := json.Unmarshal(payload.Payload, &config); err != nil {
|
||||
return "", fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return config.Config, nil
|
||||
}
|
||||
|
||||
func (ipc *SwayIPC) SendTick(payload string) error {
|
||||
err := ipc.sendHeader(SEND_TICK, uint32(len(payload)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ipc.sendPayload([]byte(payload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
98
v1/swayipc.go
Normal file
98
v1/swayipc.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
VERSION = "v1.0.0"
|
||||
|
||||
IPC_HEADER_SIZE = 14
|
||||
IPC_MAGIC = "i3-ipc"
|
||||
IPC_MAGIC_LEN = 6
|
||||
)
|
||||
|
||||
const (
|
||||
// message types
|
||||
RUN_COMMAND = iota
|
||||
GET_WORKSPACES
|
||||
SUBSCRIBE
|
||||
GET_OUTPUTS
|
||||
GET_TREE
|
||||
GET_MARKS
|
||||
GET_BAR_CONFIG
|
||||
GET_VERSION
|
||||
GET_BINDING_MODES
|
||||
GET_CONFIG
|
||||
SEND_TICK
|
||||
SYNC
|
||||
GET_BINDING_STATE
|
||||
|
||||
GET_INPUTS = 100
|
||||
GET_SEATS = 101
|
||||
)
|
||||
|
||||
// This is the primary struct to work with the swayipc module.
|
||||
type SwayIPC struct {
|
||||
socket net.Conn
|
||||
SocketFile string // filename of the i3 IPC socket
|
||||
Events *Event // store subscribed events, see swayipc.Subscribe()
|
||||
}
|
||||
|
||||
// A rectangle struct, used at various places for geometry etc.
|
||||
type Rect struct {
|
||||
X int `json:"x"` // X coordinate
|
||||
Y int `json:"y"` // Y coordinate
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
// Stores responses retrieved via ipc
|
||||
type Response struct {
|
||||
Success bool `json:"success"`
|
||||
ParseError bool `json:"parse_error"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// Stores the user config for the WM
|
||||
type Config struct {
|
||||
Config string `json:"config"`
|
||||
}
|
||||
|
||||
// Stores the binding state
|
||||
type State struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Create a new swayipc.SwayIPC object. Filename argument is optional and
|
||||
// may denote a filename or the name of an environment variable.
|
||||
//
|
||||
// By default and if nothing is specified we look for the environment
|
||||
// variable SWAYSOCK and use the file it points to as unix domain
|
||||
// socket to communicate with sway (and possible i3).
|
||||
func NewSwayIPC(file ...string) *SwayIPC {
|
||||
ipc := &SwayIPC{}
|
||||
|
||||
if len(file) == 0 {
|
||||
ipc.SocketFile = "SWAYSOCK"
|
||||
} else {
|
||||
ipc.SocketFile = file[0]
|
||||
}
|
||||
|
||||
return ipc
|
||||
}
|
||||
|
||||
// internal convenience wrapper
|
||||
func (ipc *SwayIPC) get(command uint32) (*RawResponse, error) {
|
||||
err := ipc.sendHeader(command, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload, err := ipc.readResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
29
v1/version.go
Normal file
29
v1/version.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package swayipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A version struct holding the sway wm version
|
||||
type Version struct {
|
||||
HumanReadable string `json:"human_readable"`
|
||||
Major int `json:"major"`
|
||||
Minor int `json:"minor"`
|
||||
Patch int `json:"patch"`
|
||||
}
|
||||
|
||||
// Get the sway software version
|
||||
func (ipc *SwayIPC) GetVersion() (*Version, error) {
|
||||
payload, err := ipc.get(GET_VERSION)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version := &Version{}
|
||||
if err := json.Unmarshal(payload.Payload, &version); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
Reference in New Issue
Block a user