// 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 }