155 lines
4.0 KiB
Rust
155 lines
4.0 KiB
Rust
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::error;
|
|
|
|
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 = inner
|
|
.write()
|
|
.expect(error::ERR_WRITE_LOCK);
|
|
toplevel_implem(event, &mut inner, &mut callback, ddata);
|
|
});
|
|
|
|
Self
|
|
}
|
|
}
|