A clipboard manager written in Rust Programming Language.
Installation | Usage | Integration | Contributing
Table of contents- Features
- Installation
- Architecture
- Usage
- Configuration
- Integration
- Programs in this Repository
- Contributing
- License
Features
- Copy/Paste plaintext
- Copy/Paste images
- Persistent clipboard contents
- Support for snippets
- Support for
X11 - Support for
Wayland(experimental) - Support for
macOS - Support for
gRPC- gRPC over
HTTP - gRPC over
Unix domain socket
- gRPC over
- Support for
D-Bus
Screenshots and Demonstration
-
Demonstration with Rofi
clipcat.webm -
Use Rofi to select clip
-
Use dmenu to select clip
-
Use skim to select clip
Installation
Clipcat can be installed using various package managers on Linux.
Pre-built binaries can also be downloaded from the GitHub releases page.
Detailed instructions for installing Clipcat can be found here.
Architecture
Clipcat uses a Client-Server architecture. There are two role types in this architecture: Server and Client.
Clipcat Server
The clipcat daemon runs as a background process and performs the following tasks:
- Watches for changes to the clipboard.
- Caches clipboard content.
- Inserts content into the clipboard.
- Acts as a gRPC server, waiting for remote procedure calls from clients.
Currently, clipcat supports the following windowing systems:
X11, the followingcrates are leveraged:Wayland(experimentally), the followingcrates are leveraged:
Clipcat Client
A clipcat client sends requests to the server for the following operations:
List: list the cached clips from server.Insert: replace the current content ofclipboardwith a clip.Remove: remove the cached clips from server.
Understanding X11/Wayland Clipboard Selections
On X11 and Wayland, there are multiple clipboard-like selections:
| Selection | Description | How to Copy |
|---|---|---|
Clipboard |
Standard clipboard (Ctrl+C / Ctrl+V) | Copy with Ctrl+C |
Primary |
Mouse selection - text highlighted with mouse (paste with middle mouse button) | Select text with mouse |
Secondary |
Rarely used, historical X11 feature | Rarely used in modern applications |
Clipcat can watch any combination of these selections using enable_clipboard and enable_primary in the [watcher] section of your configuration.
Tip
If you want to track only standard Ctrl+C copying, set enable_primary = false and synchronize_selection_with_clipboard = false.
List of Implementations
| Program | Role Type | Comment |
|---|---|---|
clipcatd |
Server |
The clipcat server (daemon). |
clipcatctl |
Client |
The clipcat client providing a command line interface. |
clipcat-menu |
Client |
The clipcat client that calls a built-in finder or an external finder for selecting clips. |
Usage
- Setup configurations for
clipcat. Read configuration section for more details.
Important
Each program has its own separate configuration file. Use the correct default-config command for each program:
clipcatd default-config→clipcatd.toml(server config)clipcatctl default-config→clipcatctl.toml(client config)clipcat-menu default-config→clipcat-menu.toml(client config)
Using the wrong config file (e.g., using clipcatctl config for clipcatd) will result in parse errors.
# Create the config directory first mkdir -p "$XDG_CONFIG_HOME/clipcat" # Generate default configs for each program clipcatd default-config > "$XDG_CONFIG_HOME/clipcat/clipcatd.toml" clipcatctl default-config > "$XDG_CONFIG_HOME/clipcat/clipcatctl.toml" clipcat-menu default-config > "$XDG_CONFIG_HOME/clipcat/clipcat-menu.toml"
- Start
clipcatdfor watching clipboard events.
# Show the usage. Please read the usage before doing any other operations. clipcatd help # Start and daemonize clipcatd. It will run in the background. # You can use `pkill clipcatd` to stop it; a `SIGTERM` signal will be sent to clipcatd. clipcatd # Alternatively, you can start clipcatd but keep it in the foreground. # You can press `Ctrl+C` in your terminal to stop it; a `SIGINT` signal will be sent to clipcatd. clipcatd --no-daemon
-
Copy arbitrary text or images from other processes using your mouse or keyboard.
-
You can run the following commands with
clipcatctlorclipcat-menu:
| Command | Comment |
|---|---|
clipcatctl list |
List cached clipboard history |
clipcatctl tail [-f] |
Print the most recent entries (default 10); -f streams new clips |
clipcatctl promote <id> |
Insert cached clip with <id> into the X11 clipboard |
clipcatctl remove [ids] |
Remove cached clips with [ids] from the server |
clipcatctl clear |
Clear cached clipboard history |
| Command | Comment |
|---|---|
clipcat-menu insert |
Insert a cached clip into the X11 clipboard |
clipcat-menu remove |
Remove cached clips from the server |
clipcat-menu edit |
Edit a cached clip with $EDITOR |
The following finders are supported by clipcat-menu:
Configuration
| Program | Default Configuration File Path |
|---|---|
clipcatd |
$XDG_CONFIG_HOME/clipcat/clipcatd.toml |
clipcatctl |
$XDG_CONFIG_HOME/clipcat/clipcatctl.toml |
clipcat-menu |
$XDG_CONFIG_HOME/clipcat/clipcat-menu.toml |
# Run as a traditional UNIX daemon. daemonize = true # Maximum number of clips in history. max_history = 50 # Synchronize the primary selection with clipboard selection. # When enabled, copying to clipboard will also update the primary selection, # and vice versa. This is useful for X11 environments where both selections # are commonly used together. synchronize_selection_with_clipboard = true # Clears the history on startup when set to true clear_history_on_start = false # File path for clip history. # If this value is omitted, `clipcatd` will persist history in `$XDG_CACHE_HOME/clipcat/clipcatd-history`. history_file_path = "/home/<username>/.cache/clipcat/clipcatd-history" # File path for the PID file. # If this value is omitted, `clipcatd` will place the PID file in `$XDG_RUNTIME_DIR/clipcatd.pid`. pid_file = "/run/user/<user-id>/clipcatd.pid" # Controls how often the program updates its stored value of the Linux primary selection. # In the Linux environment, the primary selection automatically updates to reflect the currently highlighted text or object, # typically updating with every mouse movement. primary_threshold_ms = 5000 [log] # Emit log messages to a log file. # If this value is omitted, `clipcatd` will disable logging to a file. file_path = "/path/to/log/file" # Emit log messages to systemd-journald. emit_journald = true # Emit log messages to stdout. emit_stdout = false # Emit log messages to stderr. emit_stderr = false # Log level. level = "INFO" [watcher] # Enable watching the X11/Wayland clipboard selection. # This is the standard clipboard - content copied with Ctrl+C / Ctrl+V (or Cmd+C / Cmd+V on macOS). enable_clipboard = true # Enable watching the X11/Wayland primary selection. # This is the "primary" selection - text highlighted with mouse selection. # In X11, there are three selections: clipboard, primary, and secondary. # - Primary: text highlighted with mouse (paste with middle mouse button) # - Secondary: less commonly used, rarely used in practice # - Clipboard: standard clipboard (paste with Ctrl+V) enable_primary = true # Ignore clips that match any of the MIME types. sensitive_mime_types = ["x-kde-passwordManagerHint"] # Ignore text clips that match any of the provided regular expressions. # The regular expression engine is powered by https://github.com/rust-lang/regex. denied_text_regex_patterns = [] # Ignore text clips with a length less than or equal to `filter_text_min_length`, in characters (Unicode scalar value), not bytes. filter_text_min_length = 1 # Ignore text clips with a length greater than `filter_text_max_length`, in characters (Unicode scalar value), not bytes. filter_text_max_length = 20000000 # Enable or disable capturing images. capture_image = true # Ignore image clips with a size greater than `filter_image_max_size`, in bytes. filter_image_max_size = 5242880 [grpc] # Enable gRPC over HTTP. enable_http = true # Enable gRPC over Unix domain socket. enable_local_socket = true # Host address for gRPC. host = "127.0.0.1" # Port number for gRPC. port = 45045 # Path for the Unix domain socket. # If this value is omitted, `clipcatd` will place the socket in `$XDG_RUNTIME_DIR/clipcat/grpc.sock`. local_socket = "/run/user/<user-id>/clipcat/grpc.sock" [dbus] # Enable D-Bus. enable = true # Specify the identifier for the current `clipcat` instance. # The D-Bus service name will appear as "org.clipcat.clipcat.instance-0". # If the identifier is not provided, the D-Bus service name will appear as "org.clipcat.clipcat". identifier = "instance-0" [desktop_notification] # Enable desktop notifications. enable = true # Path for an icon; the given icon will be displayed in the desktop notification, # if your desktop notification server supports showing an icon. # If this value is not provided, the default value `accessories-clipboard` will be used. icon = "/path/to/the/icon" # Timeout duration in milliseconds. # This sets the time from when the notification is displayed until it is closed by the notification server. timeout_ms = 2000 # Define the length of long plaintext. # If the length of plaintext is greater than or equal to `long_plaintext_length`, # a desktop notification will be emitted. # If this value is 0, no desktop notification will be emitted for long plaintext. long_plaintext_length = 2000 # Snippets, only UTF-8 text is supported. [[snippets]] [snippets.Directory] # Name of snippet. name = "my-snippets" # File path to the directory containing snippets. path = "/home/user/snippets" [[snippets]] [snippets.File] # Name of snippet. name = "os-release" # File path to the snippet. path = "/etc/os-release" [[snippets]] [snippets.Text] # Name of snippet. name = "cxx-io-speed-up" # Content of the snippet. content = ''' int io_speed_up = [] { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); return 0; }(); ''' [[snippets]] [snippets.Text] name = "rust-sieve-primes" content = ''' fn sieve_primes(n: usize) -> Vec<usize> { if n < 2 { return Vec::new(); } let root_n = f64::from(n as i32).sqrt().floor() as usize; let mut is_prime = vec![true; n + 1]; for i in 2..=root_n { if !is_prime[i] { continue; } for j in ((i << 1)..=n).step_by(i) { is_prime[j] = false; } } is_prime .into_iter() .enumerate() .skip(2) .filter_map(|(i, x)| if x { Some(i) } else { None }) .collect() } '''
Configuration for clipcatctl[!WARNING] Setting
max_history = 0will disable history storage. No clips will be stored or retrievable, andclipcat-menu/clipcatctlwill display an empty list.
# Server endpoint. # `clipcatctl` connects to the server via a Unix domain socket if `server_endpoint` is a file path, such as: # "/run/user/<user-id>/clipcat/grpc.sock". # It connects via HTTP if `server_endpoint` is a URL, like: "http://127.0.0.1:45045". server_endpoint = "/run/user/<user-id>/clipcat/grpc.sock" # Maximum gRPC message size in bytes that can be received from the server. # The default value is 8MB (8 * 1024 * 1024 bytes). # Increase this value if you encounter "message length too large" errors with large clipboard entries. grpc_max_message_size = 8388608 [log] # Emit log messages to a log file. # Delete this line to disable logging to a file. file_path = "/path/to/log/file" # Emit log messages to systemd-journald. emit_journald = true # Emit log messages to stdout. emit_stdout = false # Emit log messages to stderr. emit_stderr = false # Log level. level = "INFO"
# Server endpoint # The `clipcat-menu` connects to the server via a Unix domain socket if `server_endpoint` is a file path, such as: # "/run/user/<user-id>/clipcat/grpc.sock". # It connects via HTTP if `server_endpoint` is a URL, like: "http://127.0.0.1:45045". server_endpoint = "/run/user/<user-id>/clipcat/grpc.sock" # The default finder to invoke when no "--finder=<finder>" option is provided. finder = "rofi" # Maximum gRPC message size in bytes that can be received from the server. # The default value is 8MB (8 * 1024 * 1024 bytes). # Increase this value if you encounter "message length too large" errors with large clipboard entries. grpc_max_message_size = 8388608 [log] # Emit log messages to a log file. # Delete this line to disable logging to a file. file_path = "/path/to/log/file" # Emit log messages to systemd-journald. emit_journald = true # Emit log messages to stdout. emit_stdout = false # Emit log messages to stderr. emit_stderr = false # Log level. level = "INFO" # Options for "rofi". [rofi] # Length of line. line_length = 100 # Length of menu. menu_length = 30 # Prompt for the menu. menu_prompt = "Clipcat" # Extra arguments to pass to `rofi`. # Rofi supports deleting entries using kb-custom-1 (default: Alt+1). # To rebind to Shift+Delete: # extra_arguments = ["-kb-custom-1", "shift+Delete", "-kb-delete-entry", ""] extra_arguments = ["-mesg", "Please select a clip"] # Options for "dmenu". [dmenu] # Length of line. line_length = 100 # Length of menu. menu_length = 30 # Prompt for the menu. menu_prompt = "Clipcat" # Extra arguments to pass to `dmenu`. extra_arguments = [ "-fn", "SauceCodePro Nerd Font Mono-12", "-nb", "#282828", "-nf", "#ebdbb2", "-sb", "#d3869b", "-sf", "#282828", ] # Customize your finder. [custom_finder] # External program name. program = "fzf" # Arguments for calling the external program. args = []
Integration
Integrating with ZshFor zsh users, it is useful to integrate clipcat with zsh.
Add the following commands to your zsh configuration file (~/.zshrc):
if type clipcat-menu >/dev/null 2>&1; then alias clipedit=' clipcat-menu --finder=builtin edit' alias clipdel=' clipcat-menu --finder=builtin remove' bindkey -s '^\' "^Q clipcat-menu --finder=builtin insert ^J" bindkey -s '^]' "^Q clipcat-menu --finder=builtin remove ^J" fi
For i3 window manager users, it is useful to integrate clipcat with i3.
Add the following options to your i3 configuration file ($XDG_CONFIG_HOME/i3/config):
exec_always --no-startup-id clipcatd # start clipcatd at startup
set $launcher-clipboard-insert clipcat-menu insert
set $launcher-clipboard-remove clipcat-menu remove
bindsym $mod+p exec $launcher-clipboard-insert
bindsym $mod+o exec $launcher-clipboard-remove
Integrating with LeftWM[!Note] You can use
rofi,dmenuorfuzzelas the default finder.
For leftwm users, it is useful to integrate clipcat with leftwm.
Add the following keybindings to your leftwm configuration file ($XDG_CONFIG_HOME/leftwm/config.ron):
(
/* other configurations */
keybind: [
/* select clip from clipboard */
(command: Execute, value: "clipcat-menu insert", modifier: ["modkey"], key: "p"),
(command: Execute, value: "clipcat-menu remove", modifier: ["modkey"], key: "o"),
/* other configurations */
],
/* other configurations */
)[!Note] You can use
rofi,dmenuorfuzzelas the default finder.
Add the following commands to your $XDG_CONFIG_HOME/leftwm/themes/current/up:
# other configurations # Start clipcatd clipcatd # other configurations
Add the following commands to your $XDG_CONFIG_HOME/leftwm/themes/current/down:
# other configurations # Terminate clipcatd pkill clipcatd # other configurations
clipcatctl tail
clipcatctl tail -f streams each new clip's id and preview as it is
added, in the same format as clipcatctl list. This lets you write
small shell hooks that mutate clips out-of-band.
Run one hook script for every transform you want, even unrelated
ones. Independent tail -f listeners each see the other listeners'
outputs as new events, so two separate hooks can cascade-trigger each
other indefinitely. A single script applies all transforms in one pass
and records the result in a shared state file:
#!/usr/bin/env bash # `-n 0` → react only to clips added after the hook starts. SEEN=~/.cache/clipcat-hooks/produced-ids mkdir -p "$(dirname "$SEEN")" && touch "$SEEN" clipcatctl tail -f -n 0 | while IFS= read -r line; do id=${line%%:*} grep -qxF "$id" "$SEEN" && continue raw=$(clipcatctl get "$id") || continue # Apply your transforms here (compose as many as you need). out=${raw#"${raw%%[![:space:]]*}"} # trim leading whitespace out=${out%"${out##*[![:space:]]}"} # trim trailing whitespace # out="[$(date -I)] $out" # uncomment to date-stamp [ "$raw" = "$out" ] && continue new_id=$(clipcatctl update "$id" "$out") || continue printf '%s\n' "$new_id" >>"$SEEN" # Cap the state file so it does not grow without bound. if [ "$(wc -l <"$SEEN")" -gt 1024 ]; then tail -n 512 "$SEEN" >"$SEEN.tmp" && mv "$SEEN.tmp" "$SEEN" fi done
clipcatctl update prints the new clip's id, which the hook records in
$SEEN so it's skipped when tail -f re-emits it. clipcatctl tail -f
also reconnects automatically on clipcatd restarts and resumes
streaming new clips, so the script can run as a long-lived service.
Put the following snippet in $XDG_CONFIG_HOME/systemd/user/clipcat.service:
[Unit]
Description=Clipcat Daemon
PartOf=graphical-session.target
[Install]
WantedBy=graphical-session.target
[Service]
# NOTE: We assume that your `clipcatd` is located at `/usr/bin/clipcatd`.
ExecStartPre=/bin/rm -f %t/clipcat/grpc.sock
ExecStart=/usr/bin/clipcatd --no-daemon --replace
Restart=on-failure
Type=simple
Enable and start clipcat with the following commands:
systemctl --user daemon-reload
systemctl --user enable clipcat.service
systemctl --user start clipcat.service
systemctl --user status clipcat.servicePrograms in this Repository
-
clipcatd: Theclipcatserver (daemon). -
clipcatctl: Theclipcatclient that provides a command line interface. -
clipcat-menu: Theclipcatclient that utilizes a built-in or external finder to select clips. -
clipcat-notify: A tool for monitoring clipboard events. It watches the clipboard and exits when a change is detected, returning an exit code of 0 for success and 1 for errors.
Note
clipcat-notify is a standalone tool that does NOT read the clipcatd.toml configuration file. By default, it monitors all clipboard selections (clipboard, primary, and secondary). Use command-line flags to filter which selections to watch:
--no-clipboard: Don't watch the clipboard selection--no-primary: Don't watch the primary selection--no-secondary: Don't watch the secondary selection
Example: clipcat-notify --no-primary to only watch the clipboard selection.
Contributing
Contributions are welcome! Before you start, please read:
- Contributing Guide — Development workflow, git conventions, commit message format, and PR process
- Coding Conventions — Rust coding standards covering imports, attributes, error handling, async patterns, and testing
Quick start:
# Enter development environment nix develop # Or without Nix cargo build cargo nextest run cargo fmt cargo clippy
License
Clipcat is licensed under the GNU General Public License version 3. See LICENSE for more information.


