fix(natives): gated pipewire behind opt-in feature, fixed linux clippy

- libspa-sys hard-links system libpipewire-0.3 via pkg-config, which no CI
  or cross triple (musl, arm64) can satisfy; bazel addons already build with
  crate_features = [], so shipping builds lose nothing. Linux devs opt in
  with --features wayland-pipewire.
- Gated normalize_role_macos and the wayland capture helpers to the configs
  that use them; handled the cfg(test) AxHandle variant in AtSpiAx::object.
- Fixed clippy-strict violations (redundant_pub_crate, missing_const_for_fn,
  unnecessary_wraps, collapsible_if, map_unwrap_or, needless_return,
  needless_pass_by_ref_mut) across the new linux desktop backend.
This commit is contained in:
can1357
2026-08-02 22:55:29 +02:00
parent 0b1649784a
commit 800b23dd6c
9 changed files with 69 additions and 66 deletions
+9 -11
View File
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -14,7 +14,11 @@ workspace = true
[features]
default = ["wayland-pipewire"]
# Off by default: the pipewire crate hard-links system libpipewire-0.3 via
# pkg-config, which no CI/cross triple (musl, arm64) can satisfy, and the
# Bazel-shipped addons build with crate_features = [] anyway. Linux devs who
# want Wayland screencast opt in with `--features wayland-pipewire`.
default = []
wayland-pipewire = ["dep:pipewire"]
[dependencies]
+4 -1
View File
@@ -445,6 +445,9 @@ pub fn ax_press(backend: &mut dyn AxBackend, handle: &AxHandle) -> CoreResult<()
backend.perform(handle, "press")
}
/// Maps a raw `AX*` macOS accessibility role onto the cross-platform role
/// vocabulary. Compiled only where it has a caller (macOS backend + tests).
#[cfg(any(target_os = "macos", test))]
pub fn normalize_role_macos(native: &str) -> String {
match native {
"AXTextArea" => "textarea",
@@ -481,7 +484,7 @@ pub(crate) fn normalize_role_uia(native: &str) -> String {
.to_string()
}
#[cfg(any(target_os = "linux", test))]
pub(crate) fn normalize_role_atspi(native: &str, multiline: bool) -> String {
pub fn normalize_role_atspi(native: &str, multiline: bool) -> String {
match native.to_ascii_lowercase().as_str() {
"push button" | "toggle button" => "button".into(),
"entry" | "text" if multiline => "textarea".into(),
+20 -19
View File
@@ -8,7 +8,7 @@ use crate::desktop::{
types::DesktopWindow,
};
pub(crate) struct AtSpiAx {
pub struct AtSpiAx {
rt: Runtime,
connection: atspi::AccessibilityConnection,
}
@@ -25,12 +25,15 @@ impl AtSpiAx {
Ok(Self { rt, connection })
}
fn object(h: &AxHandle) -> CoreResult<&ObjectRefOwned> {
let AxHandle::AtSpi(object) = h;
Ok(object)
const fn object(h: &AxHandle) -> &ObjectRefOwned {
match h {
AxHandle::AtSpi(object) => object,
#[cfg(test)]
_ => panic!("AT-SPI backend received a non-AT-SPI handle"),
}
}
pub(crate) fn windows(&mut self) -> CoreResult<Vec<DesktopWindow>> {
pub(crate) fn windows(&self) -> CoreResult<Vec<DesktopWindow>> {
self.rt.block_on(async {
let mut windows = Vec::new();
for app in Self::apps(&self.connection)
@@ -228,8 +231,7 @@ impl AtSpiAx {
if proxy
.get_state()
.await
.map(|state| state.contains(State::Focused))
.unwrap_or(false)
.is_ok_and(|state| state.contains(State::Focused))
{
return Ok(Some(root));
}
@@ -268,7 +270,7 @@ impl AxBackend for AtSpiAx {
}
fn props(&mut self, h: &AxHandle) -> CoreResult<AxProps> {
let object = Self::object(h)?.clone();
let object = Self::object(h).clone();
self.rt.block_on(async {
let proxy = object
.as_accessible_proxy(self.connection.connection())
@@ -351,7 +353,7 @@ impl AxBackend for AtSpiAx {
}
fn children(&mut self, h: &AxHandle) -> CoreResult<Vec<AxHandle>> {
let object = Self::object(h)?.clone();
let object = Self::object(h).clone();
self.rt.block_on(async {
let proxy = object
.as_accessible_proxy(self.connection.connection())
@@ -372,7 +374,7 @@ impl AxBackend for AtSpiAx {
}
fn parent(&mut self, h: &AxHandle) -> CoreResult<Option<AxHandle>> {
let object = Self::object(h)?.clone();
let object = Self::object(h).clone();
self.rt.block_on(async {
let proxy = object
.as_accessible_proxy(self.connection.connection())
@@ -387,7 +389,7 @@ impl AxBackend for AtSpiAx {
}
fn perform(&mut self, h: &AxHandle, action: &str) -> CoreResult<()> {
let object = Self::object(h)?.clone();
let object = Self::object(h).clone();
self.rt.block_on(async {
let proxy = Self::action(&self.connection, &object)
.await
@@ -420,7 +422,7 @@ impl AxBackend for AtSpiAx {
}
fn set_value(&mut self, h: &AxHandle, value: &str) -> CoreResult<()> {
let object = Self::object(h)?.clone();
let object = Self::object(h).clone();
self.rt.block_on(async {
let name = object
.name()
@@ -433,14 +435,13 @@ impl AxBackend for AtSpiAx {
.map_err(|err| DesktopError::ax_failed(err.to_string()))?
.build()
.await;
if let Ok(editable) = editable {
if editable
if let Ok(editable) = editable
&& editable
.set_text_contents(value)
.await
.map_err(|err| DesktopError::ax_failed(format!("AT-SPI text value: {err}")))?
{
return Ok(());
}
{
return Ok(());
}
let numeric = value.parse::<f64>().map_err(|_| {
DesktopError::ax_failed("AT-SPI value is not editable text or a number")
@@ -460,7 +461,7 @@ impl AxBackend for AtSpiAx {
}
fn focus(&mut self, h: &AxHandle) -> CoreResult<()> {
let object = Self::object(h)?.clone();
let object = Self::object(h).clone();
self.rt.block_on(async {
let component = Self::component(&self.connection, &object)
.await
@@ -530,7 +531,7 @@ impl AxBackend for AtSpiAx {
}
fn attributes(&mut self, h: &AxHandle) -> CoreResult<Vec<(String, String)>> {
let object = Self::object(h)?.clone();
let object = Self::object(h).clone();
self.rt.block_on(async {
let proxy = object
.as_accessible_proxy(self.connection.connection())
+5 -5
View File
@@ -1,6 +1,6 @@
pub(crate) mod ax;
pub(crate) mod wayland;
pub(crate) mod x11;
pub mod ax;
pub mod wayland;
pub mod x11;
use super::{
backend::Backend,
@@ -8,9 +8,9 @@ use super::{
types::DisplaySelector,
};
pub(crate) fn new_backend(display: DisplaySelector) -> CoreResult<Box<dyn Backend>> {
pub fn new_backend(display: DisplaySelector) -> CoreResult<Box<dyn Backend>> {
if std::env::var_os("WAYLAND_DISPLAY").is_some() {
return Ok(Box::new(wayland::WaylandBackend::new(display)?));
return Ok(Box::new(wayland::WaylandBackend::new(display)));
}
if std::env::var_os("DISPLAY").is_some() {
return Ok(Box::new(x11::X11Backend::new(display)?));
@@ -16,7 +16,11 @@ use crate::desktop::{
},
};
pub(crate) struct WaylandBackend {
pub struct WaylandBackend {
#[cfg_attr(
not(feature = "wayland-pipewire"),
expect(dead_code, reason = "only read by the pipewire capture path")
)]
display: DisplaySelector,
ax: Option<AtSpiAx>,
ax_error: Option<DesktopError>,
@@ -26,7 +30,7 @@ pub(crate) struct WaylandBackend {
}
impl WaylandBackend {
pub(crate) fn new(display: DisplaySelector) -> CoreResult<Self> {
pub fn new(display: DisplaySelector) -> Self {
let (ax, ax_error) = match AtSpiAx::new() {
Ok(ax) => (Some(ax), None),
Err(err) => (None, Some(err)),
@@ -35,7 +39,7 @@ impl WaylandBackend {
Ok(input) => (Some(input), None),
Err(err) => (None, Some(err)),
};
Ok(Self { display, ax, ax_error, input, input_error, displays: Vec::new() })
Self { display, ax, ax_error, input, input_error, displays: Vec::new() }
}
fn background_error(target: &Target, kind: &str) -> CoreResult<()> {
@@ -52,10 +56,10 @@ impl WaylandBackend {
if mode == DeliveryMode::Background {
Self::background_error(target, kind)?;
}
if mode == DeliveryMode::Foreground {
if let Target::Window(id) = target {
self.raise_window(id)?;
}
if mode == DeliveryMode::Foreground
&& let Target::Window(id) = target
{
self.raise_window(id)?;
}
if self.input.is_none() {
return Err(self.input_error.clone().unwrap_or_else(|| {
@@ -67,6 +71,7 @@ impl WaylandBackend {
Ok(())
}
#[cfg(feature = "wayland-pipewire")]
fn synthetic_display(image: &RgbaImage) -> DesktopDisplay {
DesktopDisplay {
id: "wayland-portal-0".to_string(),
@@ -84,6 +89,7 @@ impl WaylandBackend {
}
}
#[cfg(feature = "wayland-pipewire")]
fn selected_display_allowed(&self) -> CoreResult<()> {
match &self.display {
DisplaySelector::All => Ok(()),
@@ -145,9 +151,7 @@ impl Backend for WaylandBackend {
#[cfg(not(feature = "wayland-pipewire"))]
{
let _ = target;
return Err(DesktopError::capture_failed(
"Wayland capture requires the wayland-pipewire feature",
));
Err(DesktopError::capture_failed("Wayland capture requires the wayland-pipewire feature"))
}
#[cfg(feature = "wayland-pipewire")]
{
@@ -58,7 +58,7 @@ impl ComponentMask {
}
}
pub(crate) struct X11Capture {
pub struct X11Capture {
conn: Arc<RustConnection>,
root: Window,
root_width: u32,
@@ -38,7 +38,7 @@ use crate::desktop::{
const CLICK_DELAY: Duration = Duration::from_millis(12);
const MPX_SETTLE: Duration = Duration::from_millis(100);
pub(crate) struct X11Input {
pub struct X11Input {
conn: Arc<RustConnection>,
root: Window,
min_keycode: u8,
@@ -93,10 +93,8 @@ impl X11Input {
(Target::Window(id), DeliveryMode::Background) => {
let window = parse_window(id)?;
let filtering = self.send_event_filtering_toolkit(window);
if filtering && self.mpx_probe {
if self.pointer_mpx(window, &event).is_ok() {
return Ok(());
}
if filtering && self.mpx_probe && self.pointer_mpx(window, &event).is_ok() {
return Ok(());
}
if filtering {
return Err(background_unavailable(
@@ -283,7 +281,6 @@ impl X11Input {
}
fn pointer_mpx(&self, window: Window, event: &PointerEvent) -> CoreResult<()> {
let kind = event_kind(event);
if matches!(
event,
PointerEvent::Click { modifiers, .. } | PointerEvent::Drag { modifiers, .. }
@@ -334,16 +331,13 @@ impl X11Input {
session.warp(x, y)
},
};
let cleanup = session.finish();
session.finish();
if let Some(previous) = previous_focus
&& self.active_window() != Some(previous)
{
let _ = self.activate(previous);
}
result?;
cleanup.map_err(|error| {
DesktopError::input_failed(format!("XI2-MPX {kind} cleanup failed: {}", error.message))
})
result
}
fn with_foreground<T>(
@@ -731,11 +725,11 @@ fn checked_i16(value: f64, axis: &str) -> CoreResult<i16> {
Ok(value.round() as i16)
}
pub(crate) fn validate_xtest_point(x: f64, y: f64) -> CoreResult<(i16, i16)> {
pub fn validate_xtest_point(x: f64, y: f64) -> CoreResult<(i16, i16)> {
Ok((checked_i16(x, "x")?, checked_i16(y, "y")?))
}
fn button_detail(button: MouseButton) -> u8 {
const fn button_detail(button: MouseButton) -> u8 {
match button {
MouseButton::Left => 1,
MouseButton::Middle => 2,
@@ -743,7 +737,7 @@ fn button_detail(button: MouseButton) -> u8 {
}
}
fn button_mask(detail: u8) -> Option<KeyButMask> {
const fn button_mask(detail: u8) -> Option<KeyButMask> {
match detail {
1 => Some(KeyButMask::BUTTON1),
2 => Some(KeyButMask::BUTTON2),
@@ -782,7 +776,7 @@ fn scroll_buttons(dx: f64, dy: f64) -> Vec<(u8, u32)> {
result
}
fn event_kind(event: &PointerEvent) -> &'static str {
const fn event_kind(event: &PointerEvent) -> &'static str {
match event {
PointerEvent::Click { .. } => "click",
PointerEvent::Move { .. } => "move",
@@ -1054,10 +1048,9 @@ impl<'a> MpxSession<'a> {
self.uinput.scroll(dx, dy)
}
fn finish(mut self) -> CoreResult<()> {
fn finish(mut self) {
self.cleanup();
self.finished = true;
Ok(())
}
fn cleanup(&mut self) {
@@ -16,7 +16,7 @@ use crate::desktop::{
},
};
pub(crate) struct X11Backend {
pub struct X11Backend {
capture: X11Capture,
input: X11Input,
ax: Option<AtSpiAx>,