kitty_panels

kitty_panels

A Crystal shard that turns kitty terminal windows into desktop panels: dropdowns, docks and overlays, each running its own command with its own environment and per-panel kitty settings. Everything is driven over kitty's remote control protocol, so a stock kitty build is all it needs.

Requirements

  • Crystal >= 1.21
  • kitty >= 0.34 (the remote control protocol version this shard speaks)

Installation

dependencies:
  kitty_panels:
    path: /path/to/kitty_panels
require "kitty_panels"

Usage

require "kitty_panels"

definitions = KittyPanels.load_definitions("panels.yaml")

engine = KittyPanels::Engine.new("/tmp/kitty-panels.sock")
engine.start
engine.wait_ready

rc = KittyPanels::RC.new("/tmp/kitty-panels.sock")
definitions.each { |panel| rc.launch_panel(panel) }

# ...
engine.terminate

KittyPanels::RC can also drive an already running kitty that listens on a socket (kitty --listen-on unix:/path/to/socket). Pass a password to use kitty's encrypted protocol (KITTY_PUBLIC_KEY must come from the kitty instance that owns the socket).

Configuration

panels: maps panel names to definitions. Panel names may use letters, digits, - and _. Every panel needs a cmd (argv array):

panels:
  dropdown:
    mode: dropdown
    edge: top
    lines: "40"
    layer: overlay
    focus_policy: exclusive
    hide_on_focus_loss: true
    kitty_overrides:
      background_opacity: "0.85"
    cmd: [/bin/bash, -l]

  statusbar:
    mode: dock
    edge: bottom
    lines: "2"
    focus_policy: not-allowed
    exclusive_zone: -1
    cmd: [/bin/sh, -c, "while true; do date; sleep 5; done"]

See examples/panels.yaml for the shipped example.

key default notes
cmd — required argv of the command that runs inside the panel
mode dropdown dropdown, dock, overlay; cannot change on a live panel
edge top top, bottom, left, right, center, center-sized
lines 25 height; cells, 200px or 10c
columns unset width; same units as lines
layer top background, bottom, top, overlay
focus_policy on-demand not-allowed, exclusive, on-demand
margin_top/bottom/left/right 0 integer
output_name unset restrict the panel to one monitor
exclusive_zone unset integer, e.g. -1
override_exclusive_zone false boolean
hide_on_focus_loss false boolean
app_id kitty-panels OS window class
env {} KEY: VALUE mapping exported into the panel
kitty_overrides {} per-panel kitty options, see below

kitty_overrides

Only options kitty can apply to an individual window are accepted; anything else is rejected at load time. Supported keys:

  • background_opacity — a number between 0 and 1
  • any kitty color option (foreground, background, cursor, selection_*, tab bar and mark colors, border colors, titlebar colors) plus colorN for N <= 255 — values are #rgb, #rrggbb, an X11 color name or none
  • window_padding_width, window_margin_width — numbers
  • enabled_layouts — comma-separated layout names
  • ligatures — never, always or cursor
  • background_image — image path or none; background_image_layout — configured, tiled, mirror-tiled, scaled, clamped, centered or cscaled (requires background_image)
  • window_logo — image path or none; window_logo_position and window_logo_alpha (0 to 1) require window_logo

Overrides are applied right after the panel launches. They cannot be changed on a live panel — recreate the panel instead.

Live reconfiguration

A subset of settings can be changed on a live panel with PanelDefinition#with_settings(settings), which returns a validated copy without touching the original: edge, layer, lines, columns, margin_*, focus_policy, output_name, exclusive_zone, override_exclusive_zone and hide_on_focus_loss. Flags can only be switched on and settings cannot be cleared — recreate the panel for that. Send the result to kitty with rc.configure_panel(name, args).

API

  • KittyPanels.load_definitions(path) : Array(PanelDefinition) — YAML loader; validation errors name the file, panel and line.
  • KittyPanels::Engine.new(socket_path, kitty = "kitty", config_path = nil, log_path = nil) — spawns a dedicated hidden kitty listening on a UNIX socket with socket-only remote control. Config and log default to <socket_path>.conf and <socket_path>.log. Methods: start, wait_ready(timeout), exited?, wait_exit, terminate(grace).
  • KittyPanels::RC.new(socket_path, password = nil) — remote control client: alive?, ls, live_panels : Hash(String, LivePanel), first_live_panel, launch_panel(defn, hidden = false) : Int64, configure_panel(name, os_panel_args), send_text(name, text), close_panel(name), panel_match(name). A failed launch rolls back.
  • KittyPanels::PanelDefinition — apply(key, value) (string-based, for CLI front ends), with_settings(settings), to_os_panel_args(only), validate!, clone; JSON/YAML serializable using wire spellings such as center-sized or not-allowed.

Every panel runs with KITTY_PANELS_PANEL_NAME in its environment and the user var kitty_panels_name, which panel_match turns into kitty's var:kitty_panels_name=<name> matching expression.

Panel manager

The shard ships a kitty_panels executable that runs a panel manager daemon: it starts a dedicated hidden kitty, launches every panel from the config, arranges tiled panels from a panel tree (columns, rows, grid, master-stack), keeps docks as screen-edge struts, and reconciles geometry against KWin on KDE Plasma Wayland.

kitty_panels daemon --config panels.yaml
kitty_panels ctl tree
kitty_panels ctl resize left 0.5

The same YAML file can carry a manager: section:

manager:
  layout: master-stack    # columns, rows, grid or master-stack
  master_ratio: 0.6
  grid_columns: 3         # used by grid
  gap: 8                  # pixels between tiled panels
  min_size: 200           # minimum tiled panel size
  panels: [a, b]          # optional; defaults to every non-dock panel
  socket: /tmp/kitty_panels/ctl.sock

Commands: tree, launch NAME, close NAME, toggle NAME, focus NAME|next|prev, split EXISTING NEW h|v, move NAME h|v TARGET, resize NAME RATIO, layout PRESET, reconcile. Docked panels (mode: dock) keep their kitty edge settings and reserve screen space; tiled panels launch with a neutral edge and are placed by the manager in pixels, inside KWin's maximize area — so external struts like the Plasma panel are respected. output_name pins a panel to a specific monitor (default: the primary output); each output gets its own tree and focus cycles across outputs in order.

The daemon adopts an already-running kitty engine when one listens on --engine-socket, and persists window identities in <socket>.state.json, so a crash leaves the panels running and a restart re-adopts them without relaunching. focus activates the window through KWin as well as kitty. The KWin backend needs qdbus and dbus-monitor on PATH.

Testing

crystal spec   # or: make test

RC-level specs run against an in-process FakeKitty that speaks the remote control protocol, and manager specs against Positioner::Fake, so no real kitty is needed.

License

MIT

Repository

kitty_panels

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 0
  • about 2 hours ago
  • October 3, 2026
License

Links
Synced at

Sat, 17 Oct 2026 17:14:41 GMT

Languages