Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
rpm

- name: Set up Rust
uses: dtolnay/rust-toolchain@1.95.0
uses: dtolnay/rust-toolchain@1.98.1

- name: Install nfpm
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
echo "LD_LIBRARY_PATH=$RUNNER_TEMP/gtk4-layer-shell/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" >> "$GITHUB_ENV"

- name: Set up Rust
uses: dtolnay/rust-toolchain@1.95.0
uses: dtolnay/rust-toolchain@1.98.1
with:
components: clippy, rustfmt

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- This file applies to the whole wayscriber workspace.
- wayscriber is a Rust desktop annotation app for Wayland compositors.
- The workspace root crate is `wayscriber`; `configurator/` is a separate GTK4/libadwaita (Relm4) desktop configurator crate.
- Both crates use Rust 1.95 and edition 2024.
- Both crates use Rust 1.98.1 and edition 2024; `rust-toolchain.toml` pins the development toolchain.
- The workspace default member is only the root crate; use `-p wayscriber-configurator` or `--workspace` when configurator coverage is needed.

## Architecture
Expand Down
9 changes: 6 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ module boundary.

## Workspace and toolchain

The repository is one Cargo workspace using Rust 1.95 and edition 2024. The
Nix flake compiles with `nixpkgs` rustc from `flake.lock`; after raising
`rust-version`, run `nix flake update` so that lock is new enough.
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 `wayscriber` package owns the overlay, daemon, CLI, shared domain/config/session code,
rendering, and integration tests;
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "wayscriber"
version = "0.9.24"
edition = "2024"
rust-version = "1.95"
rust-version = "1.98.1"
description = "Screen annotation tool for Wayland compositors"
homepage = "https://wayscriber.com"
repository = "https://github.com/devmobasa/wayscriber"
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# wayscriber

[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Rust](https://img.shields.io/badge/rust-1.95%2B-orange.svg)](https://www.rust-lang.org/)
[![Rust](https://img.shields.io/badge/rust-1.98.1%2B-orange.svg)](https://www.rust-lang.org/)

A ZoomIt-like real-time screen annotation tool for Linux/Wayland, written in Rust.

Expand Down Expand Up @@ -480,7 +480,7 @@ sudo dnf install ./wayscriber-configurator-x86_64.rpm

### From source

Rust 1.95 or newer is required. If `rustup` is not already installed, install it (this also installs `cargo`), then load Cargo into your current shell:
Rust 1.98.1 or newer is required. If `rustup` is not already installed, install it (this also installs `cargo`), then load Cargo into your current shell:

```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Expand All @@ -492,12 +492,13 @@ Then clone the repository — the dependency and build steps below run from insi
```bash
git clone https://github.com/devmobasa/wayscriber.git
cd wayscriber
rustup toolchain install 1.95.0
rustup override set 1.95.0
rustup toolchain install 1.98.1 --component clippy --component rustfmt
cargo --version
```

The override selects Rust 1.95 only inside this checkout and leaves your global default unchanged. You can instead keep any existing default that is already Rust 1.95 or newer.
The repository's `rust-toolchain.toml` selects Rust 1.98.1 inside this checkout. If an older
directory override is already set, remove it with `rustup override unset` from the checkout
so the file can select the toolchain.

**Dependencies:**

Expand Down
2 changes: 1 addition & 1 deletion configurator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "wayscriber-configurator"
version = "0.9.24"
edition = "2024"
rust-version = "1.95"
rust-version = "1.98.1"

[lints]
workspace = true
Expand Down
2 changes: 1 addition & 1 deletion configurator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This file covers building and running the configurator from source. For screensh

## Prerequisites

- Rust toolchain 1.95 or newer.
- Rust toolchain 1.98.1 or newer (`../rust-toolchain.toml` pins development builds).
- System development packages for GTK 4 and libadwaita.

## Run It
Expand Down
2 changes: 2 additions & 0 deletions docs/codebase-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ Daemon mode therefore provides a persistent background service that reacts to us

`WaylandState` coordinates the runtime owners handlers need. `FocusState` owns activation, focus, and startup acquisition; `ProtocolGlobals` owns bound globals and toolkit handler state; `PointerRuntime` owns pointer position, board-pan and chrome gestures, cursor, pointer-lock, and single-contact touch protocol lifecycles; `ToolbarChrome` owns toolbar placement, inline interaction, and fade state; `ToolbarDrag` owns built-in and GTK drag lifecycles; `RegionCaptureRuntime` owns region selection generations, active/review/window-snap state, and the window-query and cut-preview workers; `AcquisitionRuntime` owns the capacity-one screen-acquisition and zoom-waiter registries plus eyedropper source correlation; `FrozenState` owns its availability and one-shot startup gate; `SurfaceState` owns output/fullscreen/layer placement and frozen-fullscreen transitions; `OverlaySuppressionState` owns suppression reason, keyboard policy, capture barrier, and clickthrough state; `RenderRuntime` owns the canvas layer cache, render-profile baseline, and per-effect damage history; `InputHudRuntime` owns system-reader lifecycle and reconciliation; `SpotlightRuntime` owns render memory, warning latches, and wheel timing; `ClipboardRuntime` owns single-flight clipboard workers and queue policy; `PreferenceStores` groups durable preference stores and workers; `UiAnimationClock` owns animation scheduling; and `FontCatalogPrewarm` owns the one-shot font scan. The root retains cross-owner coordination. `handlers::route::SurfaceRouter` is the single classifier for pointer, touch, and stylus surfaces and supplies overlay screen coordinates before modality-specific dispatch.

Within `PointerRuntime`, pending chrome targets and device-owned release suppression have separate lifecycles. Clearing a toast, HUD, or zoom-chip press preserves mouse and touch suppression; cancelling a touch clears only its own release latch.

Freeze capture waits for the overlay-suppression frame, then selects `wlr-screencopy`, `ext-image-copy-capture`, or the screenshot portal in that order. The two direct protocols capture the active output into shared memory; the portal captures the desktop and the client crops the selected output when needed. Direct capture and portal crop both require compositor-reported output pixels; a missing current mode fails instead of guessing from the overlay buffer.

---
Expand Down
23 changes: 22 additions & 1 deletion flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 22 additions & 12 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,41 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs = { self, nixpkgs, flake-utils }:
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
# Linux only. eachDefaultSystem also evaluates x86_64-darwin, which
# nixpkgs 26.11 dropped.
flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system:
let
pkgs = import nixpkgs { inherit system; };
pkgs = import nixpkgs {
inherit system;
overlays = [ rust-overlay.overlays.default ];
};
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
version = cargoToml.package.version;
rustVersion = cargoToml.package.rust-version;
rustToolchain = pkgs.rust-bin.stable.${rustVersion}.default.override {
extensions = [ "rust-analyzer" ];
};
rustPlatform = pkgs.makeRustPlatform {
cargo = rustToolchain;
rustc = rustToolchain;
};
servicePath = pkgs.lib.makeBinPath [ pkgs.grim pkgs.slurp pkgs.wl-clipboard ];
in
# Instantiation (and CI `nix eval`) must fail before cargoBuildHook
# when flake.lock's nixpkgs rustc lags Cargo.toml's rust-version.
# when the selected compiler lags Cargo.toml's rust-version.
assert pkgs.lib.assertMsg
(pkgs.lib.versionAtLeast pkgs.rustc.version rustVersion)
"nixpkgs rustc ${pkgs.rustc.version} is older than Cargo.toml rust-version ${rustVersion}; run `nix flake update`.";
(pkgs.lib.versionAtLeast rustToolchain.version rustVersion)
"selected rustc ${rustToolchain.version} is older than Cargo.toml rust-version ${rustVersion}; run `nix flake update rust-overlay`.";
{
packages = {
wayscriber = pkgs.rustPlatform.buildRustPackage {
wayscriber = rustPlatform.buildRustPackage {
pname = "wayscriber";
inherit version;
src = ./.;
Expand Down Expand Up @@ -78,7 +92,7 @@
};
};

wayscriber-configurator = pkgs.rustPlatform.buildRustPackage {
wayscriber-configurator = rustPlatform.buildRustPackage {
pname = "wayscriber-configurator";
inherit version;
src = ./.;
Expand Down Expand Up @@ -127,11 +141,7 @@

devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
cargo
rustc
rust-analyzer
clippy
rustfmt
rustToolchain
pkg-config
cairo
pango
Expand Down
4 changes: 4 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[toolchain]
channel = "1.98.1"
profile = "minimal"
components = ["clippy", "rustfmt"]
2 changes: 1 addition & 1 deletion src/about_window/icon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ mod tests {
fn transparent_pixels_are_fully_premultiplied_away() {
let (_, _, argb) = decode_premultiplied_argb(ICON_PNG).expect("icon decodes");

for pixel in argb.chunks_exact(4) {
for pixel in argb.as_chunks::<4>().0 {
let alpha = pixel[3];
assert!(
pixel[0] <= alpha && pixel[1] <= alpha && pixel[2] <= alpha,
Expand Down
10 changes: 8 additions & 2 deletions src/backend/wayland/frozen/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ pub(in crate::backend::wayland) fn copy_shm_argb(
}

if format == wl_shm::Format::Xrgb8888 {
for pixel in data.chunks_exact_mut(4) {
for pixel in data.as_chunks_mut::<4>().0 {
pixel[3] = 0xff;
}
}
Expand Down Expand Up @@ -252,7 +252,13 @@ mod tests {
}

fn values(image: &FrozenImage) -> Vec<u8> {
image.data.chunks_exact(4).map(|chunk| chunk[0]).collect()
image
.data
.as_chunks::<4>()
.0
.iter()
.map(|chunk| chunk[0])
.collect()
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions src/backend/wayland/handlers/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
## Invariants
- Keep handlers thin; do not bury durable business logic in protocol callbacks.
- Preserve coordinate transforms, modifier synchronization, seat/device lifetimes, frame/callback ordering, and tablet feature gating.
- Keep pending chrome targets separate from device-owned release suppression; target cleanup preserves both latches, and touch cancellation clears only Touch suppression.
- Route protocol surfaces through `SurfaceRouter`; do not add modality-specific canvas/toolbar classifiers.
- Avoid blocking protocol callback paths.

Expand Down
4 changes: 0 additions & 4 deletions src/backend/wayland/handlers/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ impl CompositorHandler for WaylandState {
let previous_output = self.surface.current_output();
let output_changed = previous_output.as_ref() != Some(output);
self.surface.set_current_output(output.clone());
self.focus.note_surface_enter();
if output_changed {
// Keep layer-shell toolbars pinned to the monitor that owns the drawing surface.
self.toolbar_chrome.set_needs_recreate(true);
Expand Down Expand Up @@ -171,9 +170,6 @@ impl CompositorHandler for WaylandState {

debug!("Surface left output");
self.surface.clear_output(output);
if self.surface.current_output().is_none() {
self.focus.clear_surface_enter();
}
self.refresh_active_output_label();
self.frozen.set_active_output(None, None);
self.zoom.set_active_output(None, None);
Expand Down
3 changes: 0 additions & 3 deletions src/backend/wayland/handlers/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ impl OutputHandler for WaylandState {
) {
debug!("Output destroyed");
self.surface.clear_output(&output);
if self.surface.current_output().is_none() {
self.focus.clear_surface_enter();
}
self.refresh_active_output_label();
// SCTK 0.20 calls this before removing the output from OutputState, so
// a walk of current outputs would still include it. Exclude it here;
Expand Down
2 changes: 2 additions & 0 deletions src/backend/wayland/handlers/touch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ impl WaylandState {
// is dragging is untouched.
self.cancel_region_selection_from(RegionInputSource::Touch);
self.pointer.clear_chrome_press();
self.pointer
.clear_suppressed_release(RegionInputSource::Touch);
self.input_state
.clear_help_overlay_press_for(HelpOverlayPressSource::Touch);

Expand Down
1 change: 0 additions & 1 deletion src/backend/wayland/handlers/xdg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ impl WindowHandler for WaylandState {
&& let Some(output) = self.protocol.output().outputs().next()
{
self.surface.set_current_output(output);
self.focus.clear_surface_enter();
}
self.refresh_active_output_label();

Expand Down
2 changes: 1 addition & 1 deletion src/backend/wayland/state/buffer_damage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ impl BufferDamageTracker {
(true, full_reason, Vec::new())
} else {
// Take and deduplicate regions
let mut regions: Vec<Rect> = damage.regions.drain(..).collect();
let mut regions = std::mem::take(&mut damage.regions);
regions.retain(Rect::is_valid);
(false, None, regions)
}
Expand Down
2 changes: 0 additions & 2 deletions src/backend/wayland/state/core/output/focus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ impl WaylandState {
window.set_fullscreen(Some(&target_output));
window.commit();
self.surface.set_current_output(target_output);
self.focus.clear_surface_enter();
self.refresh_active_output_label();
self.begin_session_output_transition(target_identity, "output switch");
self.request_xdg_activation(qh);
Expand All @@ -101,7 +100,6 @@ impl WaylandState {
self.teardown_keyboard_focus();
self.recreate_layer_surface_for_output(qh, &target_output);
self.surface.set_current_output(target_output);
self.focus.clear_surface_enter();
self.refresh_active_output_label();
self.begin_session_output_transition(target_identity, "output switch");
self.input_state.needs_redraw = true;
Expand Down
10 changes: 0 additions & 10 deletions src/backend/wayland/state/focus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pub(in crate::backend::wayland) struct FocusState {
has_pointer_focus: bool,
current_seat: Option<wl_seat::WlSeat>,
last_activation_serial: Option<u32>,
has_seen_surface_enter: bool,
overlay_ready: bool,
suppress_focus_exit_until: Option<Instant>,
xdg_close_guard_until: Option<Instant>,
Expand All @@ -37,7 +36,6 @@ impl FocusState {
has_pointer_focus: false,
current_seat: None,
last_activation_serial: None,
has_seen_surface_enter: false,
overlay_ready: false,
suppress_focus_exit_until: None,
xdg_close_guard_until: None,
Expand Down Expand Up @@ -203,14 +201,6 @@ impl FocusState {
std::mem::take(&mut self.xdg_explicit_close_requested)
}

pub(in crate::backend::wayland) fn note_surface_enter(&mut self) {
self.has_seen_surface_enter = true;
}

pub(in crate::backend::wayland) fn clear_surface_enter(&mut self) {
self.has_seen_surface_enter = false;
}

pub(in crate::backend::wayland) fn activation_token_to_apply(&self) -> Option<String> {
self.pending_activation_token.clone()
}
Expand Down
Loading
Loading