2107 Commits
Author SHA1 Message Date
devmobasa 4e35f55a16 Merge pull request #427 from devmobasa/chore/dependency-hardening
Harden dependencies and move kernel calls to rustix
2026-10-31 23:39:34 +01:00
devmobasa 5966f2d7b8 test: treat non-wake readiness as a failure in wake assertions
The old assertions required poll to report nothing, but the shared
helpers only looked for readable input, so a hangup or a bad descriptor
could satisfy a test that expects no wake. Reject any other readiness in
both helpers and pin that with a hung-up pipe.
2026-10-30 22:54:45 +01:00
devmobasa b7bfbd6df6 fix(broker): validate inherited descriptors before borrowing them
Borrowing a descriptor requires it to be open at that moment, so the
broker entry cannot check a raw number through a borrow. Check both
numbers with getsockopt first, as main did, which keeps the rejection of
closed descriptors and makes the borrow's safety claim true.

The entry exits 126 either way, so this only restores the contract. Pin
that rejection with an integration test against the real binary.
2026-10-30 22:53:29 +01:00
devmobasa 66e9259f6f refactor: finish moving kernel calls off raw libc
Detach from the terminal with rustix and std, and drop the remaining
unsafe process, poll, and shutdown calls from the tests through small
shared helpers. Prune the rustix features nothing ended up using, and
record which call sites stay on libc and why in the agent guidance.
2026-10-30 20:13:25 +01:00
devmobasa 82a8d5e9c6 refactor(broker): use rustix for descriptors and processes
Replace the hand-built sendmsg and recvmsg control messages, the sealed
memfd handling, and the polling, signalling, and child status calls with
typed rustix calls. Broker functions now take borrowed descriptors instead
of raw integers, so fd lifetimes are checked by the compiler.

The packet and descriptor caps, the truncation checks, and the signal
allow-list stay. Passed descriptors now also close when a packet with no
data is rejected.

One check narrows: rustix skips ancillary types it does not know instead
of reporting them, so only descriptors and credentials are visible here,
and credentials are still rejected. The skipped case is SCM_SECURITY,
which needs SO_PASSSEC on the socket, and nothing here sets it.

The post-clone child stub stays on raw syscalls by design, and the
broker entry keeps one borrow of its inherited channels, which live until
the process exits.
2026-10-30 20:13:25 +01:00
devmobasa c115c907ec refactor(daemon): use rustix for pidfds, timers, and inotify
Move pidfd opening and waiting, the boot clock and timerfd, the command
queue watcher, and the overlay watchdog to rustix, removing their unsafe
blocks. Deadlines, retries, and failure handling stay the same.

Three unsafe sites remain for a reason: the zero-signal pidfd check has
no rustix equivalent, the inotify parser keeps its buffer across calls,
and fail_stop exits without any cleanup. The watch mask drops two bits
the kernel ignores, because those events always arrive.
2026-10-30 20:04:02 +01:00
devmobasa f108e45964 refactor: use rustix for anchored file operations
Move the no-replace renames, the pinned runtime state paths, and the
input node access check to rustix, removing their unsafe blocks. The
paths keep their existing NUL checks and error messages.

Share one safe FIFO helper across the tests that repeated an unsafe
mkfifo block, since the std version is still unstable.
2026-10-30 20:04:02 +01:00
devmobasa b9e5f8f7fb refactor: poll and wake through rustix
Move the runtime wake eventfd, the Wayland and daemon poll loops, and the
input monitor to rustix, removing their unsafe blocks.

Timeout rounding, interruption retries, and readiness rules stay the
same. The runtime poll seam now passes readiness flags, since rustix does
not let tests write them into a poll descriptor. A test helper replaces
the repeated poll blocks in the wake tests.
2026-10-30 20:04:02 +01:00
devmobasa 572bc3ba44 refactor: take advisory locks through rustix
Add rustix as a direct dependency. It is already compiled through the
Wayland stack, so this adds no new crate.

Use its typed flock operations for the durable, daemon protocol, and
action journal locks, which removes their unsafe blocks. Deadline locks
still always retry without blocking.
2026-10-30 20:04:02 +01:00
devmobasa d3b67cae56 refactor(cli): tokenize arguments with lexopt
Replace the hand-written tokenizer and its short-cluster and attached
value helpers. Every accepted form and value rule stays the same, now
pinned by tests, including that a value starting with - must be attached.

Two edges change: a bare -- now ends options as usual, and an unknown -x
reports as an unknown short option like one inside a cluster. Non-UTF-8
session paths now reach the specific error for that case instead of the
generic one; they are still rejected.
2026-10-30 20:04:02 +01:00
devmobasa 1dafb9385b docs: add nextest as the fast local test run
The local gate serializes tests because of a native font race. nextest
gives each test its own process, so the whole suite can run in parallel
while iterating. It is local only: it skips doctests and the isolated
render tests, so the gate stays as it is. Add it to the Nix shell.
2026-10-30 19:25:15 +01:00
devmobasa 8d0c13f946 ci: add weekly dependabot updates
Propose Cargo and GitHub Actions updates, grouping compatible Cargo
bumps. Hold zbus at its current major while ksni requires zbus 5.
2026-10-30 19:25:05 +01:00
devmobasa e009114d63 ci: enforce a dependency policy with cargo-deny
Add deny.toml and a dependency-policy job covering the whole workspace.
Bans, licenses, and sources block; advisories report without blocking,
so a newly published advisory cannot fail an unrelated pull request.

zbus 6 absorbed zvariant, zvariant_derive, and zbus_names, so RustSec
now lists them as unmaintained. ksni still requires zbus 5, so those
advisories are ignored with that reason until it moves.

Both packages now state their MIT license instead of leaving the check
to detect it. The configurator is marked publish = false: it has an
unversioned path dependency and cannot be published.
2026-10-30 19:25:05 +01:00
devmobasa 837d2d2d43 chore(deps): use one random source on getrandom 0.4
Bump the crate to the version tempfile already compiles, which removes
the duplicate 0.3 build. Make it required and replace the hand-written
libc getrandom loop in the daemon protocol, so portal tokens and protocol
identifiers share one source and one unsafe block goes away.
2026-10-30 19:21:16 +01:00
devmobasa 283c90a562 chore(release): bump version to 0.9.28 v0.9.28 2026-10-27 10:14:18 +01:00
devmobasa 0cd97c313c chore: update Rust toolchain to 1.99.1 2026-10-27 10:13:17 +01:00
devmobasa 297a84e7a8 chore(deps): update compatible Cargo dependencies 2026-10-27 10:13:17 +01:00
devmobasa 908d12b763 Merge pull request #426 from devmobasa/style/pedantic-lints
Enable pedantic Clippy lints one at a time
2026-10-26 18:18:31 +01:00
devmobasa 46302a300a docs(agents): describe the newly enforced Rust style rules
State the rules behind the pedantic lints in plain terms, and note that
pedantic lints are enabled one at a time, never as the whole group.
2026-10-26 14:31:40 +01:00
devmobasa 86894088f2 style: enable three pedantic lints fixed by hand
comparison_chain, ignore_without_reason, and
unnecessary_debug_formatting.

- The one-edit keybinding comparison matches on `a.len().cmp(&b.len())`.
- The Wayland smoke test says why it is ignored.
- The configurator search text includes session paths with `display()`
  instead of `{:?}`, so searches match the path without debug quotes or
  escapes.
2026-10-26 14:31:40 +01:00
devmobasa ede5426ec7 style: enable the elided_lifetimes_in_paths rustc lint
Write the anonymous lifetime in paths: `BindingInserter<'_>`, not
`BindingInserter`, so a borrowed type is visible in the signature. The
lint goes in a new `[workspace.lints.rust]` table.

Applied from the compiler's suggestions, then `cargo fmt --all`.
2026-10-26 14:31:40 +01:00
devmobasa 44dba8de2a style: enable clippy::manual_let_else
Use `let ... else` for a `match` or `if let` that only unwraps a value
when the other branch diverges, for example with `return`, `continue`,
or `panic!`.

Clippy's suggestions for this lint are not machine-applicable, so they
were applied and each site was read. One suggestion lost a binding name
in the context-menu focus test; that site was fixed by hand. Every
`panic!` and `bail!` in the new `else` blocks ends with a semicolon.
2026-10-26 14:31:40 +01:00
devmobasa 0be2ab4353 style: enable nine small pedantic lints
duration_suboptimal_units, elidable_lifetime_names, explicit_iter_loop,
ignored_unit_patterns, implicit_clone, manual_is_variant_and,
manual_string_new, option_as_ref_cloned, and redundant_else.

Mechanical, with the lints enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all

The tray shutdown calls return `()`, so they are plain `.await;` rather
than the `let () = ...` that the unit-pattern fix writes.
2026-10-26 14:31:40 +01:00
devmobasa 12e81922d7 style: enable clippy::needless_raw_string_hashes
Give a raw string only the `#` delimiters its content needs: `r"..."`
when it contains no `"`, and `r#"..."#` when it contains `"` but not
`"#`.

Mechanical, with the lint enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all
2026-10-26 14:31:40 +01:00
devmobasa dba123b843 style: enable clippy::if_not_else
Put the positive condition first: `if x { a } else { b }` instead of
`if !x { b } else { a }`.

Mechanical, with the lint enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all
2026-10-26 12:46:35 +01:00
devmobasa 26937f03eb style: enable clippy::manual_midpoint
Use `midpoint` instead of `(a + b) / 2`. The result is the same
wherever the sum does not overflow. Integer `midpoint` rounds toward
zero like `/ 2`. `f64::midpoint` computes `(a + b) * 0.5` when both
inputs are at most `f64::MAX / 2` in magnitude. Only where the sum
would overflow does `midpoint` differ: it still returns a value
between the two inputs.

Mechanical, with the lint enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all
2026-10-26 12:46:35 +01:00
devmobasa 105de143fe style: enable clippy::unnested_or_patterns
Nest or-patterns inside the shared constructor:
`Some(A | B)` instead of `Some(A) | Some(B)`.

Mechanical, with the lint enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all
2026-10-26 12:46:35 +01:00
devmobasa eac0079e57 style: enable clippy::borrow_as_ptr and ptr_cast_constness
Take raw pointers with `&raw const` and `&raw mut` instead of casting a
reference, and change pointer constness with `cast_mut()`. The FFI calls
receive the same pointers, without an intermediate reference.

Mechanical, with the lints enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all
2026-10-26 12:46:35 +01:00
devmobasa d685951430 style: enable clippy::single_match_else
Write a `match` with one pattern and a fallback as `if let ... else`.

Mostly mechanical, with the lint enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all

Four matches with comments in their arms were converted by hand, so the
comments stay with their branch: config edit completion, the properties
toggle knob, a toolbar preset-slot test, and the configurator caret
restore.
2026-10-26 12:46:35 +01:00
devmobasa 9baeb9fdeb style: enable clippy::semicolon_if_nothing_returned
End unit-valued trailing expressions with a semicolon.

Mechanical, with the lint enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all
2026-10-26 12:46:35 +01:00
devmobasa 2b21ff1d80 style: enable clippy::doc_markdown
Put identifiers and Wayland protocol names in doc comments in backticks
(`wl_output`, `zwp_tablet_v2`). Product names that docs write plainly
(GLib, HarfBuzz, HiDPI, JetBrains, MSBuild, StatusNotifierItem, VSync)
go in `doc-valid-idents` in clippy.toml, next to Clippy's default list.

Mechanical, with the lint enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all

Two comments that the fix could not mark up cleanly were reworded by
hand: the surface module summary and the zoom-chip invariant.
2026-10-26 12:46:35 +01:00
devmobasa 9c4336c645 style: enable clippy::map_unwrap_or
Use `map_or`, `map_or_else`, `is_some_and`, and `is_ok_and` instead of
`map(..).unwrap_or(..)`.

Mostly mechanical, with the lint enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all

Written by hand:
- The four caret-movement arms in caret_edit.rs are a `match`. The
  `map_or_else` form needs `caret` and `anchor` mutably in both
  closures.
- `sub_ring_children` is a `match`: the `&[]` default does not infer
  as a slice in `map_or`.
- The configurator's recorded-device mapping matches on
  `Option<InputSource>` directly, instead of a `map_or` with a nested
  `match`.

The translations guard lists the `tr(...)` arguments it accepts without
a literal as source text, so its two action-metadata entries follow the
new spelling.
2026-10-26 12:46:35 +01:00
devmobasa 85d0c9804b style: enable clippy::uninlined_format_args
Inline named arguments in format strings: `format!("{name}: {err}")`.

Mechanical, with the lint enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all
2026-10-26 12:46:35 +01:00
devmobasa 10f743ac79 style: enable clippy::cast_lossless
Convert numbers that always fit with `From` (`f64::from(width)`) instead
of `as`. The lint skips `const` initializers, where `From` cannot be
called, and conversions to `usize` and `isize`, whose width depends on
the target, so those keep `as`.

Mechanical, with the lint enabled in Cargo.toml:

    cargo clippy --fix --workspace --all-targets --all-features
    cargo clippy --fix --workspace --all-targets --no-default-features
    cargo fmt --all
2026-10-26 12:46:35 +01:00
devmobasa abb03bb6bf Merge pull request #425 from gsmith-alvarez/fix/unconstrained-panned-selection-move
fix(input): allow unconstrained selection translation on pannable boards and sync drag anchor
2026-10-26 08:58:11 +01:00
devmobasa da68d152ac fix(input): keep selection movement stable across canvas bounds
Keep the press as the drag anchor until the pointer leaves the click
radius, so the first drag motion is not lost. After that the anchor
follows the pointer on every motion, including blocked ones, so
reversing at an edge moves the selection at once.

Apply coordinate limits on every board. Pannable boards still move
freely, but each translated coordinate and the painted bounds now stay
in the i32 range. Before, a nudge near i32::MAX wrapped in release
builds: the line jumped to span the whole coordinate range and was
saved that way. Debug builds panicked instead.

On a fixed canvas (the transparent overlay, or a board with panning
disabled), a selection that already crosses an edge, or lies outside
the canvas, can no longer move further out, and it is never pulled
back. Before, any nudge snapped it inside, up to thousands of pixels.

Align the move-to-edge actions with the visible canvas, including pan
and zoom, instead of the screen origin. A target further away than
i32::MAX is reached in several steps that record one undo entry.

Combine selection bounds in i64. Shape bounds are clamped to the i32
range, so a shape at the coordinate limit is measured on a copy moved
toward the origin; the limits and edge actions then see its full
stroke padding. A selection whose painted bounds end past i32::MAX
gets no selection Rect, because handle hit-testing and other selection
code compute x + width in i32. Its chrome then marks full damage, so
the halos of its other members are still repainted. The Properties
panel uses the same bounds, the text resize handle is omitted when it
would end past i32::MAX, and handle hit-testing compares pointer
distances in i64.

Test changes:
- nudge_selection_clamps_right_and_bottom_edges starts at (87, 87).
  At (90, 90) the stroke padding already crossed the canvas edge, and
  the test passed only because the old clamp pulled the shape back.
- moving_a_selection_on_transparent_board_clamps_to_screen checks the
  exact landing edge. It also passed when nothing moved.

The coordinate guard and the view-relative edge targets are adapted
from #424.
2026-10-25 21:34:40 +01:00
Giovanni Smith-Alvarez f9b8622d8a fix(input): allow unconstrained selection translation on pannable boards and sync drag anchor
- Pannable boards (solid boards) have unbounded canvases; do not clamp selection
  translation against the viewport rectangle.
- Retain clamping on transparent overlay boards where the canvas is bound to the output.
- Always advance MovingSelection last_x/last_y on pointer motion so dragging against
  a boundary does not leave a stale reference anchor that causes jumping.
2026-10-25 12:00:09 -04:00
devmobasa f1bf4841a4 Merge pull request #423 from gsmith-alvarez/fix/issue-422-selection-move
fix(input): clamp selection translation against visible canvas rect (#422)
2026-10-25 16:42:00 +01:00
Giovanni Smith-Alvarez cd96d7e237 fix(input): clamp selection translation against visible canvas rect on panned boards
Issue #422: Clamping against screen dimensions on panned or zoomed boards
uses mismatched units (canvas bounds vs screen dimensions), causing selections
to jump offscreen on the first drag motion or arrow-key nudge. Clamp against
the visible canvas rect instead.
2026-10-25 08:51:02 -04:00
devmobasa 4c0981a336 Merge pull request #421 from devmobasa/refactor/cleanup-style
refactor: standardize Rust style across the workspace
2026-10-22 10:25:25 +02:00
devmobasa d57ad6094e test(gtk): wait for stable capture reference pixels
Several identical frames can precede a popup's final layout, leaving the
capture restoration test with a premature reference image. Require a
quiet interval before recording that image while retaining the existing
settling deadline and exact restoration assertions.
2026-10-22 10:04:07 +02:00
devmobasa 87e1d057f7 refactor: standardize Rust style across the workspace 2026-10-22 09:26:12 +02:00
devmobasa c6e89d0072 docs(i18n): point the Chinese README sync marker at the restructure
README.zh-CN.md already mirrors the English README at e2772757, so the next release sync can start its diff there.
2026-10-21 17:06:16 +02:00
devmobasa e2772757e9 docs(readme): restructure the README as a guided tour
Lead with platform support, then walk through the features with the
current screenshots and demo videos beside them. Each feature section
opens with the keys to press, keeps a short list visible, and moves
the long details into collapsed sections. The reference sections
fold their deep material the same way.

Remove the duplicated feature list from the roadmap and the repeated
help shortcuts. Split code blocks so each copy button copies one
runnable command or one sequence, and keep installer dry runs apart
from the install itself.

Mirror the new structure in the Simplified Chinese README.
2026-10-21 17:06:11 +02:00
devmobasa c9d1143e2f Merge pull request #420 from devmobasa/chore/dotnet-11-rc2
Update the tooling SDK to .NET 11 RC2
2026-10-19 12:19:07 +02:00
devmobasa 099cfea832 chore(tooling): update the pinned .NET SDK to 11 RC2
Update global.json and the SDK consistency guard to
11.0.100-rc.2.26504.105. Refresh the release and version-metadata fixtures
while preserving strict SDK selection.
2026-10-19 12:05:49 +02:00
devmobasa 7950b621c9 Merge pull request #419 from devmobasa/feat/findable-language-row
Make the configurator language setting easier to find
2026-10-19 12:02:46 +02:00
devmobasa 70d2e70f44 feat(configurator): make the language row easy to recognize
Put the language picker first in General UI, with a globe and a title
that includes every supported language. Derive its title and search terms
from shared language metadata.

Add regression coverage for the title, icon and row order, and format the
additions with the pinned Rust toolchain.
2026-10-19 11:39:54 +02:00
devmobasa ce68c7d893 Merge pull request #418 from devmobasa/fix/417-gtk-capture-transparency
Fix GTK toolbar artifacts in screen captures
2026-10-19 00:58:54 +02:00
devmobasa 932ac6d019 test(gtk): remove compositor-dependent constrained menu case
The menu-constrained case needs a compositor that shrinks an oversized
popup. Weston 13 on the Ubuntu 24.04 CI runner does not shrink it, so the
menu never overflows and the case fails before capture.
2026-10-19 00:40:59 +02:00