From bd04d68106c0cdbb95744d410e58579a0ff2ff4a Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 19 Sep 2024 18:11:54 +0200 Subject: [PATCH] overlap-notify: Initial implementation --- Cargo.lock | 1 + Cargo.toml | 1 + src/wayland/protocols/mod.rs | 1 + src/wayland/protocols/overlap_notify.rs | 394 ++++++++++++++++++++++++ src/wayland/protocols/toplevel_info.rs | 8 + 5 files changed, 405 insertions(+) create mode 100644 src/wayland/protocols/overlap_notify.rs diff --git a/Cargo.lock b/Cargo.lock index d3eb3b19..652b9d67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -847,6 +847,7 @@ dependencies = [ "ordered-float", "png", "profiling", + "rand", "regex", "ron", "rust-embed", diff --git a/Cargo.toml b/Cargo.toml index bb623462..5ba9b836 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ zbus = "4.4.0" profiling = { version = "1.0" } rustix = { version = "0.38.32", features = ["process"] } smallvec = "1.13.2" +rand = "0.8.5" [dependencies.id_tree] branch = "feature/copy_clone" diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index c35b0b89..6010d69e 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -3,6 +3,7 @@ pub mod drm; pub mod image_source; pub mod output_configuration; +pub mod overlap_notify; pub mod screencopy; pub mod toplevel_info; pub mod toplevel_management; diff --git a/src/wayland/protocols/overlap_notify.rs b/src/wayland/protocols/overlap_notify.rs new file mode 100644 index 00000000..2a79c268 --- /dev/null +++ b/src/wayland/protocols/overlap_notify.rs @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::{collections::HashMap, sync::Mutex}; + +use cosmic_protocols::{ + overlap_notify::v1::server::{ + zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1, + zcosmic_overlap_notify_v1::{self, ZcosmicOverlapNotifyV1}, + }, + toplevel_info::v1::server::{ + zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, + }, +}; +use rand::distributions::{Alphanumeric, DistString}; +use smithay::{ + desktop::{layer_map_for_output, LayerSurface}, + output::Output, + reexports::{ + wayland_protocols::ext::foreign_toplevel_list::v1::server::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + wayland_protocols_wlr::layer_shell::v1::server::{ + zwlr_layer_shell_v1::Layer as WlrLayer, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + }, + wayland_server::{Client, Dispatch, DisplayHandle, GlobalDispatch, Resource, Weak}, + }, + utils::{Logical, Rectangle}, + wayland::{ + foreign_toplevel_list::ForeignToplevelListHandler, + shell::wlr_layer::{ExclusiveZone, Layer}, + }, +}; +use wayland_backend::server::GlobalId; + +use crate::utils::prelude::{RectExt, RectGlobalExt, RectLocalExt}; + +use super::toplevel_info::{ + ToplevelHandleState, ToplevelInfoGlobalData, ToplevelInfoHandler, ToplevelState, Window, +}; + +pub struct OverlapNotifyState { + instances: Vec, + global: GlobalId, +} + +impl OverlapNotifyState { + pub fn new(dh: &DisplayHandle, client_filter: F) -> OverlapNotifyState + where + D: GlobalDispatch + + Dispatch + + Dispatch + + OverlapNotifyHandler + + 'static, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + let global = dh.create_global::( + 1, + OverlapNotifyGlobalData { + filter: Box::new(client_filter), + }, + ); + OverlapNotifyState { + instances: Vec::new(), + global, + } + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } + + pub fn refresh(state: &mut D) + where + D: GlobalDispatch + + Dispatch + + Dispatch + + OverlapNotifyHandler + + GlobalDispatch + + Dispatch + + Dispatch> + + ForeignToplevelListHandler + + ToplevelInfoHandler + + 'static, + W: Window + 'static, + { + for output in state.outputs() { + let map = layer_map_for_output(output); + for layer_surface in map.layers() { + if let Some(data) = layer_surface + .user_data() + .get::() + { + let mut inner = data.lock().unwrap(); + + if inner.has_active_notifications() { + let mut new_snapshot = OverlapSnapshot::default(); + + let layer_geo = layer_surface.bbox().as_local().to_global(output); + + for window in state.toplevel_info_state().registered_toplevels() { + if let Some(window_geo) = window.global_geometry() { + if let Some(intersection) = layer_geo.intersection(window_geo) { + // relative to window location + let region = Rectangle::from_loc_and_size( + intersection.loc - window_geo.loc, + intersection.size, + ) + .as_logical(); + new_snapshot.add_toplevel(window, region); + } + } + } + + for other_surface in map.layers().filter(|s| *s != layer_surface) { + let other_geo = other_surface.bbox().as_local().to_global(output); + if let Some(intersection) = layer_geo.intersection(other_geo) { + // relative to window location + let region = Rectangle::from_loc_and_size( + intersection.loc - other_geo.loc, + intersection.size, + ) + .as_logical(); + new_snapshot.add_layer(other_surface, region); + } + } + + inner.update_snapshot(new_snapshot); + } + } + } + } + } +} + +pub trait OverlapNotifyHandler: ToplevelInfoHandler { + fn overlap_notify_state(&mut self) -> &mut OverlapNotifyState; + fn layer_surface_from_resource(&self, resource: ZwlrLayerSurfaceV1) -> Option; + fn outputs(&self) -> impl Iterator; +} + +pub struct OverlapNotifyGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +type LayerOverlapNotificationData = Mutex; + +#[derive(Debug, Default)] +struct LayerOverlapNotificationDataInternal { + active_notifications: Vec>, + last_snapshot: OverlapSnapshot, +} + +impl LayerOverlapNotificationDataInternal { + pub fn has_active_notifications(&mut self) -> bool { + self.active_notifications.retain(|w| w.upgrade().is_ok()); + !self.active_notifications.is_empty() + } + + pub fn add_notification(&mut self, new_notification: ZcosmicOverlapNotificationV1) { + for (toplevel, overlap) in &self.last_snapshot.toplevel_overlaps { + if let Ok(toplevel) = toplevel.upgrade() { + new_notification.toplevel_enter( + &toplevel, + overlap.loc.x, + overlap.loc.y, + overlap.size.w, + overlap.size.h, + ); + } + } + for (layer_surface, (exclusive, layer, overlap)) in &self.last_snapshot.layer_overlaps { + new_notification.layer_enter( + layer_surface.clone(), + if *exclusive { 1 } else { 0 }, + match layer { + Layer::Background => WlrLayer::Background, + Layer::Bottom => WlrLayer::Bottom, + Layer::Top => WlrLayer::Top, + Layer::Overlay => WlrLayer::Overlay, + }, + overlap.loc.x, + overlap.loc.y, + overlap.size.w, + overlap.size.h, + ); + } + self.active_notifications.push(new_notification.downgrade()); + } + + pub fn update_snapshot(&mut self, new_snapshot: OverlapSnapshot) { + let notifications = self + .active_notifications + .iter() + .flat_map(|w| w.upgrade().ok()) + .collect::>(); + + for toplevel in self.last_snapshot.toplevel_overlaps.keys() { + if !new_snapshot.toplevel_overlaps.contains_key(toplevel) { + if let Ok(toplevel) = toplevel.upgrade() { + for notification in ¬ifications { + notification.toplevel_leave(&toplevel); + } + } + } + } + for (toplevel, overlap) in &new_snapshot.toplevel_overlaps { + if !self.last_snapshot.toplevel_overlaps.contains_key(toplevel) { + if let Ok(toplevel) = toplevel.upgrade() { + for notification in ¬ifications { + notification.toplevel_enter( + &toplevel, + overlap.loc.x, + overlap.loc.y, + overlap.size.w, + overlap.size.h, + ); + } + } + } + } + + for layer_surface in self.last_snapshot.layer_overlaps.keys() { + if new_snapshot.layer_overlaps.contains_key(layer_surface) { + for notification in ¬ifications { + notification.layer_leave(layer_surface.clone()); + } + } + } + for (layer_surface, (exclusive, layer, overlap)) in &new_snapshot.layer_overlaps { + if !self + .last_snapshot + .layer_overlaps + .contains_key(layer_surface) + { + for notification in ¬ifications { + notification.layer_enter( + layer_surface.clone(), + if *exclusive { 1 } else { 0 }, + match layer { + Layer::Background => WlrLayer::Background, + Layer::Bottom => WlrLayer::Bottom, + Layer::Top => WlrLayer::Top, + Layer::Overlay => WlrLayer::Overlay, + }, + overlap.loc.x, + overlap.loc.y, + overlap.size.w, + overlap.size.h, + ); + } + } + } + + self.last_snapshot = new_snapshot; + } +} + +#[derive(Debug, Default, Clone)] +struct OverlapSnapshot { + toplevel_overlaps: HashMap, Rectangle>, + layer_overlaps: HashMap)>, +} + +impl OverlapSnapshot { + pub fn add_toplevel(&mut self, window: &impl Window, overlap: Rectangle) { + if let Some(handles) = window + .user_data() + .get::() + .unwrap() + .lock() + .unwrap() + .foreign_handle() + .map(|handle| handle.resources()) + { + for handle in handles.into_iter().map(|h| h.downgrade()) { + self.toplevel_overlaps.insert(handle, overlap); + } + } + } + + pub fn add_layer(&mut self, layer_surface: &LayerSurface, overlap: Rectangle) { + let exclusive = matches!( + layer_surface.cached_state().exclusive_zone, + ExclusiveZone::Exclusive(_) + ); + let layer = layer_surface.layer(); + let identifier = layer_surface.user_data().get_or_insert(Identifier::default); + + self.layer_overlaps + .insert(identifier.0.clone(), (exclusive, layer, overlap)); + } +} + +struct Identifier(String); + +impl Default for Identifier { + fn default() -> Self { + Identifier(Alphanumeric.sample_string(&mut rand::thread_rng(), 32)) + } +} + +impl GlobalDispatch for OverlapNotifyState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + OverlapNotifyHandler + + 'static, +{ + fn bind( + state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: smithay::reexports::wayland_server::New, + _global_data: &OverlapNotifyGlobalData, + data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>, + ) { + let instance = data_init.init(resource, ()); + state.overlap_notify_state().instances.push(instance); + } + + fn can_view(client: Client, global_data: &OverlapNotifyGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for OverlapNotifyState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + OverlapNotifyHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _resource: &ZcosmicOverlapNotifyV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>, + ) { + match request { + zcosmic_overlap_notify_v1::Request::NotifyOnOverlap { + overlap_notification, + layer_surface, + } => { + let notification = data_init.init(overlap_notification, ()); + if let Some(surface) = state.layer_surface_from_resource(layer_surface) { + let mut data = surface + .user_data() + .get_or_insert_threadsafe(LayerOverlapNotificationData::default) + .lock() + .unwrap(); + data.add_notification(notification); + } + } + _ => {} + } + } + + fn destroyed( + state: &mut D, + _client: wayland_backend::server::ClientId, + resource: &ZcosmicOverlapNotifyV1, + _data: &(), + ) { + let overlap_state = state.overlap_notify_state(); + overlap_state.instances.retain(|i| i != resource); + } +} + +impl Dispatch for OverlapNotifyState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + OverlapNotifyHandler + + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + _resource: &ZcosmicOverlapNotificationV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + _data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>, + ) { + match request { + _ => {} + } + } +} diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 540975d7..9eb680b1 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -76,6 +76,10 @@ impl ToplevelStateInner { ..Default::default() }) } + + pub fn foreign_handle(&self) -> Option<&ForeignToplevelHandle> { + self.foreign_handle.as_ref() + } } pub struct ToplevelHandleStateInner { @@ -394,6 +398,10 @@ where pub fn global_id(&self) -> GlobalId { self.global.clone() } + + pub fn registered_toplevels(&self) -> impl Iterator { + self.toplevels.iter() + } } fn send_toplevel_to_client(