5 Commits

9 changed files with 169 additions and 29 deletions

2
do.go
View File

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
) )
func (c *Client) Action(action Action) error { func (c *Client) DoAction(action Action) error {
// print request and response to console: // print request and response to console:
// //
// io.Copy(os.Stdout, asJsonReader(action)) // io.Copy(os.Stdout, asJsonReader(action))

2
go.mod
View File

@ -1,6 +1,6 @@
module git.tordarus.net/niri-tools/niri module git.tordarus.net/niri-tools/niri
go 1.24.4 go 1.25
require ( require (
git.tordarus.net/tordarus/channel v0.1.20 git.tordarus.net/tordarus/channel v0.1.20

View File

@ -26,6 +26,10 @@ func ActionSpawn(command []string) Action {
return ActionCustom("Spawn", map[string]any{"command": command}) return ActionCustom("Spawn", map[string]any{"command": command})
} }
func ActionSpawnSh(command string) Action {
return ActionCustom("SpawnSh", map[string]any{"command": command})
}
func ActionDoScreenTransition(delay Option[time.Duration]) Action { func ActionDoScreenTransition(delay Option[time.Duration]) Action {
return ActionCustom("DoScreenTransition", map[string]any{"delay_ms": mapOption(delay, time.Duration.Milliseconds)}) return ActionCustom("DoScreenTransition", map[string]any{"delay_ms": mapOption(delay, time.Duration.Milliseconds)})
} }
@ -266,12 +270,12 @@ func ActionFocusWorkspacePrevious() Action {
return ActionCustom("FocusWorkspacePrevious", map[string]any{}) return ActionCustom("FocusWorkspacePrevious", map[string]any{})
} }
func ActionMoveWindowToWorkspaceDown() Action { func ActionMoveWindowToWorkspaceDown(focus bool) Action {
return ActionCustom("MoveWindowToWorkspaceDown", map[string]any{}) return ActionCustom("MoveWindowToWorkspaceDown", map[string]any{"focus": focus})
} }
func ActionMoveWindowToWorkspaceUp() Action { func ActionMoveWindowToWorkspaceUp(focus bool) Action {
return ActionCustom("MoveWindowToWorkspaceUp", map[string]any{}) return ActionCustom("MoveWindowToWorkspaceUp", map[string]any{"focus": focus})
} }
func ActionMoveWindowToWorkspace(id Option[WindowID], reference WorkspaceReferenceArg, focus bool) Action { func ActionMoveWindowToWorkspace(id Option[WindowID], reference WorkspaceReferenceArg, focus bool) Action {
@ -335,7 +339,7 @@ func ActionFocusMonitorNext() Action {
} }
func ActionFocusMonitor(output OutputName) Action { func ActionFocusMonitor(output OutputName) Action {
return ActionCustom("FocusMonitorNext", map[string]any{"output": output}) return ActionCustom("FocusMonitor", map[string]any{"output": output})
} }
func ActionMoveWindowToMonitorLeft() Action { func ActionMoveWindowToMonitorLeft() Action {
@ -410,14 +414,26 @@ func ActionSwitchPresetColumnWidth() Action {
return ActionCustom("SwitchPresetColumnWidth", map[string]any{}) return ActionCustom("SwitchPresetColumnWidth", map[string]any{})
} }
func ActionSwitchPresetColumnWidthBack() Action {
return ActionCustom("SwitchPresetColumnWidthBack", map[string]any{})
}
func ActionSwitchPresetWindowWidth(id Option[WindowID]) Action { func ActionSwitchPresetWindowWidth(id Option[WindowID]) Action {
return ActionCustom("SwitchPresetWindowWidth", map[string]any{"id": id}) return ActionCustom("SwitchPresetWindowWidth", map[string]any{"id": id})
} }
func ActionSwitchPresetWindowWidthBack(id Option[WindowID]) Action {
return ActionCustom("SwitchPresetWindowWidthBack", map[string]any{"id": id})
}
func ActionSwitchPresetWindowHeight(id Option[WindowID]) Action { func ActionSwitchPresetWindowHeight(id Option[WindowID]) Action {
return ActionCustom("SwitchPresetWindowHeight", map[string]any{"id": id}) return ActionCustom("SwitchPresetWindowHeight", map[string]any{"id": id})
} }
func ActionSwitchPresetWindowHeightBack(id Option[WindowID]) Action {
return ActionCustom("SwitchPresetWindowHeightBack", map[string]any{"id": id})
}
func ActionMaximizeColumn() Action { func ActionMaximizeColumn() Action {
return ActionCustom("MaximizeColumn", map[string]any{}) return ActionCustom("MaximizeColumn", map[string]any{})
} }
@ -545,3 +561,7 @@ func ActionSetWindowUrgent(id Option[WindowID]) Action {
func ActionUnsetWindowUrgent(id Option[WindowID]) Action { func ActionUnsetWindowUrgent(id Option[WindowID]) Action {
return ActionCustom("UnsetWindowUrgent", map[string]any{"id": id}) return ActionCustom("UnsetWindowUrgent", map[string]any{"id": id})
} }
func ActionLoadConfigFile() Action {
return ActionCustom("LoadConfigFile", map[string]any{})
}

View File

@ -7,18 +7,25 @@ type Event interface {
} }
type eventContainer struct { type eventContainer struct {
EventWorkspacesChanged *EventWorkspacesChanged `json:"WorkspacesChanged"` EventWorkspacesChanged *EventWorkspacesChanged `json:"WorkspacesChanged,omitempty"`
EventWorkspaceUrgencyChanged *EventWorkspaceUrgencyChanged `json:"WorkspaceUrgencyChanged"` EventWorkspaceUrgencyChanged *EventWorkspaceUrgencyChanged `json:"WorkspaceUrgencyChanged,omitempty"`
EventWorkspaceActivated *EventWorkspaceActivated `json:"WorkspaceActivated"` EventWorkspaceActivated *EventWorkspaceActivated `json:"WorkspaceActivated,omitempty"`
EventWorkspaceActiveWindowChanged *EventWorkspaceActiveWindowChanged `json:"WorkspaceActiveWindowChanged"` EventWorkspaceActiveWindowChanged *EventWorkspaceActiveWindowChanged `json:"WorkspaceActiveWindowChanged,omitempty"`
EventWindowsChanged *EventWindowsChanged `json:"WindowsChanged"` EventWindowsChanged *EventWindowsChanged `json:"WindowsChanged,omitempty"`
EventWindowOpenedOrChanged *EventWindowOpenedOrChanged `json:"WindowOpenedOrChanged"` EventWindowOpenedOrChanged *EventWindowOpenedOrChanged `json:"WindowOpenedOrChanged,omitempty"`
EventWindowClosed *EventWindowClosed `json:"WindowClosed"` EventWindowClosed *EventWindowClosed `json:"WindowClosed,omitempty"`
EventWindowFocusChanged *EventWindowFocusChanged `json:"WindowFocusChanged"` EventWindowFocusChanged *EventWindowFocusChanged `json:"WindowFocusChanged,omitempty"`
EventWindowUrgencyChanged *EventWindowUrgencyChanged `json:"WindowUrgencyChanged"` EventWindowUrgencyChanged *EventWindowUrgencyChanged `json:"WindowUrgencyChanged,omitempty"`
EventKeyboardLayoutsChanged *EventKeyboardLayoutsChanged `json:"KeyboardLayoutsChanged"` EventWindowLayoutsChanged *EventWindowLayoutsChanged `json:"WindowLayoutsChanged,omitempty"`
EventKeyboardLayoutSwitched *EventKeyboardLayoutSwitched `json:"KeyboardLayoutSwitched"` EventKeyboardLayoutsChanged *EventKeyboardLayoutsChanged `json:"KeyboardLayoutsChanged,omitempty"`
EventOverviewOpenedOrClosed *EventOverviewOpenedOrClosed `json:"OverviewOpenedOrClosed"` EventKeyboardLayoutSwitched *EventKeyboardLayoutSwitched `json:"KeyboardLayoutSwitched,omitempty"`
EventOverviewOpenedOrClosed *EventOverviewOpenedOrClosed `json:"OverviewOpenedOrClosed,omitempty"`
EventConfigLoaded *EventConfigLoaded `json:"ConfigLoaded,omitempty"`
}
func (m *eventContainer) String() string {
data, _ := json.Marshal(m)
return string(data)
} }
func (c *eventContainer) event() Event { func (c *eventContainer) event() Event {
@ -40,12 +47,16 @@ func (c *eventContainer) event() Event {
return c.EventWindowFocusChanged return c.EventWindowFocusChanged
} else if c.EventWindowUrgencyChanged != nil { } else if c.EventWindowUrgencyChanged != nil {
return c.EventWindowUrgencyChanged return c.EventWindowUrgencyChanged
} else if c.EventWindowLayoutsChanged != nil {
return c.EventWindowLayoutsChanged
} else if c.EventKeyboardLayoutsChanged != nil { } else if c.EventKeyboardLayoutsChanged != nil {
return c.EventKeyboardLayoutsChanged return c.EventKeyboardLayoutsChanged
} else if c.EventKeyboardLayoutSwitched != nil { } else if c.EventKeyboardLayoutSwitched != nil {
return c.EventKeyboardLayoutSwitched return c.EventKeyboardLayoutSwitched
} else if c.EventOverviewOpenedOrClosed != nil { } else if c.EventOverviewOpenedOrClosed != nil {
return c.EventOverviewOpenedOrClosed return c.EventOverviewOpenedOrClosed
} else if c.EventConfigLoaded != nil {
return c.EventConfigLoaded
} }
return &EventInvalid{} return &EventInvalid{}
@ -164,6 +175,17 @@ func (m *EventWindowUrgencyChanged) String() string {
return string(data) return string(data)
} }
type EventWindowLayoutsChanged struct {
Changes []WindowLayoutChange `json:"changes"`
}
func (m *EventWindowLayoutsChanged) private() {}
func (m *EventWindowLayoutsChanged) String() string {
data, _ := json.Marshal(m)
return string(data)
}
type EventKeyboardLayoutsChanged struct { type EventKeyboardLayoutsChanged struct {
KeyboardLayouts KeyboardLayouts `json:"keyboard_layouts"` KeyboardLayouts KeyboardLayouts `json:"keyboard_layouts"`
} }
@ -196,3 +218,14 @@ func (m *EventOverviewOpenedOrClosed) String() string {
data, _ := json.Marshal(m) data, _ := json.Marshal(m)
return string(data) return string(data)
} }
type EventConfigLoaded struct {
Failed bool `json:"failed"`
}
func (m *EventConfigLoaded) private() {}
func (m *EventConfigLoaded) String() string {
data, _ := json.Marshal(m)
return string(data)
}

View File

@ -13,6 +13,7 @@ type Window struct {
Urgent bool `json:"is_urgent"` Urgent bool `json:"is_urgent"`
Floating bool `json:"is_floating"` Floating bool `json:"is_floating"`
Focused bool `json:"is_focused"` Focused bool `json:"is_focused"`
Layout WindowLayout `json:"layout"`
} }
func (m Window) String() string { func (m Window) String() string {

16
model_window_layout.go Normal file
View File

@ -0,0 +1,16 @@
package niri
import "encoding/json"
type WindowLayout struct {
PosInScrollingLayout Option[[2]int] `json:"pos_in_scrolling_layout"`
TileSize [2]float64 `json:"tile_size"`
WindowSize [2]int `json:"window_size"`
TilePosInWorkspaceView Option[[2]float64] `json:"tile_pos_in_workspace_view"`
WindowOffsetInTile [2]float64 `json:"window_offset_in_tile"`
}
func (m WindowLayout) String() string {
data, _ := json.MarshalIndent(m, "", "\t")
return string(data)
}

View File

@ -0,0 +1,36 @@
package niri
import (
"encoding/json"
"fmt"
)
type WindowLayoutChange struct {
WindowID WindowID
WindowLayout WindowLayout
}
func (m *WindowLayoutChange) UnmarshalJSON(data []byte) error {
arr := []any{}
if err := json.Unmarshal(data, &arr); err != nil {
return err
}
if len(arr) != 2 {
return fmt.Errorf("expected array of length 2 but got array of length %d", len(arr))
}
if wid, ok := arr[0].(float64); !ok {
return fmt.Errorf("expected array element at index 0 to be of type int")
} else {
m.WindowID = WindowID(wid)
}
if windowLayout, err := reencodeJson[WindowLayout](arr[1]); err != nil {
return fmt.Errorf("expected array element at index 1 to be of type WindowLayout")
} else if windowLayout != nil {
m.WindowLayout = *windowLayout
}
return nil
}

View File

@ -7,7 +7,7 @@ type WorkspaceID int
type Workspace struct { type Workspace struct {
ID WorkspaceID `json:"id"` ID WorkspaceID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Output string `json:"output"` Output OutputName `json:"output"`
Urgent bool `json:"is_urgent"` Urgent bool `json:"is_urgent"`
Active bool `json:"is_active"` Active bool `json:"is_active"`
Focused bool `json:"is_focused"` Focused bool `json:"is_focused"`

View File

@ -1,6 +1,7 @@
package niri package niri
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
@ -8,6 +9,7 @@ import (
"io" "io"
"net" "net"
"os" "os"
"sync"
) )
func notNull(c *Client) bool { func notNull(c *Client) bool {
@ -77,12 +79,16 @@ func readSocketGeneric[T any](ctx context.Context, socket string, body io.Reader
defer close(out) defer close(out)
defer r.Close() defer r.Close()
dec := json.NewDecoder(r)
for ctx.Err() == nil { for ctx.Err() == nil {
value := new(T) value := new(T)
if err := json.NewDecoder(r).Decode(value); err != nil { if err := dec.Decode(value); err != nil {
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
return return
} else { } else {
// JSON parsing errors
//fmt.Println(err)
continue continue
} }
} }
@ -111,3 +117,31 @@ func asJsonReader(v any) io.Reader {
data, _ := json.Marshal(v) data, _ := json.Marshal(v)
return bytes.NewReader(append(data, '\n')) return bytes.NewReader(append(data, '\n'))
} }
func reencodeJson[T any](data any) (*T, error) {
r, w := io.Pipe()
defer w.Close()
defer r.Close()
wbuf := bufio.NewWriter(w)
rbuf := bufio.NewReader(r)
wg := &sync.WaitGroup{}
value := new(T)
var encErr error
wg.Go(func() { encErr = json.NewEncoder(wbuf).Encode(data); wbuf.Flush() })
var decErr error
wg.Go(func() { decErr = json.NewDecoder(rbuf).Decode(value) })
wg.Wait()
if encErr != nil {
return nil, encErr
} else if decErr != nil {
return nil, decErr
}
return value, nil
}