77 lines
1.8 KiB
Go
77 lines
1.8 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
// Package i3ipc observes i3 without issuing window-management commands.
|
|
package i3ipc
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
const (
|
|
workspaces = 1
|
|
subscribe = 2
|
|
tree = 4
|
|
eventBit uint32 = 1 << 31
|
|
workspaceEvent = eventBit
|
|
modeEvent = eventBit | 2
|
|
windowEvent = eventBit | 3
|
|
// A corrupt peer cannot cause an unbounded allocation or event backlog.
|
|
maxPayload = 16 << 20
|
|
maxPending = 256
|
|
)
|
|
|
|
type message struct {
|
|
kind uint32
|
|
body []byte
|
|
}
|
|
|
|
func readMessage(r io.Reader) (message, error) {
|
|
var header [14]byte
|
|
if _, err := io.ReadFull(r, header[:]); err != nil {
|
|
return message{}, err
|
|
}
|
|
if string(header[:6]) != "i3-ipc" {
|
|
return message{}, errors.New("invalid i3 magic")
|
|
}
|
|
size := binary.NativeEndian.Uint32(header[6:10])
|
|
if size > maxPayload {
|
|
return message{}, errors.New("i3 payload too large")
|
|
}
|
|
body := make([]byte, int(size))
|
|
if _, err := io.ReadFull(r, body); err != nil {
|
|
return message{}, err
|
|
}
|
|
if len(body) != 0 && !json.Valid(body) {
|
|
return message{}, errors.New("invalid i3 JSON")
|
|
}
|
|
return message{binary.NativeEndian.Uint32(header[10:]), body}, nil
|
|
}
|
|
|
|
func writeMessage(w io.Writer, kind uint32, body []byte) error {
|
|
if len(body) > maxPayload {
|
|
return errors.New("i3 payload too large")
|
|
}
|
|
var header [14]byte
|
|
copy(header[:], "i3-ipc")
|
|
binary.NativeEndian.PutUint32(header[6:10], uint32(len(body)))
|
|
binary.NativeEndian.PutUint32(header[10:], kind)
|
|
for _, part := range [][]byte{header[:], body} {
|
|
for len(part) > 0 {
|
|
n, err := w.Write(part)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n == 0 {
|
|
return io.ErrShortWrite
|
|
}
|
|
part = part[n:]
|
|
}
|
|
}
|
|
return nil
|
|
}
|