use std::{cell::RefCell, fmt};
use wayland_client::{
protocol::{wl_output, wl_registry, wl_seat, wl_shell, wl_surface},
Attached, DispatchData,
};
pub use wayland_protocols::xdg_shell::client::xdg_toplevel::State;
use wayland_protocols::{
unstable::xdg_shell::v6::client::zxdg_shell_v6,
xdg_shell::client::{xdg_toplevel, xdg_wm_base},
};
use crate::environment::{Environment, GlobalHandler};
mod wl;
mod xdg;
mod zxdg;
use crate::lazy_global::LazyGlobal;
#[derive(Clone, Debug)]
pub enum Event {
Configure {
new_size: Option<(u32, u32)>,
states: Vec<State>,
},
Close,
}
#[derive(Debug)]
pub enum Shell {
Xdg(Attached<xdg_wm_base::XdgWmBase>),
Zxdg(Attached<zxdg_shell_v6::ZxdgShellV6>),
Wl(Attached<wl_shell::WlShell>),
}
impl Shell {
pub fn needs_configure(&self) -> bool {
match self {
Shell::Wl(_) => false,
Shell::Xdg(_) => true,
Shell::Zxdg(_) => true,
}
}
}
pub(crate) fn create_shell_surface<F>(
shell: &Shell,
surface: &wl_surface::WlSurface,
callback: F,
) -> Box<dyn ShellSurface>
where
F: FnMut(Event, DispatchData) + 'static,
{
match *shell {
Shell::Wl(ref shell) => Box::new(wl::Wl::create(surface, shell, callback)) as Box<_>,
Shell::Xdg(ref shell) => Box::new(xdg::Xdg::create(surface, shell, callback)) as Box<_>,
Shell::Zxdg(ref shell) => Box::new(zxdg::Zxdg::create(surface, shell, callback)) as Box<_>,
}
}
pub trait ShellSurface: fmt::Debug + Send + Sync {
fn resize(&self, seat: &wl_seat::WlSeat, serial: u32, edges: xdg_toplevel::ResizeEdge);
fn move_(&self, seat: &wl_seat::WlSeat, serial: u32);
fn set_title(&self, title: String);
fn set_app_id(&self, app_id: String);
fn set_fullscreen(&self, output: Option<&wl_output::WlOutput>);
fn unset_fullscreen(&self);
fn set_maximized(&self);
fn unset_maximized(&self);
fn set_minimized(&self);
fn set_geometry(&self, x: i32, y: i32, width: i32, height: i32);
fn set_min_size(&self, size: Option<(i32, i32)>);
fn set_max_size(&self, size: Option<(i32, i32)>);
fn show_window_menu(&self, seat: &wl_seat::WlSeat, serial: u32, x: i32, y: i32);
fn get_xdg(&self) -> Option<&xdg_toplevel::XdgToplevel>;
}
#[derive(Debug)]
struct ShellInner {
registry: Option<Attached<wl_registry::WlRegistry>>,
wl_shell: LazyGlobal<wl_shell::WlShell>,
xdg_shell: LazyGlobal<xdg_wm_base::XdgWmBase>,
zxdg_shell: LazyGlobal<zxdg_shell_v6::ZxdgShellV6>,
}
#[derive(Debug)]
pub struct ShellHandler {
inner: RefCell<ShellInner>,
}
impl ShellHandler {
pub fn new() -> ShellHandler {
ShellHandler {
inner: RefCell::new(ShellInner {
registry: None,
wl_shell: LazyGlobal::Unknown,
xdg_shell: LazyGlobal::Unknown,
zxdg_shell: LazyGlobal::Unknown,
}),
}
}
}
impl GlobalHandler<wl_shell::WlShell> for ShellHandler {
fn created(
&mut self,
registry: Attached<wl_registry::WlRegistry>,
id: u32,
version: u32,
_: DispatchData,
) {
let mut inner = self.inner.borrow_mut();
if inner.registry.is_none() {
inner.registry = Some(registry);
}
if let LazyGlobal::Unknown = inner.wl_shell {
inner.wl_shell = LazyGlobal::Seen { id, version };
} else {
log::warn!("Compositor advertised wl_shell multiple times, ignoring.")
}
}
fn get(&self) -> Option<Attached<wl_shell::WlShell>> {
let mut inner = self.inner.borrow_mut();
match inner.wl_shell {
LazyGlobal::Bound(ref shell) => Some(shell.clone()),
LazyGlobal::Unknown => None,
LazyGlobal::Seen { id, .. } => {
let registry = inner.registry.as_ref().unwrap();
let shell = registry.bind::<wl_shell::WlShell>(1, id);
inner.wl_shell = LazyGlobal::Bound((*shell).clone());
Some((*shell).clone())
}
}
}
}
impl GlobalHandler<xdg_wm_base::XdgWmBase> for ShellHandler {
fn created(
&mut self,
registry: Attached<wl_registry::WlRegistry>,
id: u32,
version: u32,
_: DispatchData,
) {
let mut inner = self.inner.borrow_mut();
if inner.registry.is_none() {
inner.registry = Some(registry);
}
if let LazyGlobal::Unknown = inner.xdg_shell {
inner.xdg_shell = LazyGlobal::Seen { id, version };
} else {
log::warn!("Compositor advertised xdg_wm_base multiple times, ignoring.")
}
}
fn get(&self) -> Option<Attached<xdg_wm_base::XdgWmBase>> {
let mut inner = self.inner.borrow_mut();
match inner.xdg_shell {
LazyGlobal::Bound(ref shell) => Some(shell.clone()),
LazyGlobal::Unknown => None,
LazyGlobal::Seen { version, id } => {
let registry = inner.registry.as_ref().unwrap();
let version = std::cmp::min(2, version);
let shell = registry.bind::<xdg_wm_base::XdgWmBase>(version, id);
shell.quick_assign(|shell, event, _| {
if let xdg_wm_base::Event::Ping { serial } = event {
shell.pong(serial);
}
});
inner.xdg_shell = LazyGlobal::Bound((*shell).clone());
Some((*shell).clone())
}
}
}
}
impl GlobalHandler<zxdg_shell_v6::ZxdgShellV6> for ShellHandler {
fn created(
&mut self,
registry: Attached<wl_registry::WlRegistry>,
id: u32,
version: u32,
_: DispatchData,
) {
let mut inner = self.inner.borrow_mut();
if inner.registry.is_none() {
inner.registry = Some(registry);
}
if let LazyGlobal::Unknown = inner.zxdg_shell {
inner.zxdg_shell = LazyGlobal::Seen { id, version };
} else {
log::warn!("Compositor advertised zxdg_shell_v6 multiple times, ignoring.")
}
}
fn get(&self) -> Option<Attached<zxdg_shell_v6::ZxdgShellV6>> {
let mut inner = self.inner.borrow_mut();
match inner.zxdg_shell {
LazyGlobal::Bound(ref shell) => Some(shell.clone()),
LazyGlobal::Unknown => None,
LazyGlobal::Seen { id, .. } => {
let registry = inner.registry.as_ref().unwrap();
let shell = registry.bind::<zxdg_shell_v6::ZxdgShellV6>(1, id);
shell.quick_assign(|shell, event, _| {
if let zxdg_shell_v6::Event::Ping { serial } = event {
shell.pong(serial);
}
});
inner.zxdg_shell = LazyGlobal::Bound((*shell).clone());
Some((*shell).clone())
}
}
}
}
impl ShellHandling for ShellHandler {
fn get_shell(&self) -> Option<Shell> {
GlobalHandler::<xdg_wm_base::XdgWmBase>::get(self)
.map(Shell::Xdg)
.or_else(|| GlobalHandler::<zxdg_shell_v6::ZxdgShellV6>::get(self).map(Shell::Zxdg))
.or_else(|| GlobalHandler::<wl_shell::WlShell>::get(self).map(Shell::Wl))
}
}
pub trait ShellHandling {
fn get_shell(&self) -> Option<Shell>;
}
impl<E: ShellHandling> Environment<E> {
pub fn get_shell(&self) -> Option<Shell> {
self.with_inner(|extras| extras.get_shell())
}
pub fn create_shell_surface<F>(
&self,
surface: &wl_surface::WlSurface,
f: F,
) -> Box<dyn ShellSurface>
where
F: FnMut(Event, DispatchData) + 'static,
{
let shell = self
.get_shell()
.expect("SCTK: trying to create a shell surface without any supported shell.");
create_shell_surface(&shell, surface, f)
}
}