#![allow(clippy::single_match)]
use super::{
clipboard, display, error::Error, events::WaylandSource, keyboard, outputs, pointers, surfaces,
window::WindowHandle,
};
use crate::{backend, mouse, AppHandler, TimerToken};
use calloop;
use std::{
cell::{Cell, RefCell},
collections::{BTreeMap, BinaryHeap},
rc::Rc,
time::{Duration, Instant},
};
use crate::backend::shared::linux;
use wayland_client::protocol::wl_keyboard::WlKeyboard;
use wayland_client::protocol::wl_registry;
use wayland_client::{
self as wl,
protocol::{
wl_compositor::WlCompositor,
wl_pointer::WlPointer,
wl_seat::{self, WlSeat},
wl_shm::{self, WlShm},
wl_surface::WlSurface,
},
};
use wayland_cursor::CursorTheme;
use wayland_protocols::wlr::unstable::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1;
use wayland_protocols::xdg_shell::client::xdg_positioner::XdgPositioner;
use wayland_protocols::xdg_shell::client::xdg_surface;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Timer(backend::shared::Timer<u64>);
impl Timer {
pub(crate) fn new(id: u64, deadline: Instant) -> Self {
Self(backend::shared::Timer::new(deadline, id))
}
pub(crate) fn id(self) -> u64 {
self.0.data
}
pub(crate) fn deadline(&self) -> Instant {
self.0.deadline()
}
pub fn token(&self) -> TimerToken {
self.0.token()
}
}
impl std::cmp::Ord for Timer {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.deadline().cmp(&other.0.deadline()).reverse()
}
}
impl std::cmp::PartialOrd for Timer {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone)]
pub struct Application {
pub(super) data: std::sync::Arc<Data>,
}
#[allow(dead_code)]
pub(crate) struct Data {
pub(super) wayland: std::rc::Rc<display::Environment>,
pub(super) zwlr_layershell_v1: Option<wl::Main<ZwlrLayerShellV1>>,
pub(super) wl_compositor: wl::Main<WlCompositor>,
pub(super) wl_shm: wl::Main<WlShm>,
pub(super) outputs: Rc<RefCell<BTreeMap<u32, outputs::Meta>>>,
pub(super) seats: Rc<RefCell<BTreeMap<u32, Rc<RefCell<Seat>>>>>,
pub(super) handles: RefCell<im::OrdMap<u64, WindowHandle>>,
pub(super) formats: RefCell<Vec<wl_shm::Format>>,
pub(super) shutdown: Cell<bool>,
pub(super) active_surface_id: RefCell<std::collections::VecDeque<u64>>,
pub(super) timer_handle: calloop::timer::TimerHandle<TimerToken>,
timer_source: RefCell<Option<calloop::timer::Timer<TimerToken>>>,
pub(super) timers: RefCell<BinaryHeap<Timer>>,
pub(super) roundtrip_requested: RefCell<bool>,
pub(super) display_flushed: RefCell<bool>,
pub(super) pointer: pointers::Pointer,
keyboard: keyboard::Manager,
clipboard: clipboard::Manager,
outputsqueue: RefCell<Option<calloop::channel::Channel<outputs::Event>>>,
}
impl Application {
pub fn new() -> Result<Self, Error> {
tracing::info!("wayland application initiated");
let dispatcher = display::Dispatcher::default();
let outputqueue = outputs::auto(&dispatcher)?;
let seats: Rc<RefCell<BTreeMap<u32, Rc<RefCell<Seat>>>>> =
Rc::new(RefCell::new(BTreeMap::new()));
let weak_seats = Rc::downgrade(&seats);
display::GlobalEventDispatch::subscribe(
&dispatcher,
move |event: &'_ wl::GlobalEvent,
registry: &'_ wl::Attached<wl_registry::WlRegistry>,
_ctx: &'_ wl::DispatchData| {
match event {
wl::GlobalEvent::New {
id,
interface,
version,
} => {
let id = *id;
let version = *version;
if interface.as_str() != "wl_seat" {
return;
}
tracing::debug!("seat detected {:?} {:?} {:?}", interface, id, version);
let version = version.min(7);
let new_seat = registry.bind::<WlSeat>(version, id);
let prev_seat = weak_seats
.upgrade()
.unwrap()
.borrow_mut()
.insert(id, Rc::new(RefCell::new(Seat::new(new_seat))));
assert!(
prev_seat.is_none(),
"internal: wayland should always use new IDs"
);
}
wl::GlobalEvent::Removed { .. } => {
}
};
},
);
let env = display::new(dispatcher)?;
display::print(&env.registry);
let zwlr_layershell_v1 = env
.registry
.instantiate_exact::<ZwlrLayerShellV1>(1)
.map_or_else(
|e| {
tracing::info!("unable to instantiate layershell {:?}", e);
None
},
Some,
);
let wl_compositor = env
.registry
.instantiate_range::<WlCompositor>(1, 5)
.map_err(|e| Error::global("wl_compositor", 1, e))?;
let wl_shm = env
.registry
.instantiate_exact::<WlShm>(1)
.map_err(|e| Error::global("wl_shm", 1, e))?;
let timer_source = calloop::timer::Timer::new().unwrap();
let timer_handle = timer_source.handle();
let pointer = pointers::Pointer::new(
CursorTheme::load(64, &wl_shm),
wl_compositor.create_surface(),
);
let appdata = std::sync::Arc::new(Data {
zwlr_layershell_v1,
wl_compositor,
wl_shm: wl_shm.clone(),
outputs: Rc::new(RefCell::new(BTreeMap::new())),
seats,
handles: RefCell::new(im::OrdMap::new()),
formats: RefCell::new(vec![]),
shutdown: Cell::new(false),
active_surface_id: RefCell::new(std::collections::VecDeque::with_capacity(20)),
timer_handle,
timer_source: RefCell::new(Some(timer_source)),
timers: RefCell::new(BinaryHeap::new()),
display_flushed: RefCell::new(false),
pointer,
keyboard: keyboard::Manager::default(),
clipboard: clipboard::Manager::new(&env.display, &env.registry)?,
roundtrip_requested: RefCell::new(false),
outputsqueue: RefCell::new(Some(outputqueue)),
wayland: std::rc::Rc::new(env),
});
wl_shm.quick_assign(with_cloned!(appdata; move |d1, event, d3| {
tracing::debug!("shared memory events {:?} {:?} {:?}", d1, event, d3);
match event {
wl_shm::Event::Format { format } => appdata.formats.borrow_mut().push(format),
_ => (), }
}));
for (id, seat) in appdata.seats.borrow().iter() {
let id = *id; let wl_seat = seat.borrow().wl_seat.clone();
wl_seat.quick_assign(with_cloned!(seat, appdata; move |d1, event, d3| {
tracing::debug!("seat events {:?} {:?} {:?}", d1, event, d3);
let mut seat = seat.borrow_mut();
appdata.clipboard.attach(&mut seat);
match event {
wl_seat::Event::Capabilities { capabilities } => {
seat.capabilities = capabilities;
if capabilities.contains(wl_seat::Capability::Keyboard)
&& seat.keyboard.is_none()
{
seat.keyboard = Some(appdata.keyboard.attach(id, seat.wl_seat.clone()));
}
if capabilities.contains(wl_seat::Capability::Pointer)
&& seat.pointer.is_none()
{
let pointer = seat.wl_seat.get_pointer();
appdata.pointer.attach(pointer.detach());
pointer.quick_assign({
let app = appdata.clone();
move |pointer, event, _| {
pointers::Pointer::consume(app.clone(), pointer.detach(), event);
}
});
seat.pointer = Some(pointer);
}
}
wl_seat::Event::Name { name } => {
seat.name = name;
}
_ => tracing::info!("seat quick assign unknown event {:?}", event), }
}));
}
appdata.sync()?;
Ok(Application { data: appdata })
}
pub fn run(mut self, _handler: Option<Box<dyn AppHandler>>) {
tracing::info!("wayland event loop initiated");
let timer_source = self.data.timer_source.borrow_mut().take().unwrap();
self.data.wayland.display.flush().unwrap();
let mut eventloop = calloop::EventLoop::try_new().unwrap();
let handle = eventloop.handle();
let wayland_dispatcher = WaylandSource::new(self.data.clone()).into_dispatcher();
self.data.keyboard.events(&handle);
handle.register_dispatcher(wayland_dispatcher).unwrap();
handle
.insert_source(self.data.outputsqueue.take().unwrap(), {
move |evt, _ignored, appdata| match evt {
calloop::channel::Event::Closed => {}
calloop::channel::Event::Msg(output) => match output {
outputs::Event::Located(output) => {
tracing::debug!("output added {:?} {:?}", output.gid, output.id());
appdata
.outputs
.borrow_mut()
.insert(output.id(), output.clone());
for (_, win) in appdata.handles_iter() {
surfaces::Outputs::inserted(&win, &output);
}
}
outputs::Event::Removed(output) => {
tracing::debug!("output removed {:?} {:?}", output.gid, output.id());
appdata.outputs.borrow_mut().remove(&output.id());
for (_, win) in appdata.handles_iter() {
surfaces::Outputs::removed(&win, &output);
}
}
},
}
})
.unwrap();
handle
.insert_source(timer_source, move |token, _metadata, appdata| {
tracing::trace!("timer source {:?}", token);
appdata.handle_timer_event(token);
})
.unwrap();
let signal = eventloop.get_signal();
let handle = handle.clone();
let res = eventloop.run(Duration::from_millis(20), &mut self.data, move |appdata| {
if appdata.shutdown.get() {
tracing::debug!("shutting down, requested");
signal.stop();
return;
}
if appdata.handles.borrow().len() == 0 {
tracing::debug!("shutting down, no window remaining");
signal.stop();
return;
}
Data::idle_repaint(handle.clone());
});
match res {
Ok(_) => tracing::info!("wayland event loop completed"),
Err(cause) => tracing::error!("wayland event loop failed {:?}", cause),
}
}
pub fn quit(&self) {
self.data.shutdown.set(true);
}
pub fn clipboard(&self) -> clipboard::Clipboard {
clipboard::Clipboard::from(&self.data.clipboard)
}
pub fn get_locale() -> String {
linux::env::locale()
}
}
impl surfaces::Compositor for Data {
fn output(&self, id: u32) -> Option<outputs::Meta> {
self.outputs.borrow().get(&id).cloned()
}
fn create_surface(&self) -> wl::Main<WlSurface> {
self.wl_compositor.create_surface()
}
fn shared_mem(&self) -> wl::Main<WlShm> {
self.wl_shm.clone()
}
fn get_xdg_positioner(&self) -> wl::Main<XdgPositioner> {
self.wayland.xdg_base.create_positioner()
}
fn get_xdg_surface(&self, s: &wl::Main<WlSurface>) -> wl::Main<xdg_surface::XdgSurface> {
self.wayland.xdg_base.get_xdg_surface(s)
}
fn zwlr_layershell_v1(&self) -> Option<wl::Main<ZwlrLayerShellV1>> {
self.zwlr_layershell_v1.clone()
}
}
impl Data {
pub(crate) fn set_cursor(&self, cursor: &mouse::Cursor) {
self.pointer.replace(cursor);
}
pub(crate) fn sync(&self) -> Result<(), Error> {
self.wayland
.queue
.borrow_mut()
.sync_roundtrip(&mut (), |evt, _, _| {
panic!("unexpected wayland event: {evt:?}")
})
.map_err(Error::fatal)?;
Ok(())
}
fn current_window_id(&self) -> u64 {
static DEFAULT: u64 = 0_u64;
*self.active_surface_id.borrow().get(0).unwrap_or(&DEFAULT)
}
pub(super) fn acquire_current_window(&self) -> Option<WindowHandle> {
self.handles
.borrow()
.get(&self.current_window_id())
.cloned()
}
fn handle_timer_event(&self, _token: TimerToken) {
let mut expired_timers = Vec::with_capacity(1);
let mut timers = self.timers.borrow_mut();
let now = Instant::now();
while matches!(timers.peek(), Some(timer) if timer.deadline() < now) {
expired_timers.push(timers.pop().unwrap());
}
drop(timers);
for expired in expired_timers {
let win = match self.handles.borrow().get(&expired.id()).cloned() {
Some(s) => s,
None => {
tracing::warn!(
"received event for surface that doesn't exist any more {:?} {:?}",
expired,
expired.id()
);
continue;
}
};
if let Some(data) = win.data() {
data.handler.borrow_mut().timer(expired.token())
}
}
for (_, win) in self.handles_iter() {
if let Some(data) = win.data() {
data.run_deferred_tasks()
}
}
if let Some(timer) = self.timers.borrow().peek() {
self.timer_handle
.add_timeout(timer.deadline() - now, timer.token());
}
self.wayland.display.flush().unwrap();
}
pub(super) fn handles_iter(&self) -> impl Iterator<Item = (u64, WindowHandle)> {
self.handles.borrow().clone().into_iter()
}
fn idle_repaint(loophandle: calloop::LoopHandle<'_, std::sync::Arc<Data>>) {
loophandle.insert_idle({
move |appdata| {
tracing::trace!("idle processing initiated");
for (_id, winhandle) in appdata.handles_iter() {
winhandle.request_anim_frame();
winhandle.run_idle();
if *appdata.display_flushed.borrow() {
tracing::trace!("idle repaint flushing display initiated");
if let Err(cause) = appdata.wayland.queue.borrow().display().flush() {
tracing::warn!("unable to flush display: {:?}", cause);
}
}
}
tracing::trace!("idle processing completed");
}
});
}
}
impl From<Application> for surfaces::CompositorHandle {
fn from(app: Application) -> surfaces::CompositorHandle {
surfaces::CompositorHandle::from(app.data)
}
}
impl From<std::sync::Arc<Data>> for surfaces::CompositorHandle {
fn from(data: std::sync::Arc<Data>) -> surfaces::CompositorHandle {
surfaces::CompositorHandle::direct(
std::sync::Arc::downgrade(&data) as std::sync::Weak<dyn surfaces::Compositor>
)
}
}
#[derive(Debug, Clone)]
pub struct Seat {
pub(super) wl_seat: wl::Main<WlSeat>,
name: String,
capabilities: wl_seat::Capability,
keyboard: Option<wl::Main<WlKeyboard>>,
pointer: Option<wl::Main<WlPointer>>,
}
impl Seat {
fn new(wl_seat: wl::Main<WlSeat>) -> Self {
Self {
wl_seat,
name: "".into(),
capabilities: wl_seat::Capability::empty(),
keyboard: None,
pointer: None,
}
}
}