rewrite
This commit is contained in:
parent
8735d4dc1c
commit
8bb43a4263
43
client.go
Normal file
43
client.go
Normal file
@ -0,0 +1,43 @@
|
||||
package sway
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
sync.Mutex
|
||||
|
||||
socket string
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
return c.conn.Close()
|
||||
}
|
173
event.go
173
event.go
@ -1,16 +1,173 @@
|
||||
package sway
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
import "encoding/json"
|
||||
|
||||
"git.milar.in/milarin/adverr"
|
||||
type EventType uint32
|
||||
|
||||
const (
|
||||
// i3 events
|
||||
EventTypeWorkspace EventType = 0x80000000 + iota
|
||||
_ EventType = 0x80000000 + iota // output event not available in sway
|
||||
EventTypeMode EventType = 0x80000000 + iota
|
||||
EventTypeWindow EventType = 0x80000000 + iota
|
||||
_ EventType = 0x80000000 + iota // TODO implement EventTypeBarConfigUpdate
|
||||
EventTypeBinding EventType = 0x80000000 + iota
|
||||
EventTypeShutdown EventType = 0x80000000 + iota
|
||||
EventTypeTick EventType = 0x80000000 + iota
|
||||
|
||||
// sway-only events
|
||||
EventTypeBarStateUpdate EventType = 0x80000014
|
||||
EventTypeInput EventType = 0x80000015
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
Change string `json:"change"`
|
||||
Container Node `json:"container"`
|
||||
var eventStringRepresentations = map[EventType]string{
|
||||
EventTypeWorkspace: "workspace",
|
||||
EventTypeMode: "mode",
|
||||
EventTypeWindow: "window",
|
||||
//EventTypeBarConfigUpdate: "barconfig_update",
|
||||
EventTypeBinding: "binding",
|
||||
EventTypeShutdown: "shutdown",
|
||||
EventTypeTick: "tick",
|
||||
EventTypeBarStateUpdate: "bar_state_update",
|
||||
EventTypeInput: "input",
|
||||
}
|
||||
|
||||
func (e Event) String() string {
|
||||
return string(adverr.Must(json.MarshalIndent(e, "", "\t")))
|
||||
func (et EventType) String() string {
|
||||
return eventStringRepresentations[et]
|
||||
}
|
||||
|
||||
type Event interface {
|
||||
Type() EventType
|
||||
}
|
||||
|
||||
type WorkspaceEvent struct {
|
||||
Change WorkspaceEventChange `json:"change"`
|
||||
Current *Workspace `json:"current"`
|
||||
Old *Workspace `json:"old"`
|
||||
}
|
||||
|
||||
var _ Event = &WorkspaceEvent{}
|
||||
|
||||
func (e WorkspaceEvent) Type() EventType {
|
||||
return EventTypeWorkspace
|
||||
}
|
||||
|
||||
func (e WorkspaceEvent) String() string {
|
||||
data, _ := json.MarshalIndent(e, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type ModeEvent struct {
|
||||
Change string `json:"change"`
|
||||
PangoMarkup bool `json:"pango_markup"`
|
||||
}
|
||||
|
||||
var _ Event = &ModeEvent{}
|
||||
|
||||
func (e ModeEvent) Type() EventType {
|
||||
return EventTypeMode
|
||||
}
|
||||
|
||||
func (e ModeEvent) String() string {
|
||||
data, _ := json.MarshalIndent(e, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type WindowEvent struct {
|
||||
Change WindowEventChange `json:"change"`
|
||||
Container *Node `json:"container"`
|
||||
}
|
||||
|
||||
var _ Event = &WindowEvent{}
|
||||
|
||||
func (e WindowEvent) Type() EventType {
|
||||
return EventTypeWindow
|
||||
}
|
||||
|
||||
func (e WindowEvent) String() string {
|
||||
data, _ := json.MarshalIndent(e, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type BindingEvent struct {
|
||||
Change string `json:"change"`
|
||||
Command string `json:"command"`
|
||||
EventStateMask []string `json:"event_state_mask"`
|
||||
InputCode int `json:"input_code"`
|
||||
Symbol string `json:"symbol"`
|
||||
InputType InputType `json:"input_type"`
|
||||
}
|
||||
|
||||
var _ Event = &BindingEvent{}
|
||||
|
||||
func (e BindingEvent) Type() EventType {
|
||||
return EventTypeBinding
|
||||
}
|
||||
|
||||
func (e BindingEvent) String() string {
|
||||
data, _ := json.MarshalIndent(e, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type ShutdownEvent struct {
|
||||
Change string `json:"change"`
|
||||
}
|
||||
|
||||
var _ Event = &ShutdownEvent{}
|
||||
|
||||
func (e ShutdownEvent) Type() EventType {
|
||||
return EventTypeShutdown
|
||||
}
|
||||
|
||||
func (e ShutdownEvent) String() string {
|
||||
data, _ := json.MarshalIndent(e, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type TickEvent struct {
|
||||
First bool `json:"first"`
|
||||
Payload string `json:"payload"`
|
||||
}
|
||||
|
||||
var _ Event = &TickEvent{}
|
||||
|
||||
func (e TickEvent) Type() EventType {
|
||||
return EventTypeTick
|
||||
}
|
||||
|
||||
func (e TickEvent) String() string {
|
||||
data, _ := json.MarshalIndent(e, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type BarStateUpdateEvent struct {
|
||||
ID string `json:"id"`
|
||||
VisibleByModifier bool `json:"visible_by_modifier"`
|
||||
}
|
||||
|
||||
var _ Event = &BarStateUpdateEvent{}
|
||||
|
||||
func (e BarStateUpdateEvent) Type() EventType {
|
||||
return EventTypeBarStateUpdate
|
||||
}
|
||||
|
||||
func (e BarStateUpdateEvent) String() string {
|
||||
data, _ := json.MarshalIndent(e, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type InputEvent struct {
|
||||
Change InputEventChange `json:"change"`
|
||||
//Input Input `json:"input"` // TODO implement
|
||||
}
|
||||
|
||||
var _ Event = &InputEvent{}
|
||||
|
||||
func (e InputEvent) Type() EventType {
|
||||
return EventTypeInput
|
||||
}
|
||||
|
||||
func (e InputEvent) String() string {
|
||||
data, _ := json.MarshalIndent(e, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
package sway
|
||||
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
EventTypeWindow EventType = "window"
|
||||
EventTypeWorkspace EventType = "workspace"
|
||||
// TODO
|
||||
)
|
31
find.go
31
find.go
@ -1,31 +0,0 @@
|
||||
package sway
|
||||
|
||||
func (n Node) FindAll(condition func(n Node) bool) []Node {
|
||||
nodes := make([]Node, 0, 10)
|
||||
|
||||
if condition(n) {
|
||||
nodes = append(nodes, n)
|
||||
}
|
||||
|
||||
for _, child := range n.Nodes {
|
||||
nodes = append(nodes, child.FindAll(condition)...)
|
||||
}
|
||||
|
||||
for _, child := range n.FloatingNodes {
|
||||
nodes = append(nodes, child.FindAll(condition)...)
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func IsOutput(n Node) bool {
|
||||
return n.Type == NodeTypeOutput
|
||||
}
|
||||
|
||||
func IsWorkspace(n Node) bool {
|
||||
return n.Type == NodeTypeWorkspace
|
||||
}
|
||||
|
||||
func IsContainer(n Node) bool {
|
||||
return n.Type == NodeTypeCon
|
||||
}
|
18
find_test.go
18
find_test.go
@ -1,18 +0,0 @@
|
||||
package sway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFind(t *testing.T) {
|
||||
root, err := GetTree(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, node := range root.FindAll(IsWorkspace) {
|
||||
fmt.Println(node.Type, node.Name)
|
||||
}
|
||||
}
|
23
get_tree.go
23
get_tree.go
@ -1,23 +0,0 @@
|
||||
package sway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func GetTree(ctx context.Context) (*Node, error) {
|
||||
cmd := exec.CommandContext(ctx, "swaymsg", "-t", "get_tree")
|
||||
|
||||
data, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var root Node
|
||||
if err := json.Unmarshal(data, &root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &root, nil
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package sway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetTree(t *testing.T) {
|
||||
root, err := GetTree(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(root.ID, root.Name)
|
||||
}
|
@ -2,22 +2,46 @@ package sway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func GetWorkspaces(ctx context.Context) ([]Node, error) {
|
||||
cmd := exec.CommandContext(ctx, "swaymsg", "-t", "get_workspaces")
|
||||
func (c *Client) GetWorkspaces(ctx context.Context) ([]Workspace, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
data, err := cmd.Output()
|
||||
if err != nil {
|
||||
if _, err := fmt.Fprint(c.conn, "i3-ipc"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var workspaces []Node
|
||||
if err := json.Unmarshal(data, &workspaces); err != nil {
|
||||
if err := binary.Write(c.conn, binary.LittleEndian, uint32(0)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return workspaces, nil
|
||||
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
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
package sway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetWorkspaces(t *testing.T) {
|
||||
workspaces, err := GetWorkspaces(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, workspace := range workspaces {
|
||||
fmt.Println(workspace.Type, workspace.Output, workspace.Name)
|
||||
}
|
||||
}
|
4
go.mod
4
go.mod
@ -2,4 +2,6 @@ module git.milar.in/milarin/sway
|
||||
|
||||
go 1.21.1
|
||||
|
||||
require git.milar.in/milarin/adverr v1.1.1
|
||||
require git.milar.in/milarin/slices v0.0.8
|
||||
|
||||
require git.milar.in/milarin/gmath v0.0.3 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -1,2 +1,4 @@
|
||||
git.milar.in/milarin/adverr v1.1.1 h1:ENtBcqT7CncLsVfaLC3KzX8QSSGiSpsC7I7wDqladu8=
|
||||
git.milar.in/milarin/adverr v1.1.1/go.mod h1:joU9sBb7ySyNv4SpTXB0Z4o1mjXsArBw4N27wjgzj9E=
|
||||
git.milar.in/milarin/gmath v0.0.3 h1:ii6rKNItS55O/wtIFhD1cTN2BMwDZjTBmiOocKURvxM=
|
||||
git.milar.in/milarin/gmath v0.0.3/go.mod h1:HDLftG5RLpiNGKiIWh+O2G1PYkNzyLDADO8Cd/1abiE=
|
||||
git.milar.in/milarin/slices v0.0.8 h1:qN9TE3tkArdTixMKSnwvNPcApwAjxpLVwA5a9k1rm2s=
|
||||
git.milar.in/milarin/slices v0.0.8/go.mod h1:qMhdtMnfWswc1rHpwgNw33lB84aNEkdBn5BDiYA+G3k=
|
||||
|
166
node.go
166
node.go
@ -1,166 +0,0 @@
|
||||
package sway
|
||||
|
||||
type Node struct {
|
||||
ID NodeID `json:"id"`
|
||||
Type NodeType `json:"type"`
|
||||
Orientation Orientation `json:"orientation"`
|
||||
Percentage *float64 `json:"percent"`
|
||||
Urgent bool `json:"urgent"`
|
||||
Marks []string `json:"marks"`
|
||||
Focused bool `json:"focused"`
|
||||
Layout string `json:"layout"`
|
||||
Border BorderStyle `json:"border"`
|
||||
CurrentBorderWidth int `json:"current_border_width"`
|
||||
Rect Rect `json:"rect"`
|
||||
DecoRect Rect `json:"deco_rect"`
|
||||
WindowRect Rect `json:"window_rect"`
|
||||
Geometry Rect `json:"geometry"`
|
||||
Name string `json:"name"`
|
||||
Nodes []Node `json:"nodes"`
|
||||
FloatingNodes []Node `json:"floating_nodes"`
|
||||
Focus []NodeID `json:"focus"`
|
||||
FullscreenMode FullscreenMode `json:"fullscreen_mode"`
|
||||
Sticky bool `json:"sticky"`
|
||||
PID int64 `json:"pid"`
|
||||
AppID string `json:"app_id"`
|
||||
Visible bool `json:"visible"`
|
||||
MaxRenderTimeMilli int64 `json:"max_render_time"`
|
||||
Shell string `json:"shell"`
|
||||
InhibitIdle bool `json:"inhibit_idle"`
|
||||
IdleInhibitors IdleInhibitors `json:"idle_inhibitors"`
|
||||
|
||||
// Xorg compatibility
|
||||
XorgWindowID *int64 `json:"window"`
|
||||
XorgProperties *XorgProperties `json:"window_properties"`
|
||||
|
||||
// NodeTypeOutput only
|
||||
Primary bool `json:"primary"`
|
||||
Make string `json:"make"`
|
||||
Model string `json:"model"`
|
||||
Serial string `json:"serial"`
|
||||
Modes []OutputMode `json:"modes"`
|
||||
NonDesktop bool `json:"non_desktop"`
|
||||
Active bool `json:"active"`
|
||||
DPMS bool `json:"dpms"`
|
||||
Power bool `json:"power"`
|
||||
Scale float64 `json:"scale"`
|
||||
ScaleFilter ScaleFilter `json:"scale_filter"`
|
||||
Transform Transform `json:"transform"`
|
||||
AdaptiveSync string `json:"adaptive_sync_status"`
|
||||
LayerShellSurfaces []LayerShellSurface `json:"layer_shell_surfaces"`
|
||||
CurrentWorkspace string `json:"current_workspace"`
|
||||
CurrentMode OutputMode `json:"current_mode"`
|
||||
|
||||
// NodeTypeWorkspace only
|
||||
WorkspaceNumber int `json:"num"`
|
||||
Output string `json:"output"`
|
||||
Representation string `json:"representation"`
|
||||
}
|
||||
|
||||
type NodeID int64
|
||||
|
||||
type NodeType string
|
||||
|
||||
const (
|
||||
NodeTypeRoot NodeType = "root"
|
||||
NodeTypeOutput NodeType = "output"
|
||||
NodeTypeCon NodeType = "con"
|
||||
NodeTypeFloatingCon NodeType = "floating_con"
|
||||
NodeTypeWorkspace NodeType = "workspace"
|
||||
NodeTypeDockarea NodeType = "dockarea"
|
||||
)
|
||||
|
||||
type Rect struct {
|
||||
Y int `json:"y"`
|
||||
X int `json:"x"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
type BorderStyle string
|
||||
|
||||
const (
|
||||
BorderStyleNormal BorderStyle = "normal"
|
||||
BorderStyleNo BorderStyle = "none"
|
||||
BorderStylePixel BorderStyle = "pixel"
|
||||
)
|
||||
|
||||
type Layout string
|
||||
|
||||
const (
|
||||
LayoutSplitH Layout = "splith"
|
||||
LayoutSplitV Layout = "splitv"
|
||||
LayoutStacked Layout = "stacked"
|
||||
LayoutTabbed Layout = "tabbed"
|
||||
LayoutDockarea Layout = "dockarea"
|
||||
LayoutOutput Layout = "output"
|
||||
)
|
||||
|
||||
type FullscreenMode int64
|
||||
|
||||
const (
|
||||
FullscreenNone FullscreenMode = 0
|
||||
FullscreenOutput FullscreenMode = 1
|
||||
FullscreenGlobal FullscreenMode = 2
|
||||
)
|
||||
|
||||
type IdleInhibitors struct {
|
||||
User string `json:"user"`
|
||||
Application string `json:"application"`
|
||||
}
|
||||
|
||||
type OutputMode struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
RefreshRate int `json:"refresh"`
|
||||
AspectRatio string `json:"picture_aspect_ratio"`
|
||||
}
|
||||
|
||||
type ScaleFilter string
|
||||
|
||||
const (
|
||||
ScaleFilterNearest ScaleFilter = "nearest"
|
||||
ScaleFilterLinear ScaleFilter = "linear"
|
||||
ScaleFilterSmart ScaleFilter = "smart"
|
||||
)
|
||||
|
||||
type Transform string
|
||||
|
||||
const (
|
||||
TransformNormal Transform = "normal"
|
||||
Transform90 Transform = "90"
|
||||
Transform180 Transform = "180"
|
||||
Transform270 Transform = "270"
|
||||
TransformFlipped Transform = "flipped"
|
||||
TransformFlipped90 Transform = "flipped-90"
|
||||
TransformFlipped180 Transform = "flipped-180"
|
||||
TransformFlipped270 Transform = "flipped-270"
|
||||
)
|
||||
|
||||
type LayerShellSurface struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Layer string `json:"layer"`
|
||||
Extent Rect `json:"extenct"`
|
||||
Effects []Effect `json:"effects"`
|
||||
}
|
||||
|
||||
type Effect string
|
||||
|
||||
const (
|
||||
EffectBlur Effect = "blur"
|
||||
)
|
||||
|
||||
type Orientation string
|
||||
|
||||
const (
|
||||
OrientationHorizontal Orientation = "horizontal"
|
||||
OrientationVertical Orientation = "vertical"
|
||||
)
|
||||
|
||||
type XorgProperties struct {
|
||||
Title string `json:"title"`
|
||||
Instance string `json:"instance"`
|
||||
Class string `json:"class"`
|
||||
Role string `json:"window_role"`
|
||||
Transient NodeID `json:"transient_for"`
|
||||
}
|
@ -2,15 +2,53 @@ package sway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"git.milar.in/milarin/slices"
|
||||
)
|
||||
|
||||
func RunCommand(ctx context.Context, command string) error {
|
||||
cmd := exec.CommandContext(ctx, "swaymsg", command)
|
||||
func (c *Client) RunCommand(ctx context.Context, cmd string) ([]error, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
if _, err := fmt.Fprint(c.conn, "i3-ipc"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Wait()
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errors := slices.Map(slices.Filter(results, commandResult.HasError), commandResult.GetError)
|
||||
return errors, nil
|
||||
}
|
||||
|
113
subscribe.go
113
subscribe.go
@ -2,37 +2,128 @@ package sway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"git.milar.in/milarin/slices"
|
||||
)
|
||||
|
||||
func Subscribe(ctx context.Context, events ...EventType) (<-chan Event, error) {
|
||||
data, err := json.Marshal(events)
|
||||
func (c *Client) Subscribe(ctx context.Context, events ...EventType) (<-chan Event, error) {
|
||||
conn, err := net.Dial("unix", c.socket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "swaymsg", "-t", "subscribe", "-m", string(data))
|
||||
if _, err := fmt.Fprint(conn, "i3-ipc"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
payload, err := json.Marshal(slices.Map(events, EventType.String))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(conn, binary.LittleEndian, uint32(len(payload))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(conn, binary.LittleEndian, uint32(2)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := conn.Write(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := conn.Read(make([]byte, 6)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var length uint32
|
||||
if err := binary.Read(conn, binary.LittleEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var messageType uint32
|
||||
if err := binary.Read(conn, binary.LittleEndian, &messageType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := commandResult{}
|
||||
if err := json.NewDecoder(io.LimitReader(conn, int64(length))).Decode(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.HasError() {
|
||||
return nil, result.GetError()
|
||||
}
|
||||
|
||||
return parseEvents(ctx, conn)
|
||||
}
|
||||
|
||||
func parseEvents(ctx context.Context, conn net.Conn) (<-chan Event, error) {
|
||||
out := make(chan Event, 10)
|
||||
|
||||
go func() {
|
||||
defer cmd.Wait()
|
||||
dec := json.NewDecoder(stdout)
|
||||
var event Event
|
||||
for err := dec.Decode(&event); err == nil; err = dec.Decode(&event) {
|
||||
defer conn.Close()
|
||||
defer close(out)
|
||||
|
||||
for ctx.Err() == nil {
|
||||
if _, err := conn.Read(make([]byte, 6)); err != nil {
|
||||
panic(err) // TODO debug flag with error printing
|
||||
}
|
||||
|
||||
var length uint32
|
||||
if err := binary.Read(conn, binary.LittleEndian, &length); err != nil {
|
||||
panic(err) // TODO debug flag with error printing
|
||||
}
|
||||
|
||||
var messageType EventType
|
||||
if err := binary.Read(conn, binary.LittleEndian, &messageType); err != nil {
|
||||
panic(err) // TODO debug flag with error printing
|
||||
}
|
||||
|
||||
event, err := parseEvent(conn, length, messageType)
|
||||
if err != nil {
|
||||
panic(err) // TODO debug flag with error printing
|
||||
}
|
||||
|
||||
out <- event
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseEvent(conn net.Conn, length uint32, messageType EventType) (Event, error) {
|
||||
var event Event
|
||||
switch messageType {
|
||||
case EventTypeWorkspace:
|
||||
event = &WorkspaceEvent{}
|
||||
case EventTypeMode:
|
||||
event = &ModeEvent{}
|
||||
case EventTypeWindow:
|
||||
event = &WindowEvent{}
|
||||
case EventTypeBinding:
|
||||
event = &BindingEvent{}
|
||||
case EventTypeShutdown:
|
||||
event = &ShutdownEvent{}
|
||||
case EventTypeTick:
|
||||
event = &TickEvent{}
|
||||
case EventTypeBarStateUpdate:
|
||||
event = &BarStateUpdateEvent{}
|
||||
case EventTypeInput:
|
||||
event = &InputEvent{}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid event type: %x", messageType)
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(io.LimitReader(conn, int64(length))).Decode(event); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
return event, nil
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
package sway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSubscribe(t *testing.T) {
|
||||
events, err := Subscribe(context.Background(), EventTypeWindow, EventTypeWorkspace)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for event := range events {
|
||||
fmt.Println(event)
|
||||
}
|
||||
}
|
195
types.go
Normal file
195
types.go
Normal file
@ -0,0 +1,195 @@
|
||||
package sway
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type NodeID int64
|
||||
|
||||
type Rectangle struct {
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
func (r Rectangle) String() string {
|
||||
data, _ := json.MarshalIndent(r, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type Workspace struct {
|
||||
Num int `json:"num"`
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Focused bool `json:"focused"`
|
||||
Urgent bool `json:"urgent"`
|
||||
Rect Rectangle `json:"rect"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
func (w Workspace) String() string {
|
||||
data, _ := json.MarshalIndent(w, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type WindowProperties struct {
|
||||
Title string `json:"title"`
|
||||
Instance string `json:"instance"`
|
||||
Class string `json:"class"`
|
||||
Role string `json:"window_role"`
|
||||
Transient NodeID `json:"transient_for"`
|
||||
}
|
||||
|
||||
func (w WindowProperties) String() string {
|
||||
data, _ := json.MarshalIndent(w, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
ID NodeID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type NodeType `json:"type"`
|
||||
Border BorderStyle `json:"border"`
|
||||
CurrentBorderWidth int `json:"current_border_width"`
|
||||
Layout Layout `json:"layout"`
|
||||
Orientation Orientation `json:"orientation"`
|
||||
Percent float64 `json:"percent"`
|
||||
Rect Rectangle `json:"rect"`
|
||||
WindowRect Rectangle `json:"window_rect"`
|
||||
DecoRect Rectangle `json:"deco_rect"`
|
||||
Geometry Rectangle `json:"geometry"`
|
||||
Urgent bool `json:"urgent"`
|
||||
Sticky bool `json:"sticky"`
|
||||
Marks []interface{} `json:"marks"` // TODO type
|
||||
Focused bool `json:"focused"`
|
||||
Focus []Node `json:"focus"`
|
||||
Nodes []Node `json:"nodes"`
|
||||
FloatingNodes []Node `json:"floating_nodes"`
|
||||
Representation string `json:"representation"`
|
||||
FullscreenMode FullscreenMode `json:"fullscreen_mode"`
|
||||
AppID string `json:"app_id"`
|
||||
PID int `json:"pid"`
|
||||
Visible bool `json:"visible"`
|
||||
Shell string `json:"shell"`
|
||||
InhibitIdle bool `json:"inhibit_idle"`
|
||||
Window int `json:"window"`
|
||||
WindowProperties WindowProperties `json:"window_properties"`
|
||||
|
||||
// Undocumented fields. Use with caution!
|
||||
Num int `json:"num"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
func (n Node) String() string {
|
||||
data, _ := json.MarshalIndent(n, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type NodeType = string
|
||||
|
||||
const (
|
||||
NodeTypeRoot NodeType = "root"
|
||||
NodeTypeOutput NodeType = "output"
|
||||
NodeTypeWorkspace NodeType = "workspace"
|
||||
NodeTypeContainer NodeType = "con"
|
||||
NodeTypeFloatingContainer NodeType = "floating_con"
|
||||
)
|
||||
|
||||
type BorderStyle = string
|
||||
|
||||
const (
|
||||
BorderStyleNormal BorderStyle = "normal"
|
||||
BorderStyleNone BorderStyle = "none"
|
||||
BorderStylePixel BorderStyle = "pixel"
|
||||
BorderStyleCSD BorderStyle = "csd"
|
||||
)
|
||||
|
||||
type Layout = string
|
||||
|
||||
const (
|
||||
LayoutSplitH Layout = "splith"
|
||||
LayoutSplitV Layout = "splitv"
|
||||
LayoutStacked Layout = "stacked"
|
||||
LayoutTabbed Layout = "tabbed"
|
||||
LayoutOutput Layout = "output"
|
||||
)
|
||||
|
||||
type Orientation = string
|
||||
|
||||
const (
|
||||
OrientationVertical Orientation = "vertical"
|
||||
OrientationHorizontal Orientation = "horizontal"
|
||||
OrientationNone Orientation = "none"
|
||||
)
|
||||
|
||||
type FullscreenMode = uint8
|
||||
|
||||
const (
|
||||
FullscreenModeNone FullscreenMode = iota
|
||||
FullscreenModeWorkspace FullscreenMode = iota
|
||||
FullscreenModeGlobal FullscreenMode = iota
|
||||
)
|
||||
|
||||
type WorkspaceEventChange = string
|
||||
|
||||
const (
|
||||
WorkspaceEventChangeInit WorkspaceEventChange = "init"
|
||||
WorkspaceEventChangeEmpty WorkspaceEventChange = "empty"
|
||||
WorkspaceEventChangeFocus WorkspaceEventChange = "focus"
|
||||
WorkspaceEventChangeMove WorkspaceEventChange = "move"
|
||||
WorkspaceEventChangeRename WorkspaceEventChange = "rename"
|
||||
WorkspaceEventChangeUrgent WorkspaceEventChange = "urgent"
|
||||
WorkspaceEventChangeReload WorkspaceEventChange = "reload"
|
||||
)
|
||||
|
||||
type WindowEventChange = string
|
||||
|
||||
const (
|
||||
WindowEventChangeNew WindowEventChange = "new"
|
||||
WindowEventChangeClose WindowEventChange = "close"
|
||||
WindowEventChangeFocus WindowEventChange = "focus"
|
||||
WindowEventChangeTitle WindowEventChange = "title"
|
||||
WindowEventChangeFullscreenMode WindowEventChange = "fullscreen_mode"
|
||||
WindowEventChangeMove WindowEventChange = "move"
|
||||
WindowEventChangeFloating WindowEventChange = "floating"
|
||||
WindowEventChangeUrgent WindowEventChange = "urgent"
|
||||
WindowEventChangeMark WindowEventChange = "mark"
|
||||
)
|
||||
|
||||
type InputEventChange = string
|
||||
|
||||
const (
|
||||
InputEventChangeAdded InputEventChange = "added"
|
||||
InputEventChangeRemoved InputEventChange = "removed"
|
||||
InputEventChangeXKBKeymap InputEventChange = "xkb_kemap"
|
||||
InputEventChangeXKBLayout InputEventChange = "xkb_layout"
|
||||
InputEventChangeLibInputConfig InputEventChange = "libinput_config"
|
||||
)
|
||||
|
||||
type InputType = string
|
||||
|
||||
const (
|
||||
InputTypeKeyboard InputType = "keyboard"
|
||||
InputTypeMouse InputType = "mouse"
|
||||
)
|
||||
|
||||
type commandResult struct {
|
||||
Success bool `json:"success"`
|
||||
ParseError bool `json:"parse_error"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func (r commandResult) String() string {
|
||||
data, _ := json.MarshalIndent(r, "", "\t")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (r commandResult) GetError() error {
|
||||
return errors.New(r.Error)
|
||||
}
|
||||
|
||||
func (r commandResult) HasError() bool {
|
||||
return !r.Success
|
||||
}
|
Loading…
Reference in New Issue
Block a user