Documentation
¶
Overview ¶
Package tmuxcc is a tmux control-mode (`tmux -C`) client: it attaches to one existing tmux session, models that session's windows, streams each window's decoded output, and drives tmux with ordinary commands. It owns no transport and no UI — an adapter subscribes to a client's event stream and renders it.
Index ¶
- Variables
- func ObserveFrameLatency(ctx context.Context, d time.Duration)
- func RunnerArgs(args []string) []string
- func RunnerEnviron(environ func(context.Context) []string) func(context.Context) []string
- type Client
- func (c *Client) Close(ctx context.Context) error
- func (c *Client) CloseWindow(ctx context.Context, windowID string) error
- func (c *Client) KillPane(ctx context.Context, pane string) error
- func (c *Client) ListPanes(ctx context.Context, windowID string) ([]Pane, error)
- func (c *Client) MoveWindow(ctx context.Context, windowID string, position int) ([]Window, error)
- func (c *Client) NewWindow(ctx context.Context) (string, error)
- func (c *Client) PaneWindow(paneID string) (Window, error)
- func (c *Client) Paste(ctx context.Context, pane string, p []byte) error
- func (c *Client) RenameWindow(ctx context.Context, windowID, name string) error
- func (c *Client) Renegotiate(ctx context.Context, cols, rows int) (err error)
- func (c *Client) Repaint(ctx context.Context) (err error)
- func (c *Client) Resize(ctx context.Context, cols, rows int) error
- func (c *Client) ResizePane(ctx context.Context, pane string, width, height int) error
- func (c *Client) SelectPane(ctx context.Context, pane string, direction PaneDirection) error
- func (c *Client) SelectWindow(ctx context.Context, windowID string) error
- func (c *Client) SplitPane(ctx context.Context, pane string, direction SplitDirection) (string, error)
- func (c *Client) Subscribe() (<-chan Event, func())
- func (c *Client) Windows() []Window
- func (c *Client) Write(ctx context.Context, pane string, p []byte) error
- func (c *Client) ZoomPane(ctx context.Context, pane string) error
- type CommandError
- type ContinueNotification
- type Event
- type ExitNotification
- type Gateway
- type IndexedWindow
- type Layout
- type LayoutChanged
- type LifecycleChanged
- type LifecycleKind
- type Manager
- func (m *Manager) Attach(ctx context.Context, slug string, cols, rows int) ([]Window, error)
- func (m *Manager) Available(ctx context.Context) error
- func (m *Manager) CapturePane(ctx context.Context, name string) (string, error)
- func (m *Manager) CheckLaunch(ctx context.Context, slug string) error
- func (m *Manager) Client(slug string) (*Client, bool)
- func (m *Manager) CurrentPath(ctx context.Context, slug string) (string, error)
- func (m *Manager) Detach(ctx context.Context, slug string) error
- func (m *Manager) HasSession(ctx context.Context, slug string) (bool, error)
- func (m *Manager) KillSession(ctx context.Context, slug string) (bool, error)
- func (m *Manager) ListAllWindows(ctx context.Context, slugs []string) (map[string][]Window, error)
- func (m *Manager) ListIndexedWindows(ctx context.Context, slugs []string) (map[string][]IndexedWindow, error)
- func (m *Manager) NewCommandWindow(ctx context.Context, slug, dir, name, command string) (string, error)
- func (m *Manager) NewSession(ctx context.Context, name, dir, command string, env []string) error
- func (m *Manager) NewWindow(ctx context.Context, slug string) (string, error)
- func (m *Manager) RenameSession(ctx context.Context, from, to string) error
- func (m *Manager) RenameSessionIfPresent(ctx context.Context, from, to string) (bool, error)
- func (m *Manager) SessionNames(ctx context.Context, prefix string) ([]string, error)
- func (m *Manager) Stop(ctx context.Context) error
- func (m *Manager) Subscribe(slug string) (<-chan Event, func(), error)
- type ManagerOptions
- type Notification
- type Options
- type Output
- type OutputNotification
- type Pane
- type PaneDirection
- type PauseNotification
- type SessionChanged
- type SessionWindowChanged
- type SplitDirection
- type SplitKind
- type Window
- type WindowAddNotification
- type WindowChanged
- type WindowCloseNotification
- type WindowEventKind
- type WindowPaneChanged
- type WindowRenamedNotification
Constants ¶
This section is empty.
Variables ¶
var ( // and when tmux is missing or older than 3.2. ErrUnavailable = errors.New("tmuxcc: control mode unavailable") // ErrInvalidSize is returned for out-of-bounds cols/rows. ErrInvalidSize = errors.New("tmuxcc: invalid terminal size") // ErrNotAttached is returned for a slug with no live client. ErrNotAttached = errors.New("tmuxcc: no client attached") // ErrUnknownWindow is returned for a window id absent from the window set. ErrUnknownWindow = errors.New("tmuxcc: unknown window") // ErrUnknownPane is returned for a pane id no tracked window owns. ErrUnknownPane = errors.New("tmuxcc: unknown pane") // ErrInvalidDirection is returned for a split or pane-selection direction // outside the vocabulary. ErrInvalidDirection = errors.New("tmuxcc: invalid direction") // ErrInvalidName is returned for a window name tmux could not carry. ErrInvalidName = errors.New("tmuxcc: invalid window name") // ErrInvalidPosition is returned for a move to a position outside the // session's window order. ErrInvalidPosition = errors.New("tmuxcc: invalid window position") )
var ErrLaunchPending = errors.New("tmux session launch is in progress or was interrupted")
ErrLaunchPending identifies an allocated session that has not committed its launch.
var ErrSessionExists = errors.New("tmuxcc: session already exists")
ErrSessionExists reports a NewSession call for a name that already addresses a live tmux session. Unlike Attach, which is meant to reuse whatever is already there, a caller of NewSession is minting a session it expects to be new.
Functions ¶
func ObserveFrameLatency ¶
ObserveFrameLatency is exported because only the transport knows when a frame reached the wire.
func RunnerArgs ¶
RunnerArgs keeps one-shot commands on an inherited custom tmux socket after RunnerEnviron removes the parent client's tmux variables.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is one control-mode connection to one Hive session.
func Attach ¶
Attach spawns `tmux -C attach -t <slug>` over the process seam and runs the attach sequence: handshake, client size, window enumeration, then a capture-pane first paint per pane. Live output produced during that sequence is buffered and replayed behind each pane's snapshot.
ctx bounds the handshake and the attach commands only; lifetime bounds the client's own goroutines, which outlive the request that opened them.
func (*Client) CloseWindow ¶
func (*Client) KillPane ¶
KillPane kills one pane. tmux closes the window with its last pane, and announces that as %window-close like any other close.
func (*Client) ListPanes ¶
ListPanes reports the panes of one window. It is a live query rather than controller state: only a window's active pane id is modelled there, and what a pane is running changes without tmux announcing anything.
func (*Client) MoveWindow ¶
MoveWindow moves windowID to position in the session's window order and answers with the order tmux settled on. position indexes the resulting order, which is what a drop on a tab strip means; the reply is authoritative because the order is tmux session state and every other client attached sees the move.
Two tmux behaviours are load-bearing. A move is an unlink and a relink, so tmux announces the window being moved as closed even though it is still there — suppressed here, or the tab would be torn down and rebuilt blank. And tmux selects whatever it moves unless told not to, while -d on the window that is already selected deselects it: the flag has to follow the moved window, or the active window changes under a reorder that was never about selection.
func (*Client) NewWindow ¶
NewWindow creates a window and returns its id. The tab set itself is driven by the %window-add notification that follows.
It opens where the active pane is rather than where the session was started, which is what a new tab means in a terminal emulator: the window the user is looking at is the one they are asking for another of.
func (*Client) PaneWindow ¶
PaneWindow resolves a pane to the window that owns it.
func (*Client) Paste ¶
Paste inserts text into a pane as a paste rather than as keystrokes. tmux applies the brackets because tmux is the only side that knows whether the pane's program asked for them: a first paint reconstructs mouse input state, but not bracketed-paste mode, and tmux never re-sends that mode to a control client (ADR pastes-are-tmux-paste-buffer-operations-not-keystrokes).
The text rides tmux's stdin rather than an argument so it stays out of the process table, and the buffer is named so it stays out of the numbered stack holding the user's own copies.
func (*Client) RenameWindow ¶
func (*Client) Renegotiate ¶
Renegotiate re-votes the client size and refreshes the window set from tmux's post-vote answer — the same vote-then-list order negotiate runs on a fresh attach, for the same reason: a snapshot painted at a stale size tears the moment the surface's own vote lands. The %layout-change notification is asynchronous, so only an explicit list after the vote reads the size tmux actually settled on.
func (*Client) Repaint ¶
Repaint re-runs the first paint for every window, so a caller re-attaching to a live client is handed the same paintable stream a fresh attach would be. A transport-only drop — a stalled write, a webview reload — leaves this client attached while taking the emulator that rendered it, and what the dropped stream already delivered is not in the backlog to replay: without a fresh snapshot the new panes open blank against a session that never stopped.
The broker is reset first, which is what makes the snapshot the caller's: see broker.reset. The captures are bounded like an attach's own, because the manager runs this under the lock every other attach queues behind.
func (*Client) Resize ¶
Resize renegotiates the client size. Every client attached to a window renders the same grid and tmux's window-size option picks whose size that is — by default the most recently used client's — so this is a vote, and %layout-change is the answer.
func (*Client) ResizePane ¶
ResizePane sets width and/or height in cells. Zero leaves that axis unchanged; nonzero values must be in 1..1000. tmux adjusts the divider after the pane, or before it when the pane is last.
func (*Client) SelectPane ¶
SelectPane makes a pane its window's active pane — the pane itself, or the neighbour direction names, which tmux picks the way its own select-pane bindings do. %window-pane-changed carries the answer.
func (*Client) SelectWindow ¶
func (*Client) SplitPane ¶
func (c *Client) SplitPane(ctx context.Context, pane string, direction SplitDirection) (string, error)
SplitPane splits a pane and returns the new pane's id. The layout itself arrives on %layout-change, and the new pane's first paint follows from the reconcile that triggers. It opens where the split pane is, the same way a new window does (ADR a-new-tab-and-a-launcher-open-where-the-terminal-s-active-pane-is).
func (*Client) Subscribe ¶
Subscribe returns this client's event channel plus its unsubscribe func. There is one active subscriber: a second call closes the first channel.
func (*Client) Write ¶
Write sends bytes to a pane. Every byte goes as hex (send-keys -H), which sidesteps tmux's key-name and literal parsing entirely. The pane is named rather than resolved from its window's active pane: a click that moves focus has its select-pane in flight while the first keystrokes follow it, and they must land where the user is typing.
type CommandError ¶
CommandError is a tmux %error reply. The app service classifies it into an error Kind; nothing matches on its text inside this package.
func (*CommandError) Error ¶
func (e *CommandError) Error() string
type ContinueNotification ¶
type ContinueNotification struct{ Pane string }
type Event ¶
type Event interface {
// contains filtered or unexported methods
}
Event is one item on a client's outbound stream.
type ExitNotification ¶
type ExitNotification struct{ Reason string }
type Gateway ¶
type Gateway struct {
// contains filtered or unexported fields
}
Gateway is the protocol engine: it serializes command writes to the control-mode client's stdin, pairs each %begin/%end block with the command that produced it, and routes interleaved notifications. It owns no transport beyond the io.Writer it was handed.
func NewGateway ¶
NewGateway wires a gateway over the control client's stdin. notify runs on the reader goroutine in stream order and MUST NOT block: a pending command's %end reply shares that goroutine.
func (*Gateway) Feed ¶
Feed advances the state machine by one control-mode line (CR already stripped). line must not be retained. It returns a non-nil error only for a fatal desync, which also fails every pending command.
Inside an open block only the guard that closes it is a guard: capture-pane replies carry screen contents verbatim, and a line of pane output can look like any of them. Anything else — a mismatched %end, a well-formed %begin — is reply content (iTerm2 reads the protocol the same way). A block that never closes therefore surfaces as the caller's Send timing out rather than as a desync, which is bounded and does not kill a session over a screenful of text.
func (*Gateway) Send ¶
Send writes cmd and blocks until its %end (reply lines) or %error (*CommandError). A ctx cancellation leaves a tombstone in the FIFO so the reply tmux still sends is drained rather than handed to the next caller.
func (*Gateway) ServerError ¶
ServerError reports the content of the last server-originated %error block, if any — the text tmux prints when it rejects the attach outright.
type IndexedWindow ¶
IndexedWindow identifies a tmux window by both its stable id and mutable index.
type Layout ¶
Layout is a window's pane tree as tmux lays it out, in cells. A leaf names a pane; a node divides its box among Cells, each a Layout of its own. Every cell carries its own box, so a renderer places a pane without walking the tree, and the one-cell gap between two siblings is tmux's border. tmux never nests a node inside one of the same kind, so the divider between two siblings always belongs to their parent.
func ParseLayout ¶
ParseLayout reads tmux's layout string: `<checksum>,<cell>`, where a cell is `WxH,X,Y` followed by `,<pane>` for a leaf, `{<cells>}` for a left-right split or `[<cells>]` for a top-bottom one. Pane ids in it are bare numbers; they come back carrying the `%` every other tmux surface spells them with.
type LayoutChanged ¶
LayoutChanged carries the window's new pane tree and whether its active pane is zoomed over it. Layout is the zero value when the string could not be read; the reconcile the same notification triggers carries the tree authoritatively.
type LifecycleChanged ¶
type LifecycleChanged struct {
Kind LifecycleKind
WindowID string
Message string
}
LifecycleChanged reports attach/pause/resume/exit/error. WindowID is set for per-window pause/resume; Message carries an exit reason or error text.
type LifecycleKind ¶
type LifecycleKind string
LifecycleKind is the connection-state vocabulary, wire strings likewise.
const ( LifecycleAttached LifecycleKind = "attached" LifecyclePaused LifecycleKind = "paused" LifecycleResumed LifecycleKind = "resumed" LifecycleExited LifecycleKind = "exited" LifecycleError LifecycleKind = "error" // LifecycleDegraded says output was lost and a repaint is following it onto // the same stream. It is an edge, not a mode: nothing clears it, because // there is no state to clear — the snapshot behind it is the recovery. LifecycleDegraded LifecycleKind = "degraded" )
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns one control-mode client per session slug.
func NewManager ¶
func NewManager(ctx context.Context, opts ManagerOptions) *Manager
NewManager builds the client set. ctx is the app's lifetime context: every tmux client's goroutines descend from it.
func (*Manager) Attach ¶
Attach opens, or returns the windows of, the client for slug, and either way leaves a first paint on its stream: a caller attaches to render, and the one re-attaching over a transport that dropped has nothing else to render from. A second attach for a live slug therefore reuses the client and repaints it, keeping the size it already voted — the vote belongs to a fresh attach. After a client exits its slug is free again and a re-attach starts fresh. cols/rows of 0x0 attach unsized — see Options.unsized.
func (*Manager) Available ¶
Available reports nil when a usable tmux (>= 3.2) is present on a supported build and platform, else ErrUnavailable. A successful probe is cached along with the binary it ran; a failing one is retried, so installing tmux does not require a restart.
func (*Manager) CapturePane ¶
CapturePane returns the current screen of name's active pane. -p prints to stdout and -J joins wrapped lines — the exact capture-pane invocation hive's own status detection runs (internal/platform/tmux/status.TmuxCapture.CapturePane), so terminal.Detector sees the input it was tuned against.
func (*Manager) CheckLaunch ¶ added in v0.63.0
CheckLaunch rejects an uncommitted Hive session before a user attaches to its placeholders.
func (*Manager) CurrentPath ¶
CurrentPath answers where a session's active pane is: the directory a prompt in it would print, which follows a cd where the session's own start directory does not. It is what "here" means for anything opened from a terminal on screen — a new window, or a launcher with no directory of its own.
It is a one-shot whether or not this app holds a control client, because the answer is tmux's session state and asking over the control stream would cost a round trip to reach the same pane.
func (*Manager) HasSession ¶
HasSession reports whether tmux is running a session named slug. Attach and start both ask before doing anything, so "there is no session" is a probe's answer rather than a reading of an attach failure's message. A live client is proof enough, which keeps a warm re-attach free of the round trip.
func (*Manager) KillSession ¶
KillSession kills the tmux session named slug and reports whether there was one to kill; a slug tmux is not running is success with nothing done, the same tolerance RenameSession has. The control client goes with it, synchronously: the child would exit on its own moments later, and a caller that re-attaches in between must not be handed the dying client's window set.
func (*Manager) ListAllWindows ¶
ListAllWindows answers the window sets of every slug in one tmux call.
Asking per slug cost two spawns for each unattached session — a has-session probe and a list-windows — so a sidebar sweep spawned twice as many processes as it had sessions, all at once. `list-windows -a` is one spawn for the whole server, and a slug it has no session for is simply absent from the output, which is the answer has-session was being paid for.
Attached slugs still answer from their live client, as ListWindows has them: it is authoritative, already in memory, and carries the sizes tmux settled on.
func (*Manager) ListIndexedWindows ¶
func (m *Manager) ListIndexedWindows(ctx context.Context, slugs []string) (map[string][]IndexedWindow, error)
ListIndexedWindows answers window identities for every requested slug in one tmux call. Status integrations report window indices, while terminal clients address stable window ids, so this is the seam that joins those views.
func (*Manager) NewCommandWindow ¶
func (m *Manager) NewCommandWindow(ctx context.Context, slug, dir, name, command string) (string, error)
NewCommandWindow runs a command in a peer window using the same environment and login-shell contract as NewSession. It never creates a missing session.
func (*Manager) NewSession ¶
NewSession creates a detached tmux session named name, in dir, running command through a login shell. command is a shell command line rather than an argv — the same contract ptyterm.Spec.Command uses (ADR subprocess-environment): tmux execs the login shell directly with -c command as its own argv (more than one trailing argument after new-session's flags is executed as-is, never re-parsed by a shell), so the shell's aliases and functions resolve. Empty command opens an interactive login shell instead.
A login shell with -c is not an interactive one, so zsh reads .zprofile but not .zshrc — the file a PATH is as often set in (ADR tmux-runs-in-the-resolved-environment). The environment tmux itself is spawned with is therefore load-bearing rather than incidental: a pane inherits the client that created it, so ManagerOptions.Environ is the floor under whatever the startup files that do run add to it.
env entries ("KEY=VALUE") are set in the session's own environment via new-session -e (tmux ≥ 3.2, which Available already requires), so — unlike prefixing the command line — they also seed any window the process opens in the session later.
func (*Manager) NewWindow ¶
NewWindow creates a window in a session this app holds no control client for, and returns its id. An attached client has its own NewWindow; this is for the row a user pressed + on before anything attached to it.
The directory is spelled out because tmux resolves an unset start-directory against the *client* running the command — and a one-shot command client is this process, so the window would open in the app's working directory. It is the active pane's rather than the session's for the reason CurrentPath explains.
func (*Manager) RenameSession ¶
RenameSession renames a live tmux session when one exists.
func (*Manager) RenameSessionIfPresent ¶
RenameSessionIfPresent renames the live tmux session named from to to and reports whether it changed tmux. It drops the control client registered under the old slug so nothing keeps addressing a name tmux no longer answers to.
A session hive knows about does not have to have a tmux session behind it -- it may never have been spawned, or its tmux server may have been restarted -- so an absent one is success, not an error. Existence is probed with has-session rather than inferred from rename-session's stderr, because the alternative is matching on tmux's message text.
func (*Manager) SessionNames ¶
SessionNames lists every live tmux session name carrying prefix. A server with no sessions at all — or none matching — reports zero names rather than an error, the same tolerance ListAllWindows extends to an empty tmux server.
func (*Manager) Stop ¶
Stop closes every client, joins their readers, and cancels the app-lifetime context. Idempotent.
Clients close concurrently and the join is bounded by ctx, because closing one is not: teardown waits on the reader, the command worker and the tmux child with no deadline of its own, and the detach it sends first writes to a pipe that nothing guarantees is being read. Serially and unbounded, a single client that will not come down holds App.Close — and therefore the whole quit — open forever. Every client has already been sent its kill by the time the deadline can expire, so what is abandoned here is the join, not the teardown.
type ManagerOptions ¶
type ManagerOptions struct {
Logger zerolog.Logger
BufferBytes int
// Binary answers which tmux to exec, and is consulted on every failed
// availability probe rather than once at startup, so installing tmux does not
// need a relaunch. nil means $PATH.
Binary func() (string, error)
// Environ answers the environment tmux is spawned with — execenv's resolved
// PATH in the app, whatever a test hands it otherwise. nil means this
// process's own. It is the same hook ptyterm.ManagerOptions carries, held
// separately because the two backends share no interface (ADR ephemeral-popup-terminals).
Environ func(context.Context) []string
// contains filtered or unexported fields
}
ManagerOptions configures the client set.
type Notification ¶
type Notification interface {
// contains filtered or unexported methods
}
Notification is a parsed %-line the gateway dispatches to the controller.
type Options ¶
type Options struct {
Slug string // tmux session name == Hive session slug
// Cols and Rows are the opening size vote, and 0x0 means the caller has
// nothing measured to vote with — see unsized.
Cols int
Rows int
Binary string // tmux executable; empty resolves "tmux" through $PATH
BufferBytes int // broker bound; 0 == defaultBufferBytes
Logger zerolog.Logger
OnExit func(slug, reason string)
// Environ is what tmux is spawned with, minus the client variables
// withoutTmuxClient drops — execenv's resolved environment in the app, nil
// meaning this process's own.
Environ []string
// contains filtered or unexported fields
}
Options configures one attach.
type Output ¶
Output is one pane's decoded bytes. Every pane in a window's layout streams; only a pane no tracked window owns is dropped. At is the decode timestamp the WebSocket adapter measures send latency against.
type OutputNotification ¶
OutputNotification carries already-decoded pane bytes.
type Pane ¶
Pane is one pane of a window. Command is its foreground process — the name tmux's own status line shows — while PID is the process tmux started the pane with, so the two name the same process only while nothing that process started is running. PID is 0 when tmux reported one this cannot read.
type PaneDirection ¶
type PaneDirection string
PaneDirection names a neighbour of a pane; the empty value names the pane itself.
const ( PaneSelf PaneDirection = "" PaneLeft PaneDirection = "left" PaneRight PaneDirection = "right" PaneUp PaneDirection = "up" PaneDown PaneDirection = "down" )
type PauseNotification ¶
type PauseNotification struct{ Pane string }
type SessionChanged ¶
type SessionChanged struct{ Session, Name string }
type SessionWindowChanged ¶
type SessionWindowChanged struct{ Session, Window string }
type SplitDirection ¶
type SplitDirection string
SplitDirection is which way a split lays the new pane, in tmux's words: a horizontal split puts it to the right, a vertical one below.
const ( SplitHorizontal SplitDirection = "horizontal" SplitVertical SplitDirection = "vertical" )
type SplitKind ¶
type SplitKind string
SplitKind says how a layout node divides its box among its cells.
const ( // SplitNone marks a leaf: one pane filling the box. SplitNone SplitKind = "" // SplitLeftRight lays cells side by side — what `split-window -h` makes. SplitLeftRight SplitKind = "leftright" // SplitTopBottom stacks cells — what `split-window -v` makes. SplitTopBottom SplitKind = "topbottom" )
type Window ¶
type Window struct {
ID string // tmux window id, e.g. "@275"
Name string
Active bool
ActivePane string // tmux pane id, e.g. "%512"
// Width and Height are tmux's own size for this window — whichever attached
// client tmux's window-size option picked, not necessarily ours. A renderer
// that draws at any other size mangles the pane's cursor-addressed output.
// 0 means not known yet.
Width int
Height int
// Layout is the pane tree, at the window's unzoomed geometry; the zero
// value means tmux has not reported one yet. Zoomed says the active pane is
// drawn over the whole window while Layout still records where it goes
// back to.
Layout Layout
Zoomed bool
}
Window is the app-side model of one tmux window.
type WindowAddNotification ¶
type WindowAddNotification struct{ Window string }
type WindowChanged ¶
type WindowChanged struct {
Kind WindowEventKind
Window Window
}
WindowChanged reports a change to the tab set.
type WindowCloseNotification ¶
type WindowCloseNotification struct{ Window string }
type WindowEventKind ¶
type WindowEventKind string
WindowEventKind is the tab-set change vocabulary. The values are the wire strings the WebSocket adapter emits verbatim.
const ( WindowAdded WindowEventKind = "added" WindowClosed WindowEventKind = "closed" WindowRenamed WindowEventKind = "renamed" WindowActiveChanged WindowEventKind = "active-changed" // WindowLayoutChanged covers a resize as well as a split, a closed pane or // a zoom: the window's size is its layout's root box. WindowLayoutChanged WindowEventKind = "layout-changed" )
type WindowPaneChanged ¶
type WindowPaneChanged struct{ Window, Pane string }
type WindowRenamedNotification ¶
type WindowRenamedNotification struct{ Window, Name string }