// 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 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, ObjectId}; use crate::utils::prelude::{RectExt, RectGlobalExt, RectLocalExt}; use super::{ toplevel_info::{ ToplevelHandleState, ToplevelInfoGlobalData, ToplevelInfoHandler, ToplevelState, Window, }, workspace::WorkspaceHandle, }; #[derive(Debug)] 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, { let active_workspaces: Vec<_> = state.active_workspaces().collect(); 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 = map .layer_geometry(layer_surface) .unwrap_or_default() .as_local() .to_global(&output); for window in state .toplevel_info_state() .registered_toplevels() .filter(|w| { let state = w .user_data() .get::() .unwrap() .lock() .unwrap(); active_workspaces.iter().any(|active_workspace| { state.in_workspace(&active_workspace) }) }) { if let Some(window_geo) = window.global_geometry() { if let Some(intersection) = layer_geo.intersection(window_geo) { // relative to layer location let region = Rectangle::new( intersection.loc - layer_geo.loc, intersection.size, ) .as_logical(); new_snapshot.add_toplevel(window, region); } } } for other_surface in map.layers().filter(|s| *s != layer_surface) { if other_surface.wl_surface().id() == layer_surface.wl_surface().id() { continue; } let other_geo = map .layer_geometry(other_surface) .unwrap_or_default() .as_local() .to_global(&output); if let Some(intersection) = layer_geo.intersection(other_geo) { // relative to layer location let region = Rectangle::new( intersection.loc - layer_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; fn active_workspaces(&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) { if let Some(client) = new_notification.client() { for (toplevel, overlap) in &self.last_snapshot.toplevel_overlaps { if let Some(toplevel) = toplevel .upgrade() .ok() .filter(|handle| handle.client().is_some_and(|c| c == client)) { new_notification.toplevel_enter( &toplevel, overlap.loc.x, overlap.loc.y, overlap.size.w, overlap.size.h, ); } } } for (_, (identifier, namespace, exclusive, layer, overlap)) in &self.last_snapshot.layer_overlaps { new_notification.layer_enter( identifier.clone(), namespace.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() { if let Some(client) = toplevel.client() { for notification in notifications .iter() .filter(|n| n.client().is_some_and(|c| c == client)) { notification.toplevel_leave(&toplevel); } } } } } for (toplevel, overlap) in &new_snapshot.toplevel_overlaps { if !self .last_snapshot .toplevel_overlaps .get(toplevel) .is_some_and(|old_overlap| old_overlap == overlap) { if let Ok(toplevel) = toplevel.upgrade() { if let Some(client) = toplevel.client() { for notification in notifications .iter() .filter(|n| n.client().is_some_and(|c| c == client)) { notification.toplevel_enter( &toplevel, overlap.loc.x, overlap.loc.y, overlap.size.w, overlap.size.h, ); } } } } } for (layer_surface, (identifier, ..)) in &self.last_snapshot.layer_overlaps { if !new_snapshot.layer_overlaps.contains_key(layer_surface) { for notification in ¬ifications { notification.layer_leave(identifier.clone()); } } } for (layer_surface, (identifier, namespace, exclusive, layer, overlap)) in &new_snapshot.layer_overlaps { if !self .last_snapshot .layer_overlaps .get(layer_surface) .is_some_and(|(_, _, _, _, old_overlap)| old_overlap == overlap) { for notification in ¬ifications { notification.layer_enter( identifier.clone(), namespace.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 id = layer_surface.wl_surface().id(); let identifier = layer_surface .user_data() .get_or_insert(|| Identifier::from(id.clone())); self.layer_overlaps.insert( id, ( identifier.0.clone(), layer_surface.namespace().to_string(), exclusive, layer, overlap, ), ); } } struct Identifier(String); impl From for Identifier { fn from(value: ObjectId) -> Self { Identifier(value.to_string()) } } 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 { _ => {} } } } macro_rules! delegate_overlap_notify { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::overlap_notify::v1::server::zcosmic_overlap_notify_v1::ZcosmicOverlapNotifyV1: $crate::wayland::protocols::overlap_notify::OverlapNotifyGlobalData ] => $crate::wayland::protocols::overlap_notify::OverlapNotifyState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::overlap_notify::v1::server::zcosmic_overlap_notify_v1::ZcosmicOverlapNotifyV1: () ] => $crate::wayland::protocols::overlap_notify::OverlapNotifyState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::overlap_notify::v1::server::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1: () ] => $crate::wayland::protocols::overlap_notify::OverlapNotifyState); }; } pub(crate) use delegate_overlap_notify;