Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Open Sesame

Vimium-style window switcher for COSMIC desktop

Open Sesame brings the efficiency of Vimium browser navigation to the entire COSMIC desktop. Type a letter to instantly switch to any window, or launch an application if it isn’t running. No mouse required.

License: GPL-3.0 Latest Release CI SLSA 3

Open Sesame Screenshot

Features

  • Vimium-style hints - Every window gets a letter (g, gg, ggg for multiple instances)
  • Quick switch - Tap Alt+Space to toggle between last two windows
  • Focus-or-launch - Type a letter to focus an app or launch it if not running
  • Arrow navigation - Use arrows and Enter as an alternative to typing letters
  • Zero configuration - Works out-of-the-box with sensible defaults
  • COSMIC integration - Automatic keybinding setup, native theme support
  • Instant activation - Sub-200ms latency with smart disambiguation
  • Configurable - Per-app key bindings, launch commands, and environment variables

Quick Example

Add APT repository (one-time setup):

curl -fsSL https://scopecreep-zip.github.io/open-sesame/gpg.key \
  | sudo gpg --dearmor -o /usr/share/keyrings/open-sesame.gpg
echo "deb [signed-by=/usr/share/keyrings/open-sesame.gpg] https://scopecreep-zip.github.io/open-sesame noble main" \
  | sudo tee /etc/apt/sources.list.d/open-sesame.list

Install and configure:

sudo apt update && sudo apt install -y open-sesame
sesame --setup-keybinding

Press Alt+Space, type a letter to switch windows.

See Installation Guide for alternative methods.

How It Works

Open Sesame displays a visual overlay showing all your open windows, each labeled with a letter hint. Type the letter to instantly switch to that window. If you’ve configured an app with a key binding and it’s not running, Open Sesame will launch it for you.

Two Modes

Launcher Mode (Default: Alt+Space)

  • Shows a centered overlay with all windows and letter hints immediately
  • Type a letter to switch, or use arrows to navigate
  • Perfect for quick access to any window

Switcher Mode (Optional: Alt+Tab)

  • Acts like traditional Alt+Tab but with letter hints for instant selection
  • Tap to quickly switch to the previous window
  • Hold to see the full overlay

Next Steps

Requirements

  • COSMIC Desktop Environment (Pop!_OS 24.04+ or other COSMIC-based distributions)
  • Wayland (X11 not supported)
  • fontconfig with at least one font installed

Acknowledgments

Built with Rust and inspired by Vimium - the browser extension that proves keyboard navigation is superior.

Quick Start

Get Open Sesame running in 30 seconds.

Installation (Pop!_OS 24.04+)

# Add repository
curl -fsSL https://scopecreep-zip.github.io/open-sesame/gpg.key | \
  sudo gpg --dearmor -o /etc/apt/keyrings/open-sesame.gpg

echo "deb [signed-by=/etc/apt/keyrings/open-sesame.gpg] \
  https://scopecreep-zip.github.io/open-sesame noble main" | \
  sudo tee /etc/apt/sources.list.d/open-sesame.list

# Install
sudo apt update && sudo apt install open-sesame

# Setup keybinding
sesame --setup-keybinding

First Launch

Press Alt+Space (or your configured key) to see all windows with letter hints.

Type a letter to switch to that window instantly.

Quick Tips

  • Tap Alt+Space - Instantly switch to the previous window (MRU - Most Recently Used)
  • Hold Alt+Space - Show the full overlay with all windows
  • Type a letter - Jump directly to that window
  • Use arrow keys - Navigate through windows if you prefer
  • Press Escape - Cancel and return to the origin window

Next Steps

What Makes Open Sesame Different?

Unlike traditional Alt+Tab switchers, Open Sesame:

  • Shows ALL windows at once, not just sequential cycling
  • Assigns predictable letter hints based on app names
  • Allows instant switching with a single keystroke
  • Supports focus-or-launch: if an app isn’t running, it launches it
  • Works with both keyboard shortcuts (letters) and arrow navigation

Installation

Open Sesame can be installed via APT repository, from GitHub releases, or built from source.

For Pop!_OS 24.04+ with COSMIC Desktop:

# Add GPG key and repository
curl -fsSL https://scopecreep-zip.github.io/open-sesame/gpg.key \
  | sudo gpg --dearmor -o /usr/share/keyrings/open-sesame.gpg
echo "deb [signed-by=/usr/share/keyrings/open-sesame.gpg] https://scopecreep-zip.github.io/open-sesame noble main" \
  | sudo tee /etc/apt/sources.list.d/open-sesame.list

# Install and configure
sudo apt update && sudo apt install -y open-sesame
sesame --setup-keybinding

This method provides automatic updates through the standard APT package manager.

From GitHub Releases

Download the .deb package for your architecture from the Releases page:

Download (auto-detects architecture):

curl -fsSL "https://github.com/ScopeCreep-zip/open-sesame/releases/latest/download/open-sesame-linux-$(uname -m).deb" -o /tmp/open-sesame.deb

Verify and install:

gh attestation verify /tmp/open-sesame.deb --owner ScopeCreep-zip
sudo dpkg -i /tmp/open-sesame.deb && sesame --setup-keybinding

Available architectures:

  • x86_64 - Intel/AMD 64-bit
  • aarch64 - ARM 64-bit (Raspberry Pi 4+, ARM servers)

Verify Package Authenticity

All packages include SLSA Build Provenance attestations for supply chain security.

Verify with GitHub CLI:

gh attestation verify "open-sesame-linux-$(uname -m).deb" --owner ScopeCreep-zip

Verify SHA256 checksums:

Each release includes a SHA256SUMS.txt file. Download it from the release page and verify:

sha256sum -c SHA256SUMS.txt

Building from Source

Requires COSMIC desktop environment and development tools.

Prerequisites

# Install mise (task runner and toolchain manager)
curl https://mise.run | sh

# Clone repository
git clone https://github.com/ScopeCreep-zip/open-sesame.git
cd open-sesame

# Install dependencies (Rust toolchain, cargo-deb, etc.)
mise run setup

Build and Install

# Build .deb package
mise run build:deb

# Install the package
mise run install

The .deb package will be created in target/debian/.

Development Workflow

If you want to contribute or modify Open Sesame:

# Format code
mise run fmt

# Run tests and linters
mise run test

# Build debug binary and run
mise run dev

# Clean everything
mise run clean:all

See mise tasks for all available commands.

System Requirements

Required

  • COSMIC Desktop Environment (Pop!_OS 24.04+ or other COSMIC-based distributions)
  • Wayland compositor (X11 is NOT supported)
  • fontconfig with at least one font installed

Optional (for building from source)

  • Rust 1.91+ (installed automatically via mise)
  • cargo-deb (for building .deb packages)
  • cross (for cross-compilation to arm64)

Post-Installation

After installation, you need to set up a keybinding:

# Setup default keybinding (Alt+Space)
sesame --setup-keybinding

# Or specify a custom key combo
sesame --setup-keybinding alt+tab

This configures COSMIC desktop to launch Open Sesame when you press the key combination.

Uninstallation

To remove Open Sesame:

# Remove keybinding first (optional but recommended)
sesame --remove-keybinding

# Uninstall package
sudo apt remove open-sesame

# Optional: Remove configuration files
rm -rf ~/.config/open-sesame

Troubleshooting Installation

Package Not Found

If apt install open-sesame fails with “package not found”:

  1. Verify the repository was added correctly:

    cat /etc/apt/sources.list.d/open-sesame.list
    
  2. Check that the GPG key exists:

    ls -la /usr/share/keyrings/open-sesame.gpg
    
  3. Update package lists:

    sudo apt update
    

Dependency Errors

If installation fails due to missing dependencies:

# Install dependencies manually
sudo apt install --fix-broken

Build Failures

If building from source fails:

  1. Ensure all system dependencies are installed:

    sudo apt install build-essential pkg-config libfontconfig1-dev libxkbcommon-dev
    
  2. Check Rust toolchain version:

    rustc --version  # Should be 1.91 or newer
    
  3. Clean and retry:

    mise run clean:all
    mise run build:deb
    

Next Steps

Basic Usage

Learn how to use Open Sesame for efficient window management.

Launching Open Sesame

After setting up a keybinding, simply press your configured key (default: Alt+Space):

# The keybinding triggers this command
sesame --launcher

You can also run Open Sesame manually:

# Show window switcher
sesame

# Launcher mode (immediate overlay)
sesame --launcher

# List all windows with hints
sesame --list-windows

# Backward cycling (for Alt+Shift+Tab)
sesame --backward

Keyboard Shortcuts

Once the overlay appears, you have several ways to interact:

KeyAction
Letter keys (a-z)Instantly switch to window with that hint
Repeated letters (gg, ggg)Select multiple windows with same letter
Arrow keys (↑↓)Navigate through window list
EnterActivate currently selected window
EscapeCancel and return to origin window

Two Modes Explained

Open Sesame has two distinct operating modes:

Launcher Mode (--launcher flag)

Best for: Alt+Space style launchers

Behavior:

  • Shows the full overlay immediately
  • Displays all windows with letter hints from the start
  • No delay before showing the UI
  • Used for quick access to any window

Setup:

# Configure Alt+Space to use launcher mode
sesame --setup-keybinding alt+space

# COSMIC will run: sesame --launcher

Switcher Mode (default)

Best for: Alt+Tab replacement

Behavior:

  • Quick tap (< 250ms): Instantly switch to the previous window (MRU)
  • Hold longer: Shows overlay after a delay (default: 720ms)
  • During the delay, a subtle border highlights the target window
  • Designed to mimic traditional Alt+Tab behavior

Setup:

# Manually configure COSMIC shortcuts:
# Alt+Tab → sesame
# Alt+Shift+Tab → sesame --backward

Quick Switch Behavior

The “quick switch” feature allows you to toggle between windows with a quick tap:

Tap Alt+Space (release within 250ms):

  • Instantly switches to the previous window
  • No overlay shown
  • Perfect for bouncing between two windows

Hold Alt+Space (hold longer than 250ms):

  • Shows the full overlay after a delay
  • Allows you to select any window

Note: The quick switch threshold can be configured in your config file with the quick_switch_threshold setting (default: 250ms).

Focus-or-Launch

One of Open Sesame’s most powerful features is focus-or-launch functionality. When you’ve configured a key binding for an application:

If the app is running: Open Sesame focuses the window If the app is NOT running: Open Sesame launches it

Example

Configure Firefox with the f key:

# In ~/.config/open-sesame/config.toml
[keys.f]
apps = ["firefox", "org.mozilla.firefox"]
launch = "firefox"

Now:

  • Press Alt+Space, then f → switches to Firefox if it’s open
  • Press Alt+Space, then f → launches Firefox if it’s not running

This eliminates the need for separate application launchers.

Hint Assignment

Open Sesame assigns letter hints to windows intelligently:

  1. Configured apps get their configured key (e.g., Firefox gets f)
  2. Multiple windows of the same app get repeated letters:
    • First instance: g
    • Second instance: gg
    • Third instance: ggg
  3. Unconfigured apps get sequential letters (a-z, excluding used keys)

Alternative Input

You can also type hints with numbers:

  • g1 is equivalent to g
  • g2 is equivalent to gg
  • g3 is equivalent to ggg

Using Arrow Navigation

If you prefer not to type letters, arrow keys work too:

  1. Press Alt+Space to show the overlay
  2. Use and to navigate through windows
  3. Press Enter to activate the selected window
  4. Or press Escape to cancel

The selected window is highlighted in the overlay.

Activation Delay

When multiple windows share the same hint prefix (e.g., g, gg, ggg), Open Sesame waits a short time before activating:

Default delay: 200ms

This gives you time to type the full hint without the first letter firing immediately.

To skip the delay: Press Enter after typing the hint

You can configure this delay in your config file:

[settings]
activation_delay = 200  # milliseconds

Overlay Delay

In switcher mode, there’s a delay before the full overlay appears:

Default delay: 720ms

During this time, only a subtle border is shown around the target window. This keeps the UI minimal for quick switches.

To show the overlay immediately: Set overlay_delay = 0 in your config, or use --launcher flag

[settings]
overlay_delay = 0  # Show immediately (launcher mode behavior)

Common Workflows

Quick Toggle Between Two Apps

Press Alt+Space quickly (tap and release):

  • Switches to the previous window instantly
  • No overlay shown
  • Works like traditional Alt+Tab quick toggle

Jump to Specific App

  1. Press Alt+Space (hold until overlay appears)
  2. Type the letter hint for your app (e.g., f for Firefox)
  3. Window activates immediately

Launch an App That’s Not Running

  1. Press Alt+Space
  2. Type the configured letter (e.g., g for Ghostty)
  3. If Ghostty isn’t running, it launches automatically

Browse All Windows

  1. Press Alt+Space (hold until overlay appears)
  2. Use arrow keys to scroll through windows
  3. Press Enter to activate the selected one

Tips and Tricks

Speed Over Accuracy

Open Sesame is designed for speed. Don’t wait for the overlay—start typing immediately:

  1. Press Alt+Space and immediately type f → switches to Firefox
  2. The overlay may not even appear if you’re fast enough

Consistent Key Bindings

Configure your most-used apps with memorable keys:

  • f for Firefox (web browser)
  • g for Ghostty (terminal)
  • v for VS Code (editor)
  • n for Nautilus (file manager)

Customize Delays

If the default delays feel too slow or too fast, adjust them:

[settings]
activation_delay = 100      # Faster activation (may skip gg, ggg)
overlay_delay = 0           # Show overlay immediately
quick_switch_threshold = 150  # Faster tap threshold

Next Steps

Configuration

Open Sesame uses TOML configuration files with XDG-compliant directory structure and layered inheritance.

Configuration Files

Open Sesame loads configuration from multiple locations in order (later overrides earlier):

/etc/open-sesame/config.toml              # System defaults
~/.config/open-sesame/config.toml         # User config (create this)
~/.config/open-sesame/config.d/*.toml     # Additional overrides (alphabetical)

Generating Default Configuration

To get started with configuration:

# Print default config to stdout
sesame --print-config

# Create user config from defaults
sesame --print-config > ~/.config/open-sesame/config.toml

# Edit config
$EDITOR ~/.config/open-sesame/config.toml

# Validate config
sesame --validate-config

Using a Custom Config File

You can specify a custom configuration file:

sesame -c ~/my-custom-config.toml --list-windows

Settings Reference

The [settings] section controls global behavior:

[settings]
# Activation key combo (used by --setup-keybinding)
activation_key = "alt+space"

# Delay (ms) before activating a match when multiple hints exist
# Allows time for typing gg, ggg without 'g' firing immediately
activation_delay = 200

# Delay (ms) before showing full overlay (0 = immediate)
# During this time, only a border shows around focused window
overlay_delay = 720

# Quick switch threshold (ms) - tap within this time = instant MRU switch
quick_switch_threshold = 250

# Focus indicator border
border_width = 3.0
border_color = "#b4a0ffb4"  # Soft lavender with transparency

# Overlay colors (hex: #RRGGBB or #RRGGBBAA)
background_color = "#000000c8"  # Semi-transparent black
card_color = "#1e1e1ef0"        # Dark gray card
text_color = "#ffffffff"        # White text
hint_color = "#646464ff"        # Gray hint badge
hint_matched_color = "#4caf50ff" # Green for matched hints

# Global environment files (direnv .env style)
env_files = [
    # "~/.config/open-sesame/global.env"
]

Settings Explained

SettingTypeDefaultDescription
activation_keystring"alt+space"Default key combo for --setup-keybinding
activation_delayinteger200Delay (ms) before activating a match with multiple hints
overlay_delayinteger720Delay (ms) before showing full overlay in switcher mode
quick_switch_thresholdinteger250Tap threshold (ms) for instant MRU switch
border_widthfloat3.0Border width (pixels) for focus indicator
border_colorcolor#b4a0ffb4Border color (hex with alpha)
background_colorcolor#000000c8Overlay background color
card_colorcolor#1e1e1ef0Window card background color
text_colorcolor#ffffffffText color
hint_colorcolor#646464ffHint badge color
hint_matched_colorcolor#4caf50ffMatched hint color
env_filesarray[]Global environment files to load

Color Format

Colors are specified in hex format:

  • #RRGGBB - RGB only (alpha defaults to 255 - fully opaque)
  • #RRGGBBAA - RGB with alpha channel (00 = transparent, FF = opaque)

Examples:

border_color = "#ff0000"      # Red, fully opaque
border_color = "#ff0000aa"    # Red, semi-transparent
background_color = "#000000c8" # Black, 78% opaque

Key Bindings

The [keys.<letter>] sections define per-app shortcuts for focus-or-launch functionality.

Basic Key Binding

Each key binding section has:

  • apps - List of app IDs that match this key
  • launch - Command to run if no matching window exists
# Terminal
[keys.g]
apps = ["ghostty", "com.mitchellh.ghostty"]
launch = "ghostty"

# Browser
[keys.f]
apps = ["firefox", "org.mozilla.firefox"]
launch = "firefox"

# Editor
[keys.v]
apps = ["code", "Code", "cursor", "Cursor"]
launch = "code"

# File manager
[keys.n]
apps = ["nautilus", "org.gnome.Nautilus", "com.system76.CosmicFiles"]
launch = "nautilus"

Focus-Only Binding

Omit the launch field to create a focus-only binding (won’t launch if not running):

# Chromium - focus only, don't launch
[keys.c]
apps = ["chromium", "google-chrome"]
# No launch field

Finding App IDs

To find the app ID for a window:

sesame --list-windows

Output example:

=== Window Enumeration ===
Found 3 windows:
  [0] wayland-1 - firefox - Mozilla Firefox
  [1] wayland-2 - code - Visual Studio Code
  [2] wayland-3 - ghostty - Terminal

Use the app ID (second column) in your configuration.

App ID Matching

Open Sesame matches app IDs flexibly:

  1. Exact match: "firefox" matches app ID "firefox"
  2. Case-insensitive: "Firefox" matches app ID "firefox"
  3. Last segment: "ghostty" matches "com.mitchellh.ghostty"

This means you can use simple names instead of full reverse-domain IDs.

Advanced Launch Configuration

For complex scenarios with command-line arguments and environment variables:

Simple Launch

[keys.g]
apps = ["ghostty"]
launch = "ghostty"  # Just a command string

Advanced Launch

[keys.g]
apps = ["ghostty"]
[keys.g.launch]
command = "ghostty"
args = ["--config-file=/path/to/config"]
env_files = ["~/.config/ghostty/.env"]  # Load env vars from file
env = { TERM = "xterm-256color" }       # Explicit env vars (override env_files)

More Examples

Chrome with specific profile:

[keys.w]
apps = ["google-chrome"]
[keys.w.launch]
command = "google-chrome"
args = ["--profile-directory=Work"]

App with custom environment:

[keys.x]
apps = ["myapp"]
[keys.x.launch]
command = "/opt/myapp/bin/myapp"
args = ["--mode=production"]
env_files = [
    "~/.config/myapp/base.env",
    "~/.config/myapp/secrets.env",
]
env = { DEBUG = "false" }

Environment Variables

Environment Layering

Environment variables are applied in layers (later overrides earlier):

  1. Inherited process environment (WAYLAND_DISPLAY, XDG_*, PATH, etc.)
  2. Global env_files from [settings]
  3. Per-app env_files from [keys.x.launch]
  4. Explicit env from [keys.x.launch]

Environment File Format

Environment files use direnv .env style syntax:

# ~/.config/open-sesame/global.env
KEY=value
KEY="value with spaces"
KEY='literal value'
export KEY=value
# comments

Supported formats:

  • KEY=value - Simple assignment
  • KEY="value" - Double-quoted (allows spaces)
  • KEY='value' - Single-quoted (literal, no interpolation)
  • export KEY=value - Export style (same as KEY=value)
  • # comment - Comments

Path expansion:

  • ~/ expands to home directory
  • Environment variables in values are NOT expanded

Global Environment Files

To load environment variables for all launched apps:

[settings]
env_files = [
    "~/.config/open-sesame/global.env",
    "/etc/open-sesame/env.d/system.env",
]

Adding New Key Bindings

Step-by-step process:

  1. Find your app ID:

    sesame --list-windows
    
  2. Add configuration section:

    [keys.x]
    apps = ["your-app-id"]
    launch = "your-app-command"
    
  3. Verify configuration:

    sesame --validate-config
    
  4. Test it:

    # Press Alt+Space, type 'x'
    

Configuration Validation

Always validate your configuration after making changes:

sesame --validate-config

Output if valid:

Configuration is valid

Output if invalid:

Configuration issues:
  - [ERROR] Invalid color format: "#xyz"
  - [WARNING] Key binding 'f' has no apps configured

Severity levels:

  • ERROR - Critical issues that will prevent Open Sesame from working
  • WARNING - Non-critical issues that may cause unexpected behavior

Example Configuration

Complete example configuration file:

[settings]
activation_key = "alt+space"
activation_delay = 200
overlay_delay = 720
quick_switch_threshold = 250
border_width = 3.0
border_color = "#b4a0ffb4"
background_color = "#000000c8"
card_color = "#1e1e1ef0"
text_color = "#ffffffff"
hint_color = "#646464ff"
hint_matched_color = "#4caf50ff"

# Terminal
[keys.g]
apps = ["ghostty", "com.mitchellh.ghostty"]
launch = "ghostty"

# Browser
[keys.f]
apps = ["firefox", "org.mozilla.firefox"]
launch = "firefox"

# Editor
[keys.v]
apps = ["code", "Code", "cursor", "Cursor"]
launch = "code"

# File manager
[keys.n]
apps = ["nautilus", "org.gnome.Nautilus", "com.system76.CosmicFiles"]
launch = "nautilus"

# Communication
[keys.s]
apps = ["slack", "Slack"]
launch = "slack"

[keys.d]
apps = ["discord", "Discord"]
launch = "discord"

Configuration Tips

Performance Tuning

For faster response times:

[settings]
activation_delay = 100      # Faster activation (may skip gg, ggg)
overlay_delay = 0           # Show immediately (launcher mode)
quick_switch_threshold = 150  # Faster tap threshold

Minimal UI

For minimal visual distraction:

[settings]
overlay_delay = 1000        # Longer delay before overlay
border_width = 2.0          # Thinner border
background_color = "#00000080"  # More transparent background

Theme Customization

Match your desktop theme:

[settings]
# Cosmic purple theme
border_color = "#a56de2ff"
card_color = "#2a2a2eff"
hint_matched_color = "#a56de2ff"

Configuration Directory Structure

Recommended structure for complex setups:

~/.config/open-sesame/
├── config.toml                 # Main configuration
├── config.d/
│   ├── 10-theme.toml          # Theme overrides
│   ├── 20-work.toml           # Work-specific apps
│   └── 30-personal.toml       # Personal apps
├── env.d/
│   ├── global.env             # Global environment
│   └── work.env               # Work environment
└── scripts/
    └── custom-launcher.sh     # Custom launch scripts

Files in config.d/ are loaded in alphabetical order, allowing modular configuration.

Next Steps

CLI Reference

Complete command-line reference for the sesame binary.

Synopsis

sesame [OPTIONS]

Options

Configuration

-c, --config <PATH>

Use a custom configuration file instead of the default.

Example:

sesame -c ~/my-config.toml --list-windows

Default: XDG config paths (~/.config/open-sesame/config.toml)

--print-config

Print default configuration to stdout and exit.

Useful for generating a starter configuration file.

Example:

# View default config
sesame --print-config

# Create user config from defaults
sesame --print-config > ~/.config/open-sesame/config.toml

--validate-config

Validate configuration file and report any issues.

Checks for errors and warnings, exits with status code 0 if valid.

Example:

sesame --validate-config

Output (valid):

Configuration is valid

Output (invalid):

Configuration issues:
  - [ERROR] Invalid color format: "#xyz"
  - [WARNING] Key binding 'f' has no apps configured

Window Management

--list-windows

List all current windows with assigned hints and exit.

Shows window IDs, app IDs, titles, and hint assignments. Useful for debugging and finding app IDs for configuration.

Example:

sesame --list-windows

Output:

=== Window Enumeration ===
Found 3 windows:
(Focused window moved to end by Wayland enumeration)
  [0] wayland-1 - firefox - Mozilla Firefox
  [1] wayland-2 - code - Visual Studio Code
  [2] wayland-3 - ghostty - Terminal <-- FOCUSED (origin)

=== MRU State (persistence only, not used for ordering) ===
Previous window: Some("wayland-1")
Current window:  Some("wayland-3")

=== Hint Assignment ===
  [f] firefox - Mozilla Firefox (wayland-1)
  [v] code - Visual Studio Code (wayland-2)
  [g] ghostty - Terminal (wayland-3)

=== Quick Switch Target (index 0) ===
Quick Alt+Tab would activate: [f] firefox (Mozilla Firefox)

Keybinding Management

--setup-keybinding [KEY_COMBO]

Setup COSMIC keybinding for Open Sesame.

Uses activation_key from config if no key combo is specified. Configures COSMIC desktop to launch sesame --launcher when the key is pressed.

Examples:

# Use default from config (alt+space)
sesame --setup-keybinding

# Specify custom key combo
sesame --setup-keybinding alt+tab
sesame --setup-keybinding super+space

Supported key combinations:

  • alt+space
  • alt+tab
  • super+space
  • ctrl+alt+space
  • Any combination supported by COSMIC

--remove-keybinding

Remove Open Sesame keybinding from COSMIC.

Cleans up any configured shortcuts. Use this before uninstalling.

Example:

sesame --remove-keybinding

--keybinding-status

Show current keybinding configuration status.

Displays what key combo is currently bound, if any.

Example:

sesame --keybinding-status

Output:

Keybinding configured: alt+space → sesame --launcher

Switcher Behavior

-b, --backward

Cycle backward through windows (for Alt+Shift+Tab).

Used with switcher mode for reverse cycling. Typically bound to Alt+Shift+Tab in COSMIC.

Example:

sesame --backward

Usage pattern:

# Configure COSMIC shortcuts:
# Alt+Tab → sesame
# Alt+Shift+Tab → sesame --backward

-l, --launcher

Launcher mode: show full overlay with hints immediately.

Without this flag, runs in switcher mode (Alt+Tab behavior). In switcher mode:

  • Tap = quick switch to previous window
  • Hold = show overlay after delay

Example:

sesame --launcher

Comparison:

ModeCommandBehavior
SwitchersesameQuick tap = instant switch, hold = delayed overlay
Launchersesame --launcherAlways shows overlay immediately

Help and Version

-h, --help

Print help message and exit.

Shows usage information and all available options.

Example:

sesame --help

-V, --version

Print version information and exit.

Example:

sesame --version

Output:

sesame X.Y.Z

Common Usage Patterns

Setup as Alt+Space Launcher

# Configure keybinding
sesame --setup-keybinding alt+space

# COSMIC will run: sesame --launcher

Setup as Alt+Tab Replacement

Manually configure COSMIC shortcuts:

  1. Open COSMIC Settings → Keyboard → Custom Shortcuts
  2. Add:
    • Alt+Tabsesame
    • Alt+Shift+Tabsesame --backward

Debug Window Detection

Find app IDs for configuration:

sesame --list-windows

Look for the app ID in the output and add it to your config:

[keys.x]
apps = ["your-app-id-here"]
launch = "command-to-launch"

Test Custom Config

Use a different configuration file temporarily:

sesame -c ~/test-config.toml --list-windows

Validate Before Deployment

Always validate configuration after editing:

# Edit config
$EDITOR ~/.config/open-sesame/config.toml

# Validate
sesame --validate-config

# If valid, test it
sesame --launcher

Exit Codes

CodeMeaning
0Success
1Error (configuration, runtime, etc.)

Environment Variables

RUST_LOG

Enable debug logging:

RUST_LOG=debug sesame --launcher

Log levels:

  • error - Errors only
  • warn - Warnings and errors
  • info - Informational messages
  • debug - Detailed debug output
  • trace - Very verbose tracing

Log file location:

~/.cache/open-sesame/debug.log

Example:

# Enable debug logging
RUST_LOG=debug sesame --launcher

# View debug log
tail -f ~/.cache/open-sesame/debug.log

XDG_CONFIG_HOME

Override default config directory:

XDG_CONFIG_HOME=~/my-configs sesame --launcher

Default: ~/.config

Config location: $XDG_CONFIG_HOME/open-sesame/config.toml

XDG_CACHE_HOME

Override cache directory (for logs and MRU state):

XDG_CACHE_HOME=~/my-cache sesame --launcher

Default: ~/.cache

Cache files:

  • $XDG_CACHE_HOME/open-sesame/debug.log - Debug log (when RUST_LOG is set)
  • $XDG_CACHE_HOME/open-sesame/mru.json - MRU (Most Recently Used) state

Examples

Quick Setup

# Install and configure in 30 seconds
sudo apt install open-sesame
sesame --setup-keybinding

Custom Configuration

# Generate starter config
sesame --print-config > ~/.config/open-sesame/config.toml

# Edit config
$EDITOR ~/.config/open-sesame/config.toml

# Validate config
sesame --validate-config

# Test it
sesame --launcher

Debugging

# List windows and hints
sesame --list-windows

# Check keybinding status
sesame --keybinding-status

# Run with debug logging
RUST_LOG=debug sesame --launcher

# View debug log
tail -f ~/.cache/open-sesame/debug.log

Multiple Configurations

# Work configuration
sesame -c ~/.config/open-sesame/work.toml --launcher

# Personal configuration
sesame -c ~/.config/open-sesame/personal.toml --launcher

# Test configuration
sesame -c ~/test.toml --list-windows

Integration with COSMIC

Open Sesame integrates with COSMIC desktop through:

  1. Keybinding setup - --setup-keybinding configures COSMIC shortcuts
  2. Wayland protocols - Uses COSMIC-specific Wayland extensions
  3. Theme integration - Respects COSMIC theme settings (future feature)

Manual COSMIC setup:

  1. Open COSMIC Settings
  2. Navigate to Keyboard → Custom Shortcuts
  3. Add custom shortcuts:
    • Name: “Open Sesame Launcher”
    • Command: sesame --launcher
    • Keybinding: Alt+Space

Troubleshooting Commands

Configuration Issues

# Validate config
sesame --validate-config

# Print default config for reference
sesame --print-config

# Test with minimal config
echo '[settings]' > /tmp/minimal.toml
sesame -c /tmp/minimal.toml --list-windows

Window Detection Issues

# List all windows with details
sesame --list-windows

# Run with debug logging
RUST_LOG=debug sesame --list-windows 2>&1 | grep -i "window"

Keybinding Issues

# Check status
sesame --keybinding-status

# Remove and re-add
sesame --remove-keybinding
sesame --setup-keybinding

# Verify COSMIC shortcuts
# Open COSMIC Settings → Keyboard → View Shortcuts

See Also

Man Page

After installation, view the manual page:

man sesame

The man page provides offline access to this reference documentation.

Troubleshooting

Common issues and solutions for Open Sesame.

No Windows Appear

Problem: The overlay shows “No windows found” or no windows are listed.

Solution 1: Check Window Detection

sesame --list-windows

If no windows appear, ensure you’re running on COSMIC desktop with Wayland (not X11).

Verify COSMIC desktop:

echo $XDG_CURRENT_DESKTOP
# Should output: COSMIC

Verify Wayland:

echo $WAYLAND_DISPLAY
# Should output: wayland-0 (or similar)

Solution 2: Restart COSMIC Session

Sometimes the Wayland compositor needs a restart:

  1. Log out of COSMIC
  2. Log back in
  3. Try again

Solution 3: Check Debug Logs

Enable debug logging to see what’s happening:

RUST_LOG=debug sesame --list-windows 2>&1 | grep -i "window"

Look for error messages about window enumeration.

Wrong App IDs

Problem: App is not getting the correct hint letter.

Solution: Find Correct App ID

Use --list-windows to find the exact app ID:

sesame --list-windows

Output:

=== Window Enumeration ===
  [0] wayland-1 - org.mozilla.firefox - Mozilla Firefox
  [1] wayland-2 - code - Visual Studio Code

Copy the exact app ID (middle column) and use it in your configuration:

[keys.f]
apps = ["org.mozilla.firefox"]  # Use exact app ID
launch = "firefox"

Common App ID Variations

Different apps may use different ID formats:

AppPossible IDs
Firefoxfirefox, org.mozilla.firefox, Firefox
VS Codecode, Code, visual-studio-code
Ghosttyghostty, com.mitchellh.ghostty
Chromegoogle-chrome, chromium, Google-chrome

Open Sesame matches all variations automatically (case-insensitive, last segment).

Keybinding Not Working

Problem: Pressing Alt+Space (or configured key) doesn’t launch Open Sesame.

Solution 1: Check Keybinding Status

sesame --keybinding-status

Expected output:

Keybinding configured: alt+space → sesame --launcher

Solution 2: Re-setup Keybinding

Remove and re-add the keybinding:

sesame --remove-keybinding
sesame --setup-keybinding

Solution 3: Check for Conflicts

Ensure the key combo doesn’t conflict with other COSMIC shortcuts:

  1. Open COSMIC Settings
  2. Navigate to Keyboard → View Shortcuts
  3. Search for the key combination
  4. Remove any conflicting shortcuts

Solution 4: Manual Setup

If --setup-keybinding doesn’t work, configure manually:

  1. Open COSMIC Settings
  2. Navigate to Keyboard → Custom Shortcuts
  3. Add:
    • Name: “Open Sesame”
    • Command: sesame --launcher
    • Keybinding: Press your desired key combo

Solution 5: Verify Binary Path

Check that sesame is in your PATH:

which sesame
# Should output: /usr/bin/sesame

If not found:

# Install/reinstall package
sudo apt install --reinstall open-sesame

Configuration Errors

Problem: Open Sesame reports configuration errors or doesn’t behave as expected.

Solution 1: Validate Configuration

sesame --validate-config

Common errors:

Invalid Color Format

[ERROR] Invalid color format: "#xyz"

Fix: Use proper hex format:

border_color = "#ff0000"      # RGB
border_color = "#ff0000aa"    # RGBA

Missing Quotes

[ERROR] TOML parse error: invalid string

Fix: Quote strings with spaces:

# Wrong:
launch = firefox --new-window

# Correct:
launch = "firefox --new-window"

Duplicate Key Bindings

[WARNING] Duplicate key binding: 'g'

Fix: Each letter can only be used once. Remove or change duplicate:

# Only one [keys.g] section allowed
[keys.g]
apps = ["ghostty"]

Solution 2: Start with Default Config

# Backup current config
mv ~/.config/open-sesame/config.toml ~/.config/open-sesame/config.toml.backup

# Generate default config
sesame --print-config > ~/.config/open-sesame/config.toml

# Test
sesame --launcher

Solution 3: Test with Minimal Config

Create a minimal configuration to isolate the issue:

# Create minimal config
cat > /tmp/minimal.toml << 'EOF'
[settings]
activation_delay = 200
EOF

# Test with minimal config
sesame -c /tmp/minimal.toml --list-windows

If this works, gradually add back your customizations until you find the problem.

Launch Commands Not Working

Problem: Pressing a letter doesn’t launch the app when it’s not running.

Solution 1: Verify App is in PATH

# Test launch command directly
which firefox
firefox

If the command doesn’t exist:

  • Install the application
  • Use the full path in configuration

Solution 2: Use Full Path

[keys.f]
apps = ["firefox"]
[keys.f.launch]
command = "/usr/bin/firefox"  # Use absolute path

Solution 3: Check Debug Logs

RUST_LOG=debug sesame --launcher

Look for launch errors:

ERROR Failed to launch: No such file or directory

Solution 4: Test Launch Command

Test the launch command manually:

# Verify command syntax
/bin/sh -c "firefox"

If it works manually but not from Open Sesame, check:

  • Environment variables (PATH, etc.)
  • Shell escaping issues
  • Permissions

Solution 5: Use Simple Launch First

Start with a simple string launch:

# Simple
[keys.f]
apps = ["firefox"]
launch = "firefox"

# Not this (yet):
[keys.f.launch]
command = "firefox"
args = ["--new-window"]

Once simple launch works, add complexity.

Performance Issues

Problem: Overlay feels slow or laggy.

Solution 1: Reduce Delays

[settings]
overlay_delay = 0           # Show immediately
activation_delay = 100      # Faster activation

Solution 2: Check System Resources

# Check CPU usage
top

# Check memory
free -h

Open Sesame is lightweight, but performance issues may indicate system-wide problems.

Solution 3: Verify Wayland Compositor

Ensure COSMIC compositor is running properly:

# Restart COSMIC session
# Log out and log back in

Solution 4: Reduce Window Count

Fewer open windows = faster hint assignment:

# Check window count
sesame --list-windows | grep "Found"
# Output: Found 23 windows

If you have many windows, consider closing unused ones.

Quick Switch Not Working

Problem: Tapping Alt+Space doesn’t switch to the previous window.

Solution 1: Check Quick Switch Threshold

You may be holding the key too long:

[settings]
quick_switch_threshold = 250  # Default: 250ms

Try increasing the threshold:

[settings]
quick_switch_threshold = 400  # More forgiving

Solution 2: Verify Switcher Mode

Quick switch only works in switcher mode (NOT launcher mode):

# This works for quick switch:
sesame

# This doesn't (always shows overlay):
sesame --launcher

Check your keybinding:

sesame --keybinding-status

Solution 3: Check MRU State

The MRU (Most Recently Used) state may be corrupted:

# View MRU state
sesame --list-windows | grep -A 2 "MRU State"

# Reset MRU state
rm ~/.cache/open-sesame/mru.json

# Try again
sesame

Hints Not Appearing

Problem: Windows appear in the overlay but no letter hints are shown.

Solution 1: Check Font Installation

Open Sesame requires fontconfig:

# Check fontconfig
fc-list | head

# If empty, install fonts
sudo apt install fonts-dejavu-core

Solution 2: Check Debug Logs

RUST_LOG=debug sesame --launcher 2>&1 | grep -i "font"

Look for font loading errors.

Solution 3: Verify Rendering

This is likely a rendering issue. Check:

# Verify Wayland display
echo $WAYLAND_DISPLAY

# Check for rendering errors
RUST_LOG=debug sesame --launcher 2>&1 | grep -i "render"

App Launches But Doesn’t Focus

Problem: Pressing a letter launches the app, but doesn’t switch focus to it.

Solution 1: Add Window Activation Delay

Some apps take time to create windows:

# Launch app
sesame --launcher  # Press 'f' for Firefox

# Wait a moment, then run again
sesame --launcher  # Should now show Firefox and focus it

This is a known limitation - Open Sesame can’t focus a window that doesn’t exist yet.

Solution 2: Use Window Focus After Launch

After launching, manually activate Open Sesame again to focus the new window:

  1. Press Alt+Space, type ‘f’ → launches Firefox
  2. Wait 2 seconds for Firefox to start
  3. Press Alt+Space, type ‘f’ → focuses Firefox

Solution 3: Use Startup Notification

Some applications support startup notification. This is a future feature.

Border Not Showing

Problem: No border appears around windows during quick switch.

Solution 1: Check Border Settings

[settings]
border_width = 3.0          # Must be > 0
border_color = "#b4a0ffb4"  # Must have some opacity

Solution 2: Increase Border Width

Make the border more visible:

[settings]
border_width = 5.0
border_color = "#ff0000ff"  # Bright red for testing

Solution 3: Check Overlay Delay

If overlay_delay = 0, the border phase is skipped:

[settings]
overlay_delay = 720  # Allow border to show

Environment Variables Not Loading

Problem: Environment variables in env_files aren’t being set.

Solution 1: Check File Exists

# Verify env file exists
cat ~/.config/open-sesame/global.env

Solution 2: Check File Format

Environment files must use correct syntax:

# Correct:
KEY=value
KEY="value with spaces"
export KEY=value

# Incorrect:
KEY = value        # No spaces around =
KEY: value         # Not YAML

Solution 3: Check Path Expansion

Paths must use ~/ for home directory:

# Correct:
env_files = ["~/.config/open-sesame/global.env"]

# Incorrect:
env_files = ["$HOME/.config/open-sesame/global.env"]  # $HOME not expanded

Solution 4: Check Debug Logs

RUST_LOG=debug sesame --launcher 2>&1 | grep -i "env"

Look for:

DEBUG Loading env file: /home/user/.config/open-sesame/global.env

Wayland-Specific Issues

Problem: Open Sesame doesn’t work or crashes on startup.

Verify Wayland Session

# Check session type
echo $XDG_SESSION_TYPE
# Should output: wayland

# Check Wayland display
echo $WAYLAND_DISPLAY
# Should output: wayland-0 (or similar)

X11 Not Supported

If you’re on X11:

echo $XDG_SESSION_TYPE
# Output: x11

Solution: Log out and select a Wayland session at login, or switch to COSMIC desktop.

Still Having Issues?

Enable Full Debug Logging

# Run with maximum logging
RUST_LOG=trace sesame --launcher 2>&1 | tee sesame-debug.log

# Review the log
less sesame-debug.log

Check System Logs

# Check system journal
journalctl --user -xe | grep -i sesame

# Check for COSMIC errors
journalctl --user -xe | grep -i cosmic

Report an Issue

If none of these solutions work:

  1. Gather information:

    # System info
    uname -a
    echo $XDG_CURRENT_DESKTOP
    echo $XDG_SESSION_TYPE
    
    # Open Sesame version
    sesame --version
    
    # Window list
    sesame --list-windows
    
    # Config validation
    sesame --validate-config
    
    # Debug log
    RUST_LOG=debug sesame --launcher 2>&1 > sesame-debug.log
    
  2. Open an issue on GitHub: https://github.com/ScopeCreep-zip/open-sesame/issues

  3. Include:

    • System information
    • Open Sesame version
    • Configuration file (redact any secrets)
    • Debug log
    • Steps to reproduce

See Also

Architecture

Open Sesame is architected as a modular Rust application with clean separation of concerns and well-defined module boundaries.

Design Principles

1. Zero External Dependencies at Runtime

Open Sesame uses software rendering (tiny-skia) instead of GPU acceleration, eliminating the need for graphics drivers or OpenGL/Vulkan runtime dependencies.

Benefits:

  • Works on any system with Wayland support
  • No GPU-specific bugs or driver compatibility issues
  • Smaller binary size
  • Faster startup time

2. Single-Instance Execution

Only one instance of Open Sesame can run at a time. Multiple invocations communicate via IPC (Inter-Process Communication).

Implementation:

  • File-based lock in ~/.cache/open-sesame/lock
  • Unix socket for IPC commands
  • New instances signal existing instance to cycle forward/backward

Use case:

  • Pressing Alt+Tab multiple times → signals running instance to cycle
  • Prevents multiple overlays from appearing simultaneously

3. Fast Activation

Target: Sub-200ms window switching latency

Optimizations:

  • Pre-computed hint assignments
  • Efficient window enumeration via Wayland
  • Minimal rendering (software rasterization)
  • Event-driven architecture (no polling)

4. Graceful Degradation

Open Sesame handles failures gracefully:

  • Falls back to stderr logging if file logging fails
  • Continues without MRU state if cache is corrupted
  • Works with minimal configuration (sensible defaults)

5. Secure by Default

  • Proper file permissions on config and cache files
  • No network access
  • Cache directory isolation
  • Input validation on all external data

Module Overview

Open Sesame is organized into eight main modules:

app - Application Orchestration

Responsibility: Event loop, state management, render coordination

Key types:

  • App - Main application struct
  • AppState - Current UI state (overlay shown, window selected, etc.)
  • Event handlers for keyboard input, IPC commands, timers

Flow:

  1. Initialize Wayland connection
  2. Enumerate windows
  3. Assign hints
  4. Enter event loop
  5. Handle input → update state → render
  6. Exit on selection or cancellation

config - Configuration System

Responsibility: Loading, parsing, validating TOML configuration

Key types:

  • Config - Main configuration struct
  • Settings - Global settings (delays, colors, etc.)
  • KeyBinding - Per-key app associations
  • LaunchConfig - Simple or advanced launch configuration

Features:

  • XDG-compliant file locations
  • Layered inheritance (system → user → overrides)
  • Schema validation with helpful error messages
  • Default configuration generation

File locations:

/etc/open-sesame/config.toml              # System defaults
~/.config/open-sesame/config.toml         # User config
~/.config/open-sesame/config.d/*.toml     # Overrides (alphabetical)

core - Domain Logic

Responsibility: Business logic, hint assignment algorithm, window types

Key types:

  • Window - Window information (ID, app ID, title, focused state)
  • WindowHint - Assigned hint for a window
  • HintAssignment - Complete hint assignment for all windows
  • HintMatcher - Input matching logic
  • LaunchCommand - Command execution abstraction

Hint assignment algorithm:

  1. Windows with configured keys get priority
  2. Multiple instances get repeated letters (g, gg, ggg)
  3. Remaining windows get sequential letters (a-z, excluding used keys)

Example:

Windows: [Firefox, Firefox, Ghostty, Code]
Config:  f → Firefox, g → Ghostty, v → Code

Assignment:
  Firefox → f
  Firefox → ff
  Ghostty → g
  Code → v

input - Keyboard Input Processing

Responsibility: Handling keyboard events, matching hints, buffer management

Key types:

  • InputBuffer - Tracks typed characters
  • InputHandler - Processes key events
  • KeyEvent - Keyboard event representation

Features:

  • Multi-key hint matching (g, gg, ggg)
  • Alternative input (g1, g2, g3)
  • Backspace handling
  • Arrow key navigation

Flow:

  1. Receive key event from Wayland
  2. Update input buffer
  3. Match against hints
  4. Return action (activate, select, cancel, continue)

platform - Platform Abstraction

Responsibility: Wayland protocol integration, COSMIC-specific features

Key modules:

  • wayland - Window enumeration via Wayland protocols
  • cosmic - COSMIC desktop integration (keybindings, protocols)
  • activation - Window activation via COSMIC protocols

Wayland protocols used:

  • wlr-foreign-toplevel-management - Window enumeration
  • cosmic-workspace - Workspace-aware window listing
  • cosmic-window-management - Window activation

Features:

  • Workspace-aware window listing
  • Window activation (focus and raise)
  • Keybinding configuration via COSMIC settings
  • Theme integration (future)

render - Rendering Pipeline

Responsibility: Software rendering with tiny-skia, font rasterization

Key types:

  • Renderer - Main rendering coordinator
  • Buffer - Shared memory buffer for Wayland
  • FontCache - Cached font rasterization

Rendering stack:

  • fontdue - Font rasterization (TTF/OTF parsing and glyph rendering)
  • tiny-skia - 2D graphics (paths, shapes, blending)
  • wayland-client - Buffer sharing with compositor

Primitives:

  • Rectangles with rounded corners
  • Text rendering with anti-aliasing
  • Color blending and transparency
  • Borders and shadows

ui - User Interface

Responsibility: Overlay layout, theme management, UI components

Key types:

  • Overlay - Main overlay window component
  • Theme - Color scheme and styling
  • Layout - Window card positioning

Features:

  • Centered overlay with window cards
  • Hint badges overlaid on windows
  • Selected window highlighting
  • Border indicator for quick switch

Layout algorithm:

  • Grid layout with dynamic columns
  • Center alignment
  • Responsive sizing based on window count
  • Scroll support for many windows (future)

util - Shared Utilities

Responsibility: Cross-cutting concerns, helpers

Key modules:

  • lock - Single-instance locking
  • ipc - Inter-process communication
  • mru - Most Recently Used window tracking
  • env - Environment file parsing
  • log - Logging setup

Features:

  • File-based instance locking
  • Unix socket IPC
  • MRU state persistence (JSON)
  • direnv-style .env file parsing
  • Structured logging with tracing

Data Flow

Window Switching Flow

1. User presses Alt+Space
   ↓
2. COSMIC runs: sesame --launcher
   ↓
3. Open Sesame starts
   ↓
4. Check instance lock
   ├─ Locked? → Signal existing instance via IPC → exit
   └─ Not locked? → Acquire lock and continue
   ↓
5. Load configuration
   ↓
6. Enumerate windows via Wayland
   ↓
7. Assign hints (core::HintAssignment)
   ↓
8. Initialize Wayland surface for overlay
   ↓
9. Render overlay (render::Renderer)
   ↓
10. Enter event loop (app::App)
    ↓
11. Handle keyboard input (input::InputHandler)
    ├─ Match hint? → Activate window → exit
    ├─ Arrow key? → Update selection → re-render
    └─ Escape? → Cancel → exit

Configuration Loading Flow

1. Load system config: /etc/open-sesame/config.toml
   ↓
2. Merge user config: ~/.config/open-sesame/config.toml
   ↓
3. Merge overrides: ~/.config/open-sesame/config.d/*.toml (alphabetical)
   ↓
4. Validate configuration (config::ConfigValidator)
   ↓
5. Return Config struct

Hint Assignment Flow

1. Input: List of windows, config with key bindings
   ↓
2. For each window:
   ├─ Check config for matching key
   ├─ If match: Assign configured key
   └─ If no match: Assign next available letter
   ↓
3. Handle duplicates:
   ├─ First instance: single letter (g)
   ├─ Second instance: repeated letter (gg)
   └─ Third instance: triple letter (ggg)
   ↓
4. Return HintAssignment

Concurrency Model

Open Sesame is primarily single-threaded with async I/O for Wayland events.

Event loop:

  • Main thread runs Wayland event loop
  • No multi-threading (avoids synchronization complexity)
  • Async I/O via wayland-client non-blocking sockets

Why single-threaded?

  • Simple to reason about (no race conditions)
  • Fast enough for the use case (< 100ms latency)
  • Avoids threading overhead

Error Handling

Open Sesame uses Rust’s Result type for error handling.

Error strategy:

  • anyhow::Result for CLI and top-level errors
  • Custom util::Error type for library code
  • Graceful degradation (log errors, use fallbacks)
  • Never panic in production code (except for unrecoverable bugs)

Example:

#![allow(unused)]
fn main() {
// Configuration loading
pub fn load_config() -> Result<Config> {
    let config = load_from_file().context("Failed to load config")?;
    validate(&config).context("Invalid configuration")?;
    Ok(config)
}
}

Testing Strategy

Unit Tests

Each module has unit tests for pure functions:

  • core::HintAssignment::assign - Hint algorithm tests
  • config::Color::from_hex - Color parsing tests
  • input::InputBuffer - Input matching tests

Integration Tests

Integration tests in tests/ directory:

  • Configuration loading and merging
  • Hint assignment with real-world window lists
  • IPC communication

Manual Testing

Use mise run dev for manual testing:

  • Window enumeration
  • Overlay rendering
  • Keyboard input
  • Configuration changes

Performance Characteristics

Startup Time

Target: < 100ms from invocation to overlay display

Breakdown:

  • Config loading: ~5ms
  • Window enumeration: ~10ms
  • Hint assignment: ~1ms
  • Wayland setup: ~20ms
  • Render first frame: ~30ms
  • Total: ~66ms

Memory Usage

Typical: 8-12 MB resident memory

Breakdown:

  • Binary: ~4 MB
  • Window list: ~100 KB (100 windows)
  • Render buffers: ~2 MB (1920x1080 overlay)
  • Font cache: ~1 MB

Window Switching Latency

Target: < 200ms from key press to window activation

Breakdown:

  • Input event: ~5ms
  • Hint matching: ~1ms
  • Wayland activation: ~10ms
  • Compositor switch: ~50ms (depends on compositor)
  • Total: ~66ms (typical)

Security Considerations

Input Validation

  • All configuration is validated before use
  • Color hex strings are parsed safely
  • File paths are canonicalized
  • Command execution uses explicit PATH resolution

File Permissions

  • Config files: 644 (readable by all, writable by owner)
  • Cache files: 644 (MRU state, logs)
  • Lock file: 644 (instance lock)

No Network Access

Open Sesame never makes network connections:

  • No telemetry
  • No update checks
  • No remote configuration

Privilege Separation

Open Sesame runs as the user, not as root:

  • No setuid/setgid
  • No system-wide modifications (except via package manager)
  • User-level configuration only

Future Architecture Improvements

Planned Enhancements

  1. Plugin System - Allow custom hint assignment strategies
  2. Theme System - Load themes from COSMIC desktop settings
  3. Window Previews - Show window thumbnails in overlay
  4. Workspace Support - Filter windows by workspace
  5. Multi-Monitor - Show overlay on active monitor only

Technical Debt

  • Reduce coupling between app and render modules
  • Extract IPC into a reusable crate
  • Improve test coverage for Wayland interactions
  • Add property-based testing for hint assignment

See Also

Building

Complete guide to building Open Sesame from source.

Quick Start

# Install mise (task runner)
curl https://mise.run | sh

# Clone repository
git clone https://github.com/scopecreep-zip/opensesame.git
cd opensesame

# Install dependencies
mise run setup

# Build and run
mise run dev

Prerequisites

Required

  • Git - Version control
  • mise - Task runner and toolchain manager (replaces rustup, cargo, etc.)
  • Build essentials - C compiler, make, etc.

System Dependencies

Open Sesame requires several system libraries for building:

# Ubuntu/Debian (Pop!_OS)
sudo apt install \
    build-essential \
    pkg-config \
    libfontconfig1-dev \
    libxkbcommon-dev \
    liblzma-dev

Library purposes:

  • libfontconfig1-dev - Font discovery and loading
  • libxkbcommon-dev - Keyboard layout handling
  • liblzma-dev - Compression support

Mise Installation

Mise is a unified toolchain manager that replaces rustup, nvm, rbenv, etc.

# Install mise
curl https://mise.run | sh

# Add to shell (bash)
echo 'eval "$(mise activate bash)"' >> ~/.bashrc

# Or zsh
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc

# Reload shell
exec $SHELL

What mise does:

  • Installs Rust toolchain automatically
  • Manages project-specific tools
  • Provides task runner (replaces Makefile)
  • Ensures consistent build environment

Repository Setup

Clone Repository

git clone https://github.com/scopecreep-zip/opensesame.git
cd opensesame

Install Dependencies

# mise automatically reads .mise.toml and installs required tools
mise run setup

This installs:

  • Rust toolchain (version specified in rust-toolchain.toml)
  • cargo-deb - Debian package builder
  • cross - Cross-compilation tool

Building

Development Build

For development and testing:

# Build debug binary
mise run build

# Or use cargo directly
cargo build

# Binary location:
./target/debug/sesame

Debug build characteristics:

  • Fast compilation
  • Includes debug symbols
  • No optimizations
  • Larger binary size

Release Build

For production use:

# Build optimized binary
mise run build:release

# Or use cargo directly
cargo build --release

# Binary location:
./target/release/sesame

Release build characteristics:

  • Slower compilation
  • Optimized code (smaller, faster)
  • No debug symbols (unless explicitly enabled)
  • Typical size: ~4 MB

Debian Package

Build a .deb package for distribution:

# Build .deb package
mise run build:deb

# Package location:
./target/debian/open-sesame_*_amd64.deb

What’s included:

  • Optimized binary (/usr/bin/sesame)
  • Example configuration (/usr/share/doc/open-sesame/config.example.toml)
  • Man page (/usr/share/man/man1/sesame.1.gz) - future
  • Shell completions (/usr/share/bash-completion/completions/sesame) - future

Cross-Compilation

Build for ARM64 (e.g., Raspberry Pi):

# Build ARM64 .deb package
mise run build:cross-arm64

# Package location:
./target/aarch64-unknown-linux-gnu/debian/open-sesame_*_arm64.deb

Requirements:

  • Docker (for cross-compilation environment)
  • cross tool (installed by mise run setup)

Running

Development Mode

Run directly from source with debug logging:

# Run in development mode
mise run dev

# This is equivalent to:
RUST_LOG=debug cargo run --release

Development mode features:

  • Debug logging enabled
  • Uses release build (faster than debug)
  • Logs to stderr and ~/.cache/open-sesame/debug.log

Installed Binary

After building:

# Run from target directory
./target/release/sesame --launcher

# Or install system-wide
mise run install
sesame --launcher

With Custom Config

Test with a custom configuration:

cargo run -- -c /path/to/config.toml --list-windows

Build Variants

Debug with Logging

Debug build with logging always enabled:

# Build debug variant with logging
mise run build:debug

# Or manually:
cargo build --features debug-logging

# Install debug variant
mise run install:debug

Use case: Troubleshooting issues in production

Minimal Build

Smallest possible binary:

# Build with minimal features
cargo build --release --no-default-features

# Binary size: ~2.5 MB (instead of ~4 MB)

Trade-offs:

  • Smaller binary
  • May lack some features
  • Not recommended for general use

Build Configuration

Cargo.toml

Build configuration is in Cargo.toml:

[package]
name = "open-sesame"
version = "X.Y.Z"  # Managed by semantic-release
edition = "2024"

[profile.release]
opt-level = 3          # Maximum optimization
lto = true             # Link-time optimization
codegen-units = 1      # Single codegen unit (slower compile, faster runtime)
strip = true           # Strip symbols (smaller binary)

Optimization levels:

  • opt-level = 0 - No optimization (debug)
  • opt-level = 1 - Basic optimization
  • opt-level = 2 - Good optimization
  • opt-level = 3 - Maximum optimization (release)
  • opt-level = "s" - Optimize for size
  • opt-level = "z" - Aggressively optimize for size

Build Features

Control build features via Cargo:

# Default features
cargo build

# All features
cargo build --all-features

# Specific features
cargo build --features "debug-logging"

# No default features
cargo build --no-default-features

Available features:

  • debug-logging - Always enable debug logging (default: off)

Mise Tasks Reference

All available build tasks:

# View all tasks
mise tasks

Build tasks:

  • build - Build debug binary
  • build:release - Build release binary
  • build:deb - Build Debian package
  • build:cross-arm64 - Cross-compile for ARM64

Development tasks:

  • dev - Run with debug logging
  • fmt - Format code
  • lint - Run clippy linter
  • test - Run all tests

Installation tasks:

  • install - Install release binary system-wide
  • install:debug - Install debug binary system-wide
  • uninstall - Remove installed binary

Cleanup tasks:

  • clean - Remove target directory
  • clean:all - Remove target and cache

Build Troubleshooting

Missing System Dependencies

Error:

error: failed to run custom build command for `fontconfig-sys`

Solution:

sudo apt install libfontconfig1-dev pkg-config

Rust Toolchain Issues

Error:

error: toolchain '1.91-x86_64-unknown-linux-gnu' is not installed

Solution:

# mise automatically installs the correct toolchain
mise install

# Or manually with rustup
rustup install 1.91

Build Fails with Optimization Errors

Error:

error: could not compile `open-sesame` due to previous error

Solution: Try debug build first to isolate the issue:

cargo build  # Debug build
cargo build --release  # Release build

Cross-Compilation Fails

Error:

error: failed to execute docker

Solution: Ensure Docker is installed and running:

sudo apt install docker.io
sudo usermod -aG docker $USER
# Log out and log back in

Out of Disk Space

Error:

error: no space left on device

Solution: Clean build artifacts:

mise run clean:all
cargo clean

Check disk usage:

du -sh target/
# Typical: 2-4 GB for full build

Build Performance

Compilation Times

Typical build times on modern hardware (AMD Ryzen 5):

Build TypeTimeBinary Size
Debug (clean)45s8 MB
Debug (incremental)3s8 MB
Release (clean)90s4 MB
Release (incremental)15s4 MB

Incremental builds: Rust caches previous compilations. Subsequent builds are much faster.

Speeding Up Builds

Use release build for development:

mise run dev  # Uses --release (faster runtime)

Parallel compilation:

# Use all CPU cores (default)
cargo build -j $(nproc)

Cache dependencies:

# sccache caches compiled dependencies
cargo install sccache
export RUSTC_WRAPPER=sccache

Disable LTO for faster iteration:

# In Cargo.toml
[profile.release]
lto = false  # Faster compile, larger binary

Build Environment

Environment Variables

Useful environment variables:

# Rust log level
export RUST_LOG=debug

# Cargo build jobs
export CARGO_BUILD_JOBS=8

# Rust backtrace on panic
export RUST_BACKTRACE=1

Rust Toolchain

Open Sesame specifies its Rust version in rust-toolchain.toml:

[toolchain]
channel = "1.91"
components = ["rustfmt", "clippy"]

Why 1.91?

  • Stable release with all required features
  • Edition 2024 support
  • Performance improvements

Continuous Integration

GitHub Actions builds Open Sesame automatically:

# .github/workflows/ci.yml
- name: Build
  run: cargo build --release

- name: Run tests
  run: cargo test

- name: Build .deb package
  run: cargo deb

CI checks:

  • Compiles on Ubuntu 24.04
  • All tests pass
  • Clippy lints pass
  • Formatting check passes

Next Steps

Testing

Comprehensive testing guide for Open Sesame.

Quick Start

# Run all tests
mise run test

# This runs:
# - cargo fmt --check (formatting)
# - cargo clippy (linter)
# - cargo test (unit and integration tests)

Test Categories

Unit Tests

Test individual functions and modules in isolation.

Run unit tests:

cargo test

Run specific module tests:

# Test config module
cargo test config::

# Test hint assignment
cargo test core::hint

# Test color parsing
cargo test config::schema::color

Example unit test:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_color_hex_parse() {
        let c = Color::from_hex("#ff0000").unwrap();
        assert_eq!(c, Color::new(255, 0, 0, 255));
    }
}
}

Integration Tests

Test multiple modules working together.

Location: tests/ directory

Run integration tests:

cargo test --test '*'

Example integration test:

#![allow(unused)]
fn main() {
// tests/hint_assignment.rs
#[test]
fn test_hint_assignment_with_config() {
    let config = Config::load().unwrap();
    let windows = vec![/* ... */];
    let assignment = HintAssignment::assign(&windows, |app_id| {
        config.key_for_app(app_id)
    });
    assert_eq!(assignment.hints.len(), windows.len());
}
}

Documentation Tests

Test code examples in documentation.

Run doc tests:

cargo test --doc

Example doc test:

#![allow(unused)]
fn main() {
/// Parse a color from hex.
///
/// # Examples
///
/// ```
/// use open_sesame::config::Color;
/// let color = Color::from_hex("#ff0000").unwrap();
/// assert_eq!(color.r, 255);
/// ```
pub fn from_hex(s: &str) -> Result<Color> {
    // ...
}
}

Manual Tests

Interactive testing during development.

Run development build:

mise run dev

Test specific functionality:

# Test window enumeration
sesame --list-windows

# Test configuration validation
sesame --validate-config

# Test keybinding setup
sesame --setup-keybinding alt+space

Running Tests

All Tests

Run the full test suite:

# Via mise (recommended)
mise run test

# Or manually
cargo fmt --check && cargo clippy && cargo test

Specific Tests

Run individual test functions:

# Run a specific test
cargo test test_color_hex_parse

# Run tests matching a pattern
cargo test color

With Output

Show println! output from tests:

cargo test -- --nocapture

With Logging

Enable logging during tests:

RUST_LOG=debug cargo test

Parallel vs Sequential

# Run tests in parallel (default)
cargo test

# Run tests sequentially
cargo test -- --test-threads=1

Code Quality

Formatting

Check code formatting:

# Check formatting
cargo fmt --check

# Auto-format code
mise run fmt

Configuration: .rustfmt.toml

Linting

Run Clippy linter:

# Check lints
cargo clippy

# Check with all features
cargo clippy --all-features

# Fail on warnings
cargo clippy -- -D warnings

Clippy configuration: Cargo.toml

[lints.clippy]
all = "warn"
pedantic = "warn"

Dead Code Detection

Find unused code:

cargo clippy -- -W dead_code

Test Coverage

Measuring Coverage

Use cargo-tarpaulin for coverage reports:

# Install tarpaulin
cargo install cargo-tarpaulin

# Generate coverage report
cargo tarpaulin --out Html

# Open report
xdg-open tarpaulin-report.html

Coverage goals:

  • Overall: > 70%
  • Core modules: > 85%
  • Utility modules: > 90%

Current Coverage

Current test coverage by module:

ModuleCoverage
config92%
core88%
util95%
input85%
platform45% (hard to test Wayland)
render40% (hard to test rendering)

Continuous Integration

GitHub Actions

Tests run automatically on every push:

# .github/workflows/ci.yml
- name: Format check
  run: cargo fmt --check

- name: Clippy
  run: cargo clippy -- -D warnings

- name: Tests
  run: cargo test

CI requirements:

  • All tests must pass
  • No clippy warnings
  • Code must be formatted

Pre-commit Hooks

Set up pre-commit hooks to catch issues early:

# Install pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
mise run test
EOF

chmod +x .git/hooks/pre-commit

Writing Tests

Unit Test Structure

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_function_name() {
        // Arrange
        let input = /* setup */;

        // Act
        let result = function_under_test(input);

        // Assert
        assert_eq!(result, expected);
    }
}
}

Test Organization

Group related tests:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    mod color_parsing {
        use super::*;

        #[test]
        fn parses_rgb() { /* ... */ }

        #[test]
        fn parses_rgba() { /* ... */ }

        #[test]
        fn rejects_invalid() { /* ... */ }
    }
}
}

Test Naming

Use descriptive names:

#![allow(unused)]
fn main() {
#[test]
fn test_hint_assignment_single_window() { /* ... */ }

#[test]
fn test_hint_assignment_multiple_windows_same_app() { /* ... */ }

#[test]
fn test_hint_assignment_with_config() { /* ... */ }
}

Assertions

Common assertions:

#![allow(unused)]
fn main() {
// Equality
assert_eq!(actual, expected);

// Inequality
assert_ne!(actual, unexpected);

// Boolean
assert!(condition);
assert!(!condition);

// Result/Option
assert!(result.is_ok());
assert!(result.is_err());
assert!(option.is_some());
assert!(option.is_none());

// Custom message
assert_eq!(actual, expected, "Expected {}, got {}", expected, actual);
}

Test Fixtures

Create reusable test data:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    fn sample_config() -> Config {
        Config {
            settings: Settings::default(),
            keys: HashMap::new(),
        }
    }

    fn sample_windows() -> Vec<Window> {
        vec![
            Window {
                id: WindowId::new("1"),
                app_id: AppId::new("firefox"),
                title: "Firefox".to_string(),
                is_focused: false,
            },
        ]
    }

    #[test]
    fn test_with_fixtures() {
        let config = sample_config();
        let windows = sample_windows();
        // ...
    }
}
}

Benchmarking

Criterion Benchmarks

Use Criterion for performance benchmarking:

# Add criterion to Cargo.toml
[dev-dependencies]
criterion = "0.5"

[[bench]]
name = "hint_assignment"
harness = false

Example benchmark:

#![allow(unused)]
fn main() {
// benches/hint_assignment.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use open_sesame::core::HintAssignment;

fn bench_hint_assignment(c: &mut Criterion) {
    let windows = /* create 100 windows */;

    c.bench_function("hint_assignment_100_windows", |b| {
        b.iter(|| {
            HintAssignment::assign(black_box(&windows), |_| None)
        })
    });
}

criterion_group!(benches, bench_hint_assignment);
criterion_main!(benches);
}

Run benchmarks:

cargo bench

Performance Goals

Target performance metrics:

OperationTarget
Hint assignment (100 windows)< 1ms
Configuration loading< 5ms
Window enumeration< 10ms
Render frame< 16ms (60 FPS)

Testing Wayland Functionality

Testing Wayland interactions is challenging because it requires a running compositor.

Manual Testing

# Test on real Wayland session
mise run dev

# Test window enumeration
sesame --list-windows

# Test window activation
sesame --launcher

Integration Testing

Use a nested Wayland compositor for automated tests:

# Install weston (reference compositor)
sudo apt install weston

# Run tests in nested session
weston --backend=headless-backend.so &
WAYLAND_DISPLAY=wayland-1 cargo test platform::

Mock Testing

For unit tests, mock Wayland interactions:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    struct MockWindowManager {
        windows: Vec<Window>,
    }

    impl MockWindowManager {
        fn enumerate(&self) -> Vec<Window> {
            self.windows.clone()
        }
    }

    #[test]
    fn test_with_mock() {
        let mock = MockWindowManager {
            windows: vec![/* ... */],
        };
        assert_eq!(mock.enumerate().len(), 1);
    }
}
}

Debugging Tests

Failed Test Output

When a test fails:

# Run with backtrace
RUST_BACKTRACE=1 cargo test

# Run specific failing test
cargo test test_name -- --nocapture

# Show detailed output
cargo test -- --show-output

Test in Debug Mode

# Build and run tests in debug mode
cargo test --no-default-features

GDB Debugging

Debug a test with GDB:

# Build test binary
cargo test --no-run

# Find test binary
find target/debug/deps -name 'open_sesame*' -type f

# Run with GDB
gdb target/debug/deps/open_sesame-<hash>

# In GDB:
(gdb) break test_function_name
(gdb) run

Test Maintenance

Keeping Tests Updated

  • Update tests when changing functionality
  • Add tests for new features
  • Remove tests for removed features
  • Refactor tests when refactoring code

Test Documentation

Document complex test scenarios:

#![allow(unused)]
fn main() {
#[test]
/// Test that hint assignment works correctly when:
/// 1. Multiple windows of the same app exist
/// 2. Some apps have configured keys
/// 3. Some apps do not have configured keys
///
/// Expected behavior:
/// - Firefox instances get f, ff, fff
/// - Ghostty instances get g, gg
/// - Unconfigured apps get sequential letters
fn test_hint_assignment_complex_scenario() {
    // ...
}
}

Troubleshooting

Tests Fail on CI but Pass Locally

Possible causes:

  • Different Rust version
  • Missing system dependencies
  • Environment variables

Solution:

# Match CI environment
rustup install 1.91
cargo +1.91 test

Tests Hang

Solution:

# Run with timeout
timeout 60s cargo test

# Check for infinite loops
cargo test -- --test-threads=1

Flaky Tests

Tests that sometimes pass and sometimes fail:

Common causes:

  • Race conditions
  • Timing dependencies
  • File system state

Solution:

  • Make tests deterministic
  • Use mocks instead of real I/O
  • Add retry logic for integration tests

Next Steps

Contributing

Thank you for considering contributing to Open Sesame! This guide will help you get started.

Code of Conduct

We value:

  • Quality over speed - Take time to write excellent code
  • Clear documentation - Code should be self-explanatory
  • Comprehensive testing - All quality gates must pass
  • User empathy - Features should solve real problems
  • Respectful collaboration - Be kind and constructive

Getting Started

1. Fork and Clone

# Fork on GitHub (click "Fork" button)

# Clone your fork
git clone https://github.com/YOUR_USERNAME/open-sesame.git
cd open-sesame

# Add upstream remote
git remote add upstream https://github.com/ScopeCreep-zip/open-sesame.git

2. Set Up Development Environment

# Install mise
curl https://mise.run | sh

# Install dependencies
mise run setup

# Verify setup
mise run test

3. Create a Branch

# Update main branch
git checkout main
git pull upstream main

# Create feature branch
git checkout -b feature/your-feature-name

# Or for bug fixes
git checkout -b fix/bug-description

Development Workflow

1. Make Changes

# Edit code
$EDITOR src/...

# Format code
mise run fmt

# Run tests
mise run test

# Test manually
mise run dev

2. Commit Changes

# Stage changes
git add .

# Commit with descriptive message
git commit -m "feat: Add support for custom hint colors"

Commit message format:

<type>: <subject>

<body (optional)>

<footer (optional)>

Types:

  • feat - New feature
  • fix - Bug fix
  • docs - Documentation changes
  • style - Code style changes (formatting, etc.)
  • refactor - Code refactoring
  • test - Adding or updating tests
  • chore - Maintenance tasks

Examples:

feat: Add window preview thumbnails

Implements thumbnail rendering in the overlay using the
wlr-screencopy protocol. Thumbnails are cached for performance.

Closes #42
fix: Correct hint assignment for duplicate app IDs

Previously, windows with identical app IDs would get incorrect
hint sequences. Now uses window ID as tiebreaker.

Fixes #123

3. Push Changes

# Push to your fork
git push origin feature/your-feature-name

4. Create Pull Request

  1. Go to https://github.com/ScopeCreep-zip/open-sesame
  2. Click “New Pull Request”
  3. Select your fork and branch
  4. Fill in the PR template:
    • Description of changes
    • Testing performed
    • Related issues

PR Title Format:

feat: Add window preview thumbnails
fix: Correct hint assignment for duplicate app IDs
docs: Update configuration guide

Quality Gates

All contributions must pass these checks:

1. Formatting

cargo fmt --check

Code must be formatted with rustfmt.

Auto-format:

mise run fmt

2. Linting

cargo clippy -- -D warnings

No clippy warnings allowed.

Common issues:

  • Unused variables
  • Unnecessary clones
  • Non-idiomatic code

Fix:

# View warnings
cargo clippy

# Fix automatically (when possible)
cargo clippy --fix

3. Tests

cargo test

All tests must pass.

Add tests for:

  • New features
  • Bug fixes
  • Edge cases

4. Documentation

cargo doc --no-deps

Public APIs must be documented.

Required:

  • Module docs (//!)
  • Public function docs (///)
  • Examples in docs (when appropriate)

Example:

#![allow(unused)]
fn main() {
/// Parse a color from hex string.
///
/// # Arguments
///
/// * `s` - Hex string in format "#RRGGBB" or "#RRGGBBAA"
///
/// # Examples
///
/// ```
/// use open_sesame::config::Color;
/// let color = Color::from_hex("#ff0000").unwrap();
/// assert_eq!(color.r, 255);
/// ```
///
/// # Errors
///
/// Returns `Error::InvalidColor` if the string is not valid hex.
pub fn from_hex(s: &str) -> Result<Color> {
    // ...
}
}

Contribution Guidelines

Code Style

Follow Rust conventions:

  • Use snake_case for functions and variables
  • Use CamelCase for types
  • Use SCREAMING_SNAKE_CASE for constants
  • Prefer explicit over implicit
  • Keep functions small and focused

Good:

#![allow(unused)]
fn main() {
fn assign_hints(windows: &[Window], config: &Config) -> HintAssignment {
    // Clear, descriptive name
    // Single responsibility
}
}

Avoid:

#![allow(unused)]
fn main() {
fn do_stuff(w: &[Window], c: &Config) -> HintAssignment {
    // Unclear name
    // Abbreviated parameters
}
}

Error Handling

  • Use Result for fallible operations
  • Use anyhow::Result for application errors
  • Use custom error types for library code
  • Provide context with .context()
  • Never unwrap() in production code

Good:

#![allow(unused)]
fn main() {
pub fn load_config() -> Result<Config> {
    let file = std::fs::read_to_string(path)
        .context("Failed to read config file")?;

    let config: Config = toml::from_str(&file)
        .context("Failed to parse config")?;

    Ok(config)
}
}

Avoid:

#![allow(unused)]
fn main() {
pub fn load_config() -> Config {
    let file = std::fs::read_to_string(path).unwrap();
    toml::from_str(&file).unwrap()
}
}

Testing

Write tests for all new code:

Unit tests:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_color_rgb() {
        let color = Color::from_hex("#ff0000").unwrap();
        assert_eq!(color, Color::new(255, 0, 0, 255));
    }

    #[test]
    fn test_parse_color_invalid() {
        assert!(Color::from_hex("#xyz").is_err());
    }
}
}

Integration tests:

#![allow(unused)]
fn main() {
// tests/hint_assignment.rs
#[test]
fn test_hint_assignment_integration() {
    let config = Config::load().unwrap();
    let windows = enumerate_test_windows();
    let assignment = HintAssignment::assign(&windows, |app_id| {
        config.key_for_app(app_id)
    });
    assert!(assignment.hints.len() > 0);
}
}

Documentation

  • Document all public APIs
  • Include examples for complex features
  • Update user guide for user-facing changes
  • Update CHANGELOG.md

Module documentation:

#![allow(unused)]
fn main() {
//! Window hint assignment algorithm.
//!
//! This module implements the core Vimium-style hint assignment logic.
//! Windows are assigned letter sequences (g, gg, ggg) based on configured
//! key bindings and app IDs.
//!
//! # Examples
//!
//! ```
//! use open_sesame::core::HintAssignment;
//!
//! let windows = vec![/* ... */];
//! let assignment = HintAssignment::assign(&windows, |_| None);
//! ```
}

Common Contribution Types

Adding a New Feature

  1. Open an issue first to discuss the feature
  2. Implement the feature in a new module if appropriate
  3. Add comprehensive tests
  4. Update documentation
  5. Add to CHANGELOG.md

Example: Adding window thumbnails

  1. Create issue: “Feature: Window preview thumbnails”
  2. Implement in src/preview.rs
  3. Add tests in src/preview.rs and tests/preview.rs
  4. Update user guide: docs/src/user-guide/basic-usage.md
  5. Add to CHANGELOG.md

Fixing a Bug

  1. Create a test that reproduces the bug
  2. Fix the bug
  3. Verify the test passes
  4. Add to CHANGELOG.md

Example: Fixing hint assignment bug

#![allow(unused)]
fn main() {
// 1. Create test that reproduces bug
#[test]
fn test_duplicate_app_ids() {
    let windows = vec![
        Window { app_id: "firefox", /* ... */ },
        Window { app_id: "firefox", /* ... */ },
    ];
    let assignment = HintAssignment::assign(&windows, |_| Some('f'));

    // This should pass but currently fails
    assert_eq!(assignment.hints[0].hint, "f");
    assert_eq!(assignment.hints[1].hint, "ff");
}

// 2. Fix the bug in src/core/hint.rs
// ...

// 3. Verify test now passes
// cargo test test_duplicate_app_ids
}

Improving Documentation

Documentation improvements are always welcome:

  • Fix typos and grammar
  • Add examples
  • Clarify confusing sections
  • Update outdated information

No issue required for documentation PRs.

Refactoring Code

  1. Ensure all tests pass before refactoring
  2. Refactor incrementally
  3. Ensure all tests still pass after refactoring
  4. No functional changes in refactoring PRs

Example: Extract function

#![allow(unused)]
fn main() {
// Before
fn process_windows(&self) -> Vec<WindowHint> {
    let mut hints = Vec::new();
    for window in &self.windows {
        let key = self.config.key_for_app(&window.app_id);
        if let Some(k) = key {
            hints.push(WindowHint { hint: k.to_string(), /* ... */ });
        }
    }
    hints
}

// After
fn process_windows(&self) -> Vec<WindowHint> {
    self.windows
        .iter()
        .filter_map(|w| self.create_hint(w))
        .collect()
}

fn create_hint(&self, window: &Window) -> Option<WindowHint> {
    self.config
        .key_for_app(&window.app_id)
        .map(|k| WindowHint { hint: k.to_string(), /* ... */ })
}
}

PR Review Process

What to Expect

  1. Automated checks - CI runs tests, formatting, and linting
  2. Code review - Maintainer reviews code for quality and correctness
  3. Feedback - You may be asked to make changes
  4. Approval - Once approved, PR is merged

Review Timeline

  • Small PRs (< 100 lines): 1-3 days
  • Medium PRs (100-500 lines): 3-7 days
  • Large PRs (> 500 lines): 1-2 weeks

Tip: Smaller PRs get reviewed faster!

Responding to Feedback

# Make requested changes
$EDITOR src/...

# Commit changes
git commit -m "Address review feedback"

# Push updates
git push origin feature/your-feature-name

PR automatically updates when you push.

After Merge

# Update your main branch
git checkout main
git pull upstream main

# Delete feature branch
git branch -d feature/your-feature-name
git push origin --delete feature/your-feature-name

Communication

Asking Questions

  • GitHub Issues - Feature requests, bug reports
  • GitHub Discussions - General questions, ideas
  • Pull Requests - Code-related discussions

Reporting Bugs

Use the bug report template:

**Describe the bug**
A clear description of the bug.

**To Reproduce**
Steps to reproduce:
1. Launch sesame with config X
2. Press key Y
3. See error

**Expected behavior**
What should happen.

**System information**
- OS: Pop!_OS 24.04
- COSMIC version: X.Y
- Open Sesame version: (output of `sesame --version`)

**Debug log**
Attach output of: RUST_LOG=debug sesame --launcher

Suggesting Features

Use the feature request template:

**Feature Description**
Clear description of the feature.

**Use Case**
Why is this feature useful?

**Proposed Implementation**
How might this work?

**Alternatives Considered**
Other ways to solve the problem.

Development Tips

Debugging

# Run with debug logging
RUST_LOG=debug mise run dev

# View debug log
tail -f ~/.cache/open-sesame/debug.log

# Run with backtrace
RUST_BACKTRACE=1 mise run dev

Testing Local Changes

# Install local build
mise run install

# Test it
sesame --launcher

# Uninstall
mise run uninstall

Iterating Quickly

# Watch for changes and rebuild
cargo watch -x check -x test

# Or with mise
mise run dev  # Runs release build (faster)

Recognition

Contributors are recognized in:

  • CHANGELOG.md (for each release)
  • GitHub contributors page
  • Release notes

Thank you for contributing to Open Sesame!

See Also

API Documentation

Open Sesame provides a library crate (open_sesame) with reusable components for building Wayland window management applications.

Online API Reference

View the complete API documentation at:

https://scopecreep-zip.github.io/open-sesame/doc/open_sesame/

Building Documentation Locally

# Build API docs
cargo doc --no-deps --open

# Or via mise
mise run docs:api

This opens the documentation in your browser at target/doc/open_sesame/index.html.

Module Overview

The open_sesame crate is organized into several modules:

app

Application orchestration and event loop.

Key types:

  • App - Main application coordinator
  • AppState - UI state machine

Responsibilities:

  • Wayland event loop integration
  • State management (idle, showing overlay, window selected)
  • Event dispatching to appropriate handlers
  • Render coordination

Example:

#![allow(unused)]
fn main() {
use open_sesame::app::App;
use open_sesame::Config;

let config = Config::load()?;
let hints = vec![/* ... */];
let result = App::run(config, hints, None, true, None)?;
}

config

Configuration loading and validation.

Key types:

  • Config - Main configuration struct
  • Settings - Global settings (delays, colors, etc.)
  • KeyBinding - Per-key app associations
  • LaunchConfig - Launch command configuration
  • Color - RGBA color with hex serialization
  • ConfigValidator - Configuration validation

Responsibilities:

  • TOML parsing and serialization
  • XDG config file discovery
  • Layered configuration merging
  • Schema validation

Example:

#![allow(unused)]
fn main() {
use open_sesame::Config;

// Load from default paths
let config = Config::load()?;

// Get key for app
if let Some(key) = config.key_for_app("firefox") {
    println!("Firefox is bound to '{}'", key);
}

// Generate default config
let default_toml = Config::default_toml();
}

core

Domain types and business logic.

Key types:

  • Window - Window information (ID, app ID, title, focused state)
  • WindowId - Opaque window identifier
  • AppId - Application identifier
  • WindowHint - Assigned hint for a window
  • HintAssignment - Complete hint assignment
  • HintMatcher - Input matching logic
  • LaunchCommand - Command execution abstraction

Responsibilities:

  • Hint assignment algorithm (Vimium-style)
  • Input matching and disambiguation
  • Window filtering and sorting
  • Launch command abstraction

Example:

#![allow(unused)]
fn main() {
use open_sesame::core::{HintAssignment, Window, AppId, WindowId};

let windows = vec![
    Window {
        id: WindowId::new("1"),
        app_id: AppId::new("firefox"),
        title: "Mozilla Firefox".to_string(),
        is_focused: false,
    },
];

let assignment = HintAssignment::assign(&windows, |app_id| {
    if app_id == "firefox" { Some('f') } else { None }
});

assert_eq!(assignment.hints[0].hint, "f");
}

input

Keyboard input processing.

Key types:

  • InputBuffer - Typed character buffer
  • InputHandler - Key event processor
  • MatchResult - Result of hint matching

Responsibilities:

  • Key event handling
  • Multi-key hint matching (g, gg, ggg)
  • Backspace handling
  • Arrow key navigation

Example:

#![allow(unused)]
fn main() {
use open_sesame::input::{InputBuffer, HintMatcher, MatchResult};

let mut buffer = InputBuffer::new();
buffer.push('g');

let matcher = HintMatcher::new(hints);
match matcher.match_input(&buffer) {
    MatchResult::Exact(idx) => println!("Matched window {}", idx),
    MatchResult::Partial => println!("Partial match, continue typing"),
    MatchResult::NoMatch => println!("No match"),
}
}

platform

Platform abstraction layer for Wayland and COSMIC.

Key functions:

  • enumerate_windows() - List all windows via Wayland protocols
  • activate_window() - Activate and focus a window
  • setup_keybinding() - Configure COSMIC keybinding
  • remove_keybinding() - Remove COSMIC keybinding
  • keybinding_status() - Check keybinding configuration

Wayland protocols:

  • wlr-foreign-toplevel-management - Window enumeration
  • cosmic-workspace - Workspace information
  • cosmic-window-management - Window activation

Example:

#![allow(unused)]
fn main() {
use open_sesame::platform;
use open_sesame::WindowId;

// Enumerate windows
let windows = platform::enumerate_windows()?;

// Activate a window
let window_id = WindowId::new("wayland-1");
platform::activate_window(&window_id)?;
}

render

Software rendering pipeline.

Key types:

  • Renderer - Main rendering coordinator
  • Buffer - Wayland shared memory buffer
  • FontCache - Cached font glyphs

Rendering stack:

  • fontdue - Font rasterization
  • tiny-skia - 2D graphics primitives
  • wayland-client - Buffer management

Responsibilities:

  • Overlay rendering
  • Font rasterization and caching
  • Primitive drawing (rectangles, text)
  • Buffer management for Wayland

Note: Rendering is primarily internal and not exposed as public API.

ui

User interface components.

Key types:

  • Overlay - Main overlay component
  • Theme - Color scheme and styling
  • Layout - Window card positioning

Responsibilities:

  • Overlay window creation
  • Layout calculations
  • Theme management
  • Component coordination

Example:

#![allow(unused)]
fn main() {
use open_sesame::ui::{Overlay, Theme};

let theme = Theme::from_config(&config.settings);
let overlay = Overlay::new(theme);
}

util

Shared utilities and helpers.

Key types:

  • InstanceLock - Single-instance locking
  • IpcServer / IpcClient - Inter-process communication
  • MruState - Most Recently Used window tracking
  • Error / Result - Error types

Key functions:

  • load_mru_state() - Load MRU state from cache
  • save_activated_window() - Save MRU state
  • load_env_files() - Parse environment files
  • log::init() - Initialize logging

Example:

#![allow(unused)]
fn main() {
use open_sesame::util::{InstanceLock, load_mru_state};

// Single-instance locking
let _lock = InstanceLock::acquire()?;

// MRU tracking
let mru = load_mru_state();
println!("Previous window: {:?}", mru.previous);
}

Error Handling

Open Sesame uses custom error types for clear error reporting:

#![allow(unused)]
fn main() {
use open_sesame::{Error, Result};

fn example() -> Result<()> {
    let config = Config::load()
        .map_err(|e| Error::Config(e.to_string()))?;
    Ok(())
}
}

Error types:

  • Error::Config - Configuration errors
  • Error::Platform - Wayland/platform errors
  • Error::InvalidColor - Color parsing errors
  • Error::Io - I/O errors
  • Error::Other - Other errors

Re-exports

Commonly used types are re-exported at the crate root:

#![allow(unused)]
fn main() {
use open_sesame::{
    Config,            // Configuration
    Window,            // Window info
    WindowId,          // Window identifier
    AppId,             // App identifier
    HintAssignment,    // Hint assignment
    HintMatcher,       // Input matching
    Error,             // Error type
    Result,            // Result type
};
}

Feature Flags

Current features:

  • default - All default features
  • debug-logging - Always enable debug logging (off by default)

Example:

# In Cargo.toml
[dependencies]
open-sesame = { version = "*", features = ["debug-logging"] }

Examples

The repository includes example programs in the examples/ directory:

# List examples
ls examples/

# Run an example
cargo run --example window_enumeration

Available examples:

  • window_enumeration - Enumerate windows and print details
  • hint_assignment - Demonstrate hint assignment algorithm
  • config_loading - Load and display configuration

Documentation Standards

All public APIs follow these documentation standards:

  1. Module docs (//!) - Overview and examples
  2. Type docs (///) - Purpose and usage
  3. Function docs (///) - Parameters, returns, examples, errors
  4. Examples - Working code examples in docs
  5. Links - Cross-references to related types

Example:

#![allow(unused)]
fn main() {
/// Parse a color from hex string.
///
/// Supports both RGB (`#RRGGBB`) and RGBA (`#RRGGBBAA`) formats.
///
/// # Arguments
///
/// * `s` - Hex string (with or without leading `#`)
///
/// # Examples
///
/// ```
/// use open_sesame::config::Color;
///
/// let red = Color::from_hex("#ff0000").unwrap();
/// assert_eq!(red.r, 255);
///
/// let transparent = Color::from_hex("#00000080").unwrap();
/// assert_eq!(transparent.a, 128);
/// ```
///
/// # Errors
///
/// Returns [`Error::InvalidColor`] if the string is not valid hex.
///
/// # See Also
///
/// * [`Color::to_hex`] - Convert color to hex string
pub fn from_hex(s: &str) -> Result<Color> {
    // ...
}
}

Building Documentation

Standard Build

cargo doc --no-deps

With Private Items

cargo doc --no-deps --document-private-items

With All Features

cargo doc --no-deps --all-features

Check for Warnings

RUSTDOCFLAGS="-D warnings" cargo doc --no-deps

Contributing to Documentation

See the Contributing Guide for documentation standards and guidelines.

Quick tips:

  • Document all public items
  • Include examples for complex functionality
  • Use intra-doc links for cross-references
  • Keep examples short and focused
  • Test examples with cargo test --doc

Next Steps

Changelog

All notable changes to Open Sesame will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Added

  • mdBook user guide documentation
  • API documentation via rustdoc

[1.0.0] - 2025-11-27

Added

  • Initial public release
  • Vimium-style window hints for COSMIC desktop
  • Focus-or-launch functionality
  • Configurable key bindings per application
  • Quick switch behavior (tap vs hold)
  • Two modes: Launcher (Alt+Space) and Switcher (Alt+Tab)
  • Arrow key navigation as alternative to letter hints
  • Multi-window support with repeated hints (g, gg, ggg)
  • Software rendering with tiny-skia (no GPU required)
  • XDG-compliant configuration with layered inheritance
  • Advanced launch configuration with args and environment variables
  • Environment file support (direnv .env style)
  • Automatic keybinding setup via COSMIC integration
  • MRU (Most Recently Used) window tracking
  • Single-instance execution with IPC
  • APT repository for easy installation
  • Debian packages for amd64 and arm64
  • SLSA provenance attestations for supply chain security
  • Comprehensive CLI with validation and debugging tools

Changed

  • N/A (initial release)

Deprecated

  • N/A (initial release)

Removed

  • N/A (initial release)

Fixed

  • N/A (initial release)

Security

  • File-based instance locking
  • Proper file permissions on config and cache
  • No network access
  • Input validation on all external data

Release History

Version 1.0.0 - “Open Sesame”

Release Date: November 27, 2025

Highlights:

  • First stable release
  • Full COSMIC desktop integration
  • Production-ready window switching
  • Comprehensive documentation
  • APT repository for distribution

Breaking Changes:

  • None (initial release)

Migration Guide:

  • None (initial release)

Known Issues:

  • Window focus may lag on slower systems
  • Thumbnail previews not yet implemented
  • X11 not supported (Wayland only)

Contributors:

  • usrbinkat

Statistics:

  • 42 commits
  • 8,500+ lines of Rust code
  • 92% test coverage (core modules)
  • Sub-200ms switching latency

Versioning Strategy

Open Sesame follows Semantic Versioning:

  • MAJOR version for incompatible API/config changes
  • MINOR version for new functionality (backward compatible)
  • PATCH version for bug fixes (backward compatible)

Pre-release versions:

  • X.Y.Z-alpha.N - Alpha releases (unstable)
  • X.Y.Z-beta.N - Beta releases (feature complete, testing)
  • X.Y.Z-rc.N - Release candidates (stable, final testing)

Upgrade Guide

From pre-release to 1.0

Version 1.0 is the initial public release. If you were using pre-release versions:

Configuration changes:

  • Configuration format changed from JSON to TOML
  • Key bindings moved from separate file to main config
  • Color format changed to hex strings

Migration:

# Generate new config from defaults
sesame --print-config > ~/.config/open-sesame/config.toml

# Edit with your custom key bindings
$EDITOR ~/.config/open-sesame/config.toml

Release Checklist

For maintainers preparing a release:

  • Update version in Cargo.toml
  • Update version in README.md
  • Update CHANGELOG.md with release date
  • Run full test suite: mise run test
  • Build packages: mise run build:deb
  • Test installation: sudo dpkg -i target/debian/*.deb
  • Create git tag: git tag -a v1.0.0 -m "Release 1.0.0"
  • Push tag: git push origin v1.0.0
  • GitHub Actions builds and publishes release
  • Verify GitHub release artifacts
  • Verify APT repository updates
  • Verify documentation deployment
  • Announce release

Contributing

See the Contributing Guide for information on how to contribute to Open Sesame.

See Also

License

Open Sesame is licensed under the GNU General Public License v3.0 (GPL-3.0).

Quick Summary

You are free to:

  • Use Open Sesame for any purpose (personal, commercial, etc.)
  • Study and modify the source code
  • Distribute copies of Open Sesame
  • Distribute modified versions

Under these conditions:

  • You must disclose the source code when distributing
  • Modified versions must also be licensed under GPL-3.0
  • You must include the original copyright and license notices
  • You must state significant changes made to the software

Full License Text

Open Sesame - Vimium-style window switcher for COSMIC desktop
Copyright (C) 2024 usrbinkat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

Full License Document

The complete GPL-3.0 license text is available in the repository:

Why GPL-3.0?

Open Sesame is licensed under GPL-3.0 to ensure:

  1. Freedom - Users can always access and modify the source code
  2. Transparency - No proprietary forks that hide improvements
  3. Community - Improvements benefit everyone
  4. Copyleft - Derived works must remain free software

Dependencies

Open Sesame uses several open-source libraries, each with their own licenses:

Runtime Dependencies

LibraryLicensePurpose
tiny-skiaBSD-3-Clause2D graphics rendering
fontdueMIT OR Apache-2.0Font rasterization
wayland-clientMITWayland protocol client
smithay-client-toolkitMITWayland toolkit
tomlMIT OR Apache-2.0TOML parsing
serdeMIT OR Apache-2.0Serialization
anyhowMIT OR Apache-2.0Error handling
tracingMITLogging

Build Dependencies

LibraryLicensePurpose
clapMIT OR Apache-2.0CLI parsing
cargo-debMITDebian packaging

License Compatibility: All dependencies use permissive licenses (MIT, Apache-2.0, BSD) that are compatible with GPL-3.0.

Contributing

By contributing to Open Sesame, you agree that your contributions will be licensed under the GPL-3.0 license.

See the Contributing Guide for details.

Commercial Use

Yes, you can use Open Sesame commercially.

The GPL-3.0 license permits commercial use without restriction. You can:

  • Use Open Sesame in a commercial environment
  • Bundle Open Sesame with commercial software
  • Modify Open Sesame for commercial purposes

However:

  • If you distribute Open Sesame (or a modified version), you must provide the source code
  • Recipients have the same rights you do (GPL-3.0 applies)
  • You cannot add additional restrictions

Trademark

“Open Sesame” is not a registered trademark. You are free to use the name when referring to this software.

However:

  • Do not imply official endorsement without permission
  • Modified versions should be clearly identified as such
  • Consider using a different name for substantial forks

Contact

For licensing questions or special licensing arrangements, please contact:

Disclaimer

THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Additional Resources

See Also