13 KiB
Contributing to wayscriber
Thanks for your interest in improving wayscriber. This guide covers the supported workspace, architecture boundaries, and validation workflow without trying to inventory every source file.
Contribution principles
Wayscriber is shared as a gift exchange, not a contract. Requests are welcome, but there is no guaranteed timeline or support obligation. See the gift-exchange principles for context.
Changes should preserve foreground/fullscreen safety, serialized config and session compatibility, and the feature-gate behavior described below. Prefer focused changes with tests at the owning module boundary.
Workspace and toolchain
The repository is one Cargo workspace using Rust 1.98.1 and edition 2024.
rust-toolchain.toml pins the development toolchain, including Clippy and rustfmt; CI uses
the same version. The Nix flake selects that version through the rust-overlay input pinned
in flake.lock, using the root package's rust-version for both crates and the development
shell. When raising the version, update both manifests, rust-toolchain.toml, and CI pins;
run nix flake update rust-overlay if the locked overlay does not yet provide it.
- the root
wayscriberpackage owns the overlay, daemon, CLI, shared domain/config/session code, rendering, and integration tests; wayscriber-configuratoris the GTK4/libadwaita (Relm4) GUI package underconfigurator/;- root
Cargo.lockis the single lockfile for both packages.
The workspace default member is only the root package. Use -p wayscriber-configurator or
--workspace when configurator coverage is required. A command such as
cargo build --manifest-path configurator/Cargo.toml still belongs to the root workspace and uses
the root lockfile.
The main package's build.rs only exposes an optional release-version override, embeds the current
Git hash, and tells Cargo which Git metadata should trigger a rebuild. Cargo features use standard
cfg(feature = "...") conditions directly.
Development
GitHub CI uses the C# file app. To run that route locally, build it once with
dotnet build tools/wayscriber.cs, then run commands with
dotnet run tools/wayscriber.cs --no-build -- .... The standalone scripts remain
available for development and installation on machines without .NET. Nix does
not provide .NET; global.json selects the required SDK when it is installed
separately.
Build both packages without launching a window:
cargo build --workspace
The overlay and configurator are foreground applications. Only launch them when it is safe for them to take focus:
cargo run -- --active
cargo run -p wayscriber-configurator
The CLI parser is implemented manually in src/cli.rs; keep parsing, help/version output, and CLI
integration tests aligned when flags change.
Architecture ownership
Use the codebase overview for the main runtime flow and the configurator README for the editor. The stable ownership map is:
| Area | Owner |
|---|---|
| Entry and CLI | src/main.rs, src/lib.rs, src/app/, and src/cli.rs |
| Stable values | src/domain/ for dependency-light action, tool, color, and board identities |
| Config | src/config/, config.example.toml, docs/CONFIG.md, and configurator mappings |
| Input and boards | src/input/; BoardManager owns ordered BoardState values and their pages |
| Drawing and rendering | src/draw/ for frames, shapes, history, page storage, and Cairo/Pango helpers |
| Wayland runtime | src/backend/wayland/ for protocol setup, handlers, event-loop phases, state, and rendering |
| Daemon | src/daemon/ for control requests, service lifecycle, overlay children, and tray integration |
| Sessions | src/session/ plus Wayland session transactions for snapshots, storage, locks, and recovery |
| Capture/export | src/capture/ and src/canvas_export/ |
| UI and toolbars | src/ui/, src/ui/toolbar/, src/backend/wayland/toolbar/, and src/toolbar_gtk/ |
| Configurator | configurator/src/app/ for state/update/side effects and configurator/src/models/ for typed drafts |
| Packaging and automation | packaging/, tools/, and .github/workflows/ |
Scoped AGENTS.md files record invariants and coupled-change reminders near these owners. They are
more durable than a central list of every module and should be updated when a boundary changes.
Common coupled changes
- Config fields often require core types/defaults/validation,
config.example.toml,docs/CONFIG.md, configurator draft/view/search behavior, schema behavior, and tests. - Actions, tools, and keybindings often require action metadata, default maps, input routing, help and command UI, both toolbar frontends, configurator labels/search, docs, and tests.
- Board/page changes can affect session snapshots, board picker and toolbar UI, persistence config, export behavior, and identity/ordering tests.
- Daemon/service/shortcut changes can affect
src/daemon/, shared service/path helpers, configurator daemon setup, packaging units, and setup docs. - Serialized config or session changes must preserve compatibility or provide an explicit migration with regression fixtures.
Feature gates
Preserve the intent of the root package features:
tablet-input— tablet/stylus protocol support;dbus— shared D-Bus support;portal— portal capture/runtime support, layered ondbus;tray— status tray support, layered ondbus;toolbar-gtk— GTK layer-shell toolbar frontend;config-schema— schema export support.
The configurator has its own tablet-input feature forwarding to the root package. Full CI covers
all features and no default features across the workspace.
Tests and linting
Run focused checks while iterating, for example:
cargo test config::
cargo test session::
cargo test -p wayscriber-configurator
Before submitting a broad or cross-package change, run the local CI entry point:
./tools/lint-and-test.sh
It checks release/package metadata, all three retained release contracts, package layout, Rust
source coverage, formatting, strict all-feature Clippy, all-feature tests, and no-default-feature
tests. When the pinned .NET SDK is installed, it also builds and runs the C# repository-tool tests;
otherwise it reports that optional local check as skipped. The source-coverage gate uses current
rustc dep-info and rejects tracked or unignored .rs files that are outside the supported Cargo
target/feature matrix.
The canonical gate serializes the Rust test harness because parallel rendering tests have
crashed in the native font stack through context-menu, board-picker, and region-capture paths.
This changes scheduling, not test selection; tests may still create their own threads.
The context-menu and board-picker retained-text rendering regressions each run in a separate
process under both feature configurations. Both have crashed inside Cairo/FreeType during
parallel tests. This isolation covers these observed failures; it does not prove that the
native race is fixed. Direct parallel cargo test runs can still hit it. See Releasing for
the full release and website handoff.
For offline work, prefetch dependencies first:
./tools/fetch-all-deps.sh
Freeze, zoom, and capture manual matrix
Geometry unit tests cover crop arithmetic and stale-output rejection, but they do not prove a compositor's protocol behavior. For capture-related changes, exercise every unchecked cell below on real hardware; this checklist is manual evidence, not CI coverage.
| Compositor | 1x | Fractional scale | Mixed DPI |
|---|---|---|---|
| Hyprland | [ ] | [ ] | [ ] |
| Plasma/KWin | [ ] | [ ] | [ ] |
| Niri | [ ] | [ ] | [ ] |
| Sway | [ ] | [ ] | [ ] |
| GNOME | [ ] | [ ] | [ ] |
For each cell, record the selected capture backend from the logs. Verify that Freeze, Zoom, and a PDF desktop backdrop all use the active output's exact pixels. User full-screen screenshots remain whole-desktop captures. Repeat while switching outputs and, where supported, changing scale or transform during capture: the result must either stay exact or fail visibly, never shift, stretch, or reuse another output's image.
For changes to the native region picker, use the same compositor/scale matrix and record the following permission-gated evidence (these checks open or foreground the overlay):
Ctrl+Shift+Cdelivers the selected frozen pixels; the saved PNG dimensions match the picker readout, including while zoomed or panned.- With
[capture].include_drawings = true, committed annotations are already visible over the frozen desktop while selecting and match the delivered PNG; transient strokes, handles, and UI stay hidden. In interactive Review, toggling drawings off/on updates that preview immediately. - Cancelling while pending, armed, or selecting leaves no picker-owned freeze behind, and the next capture action works.
- Output, scale, transform, zoom-level, and pan changes cancel with one visible message.
picker = "slurp"restores the external selector. Native acquisition failure hands off to it, while a dismissed portal request does not.- Pointer, touch, and stylus drags all select when the hardware is available; losing the owning device during a drag rearms the picker.
- Holding Shift before the first press produces a square crop, and the crosshair remains on the physical pointer while the squared frame follows the constrained endpoint.
Ctrl+Aduring a pointer, touch, or stylus drag captures the whole output and consumes the outstanding release without starting a canvas gesture.- Deactivating or aborting zoom while the picker waits cancels quietly; a reported zoom failure produces exactly one message.
- With
picker = "slurp"andformat = "jpg", the legacy path remains JPEG; native selection remains PNG-only. - On Hyprland and Sway,
Spaceenters window mode only after the provider is ready. Verify pointer, touch, and stylus selection, plusSuper+Arrownavigation andEnter, on overlapping tiled and floating windows. The highlighted readout and delivered PNG must have identical pixel bounds. Also switch workspaces during picker startup: candidates intentionally follow the workspace visible at provider-query time, while output/layout mismatches still disable window mode. - On Plasma/KWin, Niri, and GNOME, the window-mode legend and
Spacecontrol stay absent while area selection continues to work. Provider failure or an empty workspace must also fall back quietly.
At 1080p@1, 4K@2, and a fractional-scale 4K setup, capture the debug-level Region picker frame
timings during pointer motion. Also record the process peak-RSS delta while encoding a full-output
Ctrl+A crop. Keep these measurements with the change's manual test evidence; they are intentionally
not collected by CI.
These captures now fail when the active output does not advertise a current wl_output mode.
Plugging or unplugging any monitor also cancels in-flight Freeze and Zoom captures on unrelated
outputs, because output count is part of the layout identity used to reject stale frames.
./tools/code-health-report.sh reports navigational maintainability metrics. Its CI artifact is
observational, not a global file/function-size gate; use the report to find code worth understanding,
not as a reason for mechanical splitting.
Documentation and release metadata
- User-visible config behavior belongs in
docs/CONFIG.mdandconfig.example.toml. - Installation, service, and shortcut behavior belongs in
docs/SETUP.mdand packaging docs. - Main-crate architecture belongs in
docs/codebase-overview.md. - Drafts under
docs/temp/are planning material unless explicitly promoted. - Version changes must go through
tools/bump-version.sh; keep both package manifests, rootCargo.lock, packaging metadata, and tag/release policy aligned. - Close a user-visible "I don't have that setting / this build" report only after the change is in
a tagged GitHub release.
mainis not whatarch-install.sh, AURwayscriber-bin, or other packaged installs ship.--versionreports the crate version, not the git hash, so bump withtools/bump-version.shin the same change as a user-visible overlay, settings, or config toggle (or immediately before tagging that release). Otherwise two binaries can print the samewayscriber 0.9.xand look identical.
See tools/README.md for build, install, packaging, version, and release helpers.
Run ./tools/lint-and-test.sh for the standalone local gate. It lints, builds
binaries, and tests the whole workspace with all features and with no default
features, alongside source, packaging, retained release-contract, and C# tool
checks when .NET is installed. CI runs the equivalent C# command and additionally checks dynamic
and static gtk4-layer-shell linkage and uploads its code-health report.
GTK widget coverage runs separately with ./tools/test-gtk-widgets.sh (Weston and
dbus-run-session required). It creates a private headless display and requires
GTK initialization; an unavailable display fails this check. Successful widget
bodies print EXECUTED markers. Ordinary tests without a display report an
optional skip. Neither route proves layer-shell focus or screen capture behavior
on a user's compositor.