From 80dd0875d0223cf0ad7a3d024ceaae5afe70001e Mon Sep 17 00:00:00 2001 From: Avi Fenesh Date: Thu, 28 May 2026 10:32:25 +0300 Subject: [PATCH] Align downstream Computer Use readiness and identity hooks --- CHANGELOG.md | 14 +++ README.md | 14 ++- src/cosmic_helper.rs | 13 ++- src/diagnostics.rs | 174 ++++++++++++++++++++++++++++---- src/gnome_extension.rs | 52 ++++++++-- src/identity.rs | 25 +++++ src/main.rs | 1 + src/server.rs | 54 ++++++---- src/windowing/backends/gnome.rs | 12 ++- 9 files changed, 300 insertions(+), 59 deletions(-) create mode 100644 src/identity.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cb2d147..54ba79d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Added build-time GNOME extension / DBus identity overrides (`CUL_*`) so the + `codex-desktop-linux` embedded copy can share this source while keeping its + Codex extension identity, plus runtime `CODEX_COMPUTER_USE_*` aliases for the + embedded input/backend knobs. + +### Fixed +- Ported downstream Linux readiness fixes: `doctor` now treats direct + `/dev/uinput` and the XDG RemoteDesktop portal as valid development-input + backends instead of requiring `ydotoold` in every ready setup. +- Ported downstream session hydration fixes for X11 launches by carrying + `XAUTHORITY` through environment hydration and checking the same-user namespace + init process when it owns the graphical session environment. + ## [0.2.4] - 2026-05-25 Primarily a documentation release that refreshes the crates.io and npm README diff --git a/README.md b/README.md index 76062ce..0a25048 100644 --- a/README.md +++ b/README.md @@ -304,10 +304,16 @@ Most setups need none of these — `doctor` and the installers pick sensible def | Variable | Effect | | --- | --- | -| `COMPUTER_USE_LINUX_COSMIC_HELPER` | Path to the `computer-use-linux-cosmic` helper when it isn't next to the binary or on `PATH`. | -| `CU_DISABLE_ABS_POINTER` | Disable the uinput absolute pointer and click through `ydotool` instead (for setups where the abs-pointer device misbehaves). | -| `COMPUTER_USE_LINUX_FORCE_PORTAL_POINTER` / `…_KEYBOARD` | Always route pointer / keyboard through the RemoteDesktop portal on Wayland, skipping auto-detection. | -| `COMPUTER_USE_LINUX_FORCE_YDOTOOL_POINTER` / `…_KEYBOARD` | Always route pointer / keyboard through `ydotool`, skipping the portal and KDE clipboard paths. | +| `COMPUTER_USE_LINUX_COSMIC_HELPER` | Path to the `computer-use-linux-cosmic` helper when it isn't next to the binary or on `PATH` (`CODEX_COMPUTER_USE_COSMIC_HELPER` is also accepted by embedded Codex builds). | +| `CU_DISABLE_ABS_POINTER` | Disable the uinput absolute pointer and click through `ydotool` instead (for setups where the abs-pointer device misbehaves); embedded Codex builds may use `CODEX_COMPUTER_USE_DISABLE_ABS_POINTER`. | +| `COMPUTER_USE_LINUX_FORCE_PORTAL_POINTER` / `…_KEYBOARD` | Always route pointer / keyboard through the RemoteDesktop portal on Wayland, skipping auto-detection; embedded Codex builds may use `CODEX_COMPUTER_USE_FORCE_PORTAL_POINTER` / `…_KEYBOARD`. | +| `COMPUTER_USE_LINUX_FORCE_YDOTOOL_POINTER` / `…_KEYBOARD` | Always route pointer / keyboard through `ydotool`, skipping the portal and KDE clipboard paths; embedded Codex builds may use `CODEX_COMPUTER_USE_FORCE_YDOTOOL_POINTER` / `…_KEYBOARD`. | + +**Build-time identity overrides** (set while compiling a downstream embedded +bundle): `CUL_GNOME_EXTENSION_UUID`, `CUL_DBUS_SERVICE`, and +`CUL_DBUS_OBJECT_PATH` replace the default standalone GNOME Shell extension +UUID and DBus endpoint in both the Rust probes and the generated extension +files. **npm wrapper** (set during `npm install`, or before running): diff --git a/src/cosmic_helper.rs b/src/cosmic_helper.rs index 70cc7cd..b4fb7c5 100644 --- a/src/cosmic_helper.rs +++ b/src/cosmic_helper.rs @@ -23,10 +23,10 @@ pub struct CosmicHelperActivation { } pub fn resolve_helper_binary() -> Result { - if let Some(path) = env::var("COMPUTER_USE_LINUX_COSMIC_HELPER") - .ok() - .filter(|value| !value.trim().is_empty()) - { + if let Some(path) = env_var_first(&[ + "COMPUTER_USE_LINUX_COSMIC_HELPER", + "CODEX_COMPUTER_USE_COSMIC_HELPER", + ]) { let path = PathBuf::from(path); if path.exists() { return Ok(path); @@ -47,6 +47,11 @@ pub fn resolve_helper_binary() -> Result { bail!("COSMIC helper binary {COSMIC_HELPER_BINARY} not found") } +fn env_var_first(keys: &[&str]) -> Option { + keys.iter() + .find_map(|key| env::var(key).ok().filter(|value| !value.trim().is_empty())) +} + pub fn probe() -> Result { run_json_command(["probe"]) } diff --git a/src/diagnostics.rs b/src/diagnostics.rs index 22db3d7..d393bc4 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -8,7 +8,10 @@ use std::{ collections::{BTreeMap, HashMap}, env, fs, fs::OpenOptions, - os::unix::net::{UnixDatagram, UnixStream}, + os::unix::{ + fs::MetadataExt, + net::{UnixDatagram, UnixStream}, + }, path::{Path, PathBuf}, process::Command, }; @@ -18,6 +21,7 @@ const DESKTOP_ENV_KEYS: &[&str] = &[ "DESKTOP_SESSION", "DISPLAY", "HYPRLAND_INSTANCE_SIGNATURE", + "XAUTHORITY", "YDOTOOL_SOCKET", "XDG_SESSION_DESKTOP", "WAYLAND_DISPLAY", @@ -72,6 +76,7 @@ pub struct PlatformReport { pub xdg_current_desktop: Option, pub wayland_display: Option, pub display: Option, + pub xauthority: Option, pub dbus_session_bus_address: Option, pub xdg_runtime_dir: Option, pub gnome_shell_version: Check, @@ -170,7 +175,7 @@ pub fn doctor_report() -> DoctorReport { let accessibility = accessibility_report(); let windowing = windowing_report(&platform); let input = input_report(); - let readiness = readiness_report(&platform, &accessibility, &windowing, &input); + let readiness = readiness_report(&platform, &portals, &accessibility, &windowing, &input); let capabilities = capability_map(&platform, &portals, &accessibility, &windowing, &input); @@ -346,6 +351,7 @@ fn hydrate_desktop_env_from_map(process_env: &HashMap) { fn desktop_process_environments() -> Vec> { let mut environments = Vec::new(); + let mut visited_pids = Vec::new(); let mut pid = parent_pid("self"); for _ in 0..8 { @@ -356,12 +362,20 @@ fn desktop_process_environments() -> Vec> { break; } + visited_pids.push(current_pid); if let Some(process_env) = read_process_environ(current_pid) { environments.push(process_env); } pid = parent_pid(¤t_pid.to_string()); } + if !visited_pids.contains(&1) && process_owner_matches_current_user(1) { + if let Some(process_env) = read_process_environ(1).filter(process_env_has_graphical_display) + { + environments.push(process_env); + } + } + environments } @@ -382,6 +396,22 @@ fn read_process_environ(pid: u32) -> Option> { Some(parse_environ(&bytes)) } +fn process_owner_matches_current_user(pid: u32) -> bool { + let Some(current_uid) = user_id().and_then(|uid| uid.parse::().ok()) else { + return false; + }; + fs::metadata(format!("/proc/{pid}")) + .ok() + .is_some_and(|metadata| metadata.uid() == current_uid) +} + +fn process_env_has_graphical_display(process_env: &HashMap) -> bool { + process_env + .get("DISPLAY") + .or_else(|| process_env.get("WAYLAND_DISPLAY")) + .is_some_and(|value| !value.trim().is_empty()) +} + fn parse_environ(bytes: &[u8]) -> HashMap { bytes .split(|byte| *byte == 0) @@ -485,6 +515,7 @@ fn platform_report() -> PlatformReport { xdg_current_desktop: env_var("XDG_CURRENT_DESKTOP"), wayland_display: env_var("WAYLAND_DISPLAY"), display: env_var("DISPLAY"), + xauthority: env_var("XAUTHORITY"), dbus_session_bus_address: dbus_session_address(), xdg_runtime_dir: xdg_runtime_dir().map(|path| path.display().to_string()), gnome_shell_version: command_check("gnome-shell", &["--version"]), @@ -588,6 +619,7 @@ fn input_report() -> InputReport { fn readiness_report( platform: &PlatformReport, + portals: &PortalReport, accessibility: &AccessibilityReport, windowing: &WindowingReport, input: &InputReport, @@ -597,8 +629,7 @@ fn readiness_report( let can_query_windows = windowing.can_list_windows; let can_focus_apps = windowing.can_focus_apps; let can_focus_windows = windowing.can_focus_windows; - let can_send_development_input = - input.ydotool.ok && input.ydotoold.ok && input.ydotool_socket.ok; + let can_send_development_input = can_send_development_input(portals, input); if !can_build_accessibility_tree { blockers.push( @@ -625,7 +656,7 @@ fn readiness_report( if !can_send_development_input { blockers.push( - "Development input fallback is unavailable; ydotool needs a running ydotoold daemon with a connectable ydotoold socket." + "Development input is unavailable; enable read/write /dev/uinput, XDG RemoteDesktop portal input, or ydotool with a connectable ydotoold socket." .to_string(), ); } @@ -645,10 +676,10 @@ fn readiness_report( } else if !can_focus_windows { "Enable an exact-focus window backend before using window_id, title, or terminal-targeted input.".to_string() } else if !can_send_development_input { - "Fix ydotool input access: start ydotoold with a socket accessible to this desktop user." + "Enable a supported input backend: grant read/write /dev/uinput, enable the XDG RemoteDesktop portal, or start ydotoold with a socket accessible to this desktop user." .to_string() } else { - "Computer Use is ready: AT-SPI tree support, window targeting, and ydotool input fallback are available." + "Computer Use is ready: AT-SPI tree support, window targeting, and a Linux input backend are available." .to_string() }; @@ -664,6 +695,12 @@ fn readiness_report( } } +fn can_send_development_input(portals: &PortalReport, input: &InputReport) -> bool { + input.uinput.ok + || portals.remote_desktop.ok + || input.ydotool.ok && input.ydotoold.ok && input.ydotool_socket.ok +} + fn is_cosmic_wayland_platform(platform: &PlatformReport) -> bool { platform .xdg_current_desktop @@ -928,12 +965,25 @@ mod tests { xdg_current_desktop: Some("GNOME".to_string()), wayland_display: Some("wayland-0".to_string()), display: Some(":0".to_string()), + xauthority: Some("/run/user/1000/Xauthority".to_string()), dbus_session_bus_address: Some("unix:path=/run/user/1000/bus".to_string()), xdg_runtime_dir: Some("/run/user/1000".to_string()), gnome_shell_version: Check::ok("GNOME Shell 46.0"), } } + fn portal_report(remote_desktop: Check) -> PortalReport { + PortalReport { + desktop_portal: Check::ok("ok"), + remote_desktop, + screencast: Check::fail("missing"), + screenshot: Check::fail("missing"), + input_capture: Check::fail("missing"), + mutter_remote_desktop: Check::fail("missing"), + mutter_screencast: Check::fail("missing"), + } + } + fn accessibility_report( at_spi_bus: Check, toolkit_accessibility: Check, @@ -1031,6 +1081,23 @@ mod tests { assert!(!environment.contains_key("NO_EQUALS")); } + #[test] + fn desktop_env_hydration_includes_xauthority() { + assert!(DESKTOP_ENV_KEYS.contains(&"XAUTHORITY")); + } + + #[test] + fn graphical_process_env_requires_display() { + let with_display = HashMap::from([("DISPLAY".to_string(), ":0".to_string())]); + let with_wayland = + HashMap::from([("WAYLAND_DISPLAY".to_string(), "wayland-0".to_string())]); + let without_display = HashMap::from([("XAUTHORITY".to_string(), "/tmp/xauth".to_string())]); + + assert!(process_env_has_graphical_display(&with_display)); + assert!(process_env_has_graphical_display(&with_wayland)); + assert!(!process_env_has_graphical_display(&without_display)); + } + #[test] fn parses_systemd_show_environment_output() { let environment = parse_line_environment( @@ -1058,7 +1125,13 @@ mod tests { let windowing = windowing_report(true, false); let input = input_report(true); - let readiness = readiness_report(&platform, &accessibility, &windowing, &input); + let readiness = readiness_report( + &platform, + &portal_report(Check::fail("missing")), + &accessibility, + &windowing, + &input, + ); assert!(readiness.can_query_windows); assert!(!readiness.can_focus_windows); @@ -1082,7 +1155,13 @@ mod tests { windowing.can_focus_windows = true; let input = input_report(true); - let readiness = readiness_report(&platform, &accessibility, &windowing, &input); + let readiness = readiness_report( + &platform, + &portal_report(Check::fail("missing")), + &accessibility, + &windowing, + &input, + ); assert!(readiness.can_query_windows); assert!(readiness.can_focus_apps); @@ -1097,7 +1176,13 @@ mod tests { let windowing = windowing_report(true, true); let input = input_report(true); - let readiness = readiness_report(&platform, &accessibility, &windowing, &input); + let readiness = readiness_report( + &platform, + &portal_report(Check::fail("missing")), + &accessibility, + &windowing, + &input, + ); assert!(readiness.blockers.is_empty()); assert!(readiness @@ -1121,14 +1206,20 @@ mod tests { Check::fail("/dev/uinput: Permission denied"), ); - let readiness = readiness_report(&platform, &accessibility, &windowing, &input); + let readiness = readiness_report( + &platform, + &portal_report(Check::fail("missing")), + &accessibility, + &windowing, + &input, + ); assert!(readiness.can_send_development_input); assert!(readiness.blockers.is_empty()); } #[test] - fn readiness_rejects_direct_uinput_without_connectable_ydotool_socket() { + fn readiness_accepts_direct_uinput_without_connectable_ydotool_socket() { let platform = platform_report(); let accessibility = accessibility_report(Check::ok("bus"), Check::ok("true")); let windowing = windowing_report(true, true); @@ -1139,13 +1230,40 @@ mod tests { Check::ok("read/write: /dev/uinput"), ); - let readiness = readiness_report(&platform, &accessibility, &windowing, &input); + let readiness = readiness_report( + &platform, + &portal_report(Check::fail("missing")), + &accessibility, + &windowing, + &input, + ); - assert!(!readiness.can_send_development_input); - assert!(readiness - .blockers - .iter() - .any(|blocker| blocker.contains("connectable ydotoold socket"))); + assert!(readiness.can_send_development_input); + assert!(readiness.blockers.is_empty()); + } + + #[test] + fn readiness_accepts_remote_desktop_portal_without_local_input_backend() { + let platform = platform_report(); + let accessibility = accessibility_report(Check::ok("bus"), Check::ok("true")); + let windowing = windowing_report(true, true); + let input = input_report_parts( + Check::fail("missing ydotool"), + Check::fail("ydotoold not running"), + Check::fail("no connectable ydotool socket"), + Check::fail("/dev/uinput: Permission denied"), + ); + + let readiness = readiness_report( + &platform, + &portal_report(Check::ok("org.freedesktop.portal.RemoteDesktop")), + &accessibility, + &windowing, + &input, + ); + + assert!(readiness.can_send_development_input); + assert!(readiness.blockers.is_empty()); } #[test] @@ -1160,16 +1278,22 @@ mod tests { Check::fail("/dev/uinput: Permission denied"), ); - let readiness = readiness_report(&platform, &accessibility, &windowing, &input); + let readiness = readiness_report( + &platform, + &portal_report(Check::fail("missing")), + &accessibility, + &windowing, + &input, + ); assert!(!readiness.can_send_development_input); assert!(readiness .recommended_next_step - .contains("Fix ydotool input access")); + .contains("Enable a supported input backend")); assert!(readiness .blockers .iter() - .any(|blocker| blocker.contains("connectable ydotoold socket"))); + .any(|blocker| blocker.contains("Development input is unavailable"))); } #[test] @@ -1218,7 +1342,13 @@ mod tests { let windowing = windowing_report(false, false); let input = input_report(true); - let readiness = readiness_report(&platform, &accessibility, &windowing, &input); + let readiness = readiness_report( + &platform, + &portal_report(Check::fail("missing")), + &accessibility, + &windowing, + &input, + ); assert!(readiness .blockers diff --git a/src/gnome_extension.rs b/src/gnome_extension.rs index 07e9f24..99bbde6 100644 --- a/src/gnome_extension.rs +++ b/src/gnome_extension.rs @@ -1,4 +1,5 @@ use crate::diagnostics::hydrate_session_bus_env; +use crate::identity; use crate::windowing::backends::gnome::list_extension_windows; use crate::windows::{window_permission_hint, WindowInfo}; use schemars::JsonSchema; @@ -9,7 +10,7 @@ use std::{ process::Command, }; -pub const UUID: &str = "computer-use-linux@avifenesh.dev"; +pub const UUID: &str = identity::GNOME_EXTENSION_UUID; const METADATA_JSON: &str = include_str!("../gnome-shell-extension/computer-use-linux@avifenesh.dev/metadata.json"); const EXTENSION_JS: &str = @@ -91,13 +92,16 @@ pub async fn setup_window_targeting_report() -> WindowTargetingSetupReport { fn write_extension_files(extension_dir: &Path) -> Result<(), String> { fs::create_dir_all(extension_dir) .map_err(|error| format!("failed to create {}: {error}", extension_dir.display()))?; - fs::write(extension_dir.join("metadata.json"), METADATA_JSON).map_err(|error| { + let metadata_json = render_extension_asset(METADATA_JSON); + let extension_js = render_extension_asset(EXTENSION_JS); + + fs::write(extension_dir.join("metadata.json"), metadata_json).map_err(|error| { format!( "failed to write {}: {error}", extension_dir.join("metadata.json").display() ) })?; - fs::write(extension_dir.join("extension.js"), EXTENSION_JS).map_err(|error| { + fs::write(extension_dir.join("extension.js"), extension_js).map_err(|error| { format!( "failed to write {}: {error}", extension_dir.join("extension.js").display() @@ -106,6 +110,19 @@ fn write_extension_files(extension_dir: &Path) -> Result<(), String> { Ok(()) } +fn render_extension_asset(asset: &str) -> String { + asset + .replace( + identity::DEFAULT_GNOME_EXTENSION_UUID, + identity::GNOME_EXTENSION_UUID, + ) + .replace(identity::DEFAULT_DBUS_SERVICE, identity::DBUS_SERVICE) + .replace( + identity::DEFAULT_DBUS_OBJECT_PATH, + identity::DBUS_OBJECT_PATH, + ) +} + fn run_gnome_extensions_enable() -> SetupCommandReport { let mut command = Command::new("gnome-extensions"); command.args(["enable", UUID]); @@ -268,7 +285,7 @@ mod tests { fn enabled_extensions_literal_adds_uuid_to_existing_list() { assert_eq!( enabled_extensions_literal("['ubuntu-dock@ubuntu.com']").unwrap(), - "['ubuntu-dock@ubuntu.com', 'computer-use-linux@avifenesh.dev']" + format!("['ubuntu-dock@ubuntu.com', '{UUID}']") ); } @@ -276,14 +293,35 @@ mod tests { fn enabled_extensions_literal_handles_empty_typed_array() { assert_eq!( enabled_extensions_literal("@as []").unwrap(), - "['computer-use-linux@avifenesh.dev']" + format!("['{UUID}']") ); } #[test] fn enabled_extensions_literal_is_idempotent() { - let value = "['computer-use-linux@avifenesh.dev']"; + let value = format!("['{UUID}']"); + + assert_eq!(enabled_extensions_literal(&value).unwrap(), value); + } + + #[test] + fn rendered_metadata_uses_build_identity() { + let rendered = render_extension_asset(METADATA_JSON); + + assert!(rendered.contains(&format!("\"uuid\": \"{UUID}\""))); + } + + #[test] + fn rendered_extension_uses_build_identity() { + let rendered = render_extension_asset(EXTENSION_JS); - assert_eq!(enabled_extensions_literal(value).unwrap(), value); + assert!(rendered.contains(&format!( + "const SERVICE_NAME = '{service}'", + service = identity::DBUS_SERVICE + ))); + assert!(rendered.contains(&format!( + "const OBJECT_PATH = '{path}'", + path = identity::DBUS_OBJECT_PATH + ))); } } diff --git a/src/identity.rs b/src/identity.rs new file mode 100644 index 0000000..a8d57c6 --- /dev/null +++ b/src/identity.rs @@ -0,0 +1,25 @@ +//! Build-time identity values for the optional GNOME Shell window-control bridge. +//! +//! The standalone crate keeps the public `computer-use-linux` DBus/extension +//! identity by default. Downstream bundles can compile with `CUL_*` env vars to +//! brand the same code under their own GNOME extension UUID and DBus endpoint +//! without carrying source-only string patches. + +pub const DEFAULT_GNOME_EXTENSION_UUID: &str = "computer-use-linux@avifenesh.dev"; +pub const DEFAULT_DBUS_SERVICE: &str = "dev.avifenesh.ComputerUseLinux.WindowControl"; +pub const DEFAULT_DBUS_OBJECT_PATH: &str = "/dev/avifenesh/ComputerUseLinux/WindowControl"; + +pub const GNOME_EXTENSION_UUID: &str = match option_env!("CUL_GNOME_EXTENSION_UUID") { + Some(value) => value, + None => DEFAULT_GNOME_EXTENSION_UUID, +}; + +pub const DBUS_SERVICE: &str = match option_env!("CUL_DBUS_SERVICE") { + Some(value) => value, + None => DEFAULT_DBUS_SERVICE, +}; + +pub const DBUS_OBJECT_PATH: &str = match option_env!("CUL_DBUS_OBJECT_PATH") { + Some(value) => value, + None => DEFAULT_DBUS_OBJECT_PATH, +}; diff --git a/src/main.rs b/src/main.rs index 8b44435..9274038 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod atspi_tree; mod cosmic_helper; mod diagnostics; mod gnome_extension; +mod identity; mod remote_desktop; mod screenshot; mod server; diff --git a/src/server.rs b/src/server.rs index c2b00eb..a0f6909 100644 --- a/src/server.rs +++ b/src/server.rs @@ -376,9 +376,13 @@ impl ComputerUseLinux { /// Lazily create the uinput absolute pointer, sizing its ABS range to the /// logical desktop (the portal screenshot dimensions). Returns `false` if it - /// can't be created or is disabled via `CU_DISABLE_ABS_POINTER`. + /// can't be created or is disabled via `CU_DISABLE_ABS_POINTER` (or the + /// Codex embedded-build alias). async fn ensure_abs_pointer(&self) -> bool { - if env_flag_enabled("CU_DISABLE_ABS_POINTER") { + if env_flag_enabled_any(&[ + "CU_DISABLE_ABS_POINTER", + "CODEX_COMPUTER_USE_DISABLE_ABS_POINTER", + ]) { return false; } if self @@ -1477,33 +1481,46 @@ impl ComputerUseLinux { // grants (`org.freedesktop.portal.Error: Remote desktop sessions cannot // persist`), so the portal would otherwise re-prompt on every new session. // `COMPUTER_USE_LINUX_FORCE_YDOTOOL_*=1` always uses ydotool; - // `COMPUTER_USE_LINUX_FORCE_PORTAL_*=1` always uses the portal. + // `COMPUTER_USE_LINUX_FORCE_PORTAL_*=1` always uses the portal. The + // `CODEX_COMPUTER_USE_*` names are accepted for the embedded Codex app + // bundle so downstream can share this source without local string patches. fn should_prefer_portal_pointer_backend(&self) -> bool { - if env_flag_enabled("COMPUTER_USE_LINUX_FORCE_YDOTOOL_POINTER") { + if env_flag_enabled_any(&[ + "COMPUTER_USE_LINUX_FORCE_YDOTOOL_POINTER", + "CODEX_COMPUTER_USE_FORCE_YDOTOOL_POINTER", + ]) { return false; } - if env_flag_enabled("COMPUTER_USE_LINUX_FORCE_PORTAL_POINTER") { + if env_flag_enabled_any(&[ + "COMPUTER_USE_LINUX_FORCE_PORTAL_POINTER", + "CODEX_COMPUTER_USE_FORCE_PORTAL_POINTER", + ]) { return self.is_wayland_session(); } self.is_wayland_session() && ydotool_socket().is_none() } fn should_prefer_portal_keyboard_backend(&self) -> bool { - if env_flag_enabled("COMPUTER_USE_LINUX_FORCE_YDOTOOL_KEYBOARD") { + if env_flag_enabled_any(&[ + "COMPUTER_USE_LINUX_FORCE_YDOTOOL_KEYBOARD", + "CODEX_COMPUTER_USE_FORCE_YDOTOOL_KEYBOARD", + ]) { return false; } - if env_flag_enabled("COMPUTER_USE_LINUX_FORCE_PORTAL_KEYBOARD") { + if env_flag_enabled_any(&[ + "COMPUTER_USE_LINUX_FORCE_PORTAL_KEYBOARD", + "CODEX_COMPUTER_USE_FORCE_PORTAL_KEYBOARD", + ]) { return self.is_wayland_session() && !self.is_kde_wayland_session(); } self.is_wayland_session() && !self.is_kde_wayland_session() && ydotool_socket().is_none() } fn should_prefer_kde_clipboard_text_backend(&self) -> bool { - env::var("COMPUTER_USE_LINUX_FORCE_YDOTOOL_KEYBOARD") - .ok() - .as_deref() - != Some("1") - && self.is_kde_wayland_session() + !env_flag_enabled_any(&[ + "COMPUTER_USE_LINUX_FORCE_YDOTOOL_KEYBOARD", + "CODEX_COMPUTER_USE_FORCE_YDOTOOL_KEYBOARD", + ]) && self.is_kde_wayland_session() } fn is_kde_wayland_session(&self) -> bool { @@ -1554,11 +1571,10 @@ impl ComputerUseLinux { } async fn ensure_portal_keyboard_session(&self) -> Result> { - if env::var("COMPUTER_USE_LINUX_FORCE_YDOTOOL_KEYBOARD") - .ok() - .as_deref() - == Some("1") - || !self.is_wayland_session() + if env_flag_enabled_any(&[ + "COMPUTER_USE_LINUX_FORCE_YDOTOOL_KEYBOARD", + "CODEX_COMPUTER_USE_FORCE_YDOTOOL_KEYBOARD", + ]) || !self.is_wayland_session() { return Ok(None); } @@ -2242,6 +2258,10 @@ fn env_flag_enabled(key: &str) -> bool { env::var(key).ok().as_deref() == Some("1") } +fn env_flag_enabled_any(keys: &[&str]) -> bool { + keys.iter().any(|key| env_flag_enabled(key)) +} + /// Decode the base64 payload of a `data:` URL (or a bare base64 string) to bytes. fn decode_data_url(data_url: &str) -> std::result::Result, String> { use base64::Engine; diff --git a/src/windowing/backends/gnome.rs b/src/windowing/backends/gnome.rs index 080d91f..526b14e 100644 --- a/src/windowing/backends/gnome.rs +++ b/src/windowing/backends/gnome.rs @@ -1,4 +1,5 @@ use crate::diagnostics::hydrate_session_bus_env; +use crate::identity; use crate::terminal::enrich_terminal_windows; use crate::windowing::registry::BackendProbe; use crate::windowing::types::{WindowBounds, WindowInfo}; @@ -9,14 +10,15 @@ use zbus::{zvariant::OwnedValue, Proxy}; pub const GNOME_SHELL_INTROSPECT_BACKEND: &str = "gnome-shell-introspect"; pub const GNOME_SHELL_EXTENSION_BACKEND: &str = "gnome-shell-extension"; -pub const GNOME_SHELL_EXTENSION_SERVICE: &str = "dev.avifenesh.ComputerUseLinux.WindowControl"; -pub const GNOME_SHELL_EXTENSION_OBJECT_PATH: &str = "/dev/avifenesh/ComputerUseLinux/WindowControl"; +pub const GNOME_SHELL_EXTENSION_SERVICE: &str = identity::DBUS_SERVICE; +pub const GNOME_SHELL_EXTENSION_OBJECT_PATH: &str = identity::DBUS_OBJECT_PATH; pub fn probe_extension() -> BackendProbe { + let method = format!("{GNOME_SHELL_EXTENSION_SERVICE}.ListWindows"); let check = gdbus_call_check( - "dev.avifenesh.ComputerUseLinux.WindowControl", - "/dev/avifenesh/ComputerUseLinux/WindowControl", - "dev.avifenesh.ComputerUseLinux.WindowControl.ListWindows", + GNOME_SHELL_EXTENSION_SERVICE, + GNOME_SHELL_EXTENSION_OBJECT_PATH, + &method, &[], ); BackendProbe {