From 75ae44c1d732d45e03ad169fb8c7813bf42c6c5b Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Fri, 25 Sep 2026 06:33:48 +0100 Subject: [PATCH] feat: add private shell control protocol --- Cargo.lock | 9 + Cargo.toml | 2 + compositor/src/backends/mod.rs | 28 ++ compositor/src/main.rs | 1 + compositor/src/private_client.rs | 7 + compositor/src/shell_control.rs | 286 +++++++++++++++++++ compositor/src/state.rs | 65 +++++ crates/ferese-protocols/Cargo.toml | 1 + crates/ferese-protocols/src/lib.rs | 54 ++++ protocols/ferese-shell-v1.xml | 112 ++++++++ tools/ferese-shell-control-probe/Cargo.toml | 10 + tools/ferese-shell-control-probe/src/main.rs | 152 ++++++++++ 12 files changed, 727 insertions(+) create mode 100644 compositor/src/shell_control.rs create mode 100644 protocols/ferese-shell-v1.xml create mode 100644 tools/ferese-shell-control-probe/Cargo.toml create mode 100644 tools/ferese-shell-control-probe/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index d9fa983..5b55c80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1630,12 +1630,21 @@ version = "0.1.0" name = "ferese-protocols" version = "0.1.0" dependencies = [ + "bitflags 2.13.2", "wayland-backend", "wayland-client", "wayland-scanner", "wayland-server", ] +[[package]] +name = "ferese-shell-control-probe" +version = "0.1.0" +dependencies = [ + "ferese-protocols", + "wayland-client", +] + [[package]] name = "ferese-shell-spike" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index cd7ed8b..03a50b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "crates/ferese-protocols", "tools/ferese-effects-probe", "tools/ferese-malformed-client", + "tools/ferese-shell-control-probe", "tools/ferese-shell-spike", ] resolver = "3" @@ -20,6 +21,7 @@ license = "MIT" rust-version = "1.87" [workspace.dependencies] +bitflags = "2.9.4" calloop = { version = "0.14.4", features = ["signals"] } libc = "0.2.176" smithay = { version = "0.7.0", default-features = false, features = [ diff --git a/compositor/src/backends/mod.rs b/compositor/src/backends/mod.rs index 8330e20..d733e52 100644 --- a/compositor/src/backends/mod.rs +++ b/compositor/src/backends/mod.rs @@ -52,6 +52,10 @@ impl LaunchConfig { client_capabilities.insert(ClientCapabilities::EFFECTS); continue; } + if argument == "--grant-shell-control" { + client_capabilities.insert(ClientCapabilities::SHELL_CONTROL); + continue; + } if let Some(value) = argument .to_str() .and_then(|argument| argument.strip_prefix("--backend=")) @@ -164,6 +168,30 @@ mod tests { ); } + #[test] + fn explicitly_grants_shell_control_to_the_launched_private_client() { + let config = LaunchConfig::parse( + [ + OsString::from("--grant-shell-control"), + OsString::from("ferese-shell"), + ], + true, + ) + .unwrap(); + + assert_eq!(config.client, [OsString::from("ferese-shell")]); + assert!( + config + .client_capabilities + .contains(ClientCapabilities::SHELL_CONTROL) + ); + assert!( + !config + .client_capabilities + .contains(ClientCapabilities::EFFECTS) + ); + } + #[test] fn rejects_an_unknown_backend() { let error = diff --git a/compositor/src/main.rs b/compositor/src/main.rs index 6375aad..7ff1737 100644 --- a/compositor/src/main.rs +++ b/compositor/src/main.rs @@ -10,6 +10,7 @@ mod input; mod ipc; mod metrics; mod private_client; +mod shell_control; mod stacking; mod state; mod window_rules; diff --git a/compositor/src/private_client.rs b/compositor/src/private_client.rs index ae76853..8b8a6cd 100644 --- a/compositor/src/private_client.rs +++ b/compositor/src/private_client.rs @@ -15,6 +15,7 @@ pub(crate) struct ClientCapabilities(u8); impl ClientCapabilities { pub(crate) const EFFECTS: Self = Self(1 << 0); + pub(crate) const SHELL_CONTROL: Self = Self(1 << 1); pub(crate) fn contains(self, capability: Self) -> bool { self.0 & capability.0 == capability.0 @@ -69,9 +70,15 @@ mod tests { let mut capabilities = ClientCapabilities::default(); assert!(capabilities.is_empty()); assert!(!capabilities.contains(ClientCapabilities::EFFECTS)); + assert!(!capabilities.contains(ClientCapabilities::SHELL_CONTROL)); capabilities.insert(ClientCapabilities::EFFECTS); assert!(!capabilities.is_empty()); assert!(capabilities.contains(ClientCapabilities::EFFECTS)); + assert!(!capabilities.contains(ClientCapabilities::SHELL_CONTROL)); + + capabilities.insert(ClientCapabilities::SHELL_CONTROL); + assert!(capabilities.contains(ClientCapabilities::EFFECTS)); + assert!(capabilities.contains(ClientCapabilities::SHELL_CONTROL)); } } diff --git a/compositor/src/shell_control.rs b/compositor/src/shell_control.rs new file mode 100644 index 0000000..74ea725 --- /dev/null +++ b/compositor/src/shell_control.rs @@ -0,0 +1,286 @@ +use ferese_layout::WindowId; +use ferese_protocols::shell::v1::server::{ + ferese_shell_manager_v1::{self, FereseShellManagerV1}, + ferese_shell_v1::{self, FereseShellV1}, +}; +use smithay::{ + reexports::wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + }, + wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceData}, +}; + +use crate::{Ferese, private_client::ClientCapabilities, state::ClientState}; + +pub(crate) fn init_global(display: &DisplayHandle) { + display.create_global::(1, ()); +} + +impl GlobalDispatch for Ferese { + fn bind( + _state: &mut Self, + _display: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &(), + data_init: &mut DataInit<'_, Self>, + ) { + data_init.init(resource, ()); + } + + fn can_view(client: Client, _global_data: &()) -> bool { + client.get_data::().is_some_and(|state| { + state + .capabilities + .contains(ClientCapabilities::SHELL_CONTROL) + }) + } +} + +impl Dispatch for Ferese { + fn request( + state: &mut Self, + _client: &Client, + _manager: &FereseShellManagerV1, + request: ferese_shell_manager_v1::Request, + _data: &(), + _display: &DisplayHandle, + data_init: &mut DataInit<'_, Self>, + ) { + match request { + ferese_shell_manager_v1::Request::GetShell { id } => { + let shell = data_init.init(id, ()); + + shell.capabilities(1); + shell.overview_state(u32::from(state.overview_active)); + state.send_shell_snapshot(&shell); + state.shell_resources.push(shell.downgrade()); + } + ferese_shell_manager_v1::Request::Destroy => {} + _ => unreachable!(), + } + } +} + +impl Dispatch for Ferese { + fn request( + state: &mut Self, + _client: &Client, + shell: &FereseShellV1, + request: ferese_shell_v1::Request, + _data: &(), + _display: &DisplayHandle, + _data_init: &mut DataInit<'_, Self>, + ) { + match request { + ferese_shell_v1::Request::ActivateWindow { + window_hi, + window_lo, + } => state.handle_shell_window_request( + shell, + ferese_shell_v1::FailedRequest::ActivateWindow, + join_id(window_hi, window_lo), + ShellWindowAction::Activate, + ), + ferese_shell_v1::Request::CloseWindow { + window_hi, + window_lo, + } => state.handle_shell_window_request( + shell, + ferese_shell_v1::FailedRequest::CloseWindow, + join_id(window_hi, window_lo), + ShellWindowAction::Close, + ), + ferese_shell_v1::Request::EnterOverview => state.set_overview_active(true), + ferese_shell_v1::Request::ExitOverview => state.set_overview_active(false), + ferese_shell_v1::Request::SelectOverviewWindow { + window_hi, + window_lo, + } => { + let id = join_id(window_hi, window_lo); + + if state.activate_managed_window(id) { + state.set_overview_active(false); + } else { + send_request_failed( + shell, + ferese_shell_v1::FailedRequest::SelectOverviewWindow, + id, + ); + } + } + ferese_shell_v1::Request::Destroy => {} + _ => unreachable!(), + } + } +} + +impl Ferese { + pub(crate) fn send_shell_snapshots(&mut self) { + let resources = std::mem::take(&mut self.shell_resources); + + for weak in resources { + if let Ok(shell) = weak.upgrade() { + self.send_shell_snapshot(&shell); + self.shell_resources.push(shell.downgrade()); + } + } + } + + fn send_shell_snapshot(&mut self, shell: &FereseShellV1) { + self.shell_snapshot_serial = self.shell_snapshot_serial.wrapping_add(1); + let serial = self.shell_snapshot_serial; + let snapshots = self.managed_window_snapshots(); + + shell.snapshot_begin(serial); + for snapshot in snapshots { + let (window_hi, window_lo) = split_id(snapshot.id.0); + let (workspace_hi, workspace_lo) = split_id(snapshot.workspace); + + shell.window( + window_hi, + window_lo, + workspace_hi, + workspace_lo, + snapshot.app_id, + snapshot.title, + snapshot.state, + ); + } + shell.snapshot_end(serial); + } + + fn managed_window_snapshots(&self) -> Vec { + let mut snapshots = self + .window_ids + .iter() + .filter_map(|(window, id)| { + let workspace = self.workspaces.workspace_for_window(*id)?.0; + let toplevel = window.toplevel()?; + let (app_id, title) = with_states(toplevel.wl_surface(), |states| { + let attributes = states + .data_map + .get::() + .expect("xdg toplevel state exists") + .lock() + .expect("xdg toplevel state is not poisoned"); + + ( + attributes.app_id.clone().unwrap_or_default(), + attributes.title.clone().unwrap_or_default(), + ) + }); + let mut state = ferese_shell_v1::WindowState::empty(); + + if self.focused_window == Some(*id) { + state |= ferese_shell_v1::WindowState::Focused; + } + if self + .workspaces + .workspace_for_window(*id) + .and_then(|workspace| self.workspaces.workspace(workspace)) + .is_some_and(|workspace| workspace.fullscreen == Some(*id)) + { + state |= ferese_shell_v1::WindowState::Fullscreen; + } + if self.is_floating_window(window) { + state |= ferese_shell_v1::WindowState::Floating; + } + + Some(ManagedWindowSnapshot { + id: *id, + workspace, + app_id, + title, + state, + }) + }) + .collect::>(); + + snapshots.sort_by_key(|snapshot| snapshot.id.0); + snapshots + } + + fn handle_shell_window_request( + &mut self, + shell: &FereseShellV1, + request: ferese_shell_v1::FailedRequest, + id: WindowId, + action: ShellWindowAction, + ) { + let succeeded = match action { + ShellWindowAction::Activate => self.activate_managed_window(id), + ShellWindowAction::Close => self.close_managed_window(id), + }; + + if !succeeded { + send_request_failed(shell, request, id); + } + } + + fn set_overview_active(&mut self, active: bool) { + if self.overview_active == active { + return; + } + + self.overview_active = active; + self.shell_resources.retain(|resource| { + resource.upgrade().is_ok_and(|shell| { + shell.overview_state(u32::from(active)); + true + }) + }); + crate::backends::direct::render_all(self); + } +} + +#[derive(Clone, Copy)] +enum ShellWindowAction { + Activate, + Close, +} + +struct ManagedWindowSnapshot { + id: WindowId, + workspace: u64, + app_id: String, + title: String, + state: ferese_shell_v1::WindowState, +} + +fn split_id(id: u64) -> (u32, u32) { + ((id >> 32) as u32, id as u32) +} + +fn join_id(hi: u32, lo: u32) -> WindowId { + WindowId((u64::from(hi) << 32) | u64::from(lo)) +} + +fn send_request_failed( + shell: &FereseShellV1, + request: ferese_shell_v1::FailedRequest, + id: WindowId, +) { + let (window_hi, window_lo) = split_id(id.0); + + shell.request_failed( + request, + window_hi, + window_lo, + "unknown or unavailable managed window".to_owned(), + ); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn window_ids_round_trip_through_protocol_words() { + for id in [0, 1, u32::MAX as u64, u64::from(u32::MAX) + 1, u64::MAX] { + let (hi, lo) = split_id(id); + + assert_eq!(join_id(hi, lo), WindowId(id)); + } + } +} diff --git a/compositor/src/state.rs b/compositor/src/state.rs index 4ec2f5a..2995af0 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -154,6 +154,13 @@ pub struct Ferese { _ipc_socket: Option, pending_dmabuf_imports: Vec<(Dmabuf, ImportNotifier)>, pub(crate) pending_screencopies: Vec, + pub(crate) shell_resources: Vec< + smithay::reexports::wayland_server::Weak< + ferese_protocols::shell::v1::server::ferese_shell_v1::FereseShellV1, + >, + >, + pub(crate) shell_snapshot_serial: u32, + pub(crate) overview_active: bool, next_window_id: u64, next_output_id: u64, last_animation_tick: Instant, @@ -360,6 +367,7 @@ impl Ferese { let display_handle = display.handle(); crate::handlers::screencopy::init_global(&display_handle); crate::effects::init_global(&display_handle); + crate::shell_control::init_global(&display_handle); let alpha_modifier_state = AlphaModifierState::new::(&display_handle); let compositor_state = CompositorState::new::(&display_handle); let cursor_shape_state = CursorShapeManagerState::new::(&display_handle); @@ -470,6 +478,9 @@ impl Ferese { _ipc_socket: None, pending_dmabuf_imports: Vec::new(), pending_screencopies: Vec::new(), + shell_resources: Vec::new(), + shell_snapshot_serial: 0, + overview_active: false, next_window_id: 1, next_output_id: 1, last_animation_tick: start_time, @@ -1132,6 +1143,7 @@ impl Ferese { } self.scrolling_world_x = scrolling_world_x; self.sync_window_stacking(); + self.send_shell_snapshots(); crate::backends::direct::render_all(self); } @@ -1570,6 +1582,59 @@ impl Ferese { crate::backends::direct::render_all(self); } + pub(crate) fn close_managed_window(&mut self, id: WindowId) -> bool { + if !self.window_ids.values().any(|window_id| *window_id == id) { + return false; + } + + if self.animations_enabled { + self.closing_windows.entry(id).or_default(); + crate::backends::direct::render_all(self); + } else { + self.send_window_close(id); + } + + true + } + + pub(crate) fn activate_managed_window(&mut self, id: WindowId) -> bool { + let Some(window) = self + .window_ids + .iter() + .find_map(|(window, window_id)| (*window_id == id).then(|| window.clone())) + else { + return false; + }; + let Some(workspace) = self.workspaces.workspace_for_window(id) else { + return false; + }; + + if let Some(output) = self.output_workspaces.output_for_workspace(workspace) { + self.activate_output_workspace(output, workspace); + } else if let Some(output) = self.output_workspaces.focused_output() { + if self + .output_workspaces + .assign_workspace(output, workspace) + .is_err() + { + return false; + } + self.activate_output_workspace(output, workspace); + } else if self.workspaces.activate(workspace).is_err() { + return false; + } + + if self.workspaces.focus_window(id).is_err() { + return false; + } + + self.focused_window = Some(id); + self.raise_window(&window, true); + self.relayout(); + self.restore_keyboard_focus(); + true + } + pub(crate) fn closing_visual(&self, id: WindowId) -> (f64, f32) { let Some(animation) = self.closing_windows.get(&id) else { return (1.0, 1.0); diff --git a/crates/ferese-protocols/Cargo.toml b/crates/ferese-protocols/Cargo.toml index cf577df..5cddd5c 100644 --- a/crates/ferese-protocols/Cargo.toml +++ b/crates/ferese-protocols/Cargo.toml @@ -11,6 +11,7 @@ client = ["dep:wayland-client"] server = ["dep:wayland-server"] [dependencies] +bitflags.workspace = true wayland-backend.workspace = true wayland-client = { workspace = true, optional = true } wayland-scanner.workspace = true diff --git a/crates/ferese-protocols/src/lib.rs b/crates/ferese-protocols/src/lib.rs index 3cde75d..b7dfaa0 100644 --- a/crates/ferese-protocols/src/lib.rs +++ b/crates/ferese-protocols/src/lib.rs @@ -55,3 +55,57 @@ pub mod effects { } } } + +/// Private shell control and managed-window metadata protocol. +pub mod shell { + /// Version 1 of the Ferese shell control protocol. + pub mod v1 { + #[cfg(feature = "client")] + pub use generated::client; + #[cfg(feature = "server")] + pub use generated::server; + + mod generated { + #![allow( + dead_code, + non_camel_case_types, + non_upper_case_globals, + non_snake_case, + unused_imports, + unused_unsafe, + unused_variables, + clippy::all + )] + + #[cfg(feature = "client")] + pub mod client { + use wayland_client; + use wayland_client::protocol::*; + + pub mod __interfaces { + use wayland_client::protocol::__interfaces::*; + + wayland_scanner::generate_interfaces!("../../protocols/ferese-shell-v1.xml"); + } + use self::__interfaces::*; + + wayland_scanner::generate_client_code!("../../protocols/ferese-shell-v1.xml"); + } + + #[cfg(feature = "server")] + pub mod server { + use wayland_server; + use wayland_server::protocol::*; + + pub mod __interfaces { + use wayland_server::protocol::__interfaces::*; + + wayland_scanner::generate_interfaces!("../../protocols/ferese-shell-v1.xml"); + } + use self::__interfaces::*; + + wayland_scanner::generate_server_code!("../../protocols/ferese-shell-v1.xml"); + } + } + } +} diff --git a/protocols/ferese-shell-v1.xml b/protocols/ferese-shell-v1.xml new file mode 100644 index 0000000..354aa80 --- /dev/null +++ b/protocols/ferese-shell-v1.xml @@ -0,0 +1,112 @@ + + + + Copyright 2026 Ferese contributors + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This private global is visible only to clients granted the shell-control + capability. It does not assign a Wayland surface role. + + + + + + + + + + + + Window and workspace identifiers are unsigned 64-bit values encoded as + high and low 32-bit words. Each snapshot atomically replaces the prior + managed-window view after snapshot_end. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/ferese-shell-control-probe/Cargo.toml b/tools/ferese-shell-control-probe/Cargo.toml new file mode 100644 index 0000000..5f4df81 --- /dev/null +++ b/tools/ferese-shell-control-probe/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "ferese-shell-control-probe" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true + +[dependencies] +ferese-protocols = { path = "../../crates/ferese-protocols", default-features = false, features = ["client"] } +wayland-client.workspace = true diff --git a/tools/ferese-shell-control-probe/src/main.rs b/tools/ferese-shell-control-probe/src/main.rs new file mode 100644 index 0000000..ce1c6c3 --- /dev/null +++ b/tools/ferese-shell-control-probe/src/main.rs @@ -0,0 +1,152 @@ +use std::error::Error; + +use ferese_protocols::shell::v1::client::{ + ferese_shell_manager_v1::FereseShellManagerV1, + ferese_shell_v1::{self, FereseShellV1}, +}; +use wayland_client::{ + Connection, Dispatch, QueueHandle, WEnum, delegate_noop, protocol::wl_registry, +}; + +fn main() -> Result<(), Box> { + let expect_hidden = std::env::args_os().any(|argument| argument == "--expect-hidden"); + let connection = Connection::connect_to_env()?; + let mut queue = connection.new_event_queue(); + let qh = queue.handle(); + connection.display().get_registry(&qh, ()); + let mut state = ProbeState::default(); + + queue.roundtrip(&mut state)?; + if expect_hidden { + if state.manager.is_some() { + return Err("public client could see ferese_shell_manager_v1".into()); + } + + println!("PASS public client cannot see ferese_shell_manager_v1"); + return Ok(()); + } + + let manager = state + .manager + .clone() + .ok_or("trusted client could not see ferese_shell_manager_v1")?; + state.shell = Some(manager.get_shell(&qh, ())); + + while !state.initialized() { + queue.blocking_dispatch(&mut state)?; + } + + let shell = state.shell.clone().expect("shell initialized"); + shell.enter_overview(); + while state.overview_active != Some(true) { + queue.blocking_dispatch(&mut state)?; + } + + shell.exit_overview(); + while state.overview_active != Some(false) || state.overview_events < 3 { + queue.blocking_dispatch(&mut state)?; + } + + shell.activate_window(u32::MAX, u32::MAX); + while !state.invalid_window_rejected { + queue.blocking_dispatch(&mut state)?; + } + + println!( + "PASS shell-control snapshot={} windows={} overview round-trip and stale-id rejection", + state.snapshot_serial.unwrap_or_default(), + state.window_count + ); + Ok(()) +} + +#[derive(Default)] +struct ProbeState { + manager: Option, + shell: Option, + capabilities: Option, + snapshot_serial: Option, + snapshot_complete: bool, + window_count: usize, + overview_active: Option, + overview_events: usize, + invalid_window_rejected: bool, +} + +impl ProbeState { + fn initialized(&self) -> bool { + self.capabilities == Some(1) + && self.snapshot_complete + && self.overview_active == Some(false) + } +} + +impl Dispatch for ProbeState { + fn event( + state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _data: &(), + _connection: &Connection, + qh: &QueueHandle, + ) { + let wl_registry::Event::Global { + name, + interface, + version, + } = event + else { + return; + }; + + if interface == "ferese_shell_manager_v1" { + state.manager = Some(registry.bind(name, version.min(1), qh, ())); + } + } +} + +impl Dispatch for ProbeState { + fn event( + state: &mut Self, + _shell: &FereseShellV1, + event: ferese_shell_v1::Event, + _data: &(), + _connection: &Connection, + _qh: &QueueHandle, + ) { + match event { + ferese_shell_v1::Event::Capabilities { version } => { + state.capabilities = Some(version); + } + ferese_shell_v1::Event::SnapshotBegin { serial } => { + state.snapshot_serial = Some(serial); + state.snapshot_complete = false; + state.window_count = 0; + } + ferese_shell_v1::Event::Window { .. } => { + state.window_count += 1; + } + ferese_shell_v1::Event::SnapshotEnd { serial } => { + state.snapshot_complete = state.snapshot_serial == Some(serial); + } + ferese_shell_v1::Event::OverviewState { active } => { + state.overview_active = Some(active != 0); + state.overview_events += 1; + } + ferese_shell_v1::Event::RequestFailed { + request, + window_hi, + window_lo, + .. + } => { + state.invalid_window_rejected = request + == WEnum::Value(ferese_shell_v1::FailedRequest::ActivateWindow) + && window_hi == u32::MAX + && window_lo == u32::MAX; + } + _ => {} + } + } +} + +delegate_noop!(ProbeState: ignore FereseShellManagerV1);