#[cfg(any(feature = "gtk", all(feature = "macos", target_os = "macos")))]
#[derive(Debug)]
pub(crate) struct WindowDragArm<P> {
armed: Option<(P, f64, f64)>,
}
#[cfg(any(feature = "gtk", all(feature = "macos", target_os = "macos")))]
impl<P> WindowDragArm<P> {
pub(crate) const fn new() -> Self {
Self { armed: None }
}
pub(crate) fn arm(&mut self, press: P, origin_x: f64, origin_y: f64) {
self.armed = Some((press, origin_x, origin_y));
}
#[cfg(test)]
pub(crate) fn is_armed(&self) -> bool {
self.armed.is_some()
}
#[cfg(test)]
pub(crate) fn origin(&self) -> Option<(f64, f64)> {
self.armed.as_ref().map(|(_, x, y)| (*x, *y))
}
#[cfg(any(feature = "gtk", test))]
pub(crate) fn discard(&mut self) {
self.armed = None;
}
pub(crate) fn take(&mut self) -> Option<P> {
self.armed.take().map(|(p, _, _)| p)
}
#[cfg(any(feature = "gtk", test))]
pub(crate) fn commit_if_past_threshold(
&mut self,
current_x: f64,
current_y: f64,
threshold_px: f64,
) -> Option<P> {
let (_, origin_x, origin_y) = self.armed.as_ref()?;
let dx = current_x - origin_x;
let dy = current_y - origin_y;
if (dx * dx + dy * dy).sqrt() >= threshold_px {
self.take()
} else {
None
}
}
}
#[cfg(any(feature = "gtk", all(feature = "macos", target_os = "macos")))]
impl<P> Default for WindowDragArm<P> {
fn default() -> Self {
Self::new()
}
}
#[cfg(any(
feature = "gtk",
all(feature = "win", any(target_os = "windows", test))
))]
#[derive(Debug, Default, Clone)]
pub(crate) struct ModalPumpDepth(std::rc::Rc<std::cell::Cell<u32>>);
#[cfg(any(
feature = "gtk",
all(feature = "win", any(target_os = "windows", test))
))]
impl ModalPumpDepth {
pub(crate) fn new() -> Self {
Self(std::rc::Rc::new(std::cell::Cell::new(0)))
}
pub(crate) fn get(&self) -> u32 {
self.0.get()
}
pub(crate) fn is_pumping(&self) -> bool {
self.get() > 0
}
}
#[cfg(any(
feature = "gtk",
all(feature = "win", any(target_os = "windows", test))
))]
pub(crate) struct ModalPumpGuard<'a> {
depth: &'a ModalPumpDepth,
}
#[cfg(any(
feature = "gtk",
all(feature = "win", any(target_os = "windows", test))
))]
impl<'a> ModalPumpGuard<'a> {
pub(crate) fn new(depth: &'a ModalPumpDepth) -> Self {
depth.0.set(depth.0.get() + 1);
Self { depth }
}
}
#[cfg(any(
feature = "gtk",
all(feature = "win", any(target_os = "windows", test))
))]
impl Drop for ModalPumpGuard<'_> {
fn drop(&mut self) {
self.depth.0.set(self.depth.0.get() - 1);
}
}
#[cfg(any(
feature = "tui",
feature = "gtk",
all(feature = "macos", target_os = "macos"),
all(feature = "win", any(target_os = "windows", test))
))]
pub(crate) fn move_to_trash(path: &std::path::Path) -> crate::backend::ServiceResult<()> {
trash::delete(path).map_err(|e| crate::backend::BackendError::PlatformFailure {
context: format!("trash::delete: {e}"),
})
}
#[cfg(all(
test,
any(
feature = "tui",
feature = "gtk",
all(feature = "macos", target_os = "macos"),
feature = "win"
)
))]
mod move_to_trash_tests {
use super::*;
#[allow(clippy::print_stderr)]
#[test]
fn move_to_trash_removes_the_file_from_its_original_path() {
let path = std::env::temp_dir().join(format!(
"quadraui-956-move-to-trash-test-{}.txt",
std::process::id()
));
std::fs::write(&path, b"quadraui#956").expect("write temp file");
assert!(path.exists(), "temp file must exist before trashing it");
if let Err(e) = move_to_trash(&path) {
eprintln!(
"skipping: move_to_trash failed in this environment ({e:?}) — \
likely no live desktop/trash session"
);
let _ = std::fs::remove_file(&path);
return;
}
assert!(
!path.exists(),
"the original path must no longer exist once move_to_trash succeeds"
);
}
#[test]
fn move_to_trash_on_a_nonexistent_path_reports_a_failure() {
let path = std::env::temp_dir().join(format!(
"quadraui-956-does-not-exist-{}-{}.txt",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or_default()
));
assert!(!path.exists());
assert!(move_to_trash(&path).is_err());
}
}
#[cfg(any(feature = "tui", feature = "win"))]
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(crate) fn wide_nul_terminated(text: &str) -> Vec<u16> {
text.encode_utf16().chain(std::iter::once(0)).collect()
}
#[cfg(all(test, any(feature = "tui", feature = "win")))]
mod wide_nul_terminated_tests {
use super::*;
#[test]
fn encodes_utf16_and_appends_a_nul() {
assert_eq!(
wide_nul_terminated("ab"),
vec!['a' as u16, 'b' as u16, 0u16]
);
assert_eq!(wide_nul_terminated(""), vec![0u16]);
}
}
#[cfg(any(
feature = "tui",
all(feature = "macos", target_os = "macos"),
all(feature = "win", target_os = "windows")
))]
pub(crate) fn open_with_default(target: &std::ffi::OsStr) -> crate::backend::ServiceResult<()> {
if try_open_with_default(target) {
Ok(())
} else {
Err(crate::backend::BackendError::PlatformFailure {
context: format!("open_with_default: no opener launched for {target:?}"),
})
}
}
#[cfg(any(
feature = "tui",
all(feature = "macos", target_os = "macos"),
all(feature = "win", target_os = "windows")
))]
pub(crate) fn try_open_with_default(target: &std::ffi::OsStr) -> bool {
#[cfg(windows)]
{
windows_shell_execute_open(target)
}
#[cfg(not(windows))]
{
unix_open_with_default_command(target).spawn().is_ok()
}
}
#[cfg(all(target_os = "macos", any(feature = "tui", feature = "macos")))]
pub(crate) fn unix_open_with_default_command(target: &std::ffi::OsStr) -> std::process::Command {
let mut cmd = std::process::Command::new("open");
cmd.arg(target)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null());
cmd
}
#[cfg(all(unix, not(target_os = "macos"), feature = "tui"))]
pub(crate) fn unix_open_with_default_command(target: &std::ffi::OsStr) -> std::process::Command {
let mut cmd = std::process::Command::new("xdg-open");
cmd.arg(target)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null());
cmd
}
#[cfg(all(windows, any(feature = "tui", feature = "win")))]
fn windows_shell_execute_open(target: &std::ffi::OsStr) -> bool {
shell_execute_reports_success(windows_shell_execute_open_code(target))
}
#[cfg(any(feature = "tui", feature = "win"))]
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(crate) fn shell_execute_reports_success(code: isize) -> bool {
code > 32
}
#[cfg(all(test, any(feature = "tui", feature = "win")))]
mod shell_execute_reports_success_tests {
use super::*;
#[test]
fn success_threshold_is_strictly_above_32() {
const SE_ERR_FNF: isize = 2;
const SE_ERR_NOASSOC: isize = 31;
const SE_ERR_DDEFAIL: isize = 32;
assert!(!shell_execute_reports_success(0));
assert!(!shell_execute_reports_success(SE_ERR_FNF));
assert!(!shell_execute_reports_success(SE_ERR_NOASSOC));
assert!(!shell_execute_reports_success(SE_ERR_DDEFAIL));
assert!(shell_execute_reports_success(33));
assert!(shell_execute_reports_success(42));
}
}
#[cfg(all(windows, any(feature = "tui", feature = "win")))]
fn windows_shell_execute_open_code(target: &std::ffi::OsStr) -> isize {
#[link(name = "shell32")]
unsafe extern "system" {
fn ShellExecuteW(
hwnd: *mut core::ffi::c_void,
operation: *const u16,
file: *const u16,
parameters: *const u16,
directory: *const u16,
show_cmd: i32,
) -> isize;
}
const SW_SHOWNORMAL: i32 = 1;
let operation = wide_nul_terminated("open");
let file = wide_nul_terminated(&target.to_string_lossy());
unsafe {
ShellExecuteW(
std::ptr::null_mut(),
operation.as_ptr(),
file.as_ptr(),
std::ptr::null(),
std::ptr::null(),
SW_SHOWNORMAL,
)
}
}
#[cfg(all(
test,
unix,
any(feature = "tui", all(feature = "macos", target_os = "macos"))
))]
pub(crate) static PATH_OVERRIDE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[cfg(all(
test,
any(
feature = "tui",
all(feature = "macos", target_os = "macos"),
all(feature = "win", target_os = "windows")
)
))]
mod open_with_default_tests {
use super::*;
#[cfg(target_os = "macos")]
#[test]
fn unix_open_with_default_command_uses_macos_open() {
let cmd = unix_open_with_default_command(std::ffi::OsStr::new("https://example.com/1087"));
assert_eq!(cmd.get_program(), "open");
assert_eq!(
cmd.get_args().collect::<Vec<_>>(),
["https://example.com/1087"]
);
}
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn unix_open_with_default_command_uses_xdg_open() {
let cmd = unix_open_with_default_command(std::ffi::OsStr::new("https://example.com/1087"));
assert_eq!(cmd.get_program(), "xdg-open");
assert_eq!(
cmd.get_args().collect::<Vec<_>>(),
["https://example.com/1087"]
);
}
#[cfg(windows)]
fn unopenable_windows_target() -> std::ffi::OsString {
std::ffi::OsString::from(format!(
"C:\\quadraui-1087-no-such-directory-{}\\x.quadraui1087nohandler",
std::process::id()
))
}
#[cfg(windows)]
#[test]
fn windows_shell_execute_open_reports_a_real_failure_code() {
const SE_ERR_FNF: isize = 2;
let target = unopenable_windows_target();
assert_eq!(windows_shell_execute_open_code(&target), SE_ERR_FNF);
assert!(!windows_shell_execute_open(&target));
}
#[cfg(windows)]
#[test]
fn open_with_default_reports_err_when_windows_cannot_open_the_target() {
let target = unopenable_windows_target();
assert_eq!(
open_with_default(&target),
Err(crate::backend::BackendError::PlatformFailure {
context: format!("open_with_default: no opener launched for {target:?}"),
})
);
}
#[cfg(windows)]
#[test]
fn windows_shell_execute_open_treats_shell_metacharacters_as_inert_text() {
const SE_ERR_FNF: isize = 2;
let target = std::ffi::OsString::from(format!(
"C:\\quadraui-1087-no-such-directory-{}\\q=foo&run=bar|calc.exe^%1.quadraui1087nohandler",
std::process::id()
));
assert_eq!(windows_shell_execute_open_code(&target), SE_ERR_FNF);
}
#[cfg(unix)]
#[test]
fn try_open_with_default_returns_true_through_a_real_delegation() {
let _guard = PATH_OVERRIDE_TEST_LOCK
.lock()
.unwrap_or_else(|p| p.into_inner());
#[cfg(target_os = "macos")]
const OPENER_NAME: &str = "open";
#[cfg(all(unix, not(target_os = "macos")))]
const OPENER_NAME: &str = "xdg-open";
let tmp = tempfile::tempdir().expect("tempdir");
let stub_path = tmp.path().join(OPENER_NAME);
std::fs::write(&stub_path, b"#!/bin/sh\nexit 0\n").expect("write stub opener");
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&stub_path, std::fs::Permissions::from_mode(0o755))
.expect("chmod +x stub opener");
}
let _restore = RestorePath::capture();
std::env::set_var("PATH", tmp.path());
assert!(try_open_with_default(std::ffi::OsStr::new(
"https://example.com/1087"
)));
}
#[cfg(unix)]
#[test]
fn try_open_with_default_returns_false_when_the_opener_is_missing() {
let _guard = PATH_OVERRIDE_TEST_LOCK
.lock()
.unwrap_or_else(|p| p.into_inner());
let _restore = RestorePath::capture();
let empty_dir = tempfile::tempdir().expect("tempdir");
std::env::set_var("PATH", empty_dir.path());
assert!(!try_open_with_default(std::ffi::OsStr::new(
"https://example.com/1087"
)));
}
#[cfg(any(
all(feature = "macos", target_os = "macos"),
all(feature = "tui", unix)
))]
#[test]
fn open_with_default_reports_err_when_the_opener_is_missing() {
let _guard = PATH_OVERRIDE_TEST_LOCK
.lock()
.unwrap_or_else(|p| p.into_inner());
let _restore = RestorePath::capture();
let empty_dir = tempfile::tempdir().expect("tempdir");
std::env::set_var("PATH", empty_dir.path());
assert_eq!(
open_with_default(std::ffi::OsStr::new("https://example.com/1087")),
Err(crate::backend::BackendError::PlatformFailure {
context: "open_with_default: no opener launched for \"https://example.com/1087\""
.to_string(),
})
);
}
#[cfg(unix)]
struct RestorePath(Option<std::ffi::OsString>);
#[cfg(unix)]
impl RestorePath {
fn capture() -> Self {
Self(std::env::var_os("PATH"))
}
}
#[cfg(unix)]
impl Drop for RestorePath {
fn drop(&mut self) {
match self.0.take() {
Some(path) => std::env::set_var("PATH", path),
None => std::env::remove_var("PATH"),
}
}
}
}
#[cfg(feature = "tui")]
pub(crate) fn reveal_in_file_manager(path: &std::path::Path) -> crate::backend::ServiceResult<()> {
if try_reveal_in_file_manager(path) {
Ok(())
} else {
Err(crate::backend::BackendError::Unsupported)
}
}
#[cfg(feature = "tui")]
fn try_reveal_in_file_manager(path: &std::path::Path) -> bool {
#[cfg(windows)]
{
windows_explorer_select(path)
}
#[cfg(target_os = "macos")]
{
macos_reveal_in_file_manager_command(path).spawn().is_ok()
}
#[cfg(all(unix, not(target_os = "macos")))]
{
if linux_show_items_via_dbus(path) {
return true;
}
match path.parent() {
Some(parent) => try_open_with_default(parent.as_os_str()),
None => false,
}
}
}
#[cfg(all(target_os = "macos", feature = "tui"))]
fn macos_reveal_in_file_manager_command(path: &std::path::Path) -> std::process::Command {
let mut cmd = std::process::Command::new("open");
cmd.arg("-R")
.arg(path)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null());
cmd
}
#[cfg(all(windows, feature = "tui"))]
fn windows_explorer_select(path: &std::path::Path) -> bool {
shell_execute_reports_success(windows_explorer_select_code(path))
}
#[cfg(all(windows, feature = "tui"))]
fn windows_explorer_select_code(path: &std::path::Path) -> isize {
#[link(name = "shell32")]
unsafe extern "system" {
fn ShellExecuteW(
hwnd: *mut core::ffi::c_void,
operation: *const u16,
file: *const u16,
parameters: *const u16,
directory: *const u16,
show_cmd: i32,
) -> isize;
}
const SW_SHOWNORMAL: i32 = 1;
let operation = wide_nul_terminated("open");
let file = wide_nul_terminated("explorer.exe");
let parameters = wide_nul_terminated(&windows_explorer_select_parameters(path));
unsafe {
ShellExecuteW(
std::ptr::null_mut(),
operation.as_ptr(),
file.as_ptr(),
parameters.as_ptr(),
std::ptr::null(),
SW_SHOWNORMAL,
)
}
}
#[cfg(feature = "tui")]
#[cfg_attr(not(windows), allow(dead_code))]
fn windows_explorer_select_parameters(path: &std::path::Path) -> String {
let mut rendered = path.display().to_string();
while rendered.len() > 1 && (rendered.ends_with('\\') || rendered.ends_with('/')) {
rendered.pop();
}
format!("/select,\"{rendered}\"")
}
#[cfg(all(unix, not(target_os = "macos"), feature = "tui"))]
fn linux_show_items_via_dbus_command(path: &std::path::Path) -> std::process::Command {
let uri = path_to_file_uri(path);
let mut cmd = std::process::Command::new("dbus-send");
cmd.args([
"--session",
"--dest=org.freedesktop.FileManager1",
"--type=method_call",
"/org/freedesktop/FileManager1",
"org.freedesktop.FileManager1.ShowItems",
])
.arg(format!("array:string:{uri}"))
.arg("string:")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null());
cmd
}
#[cfg(all(unix, not(target_os = "macos"), feature = "tui"))]
fn linux_show_items_via_dbus(path: &std::path::Path) -> bool {
linux_show_items_via_dbus_command(path)
.status()
.map(|status| status.success())
.unwrap_or(false)
}
#[cfg(all(unix, not(target_os = "macos"), feature = "tui"))]
fn path_to_file_uri(path: &std::path::Path) -> String {
use std::os::unix::ffi::OsStrExt;
let mut out = String::from("file://");
for byte in path.as_os_str().as_bytes() {
let b = *byte;
let is_unreserved =
b.is_ascii_alphanumeric() || matches!(b, b'-' | b'.' | b'_' | b'~' | b'/');
if is_unreserved {
out.push(b as char);
} else {
out.push_str(&format!("%{b:02X}"));
}
}
out
}
#[cfg(all(test, feature = "tui"))]
mod reveal_in_file_manager_tests {
use super::*;
#[cfg(target_os = "macos")]
#[test]
fn macos_reveal_in_file_manager_command_uses_open_dash_r() {
let cmd = macos_reveal_in_file_manager_command(std::path::Path::new("/tmp/quadraui1092"));
assert_eq!(cmd.get_program(), "open");
assert_eq!(
cmd.get_args().collect::<Vec<_>>(),
["-R", "/tmp/quadraui1092"]
);
}
#[test]
fn windows_explorer_select_parameters_quotes_the_path() {
assert_eq!(
windows_explorer_select_parameters(std::path::Path::new(r"C:\a b\c.txt")),
r#"/select,"C:\a b\c.txt""#
);
}
#[test]
fn windows_explorer_select_parameters_strips_a_trailing_separator() {
assert_eq!(
windows_explorer_select_parameters(std::path::Path::new(r"C:\dir\")),
r#"/select,"C:\dir""#
);
assert_eq!(
windows_explorer_select_parameters(std::path::Path::new(r"C:\a b\c.txt")),
r#"/select,"C:\a b\c.txt""#
);
}
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn linux_show_items_via_dbus_command_targets_file_manager1() {
let cmd = linux_show_items_via_dbus_command(std::path::Path::new("/tmp/quadraui 1092"));
assert_eq!(cmd.get_program(), "dbus-send");
assert_eq!(
cmd.get_args().collect::<Vec<_>>(),
[
"--session",
"--dest=org.freedesktop.FileManager1",
"--type=method_call",
"/org/freedesktop/FileManager1",
"org.freedesktop.FileManager1.ShowItems",
"array:string:file:///tmp/quadraui%201092",
"string:",
]
);
}
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn path_to_file_uri_percent_encodes_spaces_and_commas() {
assert_eq!(
path_to_file_uri(std::path::Path::new("/tmp/a,b c/d.txt")),
"file:///tmp/a%2Cb%20c/d.txt"
);
}
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn reveal_in_file_manager_reports_unsupported_when_every_leg_fails() {
let _guard = PATH_OVERRIDE_TEST_LOCK
.lock()
.unwrap_or_else(|p| p.into_inner());
struct RestorePath(Option<std::ffi::OsString>);
impl Drop for RestorePath {
fn drop(&mut self) {
match self.0.take() {
Some(path) => std::env::set_var("PATH", path),
None => std::env::remove_var("PATH"),
}
}
}
let _restore = RestorePath(std::env::var_os("PATH"));
let empty_dir = tempfile::tempdir().expect("tempdir");
std::env::set_var("PATH", empty_dir.path());
let target = std::env::temp_dir().join("quadraui-1092-no-such-file.txt");
assert_eq!(
reveal_in_file_manager(&target),
Err(crate::backend::BackendError::Unsupported)
);
}
}
#[cfg(all(target_os = "macos", any(feature = "macos", feature = "tui")))]
pub(crate) fn applescript_escape(s: &str) -> String {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
#[cfg(all(target_os = "macos", any(feature = "macos", feature = "tui")))]
pub(crate) fn display_notification_script(title: &str, body: &str, silent: bool) -> String {
let mut script = format!(
"display notification \"{body}\" with title \"{title}\"",
body = applescript_escape(body),
title = applescript_escape(title),
);
if !silent {
script.push_str(" sound name \"\"");
}
script
}
#[cfg(all(target_os = "macos", feature = "tui"))]
pub(crate) fn tui_macos_notify_command(
title: &str,
body: &str,
silent: bool,
) -> std::process::Command {
let script = display_notification_script(title, body, silent);
let mut cmd = std::process::Command::new("osascript");
cmd.arg("-e").arg(script);
cmd
}
#[cfg(all(unix, not(target_os = "macos"), feature = "tui"))]
pub(crate) fn tui_notify_send_command(title: &str, body: &str) -> std::process::Command {
let mut cmd = std::process::Command::new("notify-send");
cmd.arg(title).arg(body);
cmd
}
#[cfg(all(test, unix, feature = "tui"))]
mod tui_send_notification_command_tests {
use super::*;
#[cfg(target_os = "macos")]
#[test]
fn tui_macos_notify_command_uses_osascript_with_the_display_notification_script() {
let cmd = tui_macos_notify_command("Build failed", "3 errors", false);
assert_eq!(cmd.get_program(), "osascript");
let args: Vec<_> = cmd.get_args().collect();
assert_eq!(args[0], "-e");
assert_eq!(
args[1],
display_notification_script("Build failed", "3 errors", false).as_str()
);
}
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn tui_notify_send_command_passes_title_and_body_as_separate_args() {
let cmd = tui_notify_send_command("Build failed", "3 errors");
assert_eq!(cmd.get_program(), "notify-send");
assert_eq!(
cmd.get_args().collect::<Vec<_>>(),
["Build failed", "3 errors"]
);
}
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn tui_notify_send_command_keeps_body_out_of_argv0() {
let cmd = tui_notify_send_command("t", "--urgency=critical");
assert_eq!(
cmd.get_args().collect::<Vec<_>>(),
["t", "--urgency=critical"]
);
}
}
#[cfg(all(test, target_os = "macos", any(feature = "macos", feature = "tui")))]
mod display_notification_script_tests {
use super::*;
#[test]
fn applescript_escape_handles_quotes_and_backslashes() {
assert_eq!(applescript_escape(""), "");
assert_eq!(applescript_escape("plain text"), "plain text");
assert_eq!(applescript_escape("a\"b"), "a\\\"b");
assert_eq!(applescript_escape("c\\d"), "c\\\\d");
assert_eq!(applescript_escape("e\"f\\g"), "e\\\"f\\\\g");
assert_eq!(applescript_escape("\\\""), "\\\\\\\"");
}
#[test]
fn display_notification_script_not_silent_adds_default_sound_clause() {
let script = display_notification_script("t", "b", false);
assert_eq!(
script,
"display notification \"b\" with title \"t\" sound name \"\""
);
}
#[test]
fn display_notification_script_silent_omits_sound_clause() {
let script = display_notification_script("t", "b", true);
assert_eq!(script, "display notification \"b\" with title \"t\"");
assert!(!script.contains("sound"));
}
#[test]
fn display_notification_script_escapes_title_and_body() {
let script = display_notification_script("t\"itle", "b\\ody", true);
assert_eq!(
script,
"display notification \"b\\\\ody\" with title \"t\\\"itle\""
);
}
}
#[cfg(any(feature = "gtk", all(feature = "macos", target_os = "macos")))]
pub(crate) fn map_arboard_error(call: &str, err: arboard::Error) -> crate::backend::BackendError {
crate::backend::BackendError::PlatformFailure {
context: format!("{call}: {err}"),
}
}
#[cfg(all(
test,
any(feature = "gtk", all(feature = "macos", target_os = "macos"))
))]
mod map_arboard_error_tests {
use super::*;
#[test]
fn wraps_the_call_name_and_error_into_a_platform_failure() {
let err = map_arboard_error("arboard::get_image", arboard::Error::ContentNotAvailable);
match err {
crate::backend::BackendError::PlatformFailure { context } => {
assert!(context.contains("arboard::get_image"));
assert!(context.contains(&arboard::Error::ContentNotAvailable.to_string()));
}
other => panic!("expected PlatformFailure, got {other:?}"),
}
}
}
#[cfg(any(
feature = "gtk",
all(feature = "win", any(target_os = "windows", test))
))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct SmokeConfig {
pub(crate) after_ms: u64,
pub(crate) paste_text: Option<String>,
}
#[cfg(any(
feature = "gtk",
all(feature = "win", any(target_os = "windows", test))
))]
impl SmokeConfig {
pub(crate) fn from_env(ms_var: &str, paste_var: &str) -> Option<Self> {
let after_ms = std::env::var(ms_var).ok()?.parse().ok()?;
let paste_text = std::env::var(paste_var).ok();
Some(Self {
after_ms,
paste_text,
})
}
}
#[cfg(any(
feature = "gtk",
all(feature = "win", any(target_os = "windows", test))
))]
pub(crate) fn smoke_size_ok(width: i32, height: i32, min_width: i32, min_height: i32) -> bool {
width >= min_width && height >= min_height
}
#[cfg(any(
feature = "gtk",
all(feature = "win", any(target_os = "windows", test))
))]
pub(crate) fn smoke_clipboard_round_trip_ok(written: &str, read_back: Option<&str>) -> bool {
read_back == Some(written)
}
#[cfg(any(
feature = "tui",
feature = "gtk",
all(feature = "macos", target_os = "macos"),
feature = "win"
))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub(crate) enum PasteModifier {
Ctrl,
Cmd,
}
#[cfg(any(
feature = "tui",
feature = "gtk",
all(feature = "macos", target_os = "macos"),
feature = "win"
))]
pub(crate) fn is_paste_keypress(
key: &crate::Key,
modifiers: &crate::Modifiers,
native: PasteModifier,
) -> bool {
let (native_held, other_held) = match native {
PasteModifier::Ctrl => (modifiers.ctrl, modifiers.cmd),
PasteModifier::Cmd => (modifiers.cmd, modifiers.ctrl),
};
matches!(key, crate::Key::Char('v') | crate::Key::Char('V'))
&& native_held
&& !other_held
&& !modifiers.alt
}
#[cfg(all(
test,
any(
feature = "gtk",
all(feature = "macos", target_os = "macos"),
feature = "win"
)
))]
pub(crate) const ALL_RESIZE_EDGES: [crate::backend::ResizeEdge; 8] = [
crate::backend::ResizeEdge::North,
crate::backend::ResizeEdge::South,
crate::backend::ResizeEdge::East,
crate::backend::ResizeEdge::West,
crate::backend::ResizeEdge::NorthEast,
crate::backend::ResizeEdge::NorthWest,
crate::backend::ResizeEdge::SouthEast,
crate::backend::ResizeEdge::SouthWest,
];
#[cfg(all(
test,
any(
feature = "gtk",
all(feature = "macos", target_os = "macos"),
feature = "win"
)
))]
pub(crate) fn all_pointer_shapes() -> [crate::backend::PointerShape; 9] {
use crate::backend::PointerShape;
[
PointerShape::Default,
PointerShape::Resize(ALL_RESIZE_EDGES[0]),
PointerShape::Resize(ALL_RESIZE_EDGES[1]),
PointerShape::Resize(ALL_RESIZE_EDGES[2]),
PointerShape::Resize(ALL_RESIZE_EDGES[3]),
PointerShape::Resize(ALL_RESIZE_EDGES[4]),
PointerShape::Resize(ALL_RESIZE_EDGES[5]),
PointerShape::Resize(ALL_RESIZE_EDGES[6]),
PointerShape::Resize(ALL_RESIZE_EDGES[7]),
]
}
#[cfg(any(feature = "win", all(feature = "macos", target_os = "macos")))]
pub(crate) fn report_caught_panic_once(site: &str, payload: &(dyn std::any::Any + Send)) {
static SEEN: std::sync::OnceLock<std::sync::Mutex<std::collections::HashSet<String>>> =
std::sync::OnceLock::new();
let mut seen = SEEN
.get_or_init(|| std::sync::Mutex::new(std::collections::HashSet::new()))
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
if !seen.insert(site.to_string()) {
return;
}
drop(seen);
let message = panic_payload_message(payload);
crate::diagnostics::emit(format!(
"quadraui: panic caught at {site} — frame/event skipped, process kept alive ({message})"
));
}
#[cfg(any(feature = "win", all(feature = "macos", target_os = "macos")))]
fn panic_payload_message(payload: &(dyn std::any::Any + Send)) -> String {
if let Some(s) = payload.downcast_ref::<&str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"<non-string panic payload>".to_string()
}
}
#[cfg(all(
test,
any(feature = "win", all(feature = "macos", target_os = "macos"))
))]
mod panic_report_tests {
use super::*;
use std::sync::{Arc, Mutex};
fn unique_site(tag: &str) -> String {
static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let n = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
format!("desktop::tests::{tag}#{n}")
}
fn install_recording_sink() -> Arc<Mutex<Vec<String>>> {
let received: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let received_in_sink = Arc::clone(&received);
crate::diagnostics::set_sink(move |msg: &str| {
received_in_sink.lock().unwrap().push(msg.to_string());
});
received
}
#[test]
fn first_report_at_a_site_reaches_the_sink() {
let _g = crate::diagnostics::test_guard();
let site = unique_site("first_report");
let received = install_recording_sink();
let payload: Box<dyn std::any::Any + Send> = Box::new("boom");
report_caught_panic_once(&site, payload.as_ref());
crate::diagnostics::clear_sink();
assert_eq!(received.lock().unwrap().len(), 1);
assert!(received.lock().unwrap()[0].contains(&site));
assert!(received.lock().unwrap()[0].contains("boom"));
}
#[test]
fn repeated_reports_at_the_same_site_are_reported_once() {
let _g = crate::diagnostics::test_guard();
let site = unique_site("repeated_reports");
let received = install_recording_sink();
for _ in 0..5 {
let payload: Box<dyn std::any::Any + Send> = Box::new("same panic every frame");
report_caught_panic_once(&site, payload.as_ref());
}
crate::diagnostics::clear_sink();
assert_eq!(
received.lock().unwrap().len(),
1,
"five panics at the same site must produce exactly one report, \
not one per frame"
);
}
#[test]
fn distinct_sites_each_report_independently() {
let _g = crate::diagnostics::test_guard();
let site_a = unique_site("distinct_a");
let site_b = unique_site("distinct_b");
let received = install_recording_sink();
let payload_a: Box<dyn std::any::Any + Send> = Box::new("a");
let payload_b: Box<dyn std::any::Any + Send> = Box::new("b");
report_caught_panic_once(&site_a, payload_a.as_ref());
report_caught_panic_once(&site_b, payload_b.as_ref());
crate::diagnostics::clear_sink();
assert_eq!(received.lock().unwrap().len(), 2);
}
#[test]
fn string_payload_message_is_included() {
let _g = crate::diagnostics::test_guard();
let site = unique_site("string_payload");
let received = install_recording_sink();
let payload: Box<dyn std::any::Any + Send> = Box::new(format!("formatted {}", 42));
report_caught_panic_once(&site, payload.as_ref());
crate::diagnostics::clear_sink();
assert!(received.lock().unwrap()[0].contains("formatted 42"));
}
#[test]
fn non_string_payload_does_not_panic_the_reporter() {
let _g = crate::diagnostics::test_guard();
let site = unique_site("non_string_payload");
let received = install_recording_sink();
let payload: Box<dyn std::any::Any + Send> = Box::new(42i32);
report_caught_panic_once(&site, payload.as_ref());
crate::diagnostics::clear_sink();
assert_eq!(received.lock().unwrap().len(), 1);
}
}
#[cfg(all(
test,
any(feature = "gtk", all(feature = "macos", target_os = "macos"))
))]
mod window_drag_arm_tests {
use super::*;
#[test]
fn new_is_unarmed() {
let arm: WindowDragArm<i32> = WindowDragArm::new();
assert!(!arm.is_armed());
assert!(arm.origin().is_none());
}
#[test]
fn default_is_unarmed() {
let arm: WindowDragArm<i32> = WindowDragArm::default();
assert!(!arm.is_armed());
}
#[test]
fn arm_records_press_and_origin() {
let mut arm = WindowDragArm::new();
arm.arm(42, 10.0, 20.0);
assert!(arm.is_armed());
assert_eq!(arm.origin(), Some((10.0, 20.0)));
}
#[test]
fn arm_overwrites_a_previous_unconsumed_request() {
let mut arm = WindowDragArm::new();
arm.arm(1, 0.0, 0.0);
arm.arm(2, 5.0, 5.0);
assert_eq!(arm.origin(), Some((5.0, 5.0)));
assert_eq!(arm.take(), Some(2));
}
#[test]
fn discard_clears_state() {
let mut arm = WindowDragArm::new();
arm.arm(1, 0.0, 0.0);
arm.discard();
assert!(!arm.is_armed());
assert!(arm.origin().is_none());
}
#[test]
fn take_consumes_and_clears() {
let mut arm = WindowDragArm::new();
arm.arm("press", 0.0, 0.0);
assert_eq!(arm.take(), Some("press"));
assert!(!arm.is_armed());
assert_eq!(arm.take(), None);
}
#[test]
fn commit_if_past_threshold_none_when_unarmed() {
let mut arm: WindowDragArm<i32> = WindowDragArm::new();
assert_eq!(arm.commit_if_past_threshold(100.0, 100.0, 8.0), None);
}
#[test]
fn commit_if_past_threshold_none_under_threshold_and_stays_armed() {
let mut arm = WindowDragArm::new();
arm.arm("press", 0.0, 0.0);
assert_eq!(arm.commit_if_past_threshold(3.0, 4.0, 8.0), None);
assert!(arm.is_armed(), "still under threshold: must stay armed");
}
#[test]
fn commit_if_past_threshold_takes_at_exactly_the_threshold() {
let mut arm = WindowDragArm::new();
arm.arm("press", 0.0, 0.0);
assert_eq!(arm.commit_if_past_threshold(8.0, 0.0, 8.0), Some("press"));
assert!(!arm.is_armed());
}
#[test]
fn commit_if_past_threshold_takes_past_threshold() {
let mut arm = WindowDragArm::new();
arm.arm("press", 10.0, 10.0);
assert_eq!(arm.commit_if_past_threshold(16.0, 18.0, 8.0), Some("press"));
assert!(!arm.is_armed());
}
}
#[cfg(all(test, any(feature = "gtk", feature = "win")))]
mod modal_pump_tests {
use super::*;
#[test]
fn new_starts_at_zero() {
let depth = ModalPumpDepth::new();
assert_eq!(depth.get(), 0);
assert!(!depth.is_pumping());
}
#[test]
fn default_starts_at_zero() {
let depth = ModalPumpDepth::default();
assert_eq!(depth.get(), 0);
}
#[test]
fn guard_increments_while_held_and_decrements_on_drop() {
let depth = ModalPumpDepth::new();
{
let _guard = ModalPumpGuard::new(&depth);
assert_eq!(depth.get(), 1);
assert!(depth.is_pumping());
}
assert_eq!(depth.get(), 0);
assert!(!depth.is_pumping());
}
#[test]
fn nested_guards_stay_pumping_until_the_outermost_drops() {
let depth = ModalPumpDepth::new();
let outer = ModalPumpGuard::new(&depth);
assert_eq!(depth.get(), 1);
{
let _inner = ModalPumpGuard::new(&depth);
assert_eq!(depth.get(), 2);
}
assert_eq!(depth.get(), 1, "inner drop must not clear the outer guard");
assert!(depth.is_pumping());
drop(outer);
assert_eq!(depth.get(), 0);
}
#[test]
fn cloned_handle_observes_the_same_counter() {
let depth = ModalPumpDepth::new();
let handle = depth.clone();
let _guard = ModalPumpGuard::new(&depth);
assert_eq!(
handle.get(),
1,
"clone must share the underlying Rc<Cell<>>"
);
}
}
#[cfg(all(test, any(feature = "gtk", feature = "win")))]
mod smoke_config_tests {
use super::*;
use std::env;
use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn from_env_none_when_ms_var_unset() {
let _lock = ENV_LOCK.lock().unwrap();
let ms_var = "QUADRAUI_DESKTOP_TEST_SMOKE_MS_UNSET";
let paste_var = "QUADRAUI_DESKTOP_TEST_SMOKE_PASTE_UNSET";
env::remove_var(ms_var);
env::remove_var(paste_var);
assert_eq!(SmokeConfig::from_env(ms_var, paste_var), None);
}
#[test]
fn from_env_none_when_ms_var_unparseable() {
let _lock = ENV_LOCK.lock().unwrap();
let ms_var = "QUADRAUI_DESKTOP_TEST_SMOKE_MS_BAD";
env::set_var(ms_var, "not-a-number");
assert_eq!(
SmokeConfig::from_env(ms_var, "QUADRAUI_DESKTOP_TEST_UNSET"),
None
);
env::remove_var(ms_var);
}
#[test]
fn from_env_reads_ms_and_optional_paste() {
let _lock = ENV_LOCK.lock().unwrap();
let ms_var = "QUADRAUI_DESKTOP_TEST_SMOKE_MS_OK";
let paste_var = "QUADRAUI_DESKTOP_TEST_SMOKE_PASTE_OK";
env::set_var(ms_var, "250");
env::set_var(paste_var, "hello");
assert_eq!(
SmokeConfig::from_env(ms_var, paste_var),
Some(SmokeConfig {
after_ms: 250,
paste_text: Some("hello".to_string()),
})
);
env::remove_var(ms_var);
env::remove_var(paste_var);
}
#[test]
fn from_env_paste_none_when_paste_var_unset() {
let _lock = ENV_LOCK.lock().unwrap();
let ms_var = "QUADRAUI_DESKTOP_TEST_SMOKE_MS_NOPASTE";
let paste_var = "QUADRAUI_DESKTOP_TEST_SMOKE_PASTE_NOPASTE_UNSET";
env::remove_var(paste_var);
env::set_var(ms_var, "10");
assert_eq!(
SmokeConfig::from_env(ms_var, paste_var),
Some(SmokeConfig {
after_ms: 10,
paste_text: None,
})
);
env::remove_var(ms_var);
}
#[test]
fn smoke_size_ok_accepts_at_or_above_the_floor() {
assert!(smoke_size_ok(200, 150, 200, 150));
assert!(smoke_size_ok(800, 600, 200, 150));
}
#[test]
fn smoke_size_ok_rejects_below_the_floor() {
assert!(!smoke_size_ok(199, 150, 200, 150));
assert!(!smoke_size_ok(200, 149, 200, 150));
assert!(!smoke_size_ok(8, 600, 200, 150));
}
#[test]
fn clipboard_round_trip_ok_when_read_back_matches() {
assert!(smoke_clipboard_round_trip_ok(
"quadraui smoke",
Some("quadraui smoke")
));
}
#[test]
fn clipboard_round_trip_rejects_a_missing_or_mismatched_read() {
assert!(!smoke_clipboard_round_trip_ok("quadraui smoke", None));
assert!(!smoke_clipboard_round_trip_ok(
"quadraui smoke",
Some("something else")
));
}
}
#[cfg(all(
test,
any(
feature = "tui",
feature = "gtk",
all(feature = "macos", target_os = "macos"),
feature = "win"
)
))]
mod is_paste_keypress_tests {
use super::*;
use crate::{Key, Modifiers};
fn mods(ctrl: bool, shift: bool, alt: bool, cmd: bool) -> Modifiers {
Modifiers {
ctrl,
shift,
alt,
cmd,
}
}
#[test]
fn plain_ctrl_v_is_a_paste_keypress_for_ctrl_native_backends() {
assert!(is_paste_keypress(
&Key::Char('v'),
&mods(true, false, false, false),
PasteModifier::Ctrl
));
assert!(is_paste_keypress(
&Key::Char('V'),
&mods(true, false, false, false),
PasteModifier::Ctrl
));
}
#[test]
fn plain_cmd_v_is_a_paste_keypress_for_cmd_native_backends() {
assert!(is_paste_keypress(
&Key::Char('v'),
&mods(false, false, false, true),
PasteModifier::Cmd
));
}
#[test]
fn plain_cmd_v_is_not_a_paste_keypress_for_ctrl_native_backends() {
assert!(!is_paste_keypress(
&Key::Char('v'),
&mods(false, false, false, true),
PasteModifier::Ctrl
));
}
#[test]
fn plain_ctrl_v_is_not_a_paste_keypress_for_cmd_native_backends() {
assert!(!is_paste_keypress(
&Key::Char('v'),
&mods(true, false, false, false),
PasteModifier::Cmd
));
}
#[test]
fn ctrl_shift_v_is_a_paste_keypress_for_ctrl_native_backends() {
assert!(is_paste_keypress(
&Key::Char('v'),
&mods(true, true, false, false),
PasteModifier::Ctrl
));
}
#[test]
fn cmd_shift_v_is_a_paste_keypress_for_cmd_native_backends() {
assert!(is_paste_keypress(
&Key::Char('v'),
&mods(false, true, false, true),
PasteModifier::Cmd
));
}
#[test]
fn ctrl_alt_v_is_not_a_paste_keypress() {
assert!(!is_paste_keypress(
&Key::Char('v'),
&mods(true, false, true, false),
PasteModifier::Ctrl
));
}
#[test]
fn cmd_alt_v_is_not_a_paste_keypress() {
assert!(!is_paste_keypress(
&Key::Char('v'),
&mods(false, false, true, true),
PasteModifier::Cmd
));
}
#[test]
fn both_ctrl_and_cmd_is_not_a_paste_keypress() {
assert!(!is_paste_keypress(
&Key::Char('v'),
&mods(true, false, false, true),
PasteModifier::Ctrl
));
assert!(!is_paste_keypress(
&Key::Char('v'),
&mods(true, false, false, true),
PasteModifier::Cmd
));
}
#[test]
fn shift_v_alone_is_not_a_paste_keypress() {
assert!(!is_paste_keypress(
&Key::Char('v'),
&mods(false, true, false, false),
PasteModifier::Ctrl
));
}
#[test]
fn plain_v_is_not_a_paste_keypress() {
assert!(!is_paste_keypress(
&Key::Char('v'),
&Modifiers::default(),
PasteModifier::Ctrl
));
}
#[test]
fn ctrl_c_is_not_a_paste_keypress() {
assert!(!is_paste_keypress(
&Key::Char('c'),
&mods(true, false, false, false),
PasteModifier::Ctrl
));
}
}
#[cfg(all(
test,
any(
feature = "gtk",
all(feature = "macos", target_os = "macos"),
feature = "win"
)
))]
mod pointer_shape_scaffold_tests {
use super::*;
use crate::backend::{PointerShape, ResizeEdge};
#[test]
fn all_resize_edges_has_every_variant_exactly_once() {
let mut seen: Vec<ResizeEdge> = ALL_RESIZE_EDGES.to_vec();
seen.sort_by_key(|e| format!("{e:?}"));
seen.dedup();
assert_eq!(
seen.len(),
ALL_RESIZE_EDGES.len(),
"ALL_RESIZE_EDGES must list every ResizeEdge variant exactly once"
);
}
#[test]
fn all_pointer_shapes_is_default_plus_one_resize_per_edge() {
let shapes = all_pointer_shapes();
assert_eq!(shapes.len(), ALL_RESIZE_EDGES.len() + 1);
assert_eq!(shapes[0], PointerShape::Default);
for (shape, edge) in shapes[1..].iter().zip(ALL_RESIZE_EDGES.iter()) {
assert_eq!(*shape, PointerShape::Resize(*edge));
}
}
}