feat: add private shell control protocol

This commit is contained in:
Samuel Onoja committed 2026-09-25 06:33:48 +01:00
1 parent a836b55eb9
commit 75ae44c1d7
12 files changed
+727

No files matched your search

Generated
+9
View File
@@ -1630,12 +1630,21 @@ version = "0.1.0"
name = "ferese-protocols" name = "ferese-protocols"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitflags 2.13.2",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-scanner", "wayland-scanner",
"wayland-server", "wayland-server",
] ]
[[package]]
name = "ferese-shell-control-probe"
version = "0.1.0"
dependencies = [
"ferese-protocols",
"wayland-client",
]
[[package]] [[package]]
name = "ferese-shell-spike" name = "ferese-shell-spike"
version = "0.1.0" version = "0.1.0"
+2
View File
@@ -9,6 +9,7 @@ members = [
"crates/ferese-protocols", "crates/ferese-protocols",
"tools/ferese-effects-probe", "tools/ferese-effects-probe",
"tools/ferese-malformed-client", "tools/ferese-malformed-client",
"tools/ferese-shell-control-probe",
"tools/ferese-shell-spike", "tools/ferese-shell-spike",
] ]
resolver = "3" resolver = "3"
@@ -20,6 +21,7 @@ license = "MIT"
rust-version = "1.87" rust-version = "1.87"
[workspace.dependencies] [workspace.dependencies]
bitflags = "2.9.4"
calloop = { version = "0.14.4", features = ["signals"] } calloop = { version = "0.14.4", features = ["signals"] }
libc = "0.2.176" libc = "0.2.176"
smithay = { version = "0.7.0", default-features = false, features = [ smithay = { version = "0.7.0", default-features = false, features = [
+28
View File
@@ -52,6 +52,10 @@ impl LaunchConfig {
client_capabilities.insert(ClientCapabilities::EFFECTS); client_capabilities.insert(ClientCapabilities::EFFECTS);
continue; continue;
} }
if argument == "--grant-shell-control" {
client_capabilities.insert(ClientCapabilities::SHELL_CONTROL);
continue;
}
if let Some(value) = argument if let Some(value) = argument
.to_str() .to_str()
.and_then(|argument| argument.strip_prefix("--backend=")) .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] #[test]
fn rejects_an_unknown_backend() { fn rejects_an_unknown_backend() {
let error = let error =
+1
View File
@@ -10,6 +10,7 @@ mod input;
mod ipc; mod ipc;
mod metrics; mod metrics;
mod private_client; mod private_client;
mod shell_control;
mod stacking; mod stacking;
mod state; mod state;
mod window_rules; mod window_rules;
+7
View File
@@ -15,6 +15,7 @@ pub(crate) struct ClientCapabilities(u8);
impl ClientCapabilities { impl ClientCapabilities {
pub(crate) const EFFECTS: Self = Self(1 << 0); 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 { pub(crate) fn contains(self, capability: Self) -> bool {
self.0 & capability.0 == capability.0 self.0 & capability.0 == capability.0
@@ -69,9 +70,15 @@ mod tests {
let mut capabilities = ClientCapabilities::default(); let mut capabilities = ClientCapabilities::default();
assert!(capabilities.is_empty()); assert!(capabilities.is_empty());
assert!(!capabilities.contains(ClientCapabilities::EFFECTS)); assert!(!capabilities.contains(ClientCapabilities::EFFECTS));
assert!(!capabilities.contains(ClientCapabilities::SHELL_CONTROL));
capabilities.insert(ClientCapabilities::EFFECTS); capabilities.insert(ClientCapabilities::EFFECTS);
assert!(!capabilities.is_empty()); assert!(!capabilities.is_empty());
assert!(capabilities.contains(ClientCapabilities::EFFECTS)); 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));
} }
} }
+286
View File
@@ -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::<Ferese, FereseShellManagerV1, _>(1, ());
}
impl GlobalDispatch<FereseShellManagerV1, ()> for Ferese {
fn bind(
_state: &mut Self,
_display: &DisplayHandle,
_client: &Client,
resource: New<FereseShellManagerV1>,
_global_data: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
fn can_view(client: Client, _global_data: &()) -> bool {
client.get_data::<ClientState>().is_some_and(|state| {
state
.capabilities
.contains(ClientCapabilities::SHELL_CONTROL)
})
}
}
impl Dispatch<FereseShellManagerV1, ()> 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<FereseShellV1, ()> 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<ManagedWindowSnapshot> {
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::<XdgToplevelSurfaceData>()
.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::<Vec<_>>();
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));
}
}
}
+65
View File
@@ -154,6 +154,13 @@ pub struct Ferese {
_ipc_socket: Option<crate::ipc::IpcSocketGuard>, _ipc_socket: Option<crate::ipc::IpcSocketGuard>,
pending_dmabuf_imports: Vec<(Dmabuf, ImportNotifier)>, pending_dmabuf_imports: Vec<(Dmabuf, ImportNotifier)>,
pub(crate) pending_screencopies: Vec<crate::handlers::screencopy::PendingScreencopy>, pub(crate) pending_screencopies: Vec<crate::handlers::screencopy::PendingScreencopy>,
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_window_id: u64,
next_output_id: u64, next_output_id: u64,
last_animation_tick: Instant, last_animation_tick: Instant,
@@ -360,6 +367,7 @@ impl Ferese {
let display_handle = display.handle(); let display_handle = display.handle();
crate::handlers::screencopy::init_global(&display_handle); crate::handlers::screencopy::init_global(&display_handle);
crate::effects::init_global(&display_handle); crate::effects::init_global(&display_handle);
crate::shell_control::init_global(&display_handle);
let alpha_modifier_state = AlphaModifierState::new::<Self>(&display_handle); let alpha_modifier_state = AlphaModifierState::new::<Self>(&display_handle);
let compositor_state = CompositorState::new::<Self>(&display_handle); let compositor_state = CompositorState::new::<Self>(&display_handle);
let cursor_shape_state = CursorShapeManagerState::new::<Self>(&display_handle); let cursor_shape_state = CursorShapeManagerState::new::<Self>(&display_handle);
@@ -470,6 +478,9 @@ impl Ferese {
_ipc_socket: None, _ipc_socket: None,
pending_dmabuf_imports: Vec::new(), pending_dmabuf_imports: Vec::new(),
pending_screencopies: Vec::new(), pending_screencopies: Vec::new(),
shell_resources: Vec::new(),
shell_snapshot_serial: 0,
overview_active: false,
next_window_id: 1, next_window_id: 1,
next_output_id: 1, next_output_id: 1,
last_animation_tick: start_time, last_animation_tick: start_time,
@@ -1132,6 +1143,7 @@ impl Ferese {
} }
self.scrolling_world_x = scrolling_world_x; self.scrolling_world_x = scrolling_world_x;
self.sync_window_stacking(); self.sync_window_stacking();
self.send_shell_snapshots();
crate::backends::direct::render_all(self); crate::backends::direct::render_all(self);
} }
@@ -1570,6 +1582,59 @@ impl Ferese {
crate::backends::direct::render_all(self); 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) { pub(crate) fn closing_visual(&self, id: WindowId) -> (f64, f32) {
let Some(animation) = self.closing_windows.get(&id) else { let Some(animation) = self.closing_windows.get(&id) else {
return (1.0, 1.0); return (1.0, 1.0);
+1
View File
@@ -11,6 +11,7 @@ client = ["dep:wayland-client"]
server = ["dep:wayland-server"] server = ["dep:wayland-server"]
[dependencies] [dependencies]
bitflags.workspace = true
wayland-backend.workspace = true wayland-backend.workspace = true
wayland-client = { workspace = true, optional = true } wayland-client = { workspace = true, optional = true }
wayland-scanner.workspace = true wayland-scanner.workspace = true
+54
View File
@@ -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");
}
}
}
}
+112
View File
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ferese_shell_v1">
<copyright>
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.
</copyright>
<interface name="ferese_shell_manager_v1" version="1">
<description summary="create a capability-scoped Ferese shell controller">
This private global is visible only to clients granted the shell-control
capability. It does not assign a Wayland surface role.
</description>
<request name="get_shell">
<arg name="id" type="new_id" interface="ferese_shell_v1"/>
</request>
<request name="destroy" type="destructor"/>
</interface>
<interface name="ferese_shell_v1" version="1">
<description summary="managed-window metadata and shell control">
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.
</description>
<request name="activate_window">
<arg name="window_hi" type="uint"/>
<arg name="window_lo" type="uint"/>
</request>
<request name="close_window">
<arg name="window_hi" type="uint"/>
<arg name="window_lo" type="uint"/>
</request>
<request name="enter_overview"/>
<request name="exit_overview"/>
<request name="select_overview_window">
<arg name="window_hi" type="uint"/>
<arg name="window_lo" type="uint"/>
</request>
<request name="destroy" type="destructor"/>
<enum name="window_state" bitfield="true">
<entry name="focused" value="1"/>
<entry name="urgent" value="2"/>
<entry name="fullscreen" value="4"/>
<entry name="floating" value="8"/>
</enum>
<enum name="failed_request">
<entry name="activate_window" value="0"/>
<entry name="close_window" value="1"/>
<entry name="select_overview_window" value="2"/>
</enum>
<event name="snapshot_begin">
<arg name="serial" type="uint"/>
</event>
<event name="window">
<arg name="window_hi" type="uint"/>
<arg name="window_lo" type="uint"/>
<arg name="workspace_hi" type="uint"/>
<arg name="workspace_lo" type="uint"/>
<arg name="app_id" type="string"/>
<arg name="title" type="string"/>
<arg name="state" type="uint" enum="window_state"/>
</event>
<event name="snapshot_end">
<arg name="serial" type="uint"/>
</event>
<event name="overview_state">
<arg name="active" type="uint" summary="one when overview is active"/>
</event>
<event name="request_failed">
<arg name="request" type="uint" enum="failed_request"/>
<arg name="window_hi" type="uint"/>
<arg name="window_lo" type="uint"/>
<arg name="reason" type="string"/>
</event>
<event name="capabilities">
<description summary="marks completion of initial protocol setup"/>
<arg name="version" type="uint"/>
</event>
</interface>
</protocol>
@@ -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
@@ -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<dyn Error>> {
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<FereseShellManagerV1>,
shell: Option<FereseShellV1>,
capabilities: Option<u32>,
snapshot_serial: Option<u32>,
snapshot_complete: bool,
window_count: usize,
overview_active: Option<bool>,
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<wl_registry::WlRegistry, ()> for ProbeState {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: wl_registry::Event,
_data: &(),
_connection: &Connection,
qh: &QueueHandle<Self>,
) {
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<FereseShellV1, ()> for ProbeState {
fn event(
state: &mut Self,
_shell: &FereseShellV1,
event: ferese_shell_v1::Event,
_data: &(),
_connection: &Connection,
_qh: &QueueHandle<Self>,
) {
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);