kitty-rc 0.4.0

A Rust library for controlling kitty terminal emulator via its remote control protocol
Documentation

kitty-rc

A Rust library for controlling the kitty terminal emulator via its remote control protocol

About

kitty-rc is a Rust library that provides a type-safe, async interface for controlling the kitty terminal emulator through its remote control protocol. It uses Unix domain sockets for communication and supports all major kitty remote control commands.

Features

  • Comprehensive Command Support: All major kitty remote control commands organized into modules
  • Type-Safe Builder API: Fluent builder pattern for client setup and all commands
  • Async-First: Built on tokio for asynchronous I/O
  • Single Connection: Connect once at startup, reuse for all commands
  • Password Authentication: Secure encrypted communication using ECDH X25519 key exchange and AES-256-GCM
  • Error Handling: Detailed error types for protocol, command, connection, and encryption errors
  • Streaming Support: Automatic chunking for large payloads (e.g., background images)
  • Async Commands: Support for async operations with unique ID generation
  • Comprehensive Testing: 143 unit tests ensuring reliability

Installation

Add this to your Cargo.toml:

[dependencies]
kitty-rc = "0.4.0"
tokio = { version = "1.0", features = ["full"] }

Usage

Basic Setup

use kitty_rc::Kitty;

#[tokio::main]
async fn main() -> Result<(), kitty_rc::KittyError> {
    // Basic connection (no password)
    let mut kitty = Kitty::builder()
        .socket_path("/path/to/kitty.socket")
        .connect()
        .await?;

    // Use kitty to send commands...
    Ok(())
}

Connection with Password

When kitty is configured with remote_control_password, password authentication provides encrypted communication.

From within kitty

Note: Password authentication automatically works when your application is launched by kitty or running within a kitty window, because kitty exposes its public key via KITTY_PUBLIC_KEY environment variable only to processes it launches.

use kitty_rc::Kitty;

#[tokio::main]
async fn main() -> Result<(), kitty_rc::KittyError> {
    // KITTY_PUBLIC_KEY is automatically set by kitty for processes it launches
    let mut kitty = Kitty::builder()
        .socket_path("/path/to/kitty.socket")
        .password("your-password-here")
        .connect()
        .await?;

    // All commands will be encrypted automatically
    Ok(())
}

From standalone clients

For standalone clients (not launched by kitty), you have two options:

Option 1: Automatic database query (recommended)

If you're using the kitty-pubkey-db shell integration, KittyBuilder will automatically query the database for the public key when:

  • A password is provided
  • No explicit public_key() is set
  • KITTY_PUBLIC_KEY environment variable is not set

The socket path must contain the kitty PID (e.g., /tmp/kitty-12345.sock or /run/user/1000/kitty/kitty-12345.sock).

use kitty_rc::Kitty;

#[tokio::main]
async fn main() -> Result<(), kitty_rc::KittyError> {
    let mut kitty = Kitty::builder()
        .socket_path("/run/user/1000/kitty/kitty-12345.sock")
        .password("your-password-here")
        // No need for public_key() - will auto-query database
        .connect()
        .await?;

    // All commands will be encrypted automatically
    Ok(())
}

Option 2: Explicit public key

For standalone clients (not launched by kitty), you can provide the public key explicitly using the public_key() method. The public key can be obtained from kitty's public key database (see kitty-pubkey-db binary).

use kitty_rc::Kitty;

#[tokio::main]
async fn main() -> Result<(), kitty_rc::KittyError> {
    // Get public key from kitty's public key database or other source
    let pubkey = "1:abc123..."; // Base85 encoded public key

    let mut kitty = Kitty::builder()
        .socket_path("/path/to/kitty.socket")
        .password("your-password-here")
        .public_key(pubkey)
        .connect()
        .await?;

    // All commands will be encrypted automatically
    Ok(())
}

The encryption uses kitty's protocol:

  • X25519 ECDH key exchange with kitty's public key (from KITTY_PUBLIC_KEY or provided via public_key())
  • Client generates ephemeral keys per command
  • AES-256-GCM authenticated encryption
  • SHA-256 derived shared secret

Listing Windows

use kitty_rc::{LsCommand, WindowInfo};

let cmd = LsCommand::new()
    .self_window(true)
    .build()?;

let response = kitty.execute(&cmd).await?;
let instances = LsCommand::parse_response(&response)?;

for instance in &instances {
    for tab in &instance.tabs {
        for window in &tab.windows {
            println!("Window: {} (id: {:?})", window.title, window.id);
            println!("  PID: {:?}", window.pid);
            println!("  CWD: {:?}", window.cwd);
        }
    }
}

Sending Text to Windows

use kitty_rc::commands::SendTextCommand;

let cmd = SendTextCommand::new("text:hello world")
    .match_spec("id:1")
    .build()?;

kitty.send_command(&cmd).await?;

Creating New Windows

use kitty_rc::commands::NewWindowCommand;

let cmd = NewWindowCommand::new()
    .args("bash")
    .cwd("/home/user")
    .title("My Window")
    .build()?;

kitty.send_command(&cmd).await?;

Setting Font Size

use kitty_rc::commands::SetFontSizeCommand;

// Set absolute font size
let cmd = SetFontSizeCommand::new(16)
    .all(true)
    .build()?;

kitty.execute(&cmd).await?;

// Increment font size
let cmd = SetFontSizeCommand::new(0)
    .increment_op("+")
    .build()?;

kitty.execute(&cmd).await?;

// Decrement font size
let cmd = SetFontSizeCommand::new(0)
    .increment_op("-")
    .build()?;

kitty.execute(&cmd).await?;

Setting Background Opacity

use kitty_rc::commands::SetBackgroundOpacityCommand;

let cmd = SetBackgroundOpacityCommand::new(0.8)
    .all(true)
    .build()?;

kitty.send_command(&cmd).await?;

Streaming Large Data

use kitty_rc::commands::SetBackgroundImageCommand;

let large_image_data = "..."; // Large base64 string
let cmd = SetBackgroundImageCommand::new(large_image_data).build()?;

// send_all automatically handles chunking for large payloads
kitty.send_all(&cmd.unwrap()).await?;

Or to get a response:

let response = kitty.execute_all(&cmd.unwrap()).await?;

Command Modules

Tab Management (commands::tab)

  • CloseTabCommand - Close tabs
  • DetachTabCommand - Detach tabs to different OS windows
  • FocusTabCommand - Focus a specific tab
  • SetTabTitleCommand - Set tab title

Layout Management (commands::layout)

  • GotoLayoutCommand - Switch to a specific layout
  • SetEnabledLayoutsCommand - Set available layouts
  • LastUsedLayoutCommand - Switch to last used layout

Window Management (commands::window)

  • CloseWindowCommand - Close windows
  • CreateMarkerCommand - Create scroll markers
  • DetachWindowCommand - Detach windows
  • FocusWindowCommand - Focus a window
  • GetTextCommand - Extract text from windows
  • LsCommand - List windows and tabs (supports structured response parsing)
  • NewWindowCommand - Create new windows
  • RemoveMarkerCommand - Remove scroll markers
  • ResizeWindowCommand - Resize windows
  • ScrollWindowCommand - Scroll window content
  • SelectWindowCommand - Async window selection
  • SendKeyCommand - Send keyboard shortcuts
  • SendTextCommand - Send text to windows
  • SetWindowLogoCommand - Set window logo
  • SetWindowTitleCommand - Set window title

Process Management (commands::process)

  • DisableLigaturesCommand - Disable font ligatures
  • EnvCommand - Set environment variables
  • KittenCommand - Run kitty kittens
  • LaunchCommand - Launch new windows with comprehensive options
  • LoadConfigCommand - Load configuration files
  • ResizeOSWindowCommand - Resize OS windows
  • RunCommand - Run commands with streaming support
  • SetUserVarsCommand - Set user variables
  • SignalChildCommand - Send signals to child processes

Style and Appearance (commands::style)

  • GetColorsCommand - Get current colors
  • SetBackgroundImageCommand - Set background image (supports streaming)
  • SetBackgroundOpacityCommand - Set background opacity
  • SetColorsCommand - Set color scheme
  • SetFontSizeCommand - Set font size (absolute or increment/decrement)
  • SetSpacingCommand - Set window padding/spacing
  • SetTabColorCommand - Set tab colors

Note: Kitty's remote protocol does not include a get-font-size command. To retrieve the current font size, read it from ~/.config/kitty/kitty.conf.

Response Types

The library provides structured types for parsing kitty responses:

Window Information

use kitty_rc::{WindowInfo, LsCommand};

let response = kitty.execute(&cmd).await?;
let instances = LsCommand::parse_response(&response)?;

for instance in &instances {
    for tab in &instance.tabs {
        for window in &tab.windows {
            println!("Window: {} (id: {:?})", window.title, window.id);
            println!("  PID: {:?}", window.pid);
            println!("  CWD: {:?}", window.cwd);
            println!("  Active: {:?}", window.is_active);
            println!("  Focused: {:?}", window.is_focused);
        }
    }
}

The WindowInfo struct includes all fields returned by kitty:

  • Window metadata: id, title, pid, cwd, cmdline
  • State: is_active, is_focused, is_self, at_prompt, in_alternate_screen
  • Terminal: columns, lines, created_at
  • Processes: foreground_processes, env, user_vars
  • Command: last_cmd_exit_status, last_reported_cmdline

The TabInfo struct includes tab-level information:

  • Tab metadata: id, title
  • State: is_active, is_focused, active_window_history
  • Layout: layout, enabled_layouts, layout_opts, layout_state
  • Groups: groups (window grouping information)

The OsInstance struct includes OS-level information:

  • OS metadata: id, wm_class, wm_name
  • State: is_active, is_focused, last_focused
  • Display: background_opacity, platform_window_id

Async and Streaming

The library supports async commands and streaming for large payloads:

Async Commands

use kitty_rc::commands::SelectWindowCommand;
use kitty_rc::protocol::KittyMessage;

let async_id = KittyMessage::generate_unique_id();

let cmd = SelectWindowCommand::new()
    .title("Select a window")
    .async_id(async_id.clone())
    .build()?;

Streaming

Large payloads (>4096 bytes) can be sent using the helper methods that automatically handle chunking:

let message = cmd.build()?;
client.send_all(&message).await?;  // Automatically chunks if needed

Or to get a response:

let response = client.execute_all(&message).await?;

The send_all and execute_all methods automatically detect if a message needs streaming and handle chunking internally, so you don't need to manually check or split payloads.

Error Handling

kitty-rc provides detailed error types:

use kitty_rc::{KittyError, CommandError};

match result {
    Ok(response) => println!("Success: {:?}", response),
    Err(KittyError::Command(CommandError::MissingParameter(field, _))) => {
        eprintln!("Missing required parameter: {}", field);
    }
    Err(KittyError::Connection(err)) => {
        eprintln!("Connection error: {}", err);
    }
    Err(err) => eprintln!("Error: {}", err),
}

Enabling Remote Control

To use kitty-rc, you must enable remote control in kitty. The library provides a helper script to configure kitty securely with password authentication.

Quick Setup

Run the included setup script:

./scripts/enable-rc.sh

This script will:

  • Generate a random 48-character password in ~/.config/kitty/rc.password
  • Create ~/.config/kitty/rc.conf with remote control configuration
  • Add include rc.conf to ~/.config/kitty/kitty.conf if not present
  • Set up a socket in the XDG runtime directory at kitty/kitty-{kitty_pid}.sock

Manual Configuration

To manually configure kitty for remote control, create ~/.config/kitty/rc.conf:

# Enable password-based remote control
allow_remote_control password
remote_control_password "$(cat ~/.config/kitty/rc.password)"

# Listen on socket in XDG runtime directory
listen_on unix:${XDG_RUNTIME_DIR}/kitty/kitty-{kitty_pid}.sock

Then add to ~/.config/kitty/kitty.conf:

include rc.conf

This script will:

  • Generate a random 48-character password in ~/.config/kitty/rc.password
  • Add remote control configuration to ~/.config/kitty/kitty.conf
  • Set up a socket in the XDG runtime directory at kitty/kitty-{pid}.sock
  • Fail if any remote control settings are already configured

Manual Configuration

To manually configure kitty for remote control, add to ~/.config/kitty/kitty.conf:

# Enable password-based remote control
allow_remote_control password
remote_control_password "$(cat ~/.config/kitty/rc.password)"

# Listen on socket in XDG runtime directory
listen_on unix:${XDG_RUNTIME_DIR}/kitty/kitty-{kitty_pid}.sock

Generate a secure password:

pwgen -s 48 1 > ~/.config/kitty/rc.password
chmod 600 ~/.config/kitty/rc.password

Restart kitty after making changes.

Socket Configuration

kitty-rc communicates with kitty via Unix domain sockets. The default socket location varies by system:

  • With the helper script: $XDG_RUNTIME_DIR/kitty/kitty-{kitty_pid}.sock
  • Linux/macOS: $TMPDIR/kitty-* or /tmp/kitty-*
  • You can also specify custom socket paths

Testing

Run the test suite:

cargo test

All 146 tests pass successfully.

Examples

See src/bin/list_windows.rs for a complete example of listing kitty windows and their processes.

Use scripts/enable-rc.sh to quickly enable remote control in your kitty configuration.

Contributing

Contributions are welcome! Please read the project's AGENTS.md for development guidelines.

License

[Specify your license here]

Related

Acknowledgments

Built with: