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:
Generated
+9
-11
File diff suppressed because one or more lines are too long
@@ -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]
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user