Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
43969c1
Strengthen PDF artifact acceptance and require Poppler in CI
devmobasa Oct 1, 2026
439fd9a
fix(config): normalize non-finite click highlights and share numeric …
devmobasa Oct 1, 2026
6cebac6
Restore configurator test environment through one shared guard
devmobasa Oct 1, 2026
ff034ff
Keep temporary test files under owned directory guards
devmobasa Oct 1, 2026
7eccdab
Remove obsolete toast API spelling scan
devmobasa Oct 1, 2026
baec6c0
Exercise toast press behavior without a test-only wrapper
devmobasa Oct 1, 2026
e65ee9c
Remove discarded keybinding maps from state fixtures
devmobasa Oct 1, 2026
269ac89
Cover PNG normalization formats with independent RGBA expectations
devmobasa Oct 1, 2026
31eac33
Verify image admission capacity at the real session snapshot boundary
devmobasa Oct 1, 2026
8768a6e
Verify command palette label highlighting in rendered pixels
devmobasa Oct 1, 2026
1f00b11
Resolve selection property summaries in one frame pass
devmobasa Oct 1, 2026
e42904f
fix(tooling): guard grouped and relative shared-layer imports
devmobasa Oct 1, 2026
ebe7c2e
Format the simplified zoom test fixture import
devmobasa Oct 1, 2026
f5b23b6
fix(configurator): enforce click-highlight bounds in draft conversion
devmobasa Oct 1, 2026
e68f436
refactor(config): classify changed sources with a typed error
devmobasa Oct 1, 2026
e5629e0
refactor(config): move runtime adapters and stable values to their ow…
devmobasa Oct 1, 2026
c875f59
docs(config): explain active-session autosave timing and backoff
devmobasa Oct 1, 2026
498a7a6
Use fixed pixel chunks in PDF artifact assertions
devmobasa Oct 1, 2026
9f77b94
refactor(runtime-ui): replace module-wide dead-code suppression with …
devmobasa Oct 1, 2026
0f1105a
refactor(config): isolate highlight validation and keep theme adapter…
devmobasa Oct 1, 2026
ba19123
fix(tooling): cover raw identifiers in shared-layer import guards
devmobasa Oct 1, 2026
e914ac5
Exercise live effect drains in preset and quick-color producer tests
devmobasa Oct 1, 2026
d19176f
Publish terminal session dialog outcomes through the runtime controller
devmobasa Oct 1, 2026
512d573
docs(config): describe typed write-conflict recovery
devmobasa Oct 1, 2026
921ad32
Preserve broker error categories across local and socket failures
devmobasa Oct 1, 2026
f4bf602
Avoid hashing the frame for tiny selection summaries
devmobasa Oct 1, 2026
4147c43
Exercise portal subscription transactions on a private D-Bus service
devmobasa Oct 1, 2026
c402ca1
fix(configurator): share click-highlight duration policy with core va…
devmobasa Oct 1, 2026
51ab0bd
test(tooling): run shared-layer syntax fixtures without the .NET SDK
devmobasa Oct 1, 2026
60ecf1e
perf(toolbar): share one selected-shape resolution across every snaps…
devmobasa Oct 2, 2026
f627003
fix(config): enforce finite float policy across all loaded settings
devmobasa Oct 2, 2026
4592074
test(portal): place the private bus fixture under the audited test tree
devmobasa Oct 2, 2026
c536d32
test(domain): keep dependency checks independent of the source-file c…
devmobasa Oct 2, 2026
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
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ otherwise it reports that optional local check as skipped. The source-coverage g
rustc dep-info and rejects tracked or unignored `.rs` files that are outside the supported Cargo
target/feature matrix.

The all-feature portal transport tests require `dbus-daemon`. Each fixture owns a private
session bus and connects through its explicit address, leaving the desktop session bus alone.

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.
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# Location: ~/.config/wayscriber/config.toml
#
# All settings are optional. If not specified, defaults will be used.
# Floating-point settings must be finite (not nan, inf, or -inf).
# Load-time fallbacks never rewrite this file; see docs/CONFIG.md for the policy.
#
# Every value here is a configured default: the value Wayscriber starts from.
# Edit them in the graphical configurator (`wayscriber-configurator`, or F11
Expand Down
3 changes: 3 additions & 0 deletions configurator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ gtk4 = { version = "0.11", features = ["v4_10"] }
[features]
default = ["tablet-input"]
tablet-input = ["wayscriber/tablet-input"]

[dev-dependencies]
tempfile = "3"
17 changes: 1 addition & 16 deletions configurator/src/app/daemon_setup/hyprland/tests.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
use super::*;
use std::env;
use std::sync::Mutex;
use wayscriber::env_vars::HOME_ENV;

static ENV_MUTEX: Mutex<()> = Mutex::new(());

#[test]
fn render_light_controls_quotes_binary_with_spaces() {
let rendered = render_light_controls(Path::new("/tmp/My Apps/wayscriber"));
Expand Down Expand Up @@ -80,15 +76,9 @@ fn has_source_line_matches_quoted_and_inline_commented_targets() {

#[test]
fn has_source_line_matches_tilde_target() {
let _guard = ENV_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let tmp = crate::test_temp::tempdir().unwrap();
let home = tmp.path();
let prev_home = env::var_os(HOME_ENV);
unsafe {
env::set_var(HOME_ENV, home);
}
let _env = crate::test_env::EnvGuard::set(&[(HOME_ENV, home.as_os_str())]);

let absolute = home
.join(".config")
Expand All @@ -99,11 +89,6 @@ fn has_source_line_matches_tilde_target() {
"source = ~/.config/hypr/wayscriber-light.conf # already sourced\n",
&source_line
));

match prev_home {
Some(value) => unsafe { env::set_var(HOME_ENV, value) },
None => unsafe { env::remove_var(HOME_ENV) },
}
}

#[test]
Expand Down
10 changes: 3 additions & 7 deletions configurator/src/app/pages/arrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! error text instead, on the same `.error` styling every ported page uses.

use relm4::prelude::*;
use wayscriber::config::{ARROW_ANGLE_MAX, ARROW_ANGLE_MIN, ARROW_LENGTH_MAX, ARROW_LENGTH_MIN};

use crate::messages::Message;
use crate::models::util::format_float;
Expand All @@ -15,11 +16,6 @@ use super::super::search::SearchArea;
use super::super::state::ConfiguratorApp;
use super::{BuiltPage, PageBuilder};

/// `ArrowConfig::length`, in pixels.
const LENGTH_RANGE: (f64, f64) = (5.0, 50.0);
/// `ArrowConfig::angle_degrees`, in degrees.
const ANGLE_RANGE: (f64, f64) = (15.0, 60.0);

pub(super) fn build(sender: &ComponentSender<ConfiguratorApp>) -> BuiltPage {
let mut page = PageBuilder::new(sender, TabId::Arrow);

Expand All @@ -28,13 +24,13 @@ pub(super) fn build(sender: &ComponentSender<ConfiguratorApp>) -> BuiltPage {
"Arrow length (px)",
|app| app.draft.arrow_length.clone(),
|value| Message::TextChanged(TextField::ArrowLength, value),
|app| validate_f64_range(&app.draft.arrow_length, LENGTH_RANGE.0, LENGTH_RANGE.1),
|app| validate_f64_range(&app.draft.arrow_length, ARROW_LENGTH_MIN, ARROW_LENGTH_MAX),
)
.entry_row_validated(
"Arrow angle (deg)",
|app| app.draft.arrow_angle.clone(),
|value| Message::TextChanged(TextField::ArrowAngle, value),
|app| validate_f64_range(&app.draft.arrow_angle, ANGLE_RANGE.0, ANGLE_RANGE.1),
|app| validate_f64_range(&app.draft.arrow_angle, ARROW_ANGLE_MIN, ARROW_ANGLE_MAX),
)
.switch_row(
"Place arrowhead at end of line",
Expand Down
28 changes: 25 additions & 3 deletions configurator/src/app/pages/ui/click_highlight.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use relm4::ComponentSender;
use wayscriber::config::{
CLICK_HIGHLIGHT_DURATION_MAX_MS, CLICK_HIGHLIGHT_DURATION_MIN_MS, CLICK_HIGHLIGHT_OUTLINE_MAX,
CLICK_HIGHLIGHT_OUTLINE_MIN, CLICK_HIGHLIGHT_RADIUS_MAX, CLICK_HIGHLIGHT_RADIUS_MIN,
};

use crate::messages::Message;
use crate::models::{ColorPickerId, TabId, TextField, ToggleField};
Expand Down Expand Up @@ -42,19 +46,37 @@ pub(super) fn build(sender: &ComponentSender<ConfiguratorApp>) -> BuiltPage {
"Radius",
|app| app.draft.click_highlight_radius.clone(),
|value| Message::TextChanged(TextField::HighlightRadius, value),
|app| validate_f64_range(&app.draft.click_highlight_radius, 16.0, 160.0),
|app| {
validate_f64_range(
&app.draft.click_highlight_radius,
CLICK_HIGHLIGHT_RADIUS_MIN,
CLICK_HIGHLIGHT_RADIUS_MAX,
)
},
)
.entry_row_validated(
"Outline thickness",
|app| app.draft.click_highlight_outline_thickness.clone(),
|value| Message::TextChanged(TextField::HighlightOutlineThickness, value),
|app| validate_f64_range(&app.draft.click_highlight_outline_thickness, 1.0, 12.0),
|app| {
validate_f64_range(
&app.draft.click_highlight_outline_thickness,
CLICK_HIGHLIGHT_OUTLINE_MIN,
CLICK_HIGHLIGHT_OUTLINE_MAX,
)
},
)
.entry_row_validated(
"Duration (ms)",
|app| app.draft.click_highlight_duration_ms.clone(),
|value| Message::TextChanged(TextField::HighlightDurationMs, value),
|app| validate_u32_range(&app.draft.click_highlight_duration_ms, 150, 1500),
|app| {
validate_u32_range(
&app.draft.click_highlight_duration_ms,
CLICK_HIGHLIGHT_DURATION_MIN_MS as u32,
CLICK_HIGHLIGHT_DURATION_MAX_MS as u32,
)
},
);

page.group("Colors");
Expand Down
58 changes: 10 additions & 48 deletions configurator/src/app/session_catalog/duplicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ fn duplicate_session_catalog_entry_sync(
#[cfg(test)]
mod tests {
use std::ffi::OsString;
use std::path::Path;
use std::sync::MutexGuard;

use crate::models::{
DaemonRuntimeStatus, DesktopEnvironment, LightShortcutApplyCapability,
Expand All @@ -93,50 +91,6 @@ mod tests {

use super::*;

struct EnvGuard {
catalog_hooks: Option<OsString>,
xdg_data_home: Option<OsString>,
xdg_runtime_dir: Option<OsString>,
_guard: MutexGuard<'static, ()>,
}

impl EnvGuard {
fn set_roots(path: &Path) -> Self {
let guard = crate::test_env::lock();
let catalog_hooks = std::env::var_os(CATALOG_HOOKS_TEST_ENV);
let xdg_data_home = std::env::var_os(XDG_DATA_HOME_ENV);
let xdg_runtime_dir = std::env::var_os(XDG_RUNTIME_DIR_ENV);
unsafe {
std::env::set_var(CATALOG_HOOKS_TEST_ENV, path);
std::env::set_var(XDG_DATA_HOME_ENV, path);
std::env::set_var(XDG_RUNTIME_DIR_ENV, path);
}
Self {
catalog_hooks,
xdg_data_home,
xdg_runtime_dir,
_guard: guard,
}
}
}

impl Drop for EnvGuard {
fn drop(&mut self) {
match self.catalog_hooks.take() {
Some(value) => unsafe { std::env::set_var(CATALOG_HOOKS_TEST_ENV, value) },
None => unsafe { std::env::remove_var(CATALOG_HOOKS_TEST_ENV) },
}
match self.xdg_data_home.take() {
Some(value) => unsafe { std::env::set_var(XDG_DATA_HOME_ENV, value) },
None => unsafe { std::env::remove_var(XDG_DATA_HOME_ENV) },
}
match self.xdg_runtime_dir.take() {
Some(value) => unsafe { std::env::set_var(XDG_RUNTIME_DIR_ENV, value) },
None => unsafe { std::env::remove_var(XDG_RUNTIME_DIR_ENV) },
}
}
}

fn inactive_status() -> DaemonRuntimeStatus {
DaemonRuntimeStatus {
desktop: DesktopEnvironment::Unknown,
Expand All @@ -158,7 +112,11 @@ mod tests {
#[test]
fn duplicate_session_catalog_entry_copies_primary_and_catalogs_new_entry() {
let temp = crate::test_temp::tempdir().unwrap();
let _env = EnvGuard::set_roots(temp.path());
let _env = crate::test_env::EnvGuard::set(&[
(CATALOG_HOOKS_TEST_ENV, temp.path().as_os_str()),
(XDG_DATA_HOME_ENV, temp.path().as_os_str()),
(XDG_RUNTIME_DIR_ENV, temp.path().as_os_str()),
]);
let source = temp.path().join("lecture.wayscriber-session");
let target = temp.path().join("lecture-copy.wayscriber-session");
let source_artifacts = wayscriber::session::named_session_artifact_paths(&source);
Expand Down Expand Up @@ -199,7 +157,11 @@ mod tests {
#[test]
fn duplicate_session_catalog_entry_warns_when_catalog_update_fails_after_copy() {
let temp = crate::test_temp::tempdir().unwrap();
let _env = EnvGuard::set_roots(temp.path());
let _env = crate::test_env::EnvGuard::set(&[
(CATALOG_HOOKS_TEST_ENV, temp.path().as_os_str()),
(XDG_DATA_HOME_ENV, temp.path().as_os_str()),
(XDG_RUNTIME_DIR_ENV, temp.path().as_os_str()),
]);
let source = temp.path().join("lecture.wayscriber-session");
let target = temp.path().join("lecture-copy.wayscriber-session");
std::fs::write(&source, b"primary").unwrap();
Expand Down
69 changes: 20 additions & 49 deletions configurator/src/app/session_catalog/move_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ fn reject_catalog_target_collision(
#[cfg(test)]
mod tests {
use std::ffi::OsString;
use std::sync::MutexGuard;

use crate::models::{
DaemonRuntimeStatus, DesktopEnvironment, LightShortcutApplyCapability,
Expand All @@ -139,50 +138,6 @@ mod tests {

use super::*;

struct EnvGuard {
catalog_hooks: Option<OsString>,
xdg_data_home: Option<OsString>,
xdg_runtime_dir: Option<OsString>,
_guard: MutexGuard<'static, ()>,
}

impl EnvGuard {
fn set_roots(path: &Path) -> Self {
let guard = crate::test_env::lock();
let catalog_hooks = std::env::var_os(CATALOG_HOOKS_TEST_ENV);
let xdg_data_home = std::env::var_os(XDG_DATA_HOME_ENV);
let xdg_runtime_dir = std::env::var_os(XDG_RUNTIME_DIR_ENV);
unsafe {
std::env::set_var(CATALOG_HOOKS_TEST_ENV, path);
std::env::set_var(XDG_DATA_HOME_ENV, path);
std::env::set_var(XDG_RUNTIME_DIR_ENV, path);
}
Self {
catalog_hooks,
xdg_data_home,
xdg_runtime_dir,
_guard: guard,
}
}
}

impl Drop for EnvGuard {
fn drop(&mut self) {
match self.catalog_hooks.take() {
Some(value) => unsafe { std::env::set_var(CATALOG_HOOKS_TEST_ENV, value) },
None => unsafe { std::env::remove_var(CATALOG_HOOKS_TEST_ENV) },
}
match self.xdg_data_home.take() {
Some(value) => unsafe { std::env::set_var(XDG_DATA_HOME_ENV, value) },
None => unsafe { std::env::remove_var(XDG_DATA_HOME_ENV) },
}
match self.xdg_runtime_dir.take() {
Some(value) => unsafe { std::env::set_var(XDG_RUNTIME_DIR_ENV, value) },
None => unsafe { std::env::remove_var(XDG_RUNTIME_DIR_ENV) },
}
}
}

fn inactive_status() -> DaemonRuntimeStatus {
DaemonRuntimeStatus {
desktop: DesktopEnvironment::Unknown,
Expand All @@ -204,7 +159,11 @@ mod tests {
#[test]
fn move_session_catalog_entry_moves_artifacts_and_preserves_catalog_id() {
let temp = crate::test_temp::tempdir().unwrap();
let _env = EnvGuard::set_roots(temp.path());
let _env = crate::test_env::EnvGuard::set(&[
(CATALOG_HOOKS_TEST_ENV, temp.path().as_os_str()),
(XDG_DATA_HOME_ENV, temp.path().as_os_str()),
(XDG_RUNTIME_DIR_ENV, temp.path().as_os_str()),
]);
let source = temp.path().join("lecture.wayscriber-session");
let target = temp.path().join("archive.wayscriber-session");
let source_artifacts = wayscriber::session::named_session_artifact_paths(&source);
Expand Down Expand Up @@ -243,7 +202,11 @@ mod tests {
#[test]
fn move_session_catalog_entry_failure_keeps_catalog_and_source_artifacts() {
let temp = crate::test_temp::tempdir().unwrap();
let _env = EnvGuard::set_roots(temp.path());
let _env = crate::test_env::EnvGuard::set(&[
(CATALOG_HOOKS_TEST_ENV, temp.path().as_os_str()),
(XDG_DATA_HOME_ENV, temp.path().as_os_str()),
(XDG_RUNTIME_DIR_ENV, temp.path().as_os_str()),
]);
let source = temp.path().join("lecture.wayscriber-session");
let target = temp.path().join("archive.wayscriber-session");
let source_artifacts = wayscriber::session::named_session_artifact_paths(&source);
Expand Down Expand Up @@ -280,7 +243,11 @@ mod tests {
#[test]
fn move_session_catalog_entry_rejects_catalog_target_collision_before_disk_move() {
let temp = crate::test_temp::tempdir().unwrap();
let _env = EnvGuard::set_roots(temp.path());
let _env = crate::test_env::EnvGuard::set(&[
(CATALOG_HOOKS_TEST_ENV, temp.path().as_os_str()),
(XDG_DATA_HOME_ENV, temp.path().as_os_str()),
(XDG_RUNTIME_DIR_ENV, temp.path().as_os_str()),
]);
let source = temp.path().join("lecture.wayscriber-session");
let target = temp.path().join("archive.wayscriber-session");
std::fs::write(&source, b"primary").unwrap();
Expand Down Expand Up @@ -308,7 +275,11 @@ mod tests {
#[test]
fn move_session_catalog_entry_rolls_back_when_catalog_update_fails_after_move() {
let temp = crate::test_temp::tempdir().unwrap();
let _env = EnvGuard::set_roots(temp.path());
let _env = crate::test_env::EnvGuard::set(&[
(CATALOG_HOOKS_TEST_ENV, temp.path().as_os_str()),
(XDG_DATA_HOME_ENV, temp.path().as_os_str()),
(XDG_RUNTIME_DIR_ENV, temp.path().as_os_str()),
]);
let source = temp.path().join("lecture.wayscriber-session");
let target = temp.path().join("archive.wayscriber-session");
let source_artifacts = wayscriber::session::named_session_artifact_paths(&source);
Expand Down
Loading
Loading