feat: new clipboard manager module
This commit is contained in:
152
src/clients/wayland/wlr_foreign_toplevel/handle.rs
Normal file
152
src/clients/wayland/wlr_foreign_toplevel/handle.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use tracing::trace;
|
||||
use wayland_client::{DispatchData, Main};
|
||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{Event, ZwlrForeignToplevelHandleV1};
|
||||
use crate::write_lock;
|
||||
|
||||
const STATE_ACTIVE: u32 = 2;
|
||||
const STATE_FULLSCREEN: u32 = 3;
|
||||
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
fn get_id() -> usize {
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ToplevelInfo {
|
||||
pub id: usize,
|
||||
pub app_id: String,
|
||||
pub title: String,
|
||||
pub active: bool,
|
||||
pub fullscreen: bool,
|
||||
|
||||
ready: bool,
|
||||
}
|
||||
|
||||
impl ToplevelInfo {
|
||||
fn new() -> Self {
|
||||
let id = get_id();
|
||||
Self {
|
||||
id,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Toplevel;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToplevelEvent {
|
||||
pub toplevel: ToplevelInfo,
|
||||
pub change: ToplevelChange,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ToplevelChange {
|
||||
New,
|
||||
Close,
|
||||
Title(String),
|
||||
Focus(bool),
|
||||
Fullscreen(bool),
|
||||
}
|
||||
|
||||
fn toplevel_implem<F>(event: Event, info: &mut ToplevelInfo, implem: &mut F, ddata: DispatchData)
|
||||
where
|
||||
F: FnMut(ToplevelEvent, DispatchData),
|
||||
{
|
||||
trace!("event: {event:?} (info: {info:?})");
|
||||
|
||||
let change = match event {
|
||||
Event::AppId { app_id } => {
|
||||
info.app_id = app_id;
|
||||
None
|
||||
}
|
||||
Event::Title { title } => {
|
||||
info.title = title.clone();
|
||||
|
||||
if info.ready {
|
||||
Some(ToplevelChange::Title(title))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Event::State { state } => {
|
||||
// state is received as a `Vec<u8>` where every 4 bytes make up a `u32`
|
||||
// the u32 then represents a value in the `State` enum.
|
||||
assert_eq!(state.len() % 4, 0);
|
||||
|
||||
let state = (0..state.len() / 4)
|
||||
.map(|i| {
|
||||
let slice: [u8; 4] = state[i * 4..i * 4 + 4]
|
||||
.try_into()
|
||||
.expect("Received invalid state length");
|
||||
u32::from_le_bytes(slice)
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let new_active = state.contains(&STATE_ACTIVE);
|
||||
let new_fullscreen = state.contains(&STATE_FULLSCREEN);
|
||||
|
||||
let change = if info.ready && new_active != info.active {
|
||||
Some(ToplevelChange::Focus(new_active))
|
||||
} else if info.ready && new_fullscreen != info.fullscreen {
|
||||
Some(ToplevelChange::Fullscreen(new_fullscreen))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
info.active = new_active;
|
||||
info.fullscreen = new_fullscreen;
|
||||
|
||||
change
|
||||
}
|
||||
Event::Closed => {
|
||||
if info.ready {
|
||||
Some(ToplevelChange::Close)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Event::OutputEnter { output: _ }
|
||||
| Event::OutputLeave { output: _ }
|
||||
| Event::Parent { parent: _ } => None,
|
||||
Event::Done => {
|
||||
if info.ready || info.app_id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
info.ready = true;
|
||||
Some(ToplevelChange::New)
|
||||
}
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some(change) = change {
|
||||
let event = ToplevelEvent {
|
||||
change,
|
||||
toplevel: info.clone(),
|
||||
};
|
||||
|
||||
implem(event, ddata);
|
||||
}
|
||||
}
|
||||
|
||||
impl Toplevel {
|
||||
pub fn init<F>(handle: &Main<ZwlrForeignToplevelHandleV1>, mut callback: F) -> Self
|
||||
where
|
||||
F: FnMut(ToplevelEvent, DispatchData) + 'static,
|
||||
{
|
||||
let inner = Arc::new(RwLock::new(ToplevelInfo::new()));
|
||||
|
||||
handle.quick_assign(move |_handle, event, ddata| {
|
||||
let mut inner = write_lock!(inner);
|
||||
toplevel_implem(event, &mut inner, &mut callback, ddata);
|
||||
});
|
||||
|
||||
Self
|
||||
}
|
||||
}
|
||||
163
src/clients/wayland/wlr_foreign_toplevel/manager.rs
Normal file
163
src/clients/wayland/wlr_foreign_toplevel/manager.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use super::handle::{Toplevel, ToplevelEvent};
|
||||
use crate::wayland::LazyGlobal;
|
||||
use smithay_client_toolkit::environment::{Environment, GlobalHandler};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::{self, Rc};
|
||||
use tracing::warn;
|
||||
use wayland_client::protocol::wl_registry::WlRegistry;
|
||||
use wayland_client::{Attached, DispatchData};
|
||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{
|
||||
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
||||
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
|
||||
};
|
||||
|
||||
struct ToplevelHandlerInner {
|
||||
manager: LazyGlobal<ZwlrForeignToplevelManagerV1>,
|
||||
registry: Option<Attached<WlRegistry>>,
|
||||
toplevels: Vec<Toplevel>,
|
||||
}
|
||||
|
||||
impl ToplevelHandlerInner {
|
||||
const fn new() -> Self {
|
||||
let toplevels = vec![];
|
||||
|
||||
Self {
|
||||
registry: None,
|
||||
manager: LazyGlobal::Unknown,
|
||||
toplevels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ToplevelHandler {
|
||||
inner: Rc<RefCell<ToplevelHandlerInner>>,
|
||||
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<ToplevelStatusCallback>>>>>,
|
||||
}
|
||||
|
||||
impl ToplevelHandler {
|
||||
pub fn init() -> Self {
|
||||
let inner = Rc::new(RefCell::new(ToplevelHandlerInner::new()));
|
||||
|
||||
Self {
|
||||
inner,
|
||||
status_listeners: Rc::new(RefCell::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalHandler<ZwlrForeignToplevelManagerV1> for ToplevelHandler {
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
_ddata: DispatchData,
|
||||
) {
|
||||
let mut inner = RefCell::borrow_mut(&self.inner);
|
||||
if inner.registry.is_none() {
|
||||
inner.registry = Some(registry);
|
||||
}
|
||||
if matches!(inner.manager, LazyGlobal::Unknown) {
|
||||
inner.manager = LazyGlobal::Seen { id, version }
|
||||
} else {
|
||||
warn!(
|
||||
"Compositor advertised zwlr_foreign_toplevel_manager_v1 multiple times, ignoring."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self) -> Option<Attached<ZwlrForeignToplevelManagerV1>> {
|
||||
let mut inner = RefCell::borrow_mut(&self.inner);
|
||||
|
||||
match inner.manager {
|
||||
LazyGlobal::Bound(ref mgr) => Some(mgr.clone()),
|
||||
LazyGlobal::Unknown => None,
|
||||
LazyGlobal::Seen { id, version } => {
|
||||
let registry = inner.registry.as_ref().expect("Failed to get registry");
|
||||
// current max protocol version = 3
|
||||
let version = std::cmp::min(version, 3);
|
||||
let manager = registry.bind::<ZwlrForeignToplevelManagerV1>(version, id);
|
||||
|
||||
{
|
||||
let inner = self.inner.clone();
|
||||
let status_listeners = self.status_listeners.clone();
|
||||
|
||||
manager.quick_assign(move |_, event, _ddata| {
|
||||
let mut inner = RefCell::borrow_mut(&inner);
|
||||
let status_listeners = status_listeners.clone();
|
||||
|
||||
match event {
|
||||
zwlr_foreign_toplevel_manager_v1::Event::Toplevel {
|
||||
toplevel: handle,
|
||||
} => {
|
||||
let toplevel =
|
||||
Toplevel::init(&handle.clone(), move |event, ddata| {
|
||||
notify_status_listeners(
|
||||
&handle,
|
||||
&event,
|
||||
ddata,
|
||||
&status_listeners,
|
||||
);
|
||||
});
|
||||
|
||||
inner.toplevels.push(toplevel);
|
||||
}
|
||||
zwlr_foreign_toplevel_manager_v1::Event::Finished => {}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inner.manager = LazyGlobal::Bound((*manager).clone());
|
||||
Some((*manager).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ToplevelStatusCallback =
|
||||
dyn FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static;
|
||||
|
||||
/// Notifies the callbacks of an event on the toplevel
|
||||
fn notify_status_listeners(
|
||||
toplevel: &ZwlrForeignToplevelHandleV1,
|
||||
event: &ToplevelEvent,
|
||||
mut ddata: DispatchData,
|
||||
listeners: &RefCell<Vec<rc::Weak<RefCell<ToplevelStatusCallback>>>>,
|
||||
) {
|
||||
listeners.borrow_mut().retain(|lst| {
|
||||
rc::Weak::upgrade(lst).map_or(false, |cb| {
|
||||
(cb.borrow_mut())(toplevel.clone(), event.clone(), ddata.reborrow());
|
||||
true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub struct ToplevelStatusListener {
|
||||
_cb: Rc<RefCell<ToplevelStatusCallback>>,
|
||||
}
|
||||
|
||||
pub trait ToplevelHandling {
|
||||
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
|
||||
where
|
||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static;
|
||||
}
|
||||
|
||||
impl ToplevelHandling for ToplevelHandler {
|
||||
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
|
||||
where
|
||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
||||
{
|
||||
let rc = Rc::new(RefCell::new(f)) as Rc<_>;
|
||||
self.status_listeners.borrow_mut().push(Rc::downgrade(&rc));
|
||||
ToplevelStatusListener { _cb: rc }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen_for_toplevels<E, F>(env: &Environment<E>, f: F) -> ToplevelStatusListener
|
||||
where
|
||||
E: ToplevelHandling,
|
||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
||||
{
|
||||
env.with_inner(move |inner| ToplevelHandling::listen(inner, f))
|
||||
}
|
||||
39
src/clients/wayland/wlr_foreign_toplevel/mod.rs
Normal file
39
src/clients/wayland/wlr_foreign_toplevel/mod.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::sync::RwLock;
|
||||
use indexmap::IndexMap;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
use tracing::trace;
|
||||
use super::Env;
|
||||
use handle::{ToplevelEvent, ToplevelChange, ToplevelInfo};
|
||||
use manager::{ToplevelHandling, ToplevelStatusListener};
|
||||
use wayland_client::DispatchData;
|
||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1;
|
||||
use crate::{send, write_lock};
|
||||
|
||||
pub mod handle;
|
||||
pub mod manager;
|
||||
|
||||
impl ToplevelHandling for Env {
|
||||
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
|
||||
where
|
||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
||||
{
|
||||
self.toplevel.listen(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_toplevels(
|
||||
toplevels: &RwLock<IndexMap<usize, (ToplevelInfo, ZwlrForeignToplevelHandleV1)>>,
|
||||
handle: ZwlrForeignToplevelHandleV1,
|
||||
event: ToplevelEvent,
|
||||
tx: &Sender<ToplevelEvent>,
|
||||
) {
|
||||
trace!("Received toplevel event: {:?}", event);
|
||||
|
||||
if event.change == ToplevelChange::Close {
|
||||
write_lock!(toplevels).remove(&event.toplevel.id);
|
||||
} else {
|
||||
write_lock!(toplevels).insert(event.toplevel.id, (event.toplevel.clone(), handle));
|
||||
}
|
||||
|
||||
send!(tx, event);
|
||||
}
|
||||
Reference in New Issue
Block a user