diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1c2e7f4..bcb02b3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,5 +1,9 @@
name: CI
+# Runners: uvacompute (https://uvacompute.com)
+# To enable, set the UVA_RUNNER repo variable to the correct runner label.
+# runs-on: ${{ vars.UVA_RUNNER || 'ubuntu-latest' }}
+
on:
pull_request:
branches: [main]
@@ -22,10 +26,7 @@ on:
publish_crates:
description: Publish to crates.io
type: boolean
- default: true
-
-env:
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
+ default: false
permissions:
contents: write
@@ -33,7 +34,7 @@ permissions:
jobs:
changes:
name: Changes
- runs-on: [self-hosted, netty]
+ runs-on: ubuntu-latest
outputs:
rust: ${{ steps.check.outputs.rust }}
version: ${{ steps.version.outputs.version }}
@@ -101,7 +102,7 @@ jobs:
name: Validate
needs: changes
if: needs.changes.outputs.rust == 'true'
- runs-on: [self-hosted, netty]
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -125,6 +126,9 @@ jobs:
- name: Install site dependencies
run: pnpm --dir site install --frozen-lockfile
+ - name: Install system dependencies
+ run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev
+
- name: Format check
run: make fmt-check
@@ -141,7 +145,7 @@ jobs:
name: Integration (Xvfb)
needs: changes
if: needs.changes.outputs.rust == 'true'
- runs-on: [self-hosted, netty]
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -149,6 +153,9 @@ jobs:
- uses: Swatinem/rust-cache@v2
+ - name: Install system dependencies
+ run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev xvfb
+
- name: Xvfb integration tests
run: make test-integration
@@ -156,7 +163,7 @@ jobs:
name: Distribution Validate
needs: changes
if: needs.changes.outputs.rust == 'true'
- runs-on: [self-hosted, netty]
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -168,15 +175,49 @@ jobs:
with:
node-version: 22
+ - uses: cachix/install-nix-action@v30
+ with:
+ extra_nix_config: |
+ experimental-features = nix-command flakes
+
+ - name: Install system dependencies
+ run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev
+
- name: Distribution validation
run: make dist-validate
- # --- Release pipeline: update-manifests -> build -> release -> publish ---
- # These stay on ubuntu-latest for artifact upload/download and registry publishing.
+ build:
+ name: Build Release Asset
+ needs: [changes, validate, integration, distribution]
+ if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ components: clippy
+
+ - uses: Swatinem/rust-cache@v2
+
+ - name: Install system dependencies
+ run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev
+
+ - name: Clippy
+ run: cargo clippy -- -D warnings
+
+ - name: Build
+ run: cargo build --release --locked
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: deskctl-linux-x86_64
+ path: target/release/deskctl
+ retention-days: 7
update-manifests:
name: Update Manifests
- needs: [changes, validate, integration, distribution]
+ needs: [changes, build]
if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true'
runs-on: ubuntu-latest
steps:
@@ -217,47 +258,6 @@ jobs:
git tag "${{ needs.changes.outputs.tag }}"
git push origin main --tags
- build:
- name: Build Release Asset
- needs: [changes, update-manifests]
- if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true'
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- ref: ${{ needs.changes.outputs.tag }}
-
- - uses: dtolnay/rust-toolchain@stable
- with:
- components: clippy
-
- - uses: Swatinem/rust-cache@v2
-
- - name: Install system dependencies
- run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev
-
- - name: Verify version
- run: |
- CARGO_VER=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
- EXPECTED="${{ needs.changes.outputs.version }}"
- if [ "$CARGO_VER" != "$EXPECTED" ]; then
- echo "Version mismatch: Cargo.toml=$CARGO_VER expected=$EXPECTED"
- exit 1
- fi
- echo "Building version $CARGO_VER"
-
- - name: Clippy
- run: cargo clippy -- -D warnings
-
- - name: Build
- run: cargo build --release --locked
-
- - uses: actions/upload-artifact@v4
- with:
- name: deskctl-linux-x86_64
- path: target/release/deskctl
- retention-days: 7
-
release:
name: Release
needs: [changes, build, update-manifests]
@@ -291,51 +291,10 @@ jobs:
artifacts/checksums.txt
fi
- publish-npm:
- name: Publish npm
+ publish:
+ name: Publish
needs: [changes, update-manifests, release]
- if: >-
- github.event_name != 'pull_request'
- && needs.changes.outputs.rust == 'true'
- && (inputs.publish_npm == true || inputs.publish_npm == '')
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- ref: ${{ needs.changes.outputs.tag }}
-
- - uses: actions/setup-node@v4
- with:
- node-version: 22
- registry-url: https://registry.npmjs.org
-
- - name: Check if already published
- id: published
- run: |
- VERSION="${{ needs.changes.outputs.version }}"
- if npm view "deskctl@${VERSION}" version >/dev/null 2>&1; then
- echo "npm=true" >> "$GITHUB_OUTPUT"
- else
- echo "npm=false" >> "$GITHUB_OUTPUT"
- fi
-
- - name: Validate npm package
- if: steps.published.outputs.npm != 'true'
- run: node npm/deskctl/scripts/validate-package.js
-
- - name: Publish npm
- if: steps.published.outputs.npm != 'true'
- env:
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npm publish ./npm/deskctl --access public
-
- publish-crates:
- name: Publish crates.io
- needs: [changes, update-manifests, release]
- if: >-
- github.event_name != 'pull_request'
- && needs.changes.outputs.rust == 'true'
- && (inputs.publish_crates == true || inputs.publish_crates == '')
+ if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -344,21 +303,40 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ registry-url: https://registry.npmjs.org
+
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev
- - name: Check if already published
+ - name: Check current published state
id: published
run: |
VERSION="${{ needs.changes.outputs.version }}"
+ if npm view "deskctl@${VERSION}" version >/dev/null 2>&1; then
+ echo "npm=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "npm=false" >> "$GITHUB_OUTPUT"
+ fi
if curl -fsSL "https://crates.io/api/v1/crates/deskctl/${VERSION}" >/dev/null 2>&1; then
echo "crates=true" >> "$GITHUB_OUTPUT"
else
echo "crates=false" >> "$GITHUB_OUTPUT"
fi
+ - name: Validate npm package
+ run: node npm/deskctl/scripts/validate-package.js
+
+ - name: Publish npm
+ if: steps.published.outputs.npm != 'true'
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ run: npm publish ./npm/deskctl --access public
+
- name: Publish crates.io
- if: steps.published.outputs.crates != 'true'
+ if: inputs.publish_crates && steps.published.outputs.crates != 'true'
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --locked
diff --git a/Cargo.lock b/Cargo.lock
index eb0e2ce..9680966 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -241,9 +241,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
-version = "1.2.58"
+version = "1.2.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
+checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -400,7 +400,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "deskctl"
-version = "0.1.14"
+version = "0.1.10"
dependencies = [
"ab_glyph",
"anyhow",
@@ -911,9 +911,9 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.92"
+version = "0.3.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995"
+checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -1039,9 +1039,9 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.2.0"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"wasi",
@@ -1699,9 +1699,9 @@ dependencies = [
[[package]]
name = "simd-adler32"
-version = "0.3.9"
+version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]]
name = "simd_helpers"
@@ -1861,9 +1861,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.23.0"
+version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
+checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
dependencies = [
"getrandom 0.4.2",
"js-sys",
@@ -1907,9 +1907,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.115"
+version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a"
+checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
dependencies = [
"cfg-if",
"once_cell",
@@ -1920,9 +1920,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.115"
+version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67"
+checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1930,9 +1930,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.115"
+version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf"
+checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1943,9 +1943,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.115"
+version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93"
+checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
dependencies = [
"unicode-ident",
]
@@ -2297,9 +2297,9 @@ dependencies = [
[[package]]
name = "zune-jpeg"
-version = "0.5.15"
+version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
+checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6"
dependencies = [
"zune-core",
]
diff --git a/Cargo.toml b/Cargo.toml
index be051c7..cc6d11a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "deskctl"
-version = "0.1.14"
+version = "0.1.10"
edition = "2021"
description = "X11 desktop control CLI for agents"
license = "MIT"
diff --git a/README.md b/README.md
index dccbe04..935f329 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,9 @@
# deskctl
+
[](https://www.npmjs.com/package/deskctl)
[](skills/deskctl)
-Desktop control cli for AI agents on X11.
-
-https://github.com/user-attachments/assets/e820787e-4d1a-463f-bdcf-a829588778bf
-
+Desktop control cli for AI agents on Linux X11.
## Install
diff --git a/demo/index.html b/demo/index.html
deleted file mode 100644
index 70ac230..0000000
--- a/demo/index.html
+++ /dev/null
@@ -1,969 +0,0 @@
-
-
-
-
-
-deskctl - Desktop Control for AI Agents
-
-
-
-
-
-
deskctl
-
desktop control CLI for AI agents
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 📝
- task_brief.txt
- 2.1 KB
-
-
- 📊
- nvda_q1_data.csv
- 48 KB
-
-
- 📄
- prev_report.pdf
- 1.2 MB
-
-
- 📁
- archive/
- --
-
-
-
- task: Prepare NVDA Q1 earnings summary
- source: finance.yahoo.com, local csv
- output: Google Docs report with chart
-
-
-
-
-
-
-
-
-
Chrome - Yahoo Finance
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $950
- $800
- $650
-
-
-
-
-
-
-
-
-
-
-
Chrome - Google Docs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- NVDA 1Y
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Files
-
Yahoo Finance
-
Google Docs
-
-
-
-
-
-
-
-
-
AI agent controlling a live desktop via deskctl
-
↺ Replay
-
-
-
-
-
diff --git a/docs/releasing.md b/docs/releasing.md
index 849d661..8f39d3f 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -59,12 +59,12 @@ The repository release workflow:
- publishes the canonical GitHub Release asset
- uploads `checksums.txt`
-The registry publish jobs (npm and crates.io run in parallel):
+The registry publish workflow:
-- target an existing release tag
-- check whether that version is already published on the respective registry
-- skip already-published versions
-- both default to enabled; can be toggled via workflow_dispatch inputs
+- targets an existing release tag
+- checks that Cargo, npm, and the requested tag all agree on version
+- checks whether that version is already published on npm and crates.io
+- only publishes the channels explicitly requested
## Rerun Safety
diff --git a/npm/deskctl/package.json b/npm/deskctl/package.json
index c676924..adb142c 100644
--- a/npm/deskctl/package.json
+++ b/npm/deskctl/package.json
@@ -1,6 +1,6 @@
{
"name": "deskctl",
- "version": "0.1.14",
+ "version": "0.1.10",
"description": "Installable deskctl package for Linux X11 agents",
"license": "MIT",
"homepage": "https://github.com/harivansh-afk/deskctl",
diff --git a/site/src/pages/commands.mdx b/site/src/pages/commands.mdx
index 0696558..934cdb8 100644
--- a/site/src/pages/commands.mdx
+++ b/site/src/pages/commands.mdx
@@ -37,9 +37,9 @@ preferred read surface for focused state queries.
## Wait for state transitions
```sh
-deskctl wait window --selector 'title=Chromium' --timeout 10
+deskctl wait window --selector 'title=Firefox' --timeout 10
deskctl wait focus --selector 'id=win3' --timeout 5
-deskctl --json wait window --selector 'class=chromium' --poll-ms 100
+deskctl --json wait window --selector 'class=firefox' --poll-ms 100
```
Wait commands return the matched window payload on success. In `--json` mode,
@@ -48,9 +48,9 @@ timeouts and selector failures expose structured `kind` values.
## Act on windows
```sh
-deskctl launch chromium
+deskctl launch firefox
deskctl focus @w1
-deskctl focus 'title=Chromium'
+deskctl focus 'title=Firefox'
deskctl click @w1
deskctl click 960,540
deskctl dblclick @w2
@@ -86,8 +86,8 @@ more deterministic for automation, and easier to retry safely.
```sh
ref=w1
id=win1
-title=Chromium
-class=chromium
+title=Firefox
+class=firefox
focused
```
@@ -99,7 +99,7 @@ w1
win1
```
-Bare strings like `chromium` are fuzzy matches. They resolve when there is one
+Bare strings like `firefox` are fuzzy matches. They resolve when there is one
match and fail with candidate windows when there are multiple matches.
## Global options
diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro
index 478c7a2..b914e16 100644
--- a/site/src/pages/index.astro
+++ b/site/src/pages/index.astro
@@ -43,9 +43,6 @@ import DocLayout from "../layouts/DocLayout.astro";
GitHub
-
- crates.io
-
npm
diff --git a/site/src/pages/quick-start.mdx b/site/src/pages/quick-start.mdx
index 4cc0e25..7ecf5a7 100644
--- a/site/src/pages/quick-start.mdx
+++ b/site/src/pages/quick-start.mdx
@@ -38,13 +38,13 @@ Prefer explicit selectors when you need deterministic targeting:
```sh
ref=w1
id=win1
-title=Chromium
-class=chromium
+title=Firefox
+class=firefox
focused
```
Legacy refs such as `@w1` still work after `snapshot` or `list-windows`. Bare
-strings like `chromium` are fuzzy matches and now fail on ambiguity.
+strings like `firefox` are fuzzy matches and now fail on ambiguity.
## 4. Wait, act, verify
@@ -55,16 +55,16 @@ The core loop is:
deskctl snapshot --annotate
# wait
-deskctl wait window --selector 'title=Chromium' --timeout 10
+deskctl wait window --selector 'title=Firefox' --timeout 10
# act
-deskctl focus 'title=Chromium'
+deskctl focus 'title=Firefox'
deskctl hotkey ctrl l
deskctl type "https://example.com"
deskctl press enter
# verify
-deskctl wait focus --selector 'title=Chromium' --timeout 5
+deskctl wait focus --selector 'title=Firefox' --timeout 5
deskctl snapshot
```
@@ -84,8 +84,8 @@ Every command supports `--json` and uses the same top-level envelope:
{
"ref_id": "w1",
"window_id": "win1",
- "title": "Chromium",
- "app_name": "chromium",
+ "title": "Firefox",
+ "app_name": "firefox",
"x": 0,
"y": 0,
"width": 1920,
diff --git a/skills/deskctl/SKILL.md b/skills/deskctl/SKILL.md
index c79ca21..67a77c5 100644
--- a/skills/deskctl/SKILL.md
+++ b/skills/deskctl/SKILL.md
@@ -30,8 +30,8 @@ Every desktop interaction follows: **observe -> wait -> act -> verify**.
```bash
deskctl snapshot --annotate # observe
-deskctl wait window --selector 'title=Chromium' --timeout 10 # wait
-deskctl click 'title=Chromium' # act
+deskctl wait window --selector 'title=Firefox' --timeout 10 # wait
+deskctl click 'title=Firefox' # act
deskctl snapshot # verify
```
@@ -42,12 +42,12 @@ See [workflows/observe-act.sh](workflows/observe-act.sh) for a reusable script.
```bash
ref=w1 # snapshot ref (short-lived)
id=win1 # stable window ID (session-scoped)
-title=Chromium # match by title
-class=chromium # match by WM class
+title=Firefox # match by title
+class=firefox # match by WM class
focused # currently focused window
```
-Bare strings like `chromium` do fuzzy matching but fail on ambiguity. Prefer explicit selectors.
+Bare strings like `firefox` do fuzzy matching but fail on ambiguity. Prefer explicit selectors.
## References
diff --git a/skills/deskctl/references/commands.md b/skills/deskctl/references/commands.md
index df69350..27b4310 100644
--- a/skills/deskctl/references/commands.md
+++ b/skills/deskctl/references/commands.md
@@ -23,8 +23,8 @@ deskctl get-mouse-position
## Wait
```bash
-deskctl wait window --selector 'title=Chromium' --timeout 10
-deskctl wait focus --selector 'class=chromium' --timeout 5
+deskctl wait window --selector 'title=Firefox' --timeout 10
+deskctl wait focus --selector 'class=firefox' --timeout 5
```
Returns the matched window payload on success. Failures include structured
@@ -35,8 +35,8 @@ Returns the matched window payload on success. Failures include structured
```bash
ref=w1
id=win1
-title=Chromium
-class=chromium
+title=Firefox
+class=firefox
focused
```
@@ -46,7 +46,7 @@ on ambiguity.
## Act
```bash
-deskctl focus 'class=chromium'
+deskctl focus 'class=firefox'
deskctl click @w1
deskctl dblclick @w2
deskctl type "hello world"
@@ -59,7 +59,7 @@ deskctl mouse drag 100 100 500 500
deskctl move-window @w1 100 120
deskctl resize-window @w1 1280 720
deskctl close @w3
-deskctl launch chromium
+deskctl launch firefox
```
The daemon starts automatically on first command. In normal usage you should
diff --git a/skills/deskctl/workflows/observe-act.sh b/skills/deskctl/workflows/observe-act.sh
index 8c3abc2..0e336ae 100755
--- a/skills/deskctl/workflows/observe-act.sh
+++ b/skills/deskctl/workflows/observe-act.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# observe-act.sh - main desktop interaction loop
# usage: ./observe-act.sh [action] [action-args...]
-# example: ./observe-act.sh 'title=Chromium' click
+# example: ./observe-act.sh 'title=Firefox' click
# example: ./observe-act.sh 'class=terminal' type "ls -la"
set -euo pipefail
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index 79008de..28092d7 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -48,13 +48,13 @@ pub enum Command {
/// Click a window ref or coordinates
#[command(after_help = CLICK_EXAMPLES)]
Click {
- /// Selector (ref=w1, id=win1, title=Chromium, class=chromium, focused) or x,y coordinates
+ /// Selector (ref=w1, id=win1, title=Firefox, class=firefox, focused) or x,y coordinates
selector: String,
},
/// Double-click a window ref or coordinates
#[command(after_help = DBLCLICK_EXAMPLES)]
Dblclick {
- /// Selector (ref=w1, id=win1, title=Chromium, class=chromium, focused) or x,y coordinates
+ /// Selector (ref=w1, id=win1, title=Firefox, class=firefox, focused) or x,y coordinates
selector: String,
},
/// Type text into the focused window
@@ -81,19 +81,19 @@ pub enum Command {
/// Focus a window by ref or name
#[command(after_help = FOCUS_EXAMPLES)]
Focus {
- /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring
+ /// Selector: ref=w1, id=win1, title=Firefox, class=firefox, focused, or a fuzzy substring
selector: String,
},
/// Close a window by ref or name
#[command(after_help = CLOSE_EXAMPLES)]
Close {
- /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring
+ /// Selector: ref=w1, id=win1, title=Firefox, class=firefox, focused, or a fuzzy substring
selector: String,
},
/// Move a window
#[command(after_help = MOVE_WINDOW_EXAMPLES)]
MoveWindow {
- /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring
+ /// Selector: ref=w1, id=win1, title=Firefox, class=firefox, focused, or a fuzzy substring
selector: String,
/// X position
x: i32,
@@ -103,7 +103,7 @@ pub enum Command {
/// Resize a window
#[command(after_help = RESIZE_WINDOW_EXAMPLES)]
ResizeWindow {
- /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring
+ /// Selector: ref=w1, id=win1, title=Firefox, class=firefox, focused, or a fuzzy substring
selector: String,
/// Width
w: u32,
@@ -210,19 +210,19 @@ const SNAPSHOT_EXAMPLES: &str =
const LIST_WINDOWS_EXAMPLES: &str =
"Examples:\n deskctl list-windows\n deskctl --json list-windows";
const CLICK_EXAMPLES: &str =
- "Examples:\n deskctl click @w1\n deskctl click 'title=Chromium'\n deskctl click 500,300";
+ "Examples:\n deskctl click @w1\n deskctl click 'title=Firefox'\n deskctl click 500,300";
const DBLCLICK_EXAMPLES: &str =
- "Examples:\n deskctl dblclick @w2\n deskctl dblclick 'class=chromium'\n deskctl dblclick 500,300";
+ "Examples:\n deskctl dblclick @w2\n deskctl dblclick 'class=firefox'\n deskctl dblclick 500,300";
const TYPE_EXAMPLES: &str =
"Examples:\n deskctl type \"hello world\"\n deskctl type \"https://example.com\"";
const PRESS_EXAMPLES: &str = "Examples:\n deskctl press enter\n deskctl press escape";
const HOTKEY_EXAMPLES: &str = "Examples:\n deskctl hotkey ctrl l\n deskctl hotkey ctrl shift t";
const FOCUS_EXAMPLES: &str =
- "Examples:\n deskctl focus @w1\n deskctl focus 'title=Chromium'\n deskctl focus focused";
+ "Examples:\n deskctl focus @w1\n deskctl focus 'title=Firefox'\n deskctl focus focused";
const CLOSE_EXAMPLES: &str =
- "Examples:\n deskctl close @w3\n deskctl close 'id=win2'\n deskctl close 'class=chromium'";
+ "Examples:\n deskctl close @w3\n deskctl close 'id=win2'\n deskctl close 'class=firefox'";
const MOVE_WINDOW_EXAMPLES: &str =
- "Examples:\n deskctl move-window @w1 100 200\n deskctl move-window 'title=Chromium' 0 0";
+ "Examples:\n deskctl move-window @w1 100 200\n deskctl move-window 'title=Firefox' 0 0";
const RESIZE_WINDOW_EXAMPLES: &str =
"Examples:\n deskctl resize-window @w1 1280 720\n deskctl resize-window 'id=win2' 800 600";
const GET_MONITORS_EXAMPLES: &str =
@@ -237,12 +237,12 @@ const GET_MOUSE_POSITION_EXAMPLES: &str =
const DOCTOR_EXAMPLES: &str = "Examples:\n deskctl doctor\n deskctl --json doctor";
const UPGRADE_EXAMPLES: &str =
"Examples:\n deskctl upgrade\n deskctl upgrade --yes\n deskctl --json upgrade --yes";
-const WAIT_WINDOW_EXAMPLES: &str = "Examples:\n deskctl wait window --selector 'title=Chromium' --timeout 10\n deskctl --json wait window --selector 'class=chromium' --poll-ms 100";
+const WAIT_WINDOW_EXAMPLES: &str = "Examples:\n deskctl wait window --selector 'title=Firefox' --timeout 10\n deskctl --json wait window --selector 'class=firefox' --poll-ms 100";
const WAIT_FOCUS_EXAMPLES: &str = "Examples:\n deskctl wait focus --selector 'id=win3' --timeout 5\n deskctl wait focus --selector focused --poll-ms 200";
const SCREENSHOT_EXAMPLES: &str =
"Examples:\n deskctl screenshot\n deskctl screenshot /tmp/screen.png\n deskctl screenshot --annotate";
const LAUNCH_EXAMPLES: &str =
- "Examples:\n deskctl launch chromium\n deskctl launch code -- --new-window";
+ "Examples:\n deskctl launch firefox\n deskctl launch code -- --new-window";
const MOUSE_MOVE_EXAMPLES: &str =
"Examples:\n deskctl mouse move 500 300\n deskctl mouse move 0 0";
const MOUSE_SCROLL_EXAMPLES: &str =
@@ -277,7 +277,7 @@ pub enum WaitCmd {
#[derive(Args)]
pub struct WaitSelectorOpts {
- /// Selector: ref=w1, id=win1, title=Chromium, class=chromium, focused, or a fuzzy substring
+ /// Selector: ref=w1, id=win1, title=Firefox, class=firefox, focused, or a fuzzy substring
#[arg(long)]
pub selector: String,
@@ -1103,8 +1103,8 @@ mod tests {
"windows": [{
"ref_id": "w1",
"window_id": "win1",
- "title": "Chromium",
- "app_name": "chromium",
+ "title": "Firefox",
+ "app_name": "firefox",
"x": 0,
"y": 0,
"width": 1280,
@@ -1125,37 +1125,37 @@ mod tests {
fn action_text_includes_target_identity() {
let lines = render_success_lines(
&Command::Focus {
- selector: "title=Chromium".to_string(),
+ selector: "title=Firefox".to_string(),
},
Some(&json!({
"action": "focus",
- "window": "Chromium",
- "title": "Chromium",
+ "window": "Firefox",
+ "title": "Firefox",
"ref_id": "w2",
"window_id": "win7"
})),
)
.unwrap();
- assert_eq!(lines, vec!["Focused @w2 [win7] \"Chromium\""]);
+ assert_eq!(lines, vec!["Focused @w2 [win7] \"Firefox\""]);
}
#[test]
fn timeout_errors_render_last_observation() {
let lines = render_error_lines(&Response::err_with_data(
- "Timed out waiting for focus to match selector: title=Chromium",
+ "Timed out waiting for focus to match selector: title=Firefox",
json!({
"kind": "timeout",
"wait": "focus",
- "selector": "title=Chromium",
+ "selector": "title=Firefox",
"timeout_ms": 1000,
"last_observation": {
"kind": "window_not_focused",
"window": {
"ref_id": "w1",
"window_id": "win1",
- "title": "Chromium",
- "app_name": "chromium",
+ "title": "Firefox",
+ "app_name": "firefox",
"x": 0,
"y": 0,
"width": 1280,
@@ -1167,8 +1167,10 @@ mod tests {
}),
));
- assert!(lines.iter().any(|line| line
- .contains("Timed out after 1000ms waiting for focus selector title=Chromium")));
+ assert!(lines
+ .iter()
+ .any(|line| line
+ .contains("Timed out after 1000ms waiting for focus selector title=Firefox")));
assert!(lines
.iter()
.any(|line| line.contains("matching window exists but is not focused yet")));
@@ -1188,9 +1190,9 @@ mod tests {
let summary = target_summary(&json!({
"ref_id": "w1",
"window_id": "win1",
- "title": "Chromium"
+ "title": "Firefox"
}));
- assert_eq!(summary.as_deref(), Some("@w1 [win1] \"Chromium\""));
+ assert_eq!(summary.as_deref(), Some("@w1 [win1] \"Firefox\""));
}
#[test]
diff --git a/src/core/refs.rs b/src/core/refs.rs
index 7fd7b6c..34e1ba7 100644
--- a/src/core/refs.rs
+++ b/src/core/refs.rs
@@ -412,8 +412,8 @@ mod tests {
SelectorQuery::WindowId("win4".to_string())
);
assert_eq!(
- SelectorQuery::parse("title=Chromium"),
- SelectorQuery::Title("Chromium".to_string())
+ SelectorQuery::parse("title=Firefox"),
+ SelectorQuery::Title("Firefox".to_string())
);
assert_eq!(
SelectorQuery::parse("class=Navigator"),
@@ -458,11 +458,11 @@ mod tests {
fn fuzzy_resolution_fails_with_candidates_when_ambiguous() {
let mut refs = RefMap::new();
refs.rebuild(&[
- sample_window(1, "Chromium"),
+ sample_window(1, "Firefox"),
BackendWindow {
native_id: 2,
- title: "Chromium Settings".to_string(),
- app_name: "Chromium".to_string(),
+ title: "Firefox Settings".to_string(),
+ app_name: "Firefox".to_string(),
x: 0,
y: 0,
width: 10,
@@ -472,7 +472,7 @@ mod tests {
},
]);
- match refs.resolve("chromium") {
+ match refs.resolve("firefox") {
ResolveResult::Ambiguous {
mode, candidates, ..
} => {
diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs
index 9e7e931..3df1d9a 100644
--- a/src/daemon/mod.rs
+++ b/src/daemon/mod.rs
@@ -1,7 +1,6 @@
mod handler;
mod state;
-use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::{Context, Result};
@@ -13,29 +12,6 @@ use crate::core::paths::{pid_path_from_env, socket_path_from_env};
use crate::core::session;
use state::DaemonState;
-struct RuntimePathsGuard {
- socket_path: PathBuf,
- pid_path: Option,
-}
-
-impl RuntimePathsGuard {
- fn new(socket_path: PathBuf, pid_path: Option) -> Self {
- Self {
- socket_path,
- pid_path,
- }
- }
-}
-
-impl Drop for RuntimePathsGuard {
- fn drop(&mut self) {
- remove_runtime_path(&self.socket_path);
- if let Some(ref pid_path) = self.pid_path {
- remove_runtime_path(pid_path);
- }
- }
-}
-
pub fn run() -> Result<()> {
// Validate session before starting
session::detect_session()?;
@@ -49,6 +25,7 @@ pub fn run() -> Result<()> {
async fn async_run() -> Result<()> {
let socket_path = socket_path_from_env().context("DESKCTL_SOCKET_PATH not set")?;
+
let pid_path = pid_path_from_env();
// Clean up stale socket
@@ -56,21 +33,20 @@ async fn async_run() -> Result<()> {
std::fs::remove_file(&socket_path)?;
}
+ // Write PID file
+ if let Some(ref pid_path) = pid_path {
+ std::fs::write(pid_path, std::process::id().to_string())?;
+ }
+
+ let listener = UnixListener::bind(&socket_path)
+ .context(format!("Failed to bind socket: {}", socket_path.display()))?;
+
let session = std::env::var("DESKCTL_SESSION").unwrap_or_else(|_| "default".to_string());
let state = Arc::new(Mutex::new(
DaemonState::new(session, socket_path.clone())
.context("Failed to initialize daemon state")?,
));
- let listener = UnixListener::bind(&socket_path)
- .context(format!("Failed to bind socket: {}", socket_path.display()))?;
- let _runtime_paths = RuntimePathsGuard::new(socket_path.clone(), pid_path.clone());
-
- // Write PID file only after the daemon is ready to serve requests.
- if let Some(ref pid_path) = pid_path {
- std::fs::write(pid_path, std::process::id().to_string())?;
- }
-
let shutdown = Arc::new(tokio::sync::Notify::new());
let shutdown_clone = shutdown.clone();
@@ -99,6 +75,14 @@ async fn async_run() -> Result<()> {
}
}
+ // Cleanup
+ if socket_path.exists() {
+ let _ = std::fs::remove_file(&socket_path);
+ }
+ if let Some(ref pid_path) = pid_path {
+ let _ = std::fs::remove_file(pid_path);
+ }
+
Ok(())
}
@@ -139,11 +123,3 @@ async fn handle_connection(
Ok(())
}
-
-fn remove_runtime_path(path: &Path) {
- if let Err(error) = std::fs::remove_file(path) {
- if error.kind() != std::io::ErrorKind::NotFound {
- eprintln!("Failed to remove runtime path {}: {error}", path.display());
- }
- }
-}
diff --git a/tests/support/mod.rs b/tests/support/mod.rs
index 07cc5a7..5c6f0be 100644
--- a/tests/support/mod.rs
+++ b/tests/support/mod.rs
@@ -4,7 +4,6 @@ use std::os::unix::net::UnixListener;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::{Mutex, OnceLock};
-use std::thread;
use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::{anyhow, bail, Context, Result};
@@ -61,7 +60,8 @@ pub struct FixtureWindow {
impl FixtureWindow {
pub fn create(title: &str, app_class: &str) -> Result {
- let (conn, screen_num) = connect_to_test_display()?;
+ let (conn, screen_num) =
+ x11rb::connect(None).context("Failed to connect to the integration test display")?;
let screen = &conn.setup().roots[screen_num];
let window = conn.generate_id()?;
@@ -103,26 +103,6 @@ impl FixtureWindow {
}
}
-fn connect_to_test_display() -> Result<(RustConnection, usize)> {
- let max_attempts = 10;
- let mut last_error = None;
-
- for attempt in 0..max_attempts {
- match x11rb::connect(None) {
- Ok(connection) => return Ok(connection),
- Err(error) => {
- last_error = Some(anyhow!(error));
- if attempt + 1 < max_attempts {
- thread::sleep(std::time::Duration::from_millis(100 * (attempt + 1) as u64));
- }
- }
- }
- }
-
- Err(last_error.expect("x11 connection attempts should capture an error"))
- .context("Failed to connect to the integration test display")
-}
-
impl Drop for FixtureWindow {
fn drop(&mut self) {
let _ = self.conn.destroy_window(self.window);
@@ -162,10 +142,6 @@ impl TestSession {
.expect("TestSession always has an explicit socket path")
}
- pub fn pid_path(&self) -> PathBuf {
- self.root.join("deskctl.pid")
- }
-
pub fn create_stale_socket(&self) -> Result<()> {
let listener = UnixListener::bind(self.socket_path())
.with_context(|| format!("Failed to bind {}", self.socket_path().display()))?;
@@ -211,29 +187,6 @@ impl TestSession {
)
})
}
-
- pub fn run_daemon(&self, env: I) -> Result
- where
- I: IntoIterator- ,
- K: AsRef
,
- V: AsRef,
- {
- let mut command = Command::new(env!("CARGO_BIN_EXE_deskctl"));
- command
- .env("DESKCTL_DAEMON", "1")
- .env("DESKCTL_SOCKET_PATH", self.socket_path())
- .env("DESKCTL_PID_PATH", self.pid_path())
- .env("DESKCTL_SESSION", &self.opts.session)
- .envs(env);
-
- command.output().with_context(|| {
- format!(
- "Failed to run daemon {} against {}",
- env!("CARGO_BIN_EXE_deskctl"),
- self.socket_path().display()
- )
- })
- }
}
impl Drop for TestSession {
@@ -242,9 +195,6 @@ impl Drop for TestSession {
if self.socket_path().exists() {
let _ = std::fs::remove_file(self.socket_path());
}
- if self.pid_path().exists() {
- let _ = std::fs::remove_file(self.pid_path());
- }
let _ = std::fs::remove_dir_all(&self.root);
}
}
diff --git a/tests/x11_runtime.rs b/tests/x11_runtime.rs
index 30308cb..2aac58c 100644
--- a/tests/x11_runtime.rs
+++ b/tests/x11_runtime.rs
@@ -114,31 +114,6 @@ fn daemon_start_recovers_from_stale_socket() -> Result<()> {
Ok(())
}
-#[test]
-fn daemon_init_failure_cleans_runtime_state() -> Result<()> {
- let _guard = env_lock_guard();
- let session = TestSession::new("daemon-init-failure")?;
-
- let output = session.run_daemon([("XDG_SESSION_TYPE", "x11"), ("DISPLAY", ":99999")])?;
- assert!(!output.status.success(), "daemon startup should fail");
-
- let stderr = String::from_utf8_lossy(&output.stderr);
- assert!(
- stderr.contains("Failed to initialize daemon state"),
- "unexpected stderr: {stderr}"
- );
- assert!(
- !session.socket_path().exists(),
- "failed startup should remove the socket path"
- );
- assert!(
- !session.pid_path().exists(),
- "failed startup should remove the pid path"
- );
-
- Ok(())
-}
-
#[test]
fn wait_window_returns_matched_window_payload() -> Result<()> {
let _guard = env_lock_guard();