Skip to content

varbhat/cosmicmsg

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cosmicmsg

A CLI tool and Rust library for querying and controlling the COSMIC desktop compositor. Inspired by swaymsg, but built specifically for COSMIC.

Overview

COSMIC exposes compositor functionality as Wayland protocol extensions rather than a Unix socket IPC. cosmicmsg speaks those protocols directly to query and control workspaces, windows, and outputs from the terminal — and exposes the same functionality as a library for other Rust programs to consume.

Protocol Used for
ext-workspace-v1 + zcosmic-workspace-v2 Workspace listing and control
ext-foreign-toplevel-list-v1 + zcosmic-toplevel-info-v1 Window listing
zcosmic-toplevel-management-v1 Window actions (activate, close, maximize, …)
ext-image-copy-capture-v1 + ext-image-capture-source-v1 Screen/window/workspace capture
zcosmic-workspace-image-capture-source-v1 Workspace capture source
wl-output / xdg-output Output (monitor) information

Installation

Using Nix (recommended)

If you are a Nix user, you can run cosmicmsg directly from the remote flake like this:

nix run github:varbhat/cosmicmsg -- -h

You can also clone the source code. A flake.nix is provided. Enter the dev shell with:

nix develop
cargo build --release

Or run directly from the flake:

nix run .#cosmicmsg -- get-workspaces

Format all source files:

nix fmt

Manual

Dependencies: wayland, libxkbcommon, pkg-config, and a stable Rust toolchain.

cargo build --release
# binary at target/release/cosmicmsg

CLI usage

cosmicmsg [OPTIONS] <COMMAND>

Options:
  -j, --json     Output compact JSON
  -p, --pretty   Output pretty-printed JSON
  -h, --help
  -V, --version

Query commands

get-workspaces

List all workspaces across all outputs.

cosmicmsg get-workspaces
cosmicmsg --pretty get-workspaces

Example output (human):

1 * [tiling: enabled] on DP-1
2 on DP-1
3 [pinned] on DP-1

The * marks the active workspace.

get-toplevels

List all open windows.

cosmicmsg get-toplevels
cosmicmsg --json get-toplevels | jq '.[].app_id'

Example output (human):

"kitty" (kitty) [activated] on 1
"Firefox" (firefox) on 2
"zed" (dev.zed.Zed) [maximized] on 2

get-outputs

List all connected monitors.

cosmicmsg get-outputs
cosmicmsg --pretty get-outputs

Example output (human):

DP-1 "Dell U2722D" 2560x1440@59.951Hz at (0, 0)
HDMI-A-1 "LG TV" 1920x1080@60.000Hz at (2560, 0)

get-tree

Show a full tree of outputs → workspaces → windows.

cosmicmsg get-tree
cosmicmsg --pretty get-tree

Example output (human):

Output: DP-1 "Dell U2722D" 2560x1440 at (0, 0)
  Workspace: 1 * [tiling: enabled]
    - "kitty" (kitty)
    - "nvim" (kitty)
  Workspace: 2
    - "Firefox" (firefox)
    - "zed" (dev.zed.Zed)
  Workspace: 3 [pinned]

Workspace commands

Window and workspace selectors match by name first, then fall back to case-insensitive substring. An error is returned if the selector matches more than one item.

# Switch to a workspace
cosmicmsg workspace activate 2
cosmicmsg workspace activate work   # substring match

# Rename
cosmicmsg workspace rename 1 dev
cosmicmsg workspace rename dev work

# Query tiling state (omit name for active workspace)
cosmicmsg workspace get-tiling
cosmicmsg workspace get-tiling 2

# Toggle tiling — use "active" to target the current workspace
cosmicmsg workspace set-tiling active enabled
cosmicmsg workspace set-tiling 1 disabled

# Set the tiling default for all new (empty) workspaces
cosmicmsg workspace set-tiling-default enabled

# Pin / unpin (workspace persists across output changes)
cosmicmsg workspace pin 3
cosmicmsg workspace unpin 3

# Reorder workspaces
cosmicmsg workspace move-before 3 1   # move "3" before "1"
cosmicmsg workspace move-after 1 3    # move "1" after "3"

Window commands

Windows are matched by title, app-id, or identifier (exact match preferred, substring fallback).

# Focus a window
cosmicmsg window activate firefox
cosmicmsg window activate "kitty"

# Close
cosmicmsg window close zed

# Maximize / restore
cosmicmsg window maximize firefox
cosmicmsg window unmaximize firefox

# Minimize / restore
cosmicmsg window minimize firefox
cosmicmsg window unminimize firefox

# Fullscreen / restore
cosmicmsg window fullscreen firefox
cosmicmsg window unfullscreen firefox

# Sticky (show on all workspaces)
cosmicmsg window set-sticky kitty
cosmicmsg window unset-sticky kitty

# Move to a different workspace
cosmicmsg window move-to-workspace zed 2
cosmicmsg window move-to-workspace firefox work

subscribe — live event stream

Subscribe to compositor events and print them as they arrive. Runs indefinitely until interrupted with Ctrl-C.

cosmicmsg subscribe           # human-readable
cosmicmsg --json subscribe    # ndjson (one JSON object per line)
cosmicmsg --pretty subscribe  # pretty-printed JSON per event

Example output (human):

workspace-updated 1 * [tiling: enabled] on DP-1
window-opened     "Firefox" (firefox)
window-updated    "Firefox — GitHub" (firefox) [activated]
window-closed     "Firefox — GitHub" (firefox)
workspace-added   5 on DP-1
workspace-removed 5

Example output (--json), one object per line:

{"type":"window-opened","window":{"title":"kitty","app_id":"kitty","identifier":"...","state":[],"outputs":["DP-1"],"workspaces":["1"]}}
{"type":"window-updated","window":{"title":"nvim","app_id":"kitty","identifier":"...","state":["activated"],"outputs":["DP-1"],"workspaces":["1"]}}
{"type":"workspace-updated","workspace":{"name":"1","id":null,"coordinates":[0],"active":true,"urgent":false,"hidden":false,"pinned":false,"tiling":"enabled","output":"DP-1","group_id":"..."}}
{"type":"window-closed","title":"kitty","app_id":"kitty","identifier":"..."}

Event types

type field When it fires
workspace-added A new workspace appeared
workspace-updated A workspace changed (active flag, name, tiling, pinned, …)
workspace-removed A workspace was destroyed
window-opened A new window was created
window-updated A window changed (title, state flags, workspace, …)
window-closed A window was destroyed

Scripting with subscribe

# Print a desktop notification whenever a window opens
cosmicmsg --json subscribe | jq -r --unbuffered '
  select(.type == "window-opened") |
  "New window: \(.window.title) (\(.window.app_id))"
' | while read -r msg; do notify-send "cosmicmsg" "$msg"; done

# Watch for workspace switches
cosmicmsg --json subscribe | jq -r --unbuffered '
  select(.type == "workspace-updated" and .workspace.active == true) |
  "Switched to: \(.workspace.name)"
'

# Log all window titles that ever become active
cosmicmsg --json subscribe | jq -r --unbuffered '
  select(.type == "window-updated" and (.window.state[] == "activated")) |
  .window.title
'

JSON output

Every command supports --json (compact) and --pretty (indented) flags for scripting.

# Get the name of the active workspace
cosmicmsg --json get-workspaces | jq -r '.[] | select(.active) | .name'

# List all window titles on workspace "2"
cosmicmsg --json get-toplevels | jq -r '.[] | select(.workspaces[] == "2") | .title'

# Check if any window is fullscreen
cosmicmsg --json get-toplevels | jq 'any(.[]; .state[] == "fullscreen")'

# Get the current output resolution
cosmicmsg --json get-outputs | jq '.[] | select(.name == "DP-1") | "\(.width)x\(.height)"'

capture — screen/window/workspace capture

Capture any output, window, or workspace as a PNG. Output goes to a file or stdout (for piping into image viewers like kitty or chafa).

# Capture the first output to stdout and display in kitty
cosmicmsg capture output | kitty +kitten icat

# Capture output DP-1 to a file
cosmicmsg capture output --output DP-1 --file screenshot.png

# Capture a window by title (substring match)
cosmicmsg capture window firefox | kitty +kitten icat

# Downscale to 50% before encoding
cosmicmsg capture output --scale 0.5 | kitty +kitten icat
cosmicmsg capture output --scale 50% --file small.png

# Capture a workspace
cosmicmsg capture workspace 2 | chafa -

# Include the cursor in the capture
cosmicmsg capture output --cursor | kitty +kitten icat

Pixel format: PNG RGBA8888. Capture uses shared memory (wl_shm) so no GPU is required.

Library usage

cosmicmsg is also a Rust library. Add it to your Cargo.toml:

[dependencies]
cosmicmsg = "0.1"

Query state

use cosmicmsg::{collect_state, Error};

fn main() -> Result<(), Error> {
    let (state, _eq) = collect_state()?;

    for ws in state.workspaces() {
        println!("{}: active={}, tiling={:?}", ws.name, ws.active, ws.tiling);
    }

    for win in state.toplevels() {
        println!("{} ({})", win.title, win.app_id);
    }

    for out in state.outputs() {
        println!("{}: {}x{}", out.name.as_deref().unwrap_or("?"), out.width, out.height);
    }

    // Full output → workspace → window tree
    let tree = state.tree();

    Ok(())
}

Mutate

use cosmicmsg::{collect_state, workspace_activate, workspace_set_tiling, window_close};

let (mut state, mut eq) = collect_state()?;

// Switch to workspace "2"
workspace_activate(&state, &mut eq, "2")?;

// Enable tiling on the active workspace
workspace_set_tiling(&state, &mut eq, "active", true)?;

// Close a window
window_close(&mut state, &mut eq, "firefox")?;

Available mutation functions:

Function Description
workspace_activate Switch to a workspace
workspace_rename Rename a workspace
workspace_set_tiling Enable/disable tiling ("active" targets current)
workspace_set_tiling_default Set tiling default for all empty workspaces
workspace_pin / workspace_unpin Pin/unpin a workspace
window_activate Focus a window
window_close Close a window
window_maximize / window_unmaximize Maximize/restore
window_minimize / window_unminimize Minimize/restore
window_fullscreen / window_unfullscreen Fullscreen/restore
window_set_sticky / window_unset_sticky Sticky (all-workspace)
window_move_to_workspace Move window to a workspace
capture_output Capture an output as PNG
capture_output_raw Capture an output as raw RGBA pixels
capture_window Capture a window as PNG
capture_window_raw Capture a window as raw RGBA pixels
capture_workspace Capture a workspace as PNG
capture_workspace_raw Capture a workspace as raw RGBA pixels

Subscribe to live events

use cosmicmsg::{subscribe, MonitorEvent, Error};

subscribe(|event| {
    match event {
        MonitorEvent::WindowOpened { window } => {
            println!("opened: {} ({})", window.title, window.app_id);
        }
        MonitorEvent::WorkspaceUpdated { workspace } if workspace.active => {
            println!("active workspace: {}", workspace.name);
        }
        MonitorEvent::WindowClosed { title, .. } => {
            println!("closed: {}", title);
        }
        _ => {}
    }
    Ok(()) // return Err(...) to stop the loop
})?;

Screen capture

use cosmicmsg::{collect_state, capture_output, capture_output_raw, capture_window_raw, Error};

let (mut state, mut eq) = collect_state()?;
let output = state.first_output().ok_or_else(|| Error::NotFound("no outputs".into()))?;

// PNG directly to a writer
let mut buf = Vec::new();
capture_output(&mut state, &mut eq, output.clone(), &mut buf, false, Some(0.5))?;
std::fs::write("screenshot.png", &buf)?;

// Raw RGBA8888 pixels — process them yourself
let raw = capture_output_raw(&mut state, &mut eq, output, false)?;
println!("{}x{}, {} bytes", raw.width, raw.height, raw.data.len());

// Scale and encode manually
let scaled = raw.scale(0.25);
let mut buf = Vec::new();
scaled.encode_png(&mut buf)?;

// Raw window capture
let raw = capture_window_raw(&mut state, &mut eq, "firefox", false)?;
// raw.data is [R, G, B, A, R, G, B, A, ...], row-major, alpha always 255

Exit codes

Code Meaning
0 Success
1 Failed to connect to Wayland display
2 Required protocol not advertised by compositor
3 No workspace or window matched the selector
4 Selector matched multiple items (ambiguous)
5 Other error

Notes

  • Requires COSMIC compositor (cosmic-comp). The protocols used are COSMIC-specific and won't work on other compositors.
  • Mutations are best-effort. The compositor is free to ignore requests (e.g. rename if the compositor doesn't advertise rename capability). No error is returned in that case — this matches how the underlying Wayland protocols are specified.

License

MIT

About

CLI tool and Rust library for querying and controlling the COSMIC desktop

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages