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.

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
- Quick Start Guide - Get up and running in 30 seconds
- Installation Guide - Detailed installation instructions
- Configuration Guide - Customize key bindings and behavior
- CLI Reference - Complete command-line reference
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
- Learn about Basic Usage and keyboard shortcuts
- Configure Key Bindings for your favorite apps
- Read the full CLI Reference
- Troubleshoot any issues in Troubleshooting
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.
From APT Repository (Recommended)
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-bitaarch64- 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”:
-
Verify the repository was added correctly:
cat /etc/apt/sources.list.d/open-sesame.list -
Check that the GPG key exists:
ls -la /usr/share/keyrings/open-sesame.gpg -
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:
-
Ensure all system dependencies are installed:
sudo apt install build-essential pkg-config libfontconfig1-dev libxkbcommon-dev -
Check Rust toolchain version:
rustc --version # Should be 1.91 or newer -
Clean and retry:
mise run clean:all mise run build:deb
Next Steps
- Basic Usage - Learn how to use Open Sesame
- Configuration - Customize key bindings and settings
- CLI Reference - Explore all command-line options
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:
| Key | Action |
|---|---|
| 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 |
| Enter | Activate currently selected window |
| Escape | Cancel 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_thresholdsetting (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, thenf→ switches to Firefox if it’s open - Press
Alt+Space, thenf→ 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:
- Configured apps get their configured key (e.g., Firefox gets
f) - Multiple windows of the same app get repeated letters:
- First instance:
g - Second instance:
gg - Third instance:
ggg
- First instance:
- Unconfigured apps get sequential letters (a-z, excluding used keys)
Alternative Input
You can also type hints with numbers:
g1is equivalent togg2is equivalent toggg3is equivalent toggg
Using Arrow Navigation
If you prefer not to type letters, arrow keys work too:
- Press
Alt+Spaceto show the overlay - Use
↑and↓to navigate through windows - Press
Enterto activate the selected window - Or press
Escapeto 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
- Press
Alt+Space(hold until overlay appears) - Type the letter hint for your app (e.g.,
ffor Firefox) - Window activates immediately
Launch an App That’s Not Running
- Press
Alt+Space - Type the configured letter (e.g.,
gfor Ghostty) - If Ghostty isn’t running, it launches automatically
Browse All Windows
- Press
Alt+Space(hold until overlay appears) - Use arrow keys to scroll through windows
- Press
Enterto 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:
- Press
Alt+Spaceand immediately typef→ switches to Firefox - The overlay may not even appear if you’re fast enough
Consistent Key Bindings
Configure your most-used apps with memorable keys:
ffor Firefox (web browser)gfor Ghostty (terminal)vfor VS Code (editor)nfor 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 - Set up key bindings and customize behavior
- CLI Reference - Explore all command-line options
- Troubleshooting - Fix common issues
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
| Setting | Type | Default | Description |
|---|---|---|---|
activation_key | string | "alt+space" | Default key combo for --setup-keybinding |
activation_delay | integer | 200 | Delay (ms) before activating a match with multiple hints |
overlay_delay | integer | 720 | Delay (ms) before showing full overlay in switcher mode |
quick_switch_threshold | integer | 250 | Tap threshold (ms) for instant MRU switch |
border_width | float | 3.0 | Border width (pixels) for focus indicator |
border_color | color | #b4a0ffb4 | Border color (hex with alpha) |
background_color | color | #000000c8 | Overlay background color |
card_color | color | #1e1e1ef0 | Window card background color |
text_color | color | #ffffffff | Text color |
hint_color | color | #646464ff | Hint badge color |
hint_matched_color | color | #4caf50ff | Matched hint color |
env_files | array | [] | 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 keylaunch- 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:
- Exact match:
"firefox"matches app ID"firefox" - Case-insensitive:
"Firefox"matches app ID"firefox" - 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):
- Inherited process environment (WAYLAND_DISPLAY, XDG_*, PATH, etc.)
- Global
env_filesfrom[settings] - Per-app
env_filesfrom[keys.x.launch] - Explicit
envfrom[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 assignmentKEY="value"- Double-quoted (allows spaces)KEY='value'- Single-quoted (literal, no interpolation)export KEY=value- Export style (same asKEY=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:
-
Find your app ID:
sesame --list-windows -
Add configuration section:
[keys.x] apps = ["your-app-id"] launch = "your-app-command" -
Verify configuration:
sesame --validate-config -
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 - Explore all command-line options
- Troubleshooting - Fix configuration issues
- Developer Guide - Understand the configuration system internals
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+spacealt+tabsuper+spacectrl+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:
| Mode | Command | Behavior |
|---|---|---|
| Switcher | sesame | Quick tap = instant switch, hold = delayed overlay |
| Launcher | sesame --launcher | Always 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:
- Open COSMIC Settings → Keyboard → Custom Shortcuts
- Add:
- Alt+Tab →
sesame - Alt+Shift+Tab →
sesame --backward
- Alt+Tab →
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
| Code | Meaning |
|---|---|
0 | Success |
1 | Error (configuration, runtime, etc.) |
Environment Variables
RUST_LOG
Enable debug logging:
RUST_LOG=debug sesame --launcher
Log levels:
error- Errors onlywarn- Warnings and errorsinfo- Informational messagesdebug- Detailed debug outputtrace- 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:
- Keybinding setup -
--setup-keybindingconfigures COSMIC shortcuts - Wayland protocols - Uses COSMIC-specific Wayland extensions
- Theme integration - Respects COSMIC theme settings (future feature)
Manual COSMIC setup:
- Open COSMIC Settings
- Navigate to Keyboard → Custom Shortcuts
- 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
- Configuration Guide - Detailed configuration reference
- Troubleshooting - Common issues and solutions
- Basic Usage - How to use Open Sesame
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:
- Log out of COSMIC
- Log back in
- 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:
| App | Possible IDs |
|---|---|
| Firefox | firefox, org.mozilla.firefox, Firefox |
| VS Code | code, Code, visual-studio-code |
| Ghostty | ghostty, com.mitchellh.ghostty |
| Chrome | google-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:
- Open COSMIC Settings
- Navigate to Keyboard → View Shortcuts
- Search for the key combination
- Remove any conflicting shortcuts
Solution 4: Manual Setup
If --setup-keybinding doesn’t work, configure manually:
- Open COSMIC Settings
- Navigate to Keyboard → Custom Shortcuts
- 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:
- Press Alt+Space, type ‘f’ → launches Firefox
- Wait 2 seconds for Firefox to start
- 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:
-
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 -
Open an issue on GitHub: https://github.com/ScopeCreep-zip/open-sesame/issues
-
Include:
- System information
- Open Sesame version
- Configuration file (redact any secrets)
- Debug log
- Steps to reproduce
See Also
- Configuration Guide - Detailed configuration reference
- CLI Reference - Command-line options
- Basic Usage - How to use Open Sesame
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 structAppState- Current UI state (overlay shown, window selected, etc.)- Event handlers for keyboard input, IPC commands, timers
Flow:
- Initialize Wayland connection
- Enumerate windows
- Assign hints
- Enter event loop
- Handle input → update state → render
- Exit on selection or cancellation
config - Configuration System
Responsibility: Loading, parsing, validating TOML configuration
Key types:
Config- Main configuration structSettings- Global settings (delays, colors, etc.)KeyBinding- Per-key app associationsLaunchConfig- 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 windowHintAssignment- Complete hint assignment for all windowsHintMatcher- Input matching logicLaunchCommand- Command execution abstraction
Hint assignment algorithm:
- Windows with configured keys get priority
- Multiple instances get repeated letters (g, gg, ggg)
- 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 charactersInputHandler- Processes key eventsKeyEvent- Keyboard event representation
Features:
- Multi-key hint matching (g, gg, ggg)
- Alternative input (g1, g2, g3)
- Backspace handling
- Arrow key navigation
Flow:
- Receive key event from Wayland
- Update input buffer
- Match against hints
- Return action (activate, select, cancel, continue)
platform - Platform Abstraction
Responsibility: Wayland protocol integration, COSMIC-specific features
Key modules:
wayland- Window enumeration via Wayland protocolscosmic- COSMIC desktop integration (keybindings, protocols)activation- Window activation via COSMIC protocols
Wayland protocols used:
wlr-foreign-toplevel-management- Window enumerationcosmic-workspace- Workspace-aware window listingcosmic-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 coordinatorBuffer- Shared memory buffer for WaylandFontCache- 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 componentTheme- Color scheme and stylingLayout- 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 lockingipc- Inter-process communicationmru- Most Recently Used window trackingenv- Environment file parsinglog- 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-clientnon-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::Resultfor CLI and top-level errors- Custom
util::Errortype 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 testsconfig::Color::from_hex- Color parsing testsinput::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
- Plugin System - Allow custom hint assignment strategies
- Theme System - Load themes from COSMIC desktop settings
- Window Previews - Show window thumbnails in overlay
- Workspace Support - Filter windows by workspace
- Multi-Monitor - Show overlay on active monitor only
Technical Debt
- Reduce coupling between
appandrendermodules - Extract IPC into a reusable crate
- Improve test coverage for Wayland interactions
- Add property-based testing for hint assignment
See Also
- Building Guide - How to build Open Sesame
- Testing Guide - How to run tests
- Contributing Guide - How to contribute
- API Documentation - Rustdoc API reference
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 loadinglibxkbcommon-dev- Keyboard layout handlingliblzma-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 buildercross- 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)
crosstool (installed bymise 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 optimizationopt-level = 2- Good optimizationopt-level = 3- Maximum optimization (release)opt-level = "s"- Optimize for sizeopt-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 binarybuild:release- Build release binarybuild:deb- Build Debian packagebuild:cross-arm64- Cross-compile for ARM64
Development tasks:
dev- Run with debug loggingfmt- Format codelint- Run clippy lintertest- Run all tests
Installation tasks:
install- Install release binary system-wideinstall:debug- Install debug binary system-wideuninstall- Remove installed binary
Cleanup tasks:
clean- Remove target directoryclean: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 Type | Time | Binary Size |
|---|---|---|
| Debug (clean) | 45s | 8 MB |
| Debug (incremental) | 3s | 8 MB |
| Release (clean) | 90s | 4 MB |
| Release (incremental) | 15s | 4 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 Guide - Run tests and benchmarks
- Contributing Guide - Contribute to Open Sesame
- Architecture - Understand the codebase structure
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:
| Module | Coverage |
|---|---|
config | 92% |
core | 88% |
util | 95% |
input | 85% |
platform | 45% (hard to test Wayland) |
render | 40% (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:
| Operation | Target |
|---|---|
| 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 Guide - Contribute code and tests
- Architecture - Understand the codebase
- Building Guide - Build from source
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 featurefix- Bug fixdocs- Documentation changesstyle- Code style changes (formatting, etc.)refactor- Code refactoringtest- Adding or updating testschore- 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
- Go to https://github.com/ScopeCreep-zip/open-sesame
- Click “New Pull Request”
- Select your fork and branch
- 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_casefor functions and variables - Use
CamelCasefor types - Use
SCREAMING_SNAKE_CASEfor 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
Resultfor fallible operations - Use
anyhow::Resultfor 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
- Open an issue first to discuss the feature
- Implement the feature in a new module if appropriate
- Add comprehensive tests
- Update documentation
- Add to CHANGELOG.md
Example: Adding window thumbnails
- Create issue: “Feature: Window preview thumbnails”
- Implement in
src/preview.rs - Add tests in
src/preview.rsandtests/preview.rs - Update user guide:
docs/src/user-guide/basic-usage.md - Add to CHANGELOG.md
Fixing a Bug
- Create a test that reproduces the bug
- Fix the bug
- Verify the test passes
- 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
- Ensure all tests pass before refactoring
- Refactor incrementally
- Ensure all tests still pass after refactoring
- 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
- Automated checks - CI runs tests, formatting, and linting
- Code review - Maintainer reviews code for quality and correctness
- Feedback - You may be asked to make changes
- 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
- Architecture - Understand the codebase structure
- Building Guide - Build from source
- Testing Guide - Run and write tests
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 coordinatorAppState- 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 structSettings- Global settings (delays, colors, etc.)KeyBinding- Per-key app associationsLaunchConfig- Launch command configurationColor- RGBA color with hex serializationConfigValidator- 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 identifierAppId- Application identifierWindowHint- Assigned hint for a windowHintAssignment- Complete hint assignmentHintMatcher- Input matching logicLaunchCommand- 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 bufferInputHandler- Key event processorMatchResult- 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 protocolsactivate_window()- Activate and focus a windowsetup_keybinding()- Configure COSMIC keybindingremove_keybinding()- Remove COSMIC keybindingkeybinding_status()- Check keybinding configuration
Wayland protocols:
wlr-foreign-toplevel-management- Window enumerationcosmic-workspace- Workspace informationcosmic-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 coordinatorBuffer- Wayland shared memory bufferFontCache- Cached font glyphs
Rendering stack:
fontdue- Font rasterizationtiny-skia- 2D graphics primitiveswayland-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 componentTheme- Color scheme and stylingLayout- 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 lockingIpcServer/IpcClient- Inter-process communicationMruState- Most Recently Used window trackingError/Result- Error types
Key functions:
load_mru_state()- Load MRU state from cachesave_activated_window()- Save MRU stateload_env_files()- Parse environment fileslog::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 errorsError::Platform- Wayland/platform errorsError::InvalidColor- Color parsing errorsError::Io- I/O errorsError::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 featuresdebug-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 detailshint_assignment- Demonstrate hint assignment algorithmconfig_loading- Load and display configuration
Documentation Standards
All public APIs follow these documentation standards:
- Module docs (
//!) - Overview and examples - Type docs (
///) - Purpose and usage - Function docs (
///) - Parameters, returns, examples, errors - Examples - Working code examples in docs
- 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
- Architecture Guide - Understand the design
- Building Guide - Build from source
- Testing Guide - Run tests
- Contributing Guide - Contribute code
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.mdwith 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
- GitHub Releases - Download releases
- GitHub Milestones - Upcoming releases
- GitHub Issues - Bug reports and features
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:
- File:
LICENSE - Online: https://www.gnu.org/licenses/gpl-3.0.en.html
Why GPL-3.0?
Open Sesame is licensed under GPL-3.0 to ensure:
- Freedom - Users can always access and modify the source code
- Transparency - No proprietary forks that hide improvements
- Community - Improvements benefit everyone
- Copyleft - Derived works must remain free software
Dependencies
Open Sesame uses several open-source libraries, each with their own licenses:
Runtime Dependencies
| Library | License | Purpose |
|---|---|---|
| tiny-skia | BSD-3-Clause | 2D graphics rendering |
| fontdue | MIT OR Apache-2.0 | Font rasterization |
| wayland-client | MIT | Wayland protocol client |
| smithay-client-toolkit | MIT | Wayland toolkit |
| toml | MIT OR Apache-2.0 | TOML parsing |
| serde | MIT OR Apache-2.0 | Serialization |
| anyhow | MIT OR Apache-2.0 | Error handling |
| tracing | MIT | Logging |
Build Dependencies
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:
- GitHub Issues: https://github.com/ScopeCreep-zip/open-sesame/issues
- Email: (See GitHub profile)
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
- Changelog - Version history and release notes
- Contributing Guide - How to contribute