Reorganize file layout
Moves satellite to be the root package, also allowing it to be built by default.
This commit is contained in:
parent
3afc9ffa9d
commit
c1fc38c3d2
14 changed files with 71 additions and 67 deletions
202
src/clientside.rs
Normal file
202
src/clientside.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
use crate::server::{ObjectEvent, ObjectKey};
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::mpsc;
|
||||
use wayland_client::protocol::{
|
||||
wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor,
|
||||
wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_registry::WlRegistry,
|
||||
wl_seat::WlSeat, wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface,
|
||||
wl_touch::WlTouch,
|
||||
};
|
||||
use wayland_client::{delegate_noop, Connection, Dispatch, EventQueue, Proxy, QueueHandle};
|
||||
use wayland_protocols::wp::relative_pointer::zv1::client::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1,
|
||||
};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::client::{
|
||||
self as dmabuf,
|
||||
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1 as DmabufFeedback,
|
||||
zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
},
|
||||
viewporter::client::{wp_viewport::WpViewport, wp_viewporter::WpViewporter},
|
||||
},
|
||||
xdg::{
|
||||
shell::client::{
|
||||
xdg_popup::XdgPopup, xdg_positioner::XdgPositioner, xdg_surface::XdgSurface,
|
||||
xdg_toplevel::XdgToplevel, xdg_wm_base::XdgWmBase,
|
||||
},
|
||||
xdg_output::zv1::client::{
|
||||
zxdg_output_manager_v1::ZxdgOutputManagerV1, zxdg_output_v1::ZxdgOutputV1 as XdgOutput,
|
||||
},
|
||||
},
|
||||
};
|
||||
use wayland_server::protocol as server;
|
||||
use wl_drm::client::wl_drm::WlDrm;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalData {
|
||||
pub name: u32,
|
||||
pub interface: String,
|
||||
pub version: u32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Globals {
|
||||
pub(crate) events: Vec<(ObjectKey, ObjectEvent)>,
|
||||
pub new_globals: Vec<GlobalData>,
|
||||
}
|
||||
|
||||
pub type ClientQueueHandle = QueueHandle<Globals>;
|
||||
|
||||
pub struct ClientShmPool {
|
||||
pub pool: WlShmPool,
|
||||
pub fd: OwnedFd,
|
||||
}
|
||||
|
||||
pub struct ClientState {
|
||||
pub connection: Connection,
|
||||
pub queue: EventQueue<Globals>,
|
||||
pub qh: ClientQueueHandle,
|
||||
pub globals: Globals,
|
||||
pub registry: WlRegistry,
|
||||
}
|
||||
|
||||
impl ClientState {
|
||||
pub fn new(server_connection: Option<UnixStream>) -> Self {
|
||||
let connection = if let Some(stream) = server_connection {
|
||||
Connection::from_socket(stream)
|
||||
} else {
|
||||
Connection::connect_to_env()
|
||||
}
|
||||
.unwrap();
|
||||
let mut queue = connection.new_event_queue::<Globals>();
|
||||
let qh = queue.handle();
|
||||
let mut globals = Globals::default();
|
||||
|
||||
let registry = connection.display().get_registry(&qh, ());
|
||||
// Get initial globals
|
||||
queue.roundtrip(&mut globals).unwrap();
|
||||
|
||||
Self {
|
||||
connection,
|
||||
queue,
|
||||
qh,
|
||||
globals,
|
||||
registry,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Event<T> = <T as Proxy>::Event;
|
||||
|
||||
delegate_noop!(Globals: WlCompositor);
|
||||
delegate_noop!(Globals: ignore WlShm);
|
||||
delegate_noop!(Globals: ignore ZwpLinuxDmabufV1);
|
||||
delegate_noop!(Globals: ZwpRelativePointerManagerV1);
|
||||
delegate_noop!(Globals: ignore dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1);
|
||||
delegate_noop!(Globals: XdgPositioner);
|
||||
delegate_noop!(Globals: WlShmPool);
|
||||
delegate_noop!(Globals: WpViewporter);
|
||||
delegate_noop!(Globals: WpViewport);
|
||||
delegate_noop!(Globals: ZxdgOutputManagerV1);
|
||||
|
||||
impl Dispatch<WlRegistry, ()> for Globals {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &WlRegistry,
|
||||
event: <WlRegistry as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
if let Event::<WlRegistry>::Global {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
} = event
|
||||
{
|
||||
state.new_globals.push(GlobalData {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgWmBase, ()> for Globals {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
base: &XdgWmBase,
|
||||
event: <XdgWmBase as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
if let Event::<XdgWmBase>::Ping { serial } = event {
|
||||
base.pong(serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlCallback, server::wl_callback::WlCallback> for Globals {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &WlCallback,
|
||||
event: <WlCallback as Proxy>::Event,
|
||||
s_callback: &server::wl_callback::WlCallback,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if let Event::<WlCallback>::Done { callback_data } = event {
|
||||
s_callback.done(callback_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlOutput, mpsc::Sender<Event<WlOutput>>> for Globals {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &WlOutput,
|
||||
event: <WlOutput as Proxy>::Event,
|
||||
data: &mpsc::Sender<Event<WlOutput>>,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
let _ = data.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! push_events {
|
||||
($type:ident) => {
|
||||
impl Dispatch<$type, ObjectKey> for Globals {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &$type,
|
||||
event: <$type as Proxy>::Event,
|
||||
key: &ObjectKey,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
state.events.push((*key, event.into()));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
push_events!(WlSurface);
|
||||
push_events!(WlBuffer);
|
||||
push_events!(XdgSurface);
|
||||
push_events!(XdgToplevel);
|
||||
push_events!(XdgPopup);
|
||||
push_events!(WlSeat);
|
||||
push_events!(WlPointer);
|
||||
push_events!(WlOutput);
|
||||
push_events!(WlKeyboard);
|
||||
push_events!(ZwpRelativePointerV1);
|
||||
push_events!(WlDrm);
|
||||
push_events!(DmabufFeedback);
|
||||
push_events!(XdgOutput);
|
||||
push_events!(WlTouch);
|
||||
163
src/lib.rs
Normal file
163
src/lib.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
mod clientside;
|
||||
mod server;
|
||||
pub mod xstate;
|
||||
|
||||
use crate::server::{PendingSurfaceState, ServerState};
|
||||
use crate::xstate::XState;
|
||||
use log::{error, info};
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::Arc;
|
||||
use wayland_server::{Display, ListeningSocket};
|
||||
use xcb::x;
|
||||
|
||||
pub trait XConnection: Sized + 'static {
|
||||
type ExtraData: FromServerState<Self>;
|
||||
|
||||
fn root_window(&self) -> x::Window;
|
||||
fn set_window_dims(&mut self, window: x::Window, dims: PendingSurfaceState);
|
||||
fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool, data: Self::ExtraData);
|
||||
fn focus_window(&mut self, window: x::Window, data: Self::ExtraData);
|
||||
fn close_window(&mut self, window: x::Window, data: Self::ExtraData);
|
||||
}
|
||||
|
||||
pub trait FromServerState<C: XConnection> {
|
||||
fn create(state: &ServerState<C>) -> Self;
|
||||
}
|
||||
|
||||
type RealServerState = ServerState<Arc<xcb::Connection>>;
|
||||
|
||||
pub trait RunData {
|
||||
fn display(&self) -> Option<&str>;
|
||||
fn server(&self) -> Option<UnixStream> {
|
||||
None
|
||||
}
|
||||
fn created_server(&self) {}
|
||||
fn connected_server(&self) {}
|
||||
fn xwayland_ready(&self, _display: String) {}
|
||||
}
|
||||
|
||||
pub fn main(data: impl RunData) -> Option<()> {
|
||||
let socket = ListeningSocket::bind_auto("wayland", 1..=128).unwrap();
|
||||
let mut display = Display::<RealServerState>::new().unwrap();
|
||||
let dh = display.handle();
|
||||
data.created_server();
|
||||
|
||||
let mut server_state = RealServerState::new(dh, data.server());
|
||||
|
||||
let (xsock_wl, xsock_xwl) = UnixStream::pair().unwrap();
|
||||
// Prevent creation of new Xwayland command from closing fd
|
||||
rustix::io::fcntl_setfd(&xsock_xwl, rustix::io::FdFlags::empty()).unwrap();
|
||||
|
||||
let (ready_tx, ready_rx) = UnixStream::pair().unwrap();
|
||||
rustix::io::fcntl_setfd(&ready_tx, rustix::io::FdFlags::empty()).unwrap();
|
||||
let mut xwayland = Command::new("Xwayland");
|
||||
if let Some(display) = data.display() {
|
||||
xwayland.arg(display);
|
||||
}
|
||||
let mut xwayland = xwayland
|
||||
.args([
|
||||
"-rootless",
|
||||
"-wm",
|
||||
&xsock_xwl.as_raw_fd().to_string(),
|
||||
"-displayfd",
|
||||
&ready_tx.as_raw_fd().to_string(),
|
||||
])
|
||||
.env("WAYLAND_DISPLAY", socket.socket_name().unwrap())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
let (mut finish_tx, mut finish_rx) = UnixStream::pair().unwrap();
|
||||
let stderr = xwayland.stderr.take().unwrap();
|
||||
std::thread::spawn(move || {
|
||||
let reader = BufReader::new(stderr);
|
||||
for line in reader.lines() {
|
||||
let line = line.unwrap();
|
||||
info!(target: "xwayland_process", "{line}");
|
||||
}
|
||||
let status = Box::new(xwayland.wait().unwrap());
|
||||
let status = Box::into_raw(status) as usize;
|
||||
finish_tx.write_all(&status.to_ne_bytes()).unwrap();
|
||||
});
|
||||
|
||||
let mut ready_fds = [
|
||||
PollFd::new(&socket, PollFlags::IN),
|
||||
PollFd::new(&finish_rx, PollFlags::IN),
|
||||
];
|
||||
|
||||
let connection = match poll(&mut ready_fds, -1) {
|
||||
Ok(_) => {
|
||||
if !ready_fds[1].revents().is_empty() {
|
||||
let mut data = [0; (usize::BITS / 8) as usize];
|
||||
finish_rx.read_exact(&mut data).unwrap();
|
||||
let data = usize::from_ne_bytes(data);
|
||||
let status: Box<std::process::ExitStatus> =
|
||||
unsafe { Box::from_raw(data as *mut _) };
|
||||
|
||||
error!("Xwayland exited early with {status}");
|
||||
return None;
|
||||
}
|
||||
|
||||
data.connected_server();
|
||||
socket.accept().unwrap().unwrap()
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("first poll failed: {e:?}")
|
||||
}
|
||||
};
|
||||
drop(finish_rx);
|
||||
|
||||
server_state.connect(connection);
|
||||
server_state.run();
|
||||
|
||||
let mut xstate: Option<XState> = None;
|
||||
|
||||
// Remove the lifetimes on our fds to avoid borrowing issues, since we know they will exist for
|
||||
// the rest of our program anyway
|
||||
let server_fd = unsafe { BorrowedFd::borrow_raw(server_state.clientside_fd().as_raw_fd()) };
|
||||
let display_fd = unsafe { BorrowedFd::borrow_raw(display.backend().poll_fd().as_raw_fd()) };
|
||||
|
||||
let mut fds = [
|
||||
PollFd::from_borrowed_fd(server_fd, PollFlags::IN),
|
||||
PollFd::new(&xsock_wl, PollFlags::IN),
|
||||
PollFd::from_borrowed_fd(display_fd, PollFlags::IN),
|
||||
PollFd::new(&ready_rx, PollFlags::IN),
|
||||
];
|
||||
|
||||
let mut ready = false;
|
||||
loop {
|
||||
match poll(&mut fds, -1) {
|
||||
Ok(_) => {
|
||||
if !fds[3].revents().is_empty() {
|
||||
ready = true;
|
||||
}
|
||||
}
|
||||
Err(other) => panic!("Poll failed: {other:?}"),
|
||||
}
|
||||
|
||||
if xstate.is_none() && ready {
|
||||
let xstate = xstate.insert(XState::new(xsock_wl.as_fd()));
|
||||
let mut reader = BufReader::new(&ready_rx);
|
||||
let mut display = String::new();
|
||||
reader.read_line(&mut display).unwrap();
|
||||
display.pop();
|
||||
display.insert(0, ':');
|
||||
info!("Connected to Xwayland on {display}");
|
||||
data.xwayland_ready(display);
|
||||
server_state.set_x_connection(xstate.connection.clone());
|
||||
server_state.atoms = Some(xstate.atoms.clone());
|
||||
}
|
||||
|
||||
if let Some(state) = &mut xstate {
|
||||
state.handle_events(&mut server_state);
|
||||
}
|
||||
|
||||
display.dispatch_clients(&mut server_state).unwrap();
|
||||
server_state.run();
|
||||
display.flush_clients().unwrap();
|
||||
}
|
||||
}
|
||||
24
src/main.rs
Normal file
24
src/main.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
fn main() {
|
||||
pretty_env_logger::formatted_timed_builder()
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.parse_default_env()
|
||||
.init();
|
||||
xwayland_satellite::main(RealData(get_display()));
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
struct RealData(Option<String>);
|
||||
impl xwayland_satellite::RunData for RealData {
|
||||
fn display(&self) -> Option<&str> {
|
||||
self.0.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_display() -> Option<String> {
|
||||
let mut args: Vec<_> = std::env::args().collect();
|
||||
if args.len() > 2 {
|
||||
panic!("Unexpected arguments: {:?}", &args[2..]);
|
||||
}
|
||||
|
||||
(args.len() == 2).then(|| args.swap_remove(1))
|
||||
}
|
||||
898
src/server/dispatch.rs
Normal file
898
src/server/dispatch.rs
Normal file
|
|
@ -0,0 +1,898 @@
|
|||
use super::*;
|
||||
use log::{debug, trace, warn};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
|
||||
relative_pointer::zv1::{
|
||||
client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManClient,
|
||||
server::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManServer,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1 as RelativePointerServer,
|
||||
},
|
||||
},
|
||||
viewporter::{client as c_vp, server as s_vp},
|
||||
},
|
||||
xdg::xdg_output::zv1::{
|
||||
client::zxdg_output_manager_v1::ZxdgOutputManagerV1 as OutputManClient,
|
||||
server::{
|
||||
zxdg_output_manager_v1::{
|
||||
self as s_output_man, ZxdgOutputManagerV1 as OutputManServer,
|
||||
},
|
||||
zxdg_output_v1::{self as s_xdgo, ZxdgOutputV1 as XdgOutputServer},
|
||||
},
|
||||
},
|
||||
};
|
||||
use wayland_server::{
|
||||
protocol::{
|
||||
wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor,
|
||||
wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_seat::WlSeat,
|
||||
wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface, wl_touch::WlTouch,
|
||||
},
|
||||
Dispatch, DisplayHandle, GlobalDispatch, Resource,
|
||||
};
|
||||
|
||||
// noop
|
||||
impl<C: XConnection> Dispatch<WlCallback, ()> for ServerState<C> {
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlCallback,
|
||||
_: <WlCallback as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlSurface, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlSurface,
|
||||
request: <WlSurface as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let surface: &SurfaceData = state.objects[*key].as_ref();
|
||||
let configured =
|
||||
surface.role.is_none() || surface.xdg().is_none() || surface.xdg().unwrap().configured;
|
||||
|
||||
match request {
|
||||
Request::<WlSurface>::Attach { buffer, x, y } => {
|
||||
if buffer.is_none() {
|
||||
trace!("xwayland attached null buffer to {:?}", surface.client);
|
||||
}
|
||||
let buffer = buffer.as_ref().map(|b| {
|
||||
let key: &ObjectKey = b.data().unwrap();
|
||||
let data: &Buffer = state.objects[*key].as_ref();
|
||||
&data.client
|
||||
});
|
||||
|
||||
if configured {
|
||||
surface.client.attach(buffer, x, y);
|
||||
} else {
|
||||
let buffer = buffer.cloned();
|
||||
let surface: &mut SurfaceData = state.objects[*key].as_mut();
|
||||
surface.attach = Some(SurfaceAttach { buffer, x, y });
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::DamageBuffer {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
if configured {
|
||||
surface.client.damage_buffer(x, y, width, height);
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::Frame { callback } => {
|
||||
let cb = data_init.init(callback, ());
|
||||
if configured {
|
||||
surface.client.frame(&state.qh, cb);
|
||||
} else {
|
||||
let surface: &mut SurfaceData = state.objects[*key].as_mut();
|
||||
surface.frame_callback = Some(cb);
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::Commit => {
|
||||
if configured {
|
||||
surface.client.commit();
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::Destroy => {
|
||||
let mut object = state.objects.remove(*key).unwrap();
|
||||
let surface: &mut SurfaceData = object.as_mut();
|
||||
surface.destroy_role();
|
||||
surface.client.destroy();
|
||||
debug!("deleting key: {:?}", key);
|
||||
}
|
||||
Request::<WlSurface>::SetBufferScale { scale } => {
|
||||
surface.client.set_buffer_scale(scale);
|
||||
}
|
||||
other => warn!("unhandled surface request: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<WlCompositor, ClientGlobalWrapper<client::wl_compositor::WlCompositor>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlCompositor,
|
||||
request: <WlCompositor as wayland_server::Resource>::Request,
|
||||
client: &ClientGlobalWrapper<client::wl_compositor::WlCompositor>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlCompositor>::CreateSurface { id } => {
|
||||
let mut surface_id = None;
|
||||
|
||||
let key = state.objects.insert_with_key(|key| {
|
||||
debug!("new surface with key {key:?}");
|
||||
let client = client.create_surface(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
surface_id = Some(server.id().protocol_id());
|
||||
|
||||
SurfaceData {
|
||||
client,
|
||||
server,
|
||||
key,
|
||||
attach: None,
|
||||
frame_callback: None,
|
||||
role: None,
|
||||
}
|
||||
.into()
|
||||
});
|
||||
|
||||
let surface_id = surface_id.unwrap();
|
||||
|
||||
if let Some((win, window_data)) =
|
||||
state.windows.iter_mut().find_map(|(win, data)| {
|
||||
Some(*win).zip((data.surface_id == surface_id).then_some(data))
|
||||
})
|
||||
{
|
||||
window_data.surface_key = Some(key);
|
||||
state.associated_windows.insert(key, win);
|
||||
debug!("associate surface {surface_id} with window {win:?}");
|
||||
if window_data.mapped {
|
||||
state.create_role_window(win, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
warn!("unhandled wlcompositor request: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlBuffer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlBuffer,
|
||||
request: <WlBuffer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
assert!(matches!(request, Request::<WlBuffer>::Destroy));
|
||||
|
||||
let buf: &Buffer = state.objects[*key].as_ref();
|
||||
buf.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlShmPool, ClientShmPool> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlShmPool,
|
||||
request: <WlShmPool as Resource>::Request,
|
||||
c_pool: &ClientShmPool,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlShmPool>::CreateBuffer {
|
||||
id,
|
||||
offset,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
format,
|
||||
} => {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let client = c_pool.pool.create_buffer(
|
||||
offset,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
convert_wenum(format),
|
||||
&state.qh,
|
||||
key,
|
||||
);
|
||||
let server = data_init.init(id, key);
|
||||
Buffer { server, client }.into()
|
||||
});
|
||||
}
|
||||
Request::<WlShmPool>::Destroy => {
|
||||
c_pool.pool.destroy();
|
||||
}
|
||||
other => warn!("unhandled shmpool request: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlShm, ClientGlobalWrapper<client::wl_shm::WlShm>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlShm,
|
||||
request: <WlShm as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<client::wl_shm::WlShm>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlShm>::CreatePool { id, fd, size } => {
|
||||
let c_pool = client.create_pool(fd.as_fd(), size, &state.qh, ());
|
||||
let c_pool = ClientShmPool { pool: c_pool, fd };
|
||||
data_init.init(id, c_pool);
|
||||
}
|
||||
other => {
|
||||
warn!("unhandled shm pool request: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlPointer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlPointer,
|
||||
request: <WlPointer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let Pointer {
|
||||
client: c_pointer, ..
|
||||
}: &Pointer = state.objects[*key].as_ref();
|
||||
|
||||
match request {
|
||||
Request::<WlPointer>::SetCursor {
|
||||
serial,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
surface,
|
||||
} => {
|
||||
let c_surface = surface.map(|s| state.get_client_surface_from_server(s));
|
||||
c_pointer.set_cursor(serial, c_surface, hotspot_x, hotspot_y);
|
||||
}
|
||||
Request::<WlPointer>::Release => {
|
||||
c_pointer.release();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
_ => warn!("unhandled cursor request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlKeyboard, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlKeyboard,
|
||||
request: <WlKeyboard as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlKeyboard>::Release => {
|
||||
let Keyboard { client, .. }: &_ = state.objects[*key].as_ref();
|
||||
client.release();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlTouch, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlTouch,
|
||||
request: <WlTouch as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlTouch>::Release => {
|
||||
let Touch { client, .. }: &_ = state.objects[*key].as_ref();
|
||||
client.release();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlSeat, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlSeat,
|
||||
request: <WlSeat as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlSeat>::GetPointer { id } => {
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[seat_obj], key| {
|
||||
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
|
||||
let client = client.get_pointer(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
trace!("new pointer: {server:?}");
|
||||
Pointer::new(server, client).into()
|
||||
});
|
||||
}
|
||||
Request::<WlSeat>::GetKeyboard { id } => {
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[seat_obj], key| {
|
||||
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
|
||||
let client = client.get_keyboard(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
Keyboard { client, server }.into()
|
||||
});
|
||||
}
|
||||
Request::<WlSeat>::GetTouch { id } => {
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[seat_obj], key| {
|
||||
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
|
||||
let client = client.get_touch(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
Touch { client, server }.into()
|
||||
});
|
||||
}
|
||||
other => warn!("unhandled seat request: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<RelativePointerServer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &RelativePointerServer,
|
||||
request: <RelativePointerServer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
if let Request::<RelativePointerServer>::Destroy = request {
|
||||
let obj: &RelativePointer = state.objects[*key].as_ref();
|
||||
obj.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<RelativePointerManServer, ClientGlobalWrapper<RelativePointerManClient>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &RelativePointerManServer,
|
||||
request: <RelativePointerManServer as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<RelativePointerManClient>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<RelativePointerManServer>::GetRelativePointer { id, pointer } => {
|
||||
let p_key: ObjectKey = pointer.data().copied().unwrap();
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([p_key], |[pointer_obj], key| {
|
||||
let pointer: &Pointer = pointer_obj.try_into().unwrap();
|
||||
let client = client.get_relative_pointer(&pointer.client, &state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
RelativePointer { client, server }.into()
|
||||
});
|
||||
}
|
||||
_ => warn!("unhandled relative pointer request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlOutput, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlOutput,
|
||||
request: <WlOutput as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
wayland_server::protocol::wl_output::Request::Release => {
|
||||
let Output { client, .. }: &_ = state.objects[*key].as_ref();
|
||||
client.release();
|
||||
todo!("handle wloutput destruction");
|
||||
}
|
||||
_ => warn!("unhandled output request {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, ObjectKey>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||
request: <s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1 as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_dmabuf::zwp_linux_dmabuf_feedback_v1::Request::*;
|
||||
match request {
|
||||
Destroy => {
|
||||
let dmabuf: &DmabufFeedback = state.objects[*key].as_ref();
|
||||
dmabuf.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<
|
||||
s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
c_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
> for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
request: <s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1 as Resource>::Request,
|
||||
c_params: &c_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_dmabuf::zwp_linux_buffer_params_v1::Request::*;
|
||||
match request {
|
||||
// TODO: Xwayland doesn't actually seem to use the Create request, and I don't feel like implementing it...
|
||||
Create { .. } => todo!(),
|
||||
CreateImmed {
|
||||
buffer_id,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
flags,
|
||||
} => {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let client = c_params.create_immed(
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
convert_wenum(flags),
|
||||
&state.qh,
|
||||
key,
|
||||
);
|
||||
let server = data_init.init(buffer_id, key);
|
||||
Buffer { server, client }.into()
|
||||
});
|
||||
}
|
||||
Add {
|
||||
fd,
|
||||
plane_idx,
|
||||
offset,
|
||||
stride,
|
||||
modifier_hi,
|
||||
modifier_lo,
|
||||
} => {
|
||||
c_params.add(
|
||||
fd.as_fd(),
|
||||
plane_idx,
|
||||
offset,
|
||||
stride,
|
||||
modifier_hi,
|
||||
modifier_lo,
|
||||
);
|
||||
}
|
||||
Destroy => {
|
||||
c_params.destroy();
|
||||
}
|
||||
_ => warn!("unhandled params request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
ClientGlobalWrapper<c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>,
|
||||
> for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
request: <s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_dmabuf::zwp_linux_dmabuf_v1::Request::*;
|
||||
match request {
|
||||
Destroy => {
|
||||
client.destroy();
|
||||
}
|
||||
CreateParams { params_id } => {
|
||||
let c_params = client.create_params(&state.qh, ());
|
||||
data_init.init(params_id, c_params);
|
||||
}
|
||||
GetDefaultFeedback { id } => {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let client = client.get_default_feedback(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
DmabufFeedback { client, server }.into()
|
||||
});
|
||||
}
|
||||
GetSurfaceFeedback { id, surface } => {
|
||||
let surf_key: ObjectKey = surface.data().copied().unwrap();
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([surf_key], |[surface_obj], key| {
|
||||
let SurfaceData {
|
||||
client: c_surface, ..
|
||||
}: &SurfaceData = surface_obj.try_into().unwrap();
|
||||
let client = client.get_surface_feedback(c_surface, &state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
DmabufFeedback { client, server }.into()
|
||||
});
|
||||
}
|
||||
_ => warn!("unhandled dmabuf request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlDrmServer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlDrmServer,
|
||||
request: <WlDrmServer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use wl_drm::server::wl_drm::Request::*;
|
||||
|
||||
type DrmFn = dyn FnOnce(
|
||||
&wl_drm::client::wl_drm::WlDrm,
|
||||
ObjectKey,
|
||||
&ClientQueueHandle,
|
||||
) -> client::wl_buffer::WlBuffer;
|
||||
|
||||
let mut bufs: Option<(Box<DrmFn>, wayland_server::New<WlBuffer>)> = None;
|
||||
match request {
|
||||
CreateBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
format,
|
||||
} => {
|
||||
bufs = Some((
|
||||
Box::new(move |drm, key, qh| {
|
||||
drm.create_buffer(name, width, height, stride, format, qh, key)
|
||||
}),
|
||||
id,
|
||||
));
|
||||
}
|
||||
CreatePlanarBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
offset1,
|
||||
stride1,
|
||||
offset2,
|
||||
stride2,
|
||||
} => {
|
||||
bufs = Some((
|
||||
Box::new(move |drm, key, qh| {
|
||||
drm.create_planar_buffer(
|
||||
name, width, height, format, offset0, stride0, offset1, stride1,
|
||||
offset2, stride2, qh, key,
|
||||
)
|
||||
}),
|
||||
id,
|
||||
));
|
||||
}
|
||||
CreatePrimeBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
offset1,
|
||||
stride1,
|
||||
offset2,
|
||||
stride2,
|
||||
} => {
|
||||
bufs = Some((
|
||||
Box::new(move |drm, key, qh| {
|
||||
drm.create_prime_buffer(
|
||||
name.as_fd(),
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
offset1,
|
||||
stride1,
|
||||
offset2,
|
||||
stride2,
|
||||
qh,
|
||||
key,
|
||||
)
|
||||
}),
|
||||
id,
|
||||
));
|
||||
}
|
||||
Authenticate { id } => {
|
||||
let drm: &Drm = state.objects[*key].as_ref();
|
||||
drm.client.authenticate(id);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
if let Some((buf_create, id)) = bufs {
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[drm_obj], key| {
|
||||
let drm: &Drm = drm_obj.try_into().unwrap();
|
||||
let client = buf_create(&drm.client, key, &state.qh);
|
||||
let server = data_init.init(id, key);
|
||||
Buffer { client, server }.into()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<s_vp::wp_viewport::WpViewport, c_vp::wp_viewport::WpViewport>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_vp::wp_viewport::WpViewport,
|
||||
request: <s_vp::wp_viewport::WpViewport as Resource>::Request,
|
||||
c_viewport: &c_vp::wp_viewport::WpViewport,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
simple_event_shunt! {
|
||||
c_viewport, request: s_vp::wp_viewport::Request => [
|
||||
SetSource { x, y, width, height },
|
||||
SetDestination { width, height },
|
||||
Destroy
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<
|
||||
s_vp::wp_viewporter::WpViewporter,
|
||||
ClientGlobalWrapper<c_vp::wp_viewporter::WpViewporter>,
|
||||
> for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_vp::wp_viewporter::WpViewporter,
|
||||
request: <s_vp::wp_viewporter::WpViewporter as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<c_vp::wp_viewporter::WpViewporter>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_vp::wp_viewporter;
|
||||
match request {
|
||||
wp_viewporter::Request::GetViewport { id, surface } => {
|
||||
let c_surface = state.get_client_surface_from_server(surface);
|
||||
let c_viewport = client.get_viewport(c_surface, &state.qh, ());
|
||||
data_init.init(id, c_viewport);
|
||||
}
|
||||
wp_viewporter::Request::Destroy => {
|
||||
client.destroy();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<XdgOutputServer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &XdgOutputServer,
|
||||
request: <XdgOutputServer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let s_xdgo::Request::Destroy = request else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let output: &XdgOutput = state.objects[*key].as_ref();
|
||||
output.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<OutputManServer, ClientGlobalWrapper<OutputManClient>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &OutputManServer,
|
||||
request: <OutputManServer as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<OutputManClient>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
s_output_man::Request::GetXdgOutput { id, output } => {
|
||||
let output_key: ObjectKey = output.data().copied().unwrap();
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([output_key], |[output_obj], key| {
|
||||
let output: &Output = output_obj.try_into().unwrap();
|
||||
let client = client.get_xdg_output(&output.client, &state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
XdgOutput { server, client }.into()
|
||||
});
|
||||
}
|
||||
s_output_man::Request::Destroy => {}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ClientGlobalWrapper<T: Proxy>(Arc<OnceLock<T>>);
|
||||
impl<T: Proxy> std::ops::Deref for ClientGlobalWrapper<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.get().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Proxy> Default for ClientGlobalWrapper<T> {
|
||||
fn default() -> Self {
|
||||
Self(Arc::default())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! global_dispatch_no_events {
|
||||
($server:ty, $client:ty) => {
|
||||
impl<C: XConnection> GlobalDispatch<$server, GlobalData> for ServerState<C>
|
||||
where
|
||||
ServerState<C>: Dispatch<$server, ClientGlobalWrapper<$client>>,
|
||||
Globals: wayland_client::Dispatch<$client, ()>,
|
||||
{
|
||||
fn bind(
|
||||
state: &mut Self,
|
||||
_: &DisplayHandle,
|
||||
_: &wayland_server::Client,
|
||||
resource: wayland_server::New<$server>,
|
||||
data: &GlobalData,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let client = ClientGlobalWrapper::<$client>::default();
|
||||
let server = data_init.init(resource, client.clone());
|
||||
client
|
||||
.0
|
||||
.set(
|
||||
state
|
||||
.clientside
|
||||
.registry
|
||||
.bind(data.name, server.version(), &state.qh, ()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! global_dispatch_with_events {
|
||||
($server:ty, $client:ty) => {
|
||||
impl<C: XConnection> GlobalDispatch<$server, GlobalData> for ServerState<C>
|
||||
where
|
||||
$server: Resource,
|
||||
$client: Proxy,
|
||||
ServerState<C>: Dispatch<$server, ObjectKey>,
|
||||
Globals: wayland_client::Dispatch<$client, ObjectKey>,
|
||||
GenericObject<$server, $client>: Into<Object>,
|
||||
{
|
||||
fn bind(
|
||||
state: &mut Self,
|
||||
_: &DisplayHandle,
|
||||
_: &wayland_server::Client,
|
||||
resource: wayland_server::New<$server>,
|
||||
data: &GlobalData,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let server = data_init.init(resource, key);
|
||||
let client = state.clientside.registry.bind::<$client, _, _>(
|
||||
data.name,
|
||||
server.version(),
|
||||
&state.qh,
|
||||
key,
|
||||
);
|
||||
GenericObject { server, client }.into()
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
global_dispatch_no_events!(WlShm, client::wl_shm::WlShm);
|
||||
global_dispatch_no_events!(WlCompositor, client::wl_compositor::WlCompositor);
|
||||
global_dispatch_no_events!(RelativePointerManServer, RelativePointerManClient);
|
||||
global_dispatch_no_events!(
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1
|
||||
);
|
||||
global_dispatch_no_events!(OutputManServer, OutputManClient);
|
||||
global_dispatch_no_events!(
|
||||
s_vp::wp_viewporter::WpViewporter,
|
||||
c_vp::wp_viewporter::WpViewporter
|
||||
);
|
||||
|
||||
global_dispatch_with_events!(WlSeat, client::wl_seat::WlSeat);
|
||||
global_dispatch_with_events!(WlOutput, client::wl_output::WlOutput);
|
||||
global_dispatch_with_events!(WlDrmServer, WlDrmClient);
|
||||
646
src/server/event.rs
Normal file
646
src/server/event.rs
Normal file
|
|
@ -0,0 +1,646 @@
|
|||
use super::*;
|
||||
use log::{debug, trace, warn};
|
||||
use std::os::fd::AsFd;
|
||||
use wayland_client::{protocol as client, Proxy};
|
||||
use wayland_protocols::{
|
||||
wp::relative_pointer::zv1::{
|
||||
client::zwp_relative_pointer_v1::{self, ZwpRelativePointerV1 as RelativePointerClient},
|
||||
server::zwp_relative_pointer_v1::ZwpRelativePointerV1 as RelativePointerServer,
|
||||
},
|
||||
xdg::{
|
||||
shell::client::{xdg_popup, xdg_surface, xdg_toplevel},
|
||||
xdg_output::zv1::{
|
||||
client::zxdg_output_v1::{self, ZxdgOutputV1 as ClientXdgOutput},
|
||||
server::zxdg_output_v1::ZxdgOutputV1 as ServerXdgOutput,
|
||||
},
|
||||
},
|
||||
};
|
||||
use wayland_server::protocol::{
|
||||
wl_buffer::WlBuffer, wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer,
|
||||
wl_seat::WlSeat, wl_touch::WlTouch,
|
||||
};
|
||||
|
||||
/// Lord forgive me, I am a sinner, who's probably gonna sin again
|
||||
/// This macro takes an enum variant name and a list of the field names of the enum
|
||||
/// and/or closures that take an argument that must be named the same as the field name,
|
||||
/// and converts that into a destructured enum
|
||||
/// shunt_helper_enum!(Foo [a, |b| b.do_thing(), c]) -> Foo {a, b, c}
|
||||
macro_rules! shunt_helper_enum {
|
||||
// No fields
|
||||
($variant:ident) => { $variant };
|
||||
// Starting state: variant destructure
|
||||
($variant:ident $([$($body:tt)+])?) => {
|
||||
shunt_helper_enum!($variant [$($($body)+)?] -> [])
|
||||
};
|
||||
// Add field to list
|
||||
($variant:ident [$field:ident $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_enum!($variant [$($($rest)+)?] -> [$($body)*, $field])
|
||||
};
|
||||
// Add closure field to list
|
||||
($variant:ident [|$field:ident| $conv:expr $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_enum!($variant [$($($rest)+)?] -> [$($body)*, $field])
|
||||
};
|
||||
// Finalize into enum variant
|
||||
($variant:ident [] -> [,$($body:tt)+]) => { $variant { $($body)+ } };
|
||||
}
|
||||
|
||||
/// This does the same thing as shunt_helper_enum, except it transforms the fields into the given
|
||||
/// function/method call.
|
||||
/// shunt_helper_fn!({obj.foo} [a, |b| b.do_thing(), c]) -> obj.foo(a, b.do_thing(), c)
|
||||
macro_rules! shunt_helper_fn {
|
||||
// No fields
|
||||
({$($fn:tt)+}) => { $($fn)+() };
|
||||
// Starting state
|
||||
($fn:tt [$($body:tt)+]) => {
|
||||
shunt_helper_fn!($fn [$($body)+] -> [])
|
||||
};
|
||||
// Add field to list
|
||||
($fn:tt [$field:ident $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_fn!($fn [$($($rest)+)?] -> [$($body)*, $field])
|
||||
};
|
||||
// Add closure expression to list
|
||||
($fn:tt [|$field:ident| $conv:expr $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_fn!($fn [$($($rest)+)?] -> [$($body)*, $conv])
|
||||
};
|
||||
// Finalize into function call
|
||||
({$($fn:tt)+} [] -> [,$($body:tt)+]) => { $($fn)+($($body)+) };
|
||||
}
|
||||
|
||||
/// Takes an object, the name of a variable holding an event, the event type, and a list of the
|
||||
/// variants with their fields, and converts them into function calls on their arguments
|
||||
/// Event { field1, field2 } => obj.event(field1, field2)
|
||||
macro_rules! simple_event_shunt {
|
||||
($obj:expr, $event:ident: $event_type:path => [
|
||||
$( $variant:ident $({ $($fields:tt)* })? ),+
|
||||
]) => {
|
||||
{
|
||||
use $event_type::*;
|
||||
match $event {
|
||||
$(
|
||||
shunt_helper_enum!( $variant $( [ $($fields)* ] )? ) => {
|
||||
paste::paste! {
|
||||
shunt_helper_fn!( { $obj.[<$variant:snake>] } $( [ $($fields)* ] )? )
|
||||
}
|
||||
}
|
||||
)+
|
||||
_ => log::warn!(concat!("unhandled", stringify!($event_type), ": {:?}"), $event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use shunt_helper_enum;
|
||||
pub(crate) use shunt_helper_fn;
|
||||
pub(crate) use simple_event_shunt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum SurfaceEvents {
|
||||
WlSurface(client::wl_surface::Event),
|
||||
XdgSurface(xdg_surface::Event),
|
||||
Toplevel(xdg_toplevel::Event),
|
||||
Popup(xdg_popup::Event),
|
||||
}
|
||||
macro_rules! impl_from {
|
||||
($type:ty, $variant:ident) => {
|
||||
impl From<$type> for ObjectEvent {
|
||||
fn from(value: $type) -> Self {
|
||||
Self::Surface(SurfaceEvents::$variant(value))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_from!(client::wl_surface::Event, WlSurface);
|
||||
impl_from!(xdg_surface::Event, XdgSurface);
|
||||
impl_from!(xdg_toplevel::Event, Toplevel);
|
||||
impl_from!(xdg_popup::Event, Popup);
|
||||
|
||||
impl HandleEvent for SurfaceData {
|
||||
type Event = SurfaceEvents;
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
match event {
|
||||
SurfaceEvents::WlSurface(event) => self.surface_event(event, state),
|
||||
SurfaceEvents::XdgSurface(event) => self.xdg_event(event, state),
|
||||
SurfaceEvents::Toplevel(event) => self.toplevel_event(event, state),
|
||||
SurfaceEvents::Popup(event) => self.popup_event(event, state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SurfaceData {
|
||||
fn surface_event<C: XConnection>(
|
||||
&self,
|
||||
event: client::wl_surface::Event,
|
||||
state: &mut ServerState<C>,
|
||||
) {
|
||||
let surface = &self.server;
|
||||
simple_event_shunt! {
|
||||
surface, event: client::wl_surface::Event => [
|
||||
Enter { |output| &state.get_object_from_client_object::<Output, _>(&output).server },
|
||||
Leave { |output| &state.get_object_from_client_object::<Output, _>(&output).server },
|
||||
PreferredBufferScale { factor }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn xdg_event<C: XConnection>(&mut self, event: xdg_surface::Event, state: &mut ServerState<C>) {
|
||||
let connection = state.connection.as_mut().unwrap();
|
||||
let xdg_surface::Event::Configure { serial } = event else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let xdg = self.xdg_mut().unwrap();
|
||||
xdg.surface.ack_configure(serial);
|
||||
xdg.configured = true;
|
||||
|
||||
if let Some(pending) = xdg.pending.take() {
|
||||
let window = state.associated_windows[self.key];
|
||||
let window = state.windows.get_mut(&window).unwrap();
|
||||
let width = if pending.width > 0 {
|
||||
pending.width as _
|
||||
} else {
|
||||
window.attrs.dims.width
|
||||
};
|
||||
let height = if pending.height > 0 {
|
||||
pending.height as _
|
||||
} else {
|
||||
window.attrs.dims.height
|
||||
};
|
||||
debug!(
|
||||
"configuring {:?}: {}x{}, {width}x{height}",
|
||||
window.window, pending.x, pending.y
|
||||
);
|
||||
connection.set_window_dims(
|
||||
window.window,
|
||||
PendingSurfaceState {
|
||||
x: pending.x,
|
||||
y: pending.y,
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
},
|
||||
);
|
||||
window.attrs.dims = WindowDims {
|
||||
x: pending.x as _,
|
||||
y: pending.y as _,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(SurfaceAttach { buffer, x, y }) = self.attach.take() {
|
||||
self.client.attach(buffer.as_ref(), x, y);
|
||||
}
|
||||
if let Some(cb) = self.frame_callback.take() {
|
||||
self.client.frame(&state.qh, cb);
|
||||
}
|
||||
self.client.commit();
|
||||
}
|
||||
|
||||
fn toplevel_event<C: XConnection>(
|
||||
&mut self,
|
||||
event: xdg_toplevel::Event,
|
||||
state: &mut ServerState<C>,
|
||||
) {
|
||||
match event {
|
||||
xdg_toplevel::Event::Configure {
|
||||
width,
|
||||
height,
|
||||
states,
|
||||
} => {
|
||||
debug!("configuring toplevel {width}x{height}, {states:?}");
|
||||
let activated = states.contains(&(u32::from(xdg_toplevel::State::Activated) as u8));
|
||||
|
||||
if activated {
|
||||
let window = state.associated_windows[self.key];
|
||||
state.to_focus = Some(window);
|
||||
}
|
||||
|
||||
if let Some(SurfaceRole::Toplevel(Some(toplevel))) = &mut self.role {
|
||||
let prev_fs = toplevel.fullscreen;
|
||||
toplevel.fullscreen =
|
||||
states.contains(&(u32::from(xdg_toplevel::State::Fullscreen) as u8));
|
||||
if toplevel.fullscreen != prev_fs {
|
||||
let window = state.associated_windows[self.key];
|
||||
let data = C::ExtraData::create(state);
|
||||
state.connection.as_mut().unwrap().set_fullscreen(
|
||||
window,
|
||||
toplevel.fullscreen,
|
||||
data,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
xdg_toplevel::Event::Close => {
|
||||
let window = state.associated_windows[self.key];
|
||||
state.close_x_window(window);
|
||||
}
|
||||
ref other => warn!("unhandled xdgtoplevel event: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn popup_event<C: XConnection>(&mut self, event: xdg_popup::Event, _: &mut ServerState<C>) {
|
||||
match event {
|
||||
xdg_popup::Event::Configure {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
trace!("popup configure: {x}x{y}, {width}x{height}");
|
||||
self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
xdg_popup::Event::Repositioned { .. } => {}
|
||||
other => todo!("{other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GenericObject<Server: Resource, Client: Proxy> {
|
||||
pub server: Server,
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
pub type Buffer = GenericObject<WlBuffer, client::wl_buffer::WlBuffer>;
|
||||
impl HandleEvent for Buffer {
|
||||
type Event = client::wl_buffer::Event;
|
||||
fn handle_event<C: XConnection>(&mut self, _: Self::Event, _: &mut ServerState<C>) {
|
||||
// The only event from a buffer would be the release.
|
||||
self.server.release();
|
||||
}
|
||||
}
|
||||
|
||||
pub type XdgOutput = GenericObject<ServerXdgOutput, ClientXdgOutput>;
|
||||
impl HandleEvent for XdgOutput {
|
||||
type Event = zxdg_output_v1::Event;
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: zxdg_output_v1::Event => [
|
||||
LogicalPosition { x, y },
|
||||
LogicalSize { width, height },
|
||||
Done,
|
||||
Name { name },
|
||||
Description { description }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Seat = GenericObject<WlSeat, client::wl_seat::WlSeat>;
|
||||
impl HandleEvent for Seat {
|
||||
type Event = client::wl_seat::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_seat::Event => [
|
||||
Capabilities { |capabilities| convert_wenum(capabilities) },
|
||||
Name { name }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pointer {
|
||||
server: WlPointer,
|
||||
pub client: client::wl_pointer::WlPointer,
|
||||
pending_enter: PendingEnter,
|
||||
}
|
||||
|
||||
impl Pointer {
|
||||
pub fn new(server: WlPointer, client: client::wl_pointer::WlPointer) -> Self {
|
||||
Self {
|
||||
server,
|
||||
client,
|
||||
pending_enter: PendingEnter(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingEnter(Option<client::wl_pointer::Event>);
|
||||
|
||||
impl HandleEvent for Pointer {
|
||||
type Event = client::wl_pointer::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
// Workaround GTK (stupidly) autoclosing popups if it receives an wl_pointer.enter
|
||||
// event shortly after creation.
|
||||
// When Niri creates a popup, it immediately sends wl_pointer.enter on the new surface,
|
||||
// generating an EnterNotify event, and Xwayland will send a release button event.
|
||||
// In its menu implementation, GTK treats EnterNotify "this menu is now active" and will
|
||||
// destroy the menu if this occurs within a 500 ms interval (which it always does with
|
||||
// Niri). Other compositors do not run into this problem because they appear to not send
|
||||
// wl_pointer.enter until the user actually moves the mouse in the popup.
|
||||
let mut process_event = Vec::new();
|
||||
match event {
|
||||
client::wl_pointer::Event::Enter {
|
||||
serial,
|
||||
ref surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} => {
|
||||
let do_enter = || {
|
||||
debug!("entering surface ({serial})");
|
||||
let surface = state.get_server_surface_from_client(surface.clone());
|
||||
self.server.enter(serial, surface, surface_x, surface_y);
|
||||
};
|
||||
let surface_key: ObjectKey = surface.data().copied().unwrap();
|
||||
let surface_data: &SurfaceData = state.objects[surface_key].as_ref();
|
||||
|
||||
if matches!(surface_data.role, Some(SurfaceRole::Popup(_))) {
|
||||
match self.pending_enter.0.take() {
|
||||
Some(e) => {
|
||||
let client::wl_pointer::Event::Enter {
|
||||
serial: pending_serial,
|
||||
..
|
||||
} = e
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
if serial == pending_serial {
|
||||
do_enter();
|
||||
} else {
|
||||
self.pending_enter.0 = Some(event);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.pending_enter.0 = Some(event);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.pending_enter.0.take();
|
||||
do_enter();
|
||||
}
|
||||
}
|
||||
client::wl_pointer::Event::Leave { serial, surface } => {
|
||||
debug!("leaving surface ({serial})");
|
||||
self.pending_enter.0.take();
|
||||
self.server
|
||||
.leave(serial, state.get_server_surface_from_client(surface));
|
||||
}
|
||||
client::wl_pointer::Event::Motion {
|
||||
time,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} => {
|
||||
if let Some(p) = &self.pending_enter.0 {
|
||||
let client::wl_pointer::Event::Enter {
|
||||
serial,
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} = p
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
process_event.push(client::wl_pointer::Event::Enter {
|
||||
serial: *serial,
|
||||
surface: surface.clone(),
|
||||
surface_x: *surface_x,
|
||||
surface_y: *surface_y,
|
||||
});
|
||||
process_event.push(event);
|
||||
trace!("resending enter ({serial}) before motion");
|
||||
} else {
|
||||
self.server.motion(time, surface_x, surface_y);
|
||||
}
|
||||
}
|
||||
_ => simple_event_shunt! {
|
||||
self.server, event: client::wl_pointer::Event => [
|
||||
Enter {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface),
|
||||
surface_x,
|
||||
surface_y
|
||||
},
|
||||
Leave {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface)
|
||||
},
|
||||
Motion {
|
||||
time,
|
||||
surface_x,
|
||||
surface_y
|
||||
},
|
||||
Frame,
|
||||
Button {
|
||||
serial,
|
||||
time,
|
||||
button,
|
||||
|state| convert_wenum(state)
|
||||
},
|
||||
Axis {
|
||||
time,
|
||||
|axis| convert_wenum(axis),
|
||||
value
|
||||
},
|
||||
AxisSource {
|
||||
|axis_source| convert_wenum(axis_source)
|
||||
},
|
||||
AxisStop {
|
||||
time,
|
||||
|axis| convert_wenum(axis)
|
||||
},
|
||||
AxisDiscrete {
|
||||
|axis| convert_wenum(axis),
|
||||
discrete
|
||||
},
|
||||
AxisValue120 {
|
||||
|axis| convert_wenum(axis),
|
||||
value120
|
||||
},
|
||||
AxisRelativeDirection {
|
||||
|axis| convert_wenum(axis),
|
||||
|direction| convert_wenum(direction)
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
for event in process_event {
|
||||
self.handle_event(event, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Keyboard = GenericObject<WlKeyboard, client::wl_keyboard::WlKeyboard>;
|
||||
impl HandleEvent for Keyboard {
|
||||
type Event = client::wl_keyboard::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_keyboard::Event => [
|
||||
Keymap {
|
||||
|format| convert_wenum(format),
|
||||
|fd| fd.as_fd(),
|
||||
size
|
||||
},
|
||||
Enter {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface),
|
||||
keys
|
||||
},
|
||||
Leave {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface)
|
||||
},
|
||||
Key {
|
||||
serial,
|
||||
time,
|
||||
key,
|
||||
|state| convert_wenum(state)
|
||||
},
|
||||
Modifiers {
|
||||
serial,
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group
|
||||
},
|
||||
RepeatInfo {
|
||||
rate,
|
||||
delay
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Touch = GenericObject<WlTouch, client::wl_touch::WlTouch>;
|
||||
impl HandleEvent for Touch {
|
||||
type Event = client::wl_touch::Event;
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_touch::Event => [
|
||||
Down {
|
||||
serial,
|
||||
time,
|
||||
|surface| state.get_server_surface_from_client(surface),
|
||||
id,
|
||||
x,
|
||||
y
|
||||
},
|
||||
Up {
|
||||
serial,
|
||||
time,
|
||||
id
|
||||
},
|
||||
Motion {
|
||||
time,
|
||||
id,
|
||||
x,
|
||||
y
|
||||
},
|
||||
Frame,
|
||||
Cancel,
|
||||
Shape {
|
||||
id,
|
||||
major,
|
||||
minor
|
||||
},
|
||||
Orientation {
|
||||
id,
|
||||
orientation
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Output = GenericObject<WlOutput, client::wl_output::WlOutput>;
|
||||
impl HandleEvent for Output {
|
||||
type Event = client::wl_output::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_output::Event => [
|
||||
Name { name },
|
||||
Description { description },
|
||||
Mode {
|
||||
|flags| convert_wenum(flags),
|
||||
width,
|
||||
height,
|
||||
refresh
|
||||
},
|
||||
Scale { factor },
|
||||
Geometry {
|
||||
x,
|
||||
y,
|
||||
physical_width,
|
||||
physical_height,
|
||||
|subpixel| convert_wenum(subpixel),
|
||||
make,
|
||||
model,
|
||||
|transform| convert_wenum(transform)
|
||||
},
|
||||
Done
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Drm = GenericObject<WlDrmServer, WlDrmClient>;
|
||||
impl HandleEvent for Drm {
|
||||
type Event = wl_drm::client::wl_drm::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: wl_drm::client::wl_drm::Event => [
|
||||
Device { name },
|
||||
Format { format },
|
||||
Authenticated,
|
||||
Capabilities { value }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type DmabufFeedback = GenericObject<
|
||||
s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||
c_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||
>;
|
||||
impl HandleEvent for DmabufFeedback {
|
||||
type Event = c_dmabuf::zwp_linux_dmabuf_feedback_v1::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: c_dmabuf::zwp_linux_dmabuf_feedback_v1::Event => [
|
||||
Done,
|
||||
FormatTable { |fd| fd.as_fd(), size },
|
||||
MainDevice { device },
|
||||
TrancheDone,
|
||||
TrancheTargetDevice { device },
|
||||
TrancheFormats { indices },
|
||||
TrancheFlags { |flags| convert_wenum(flags) }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type RelativePointer = GenericObject<RelativePointerServer, RelativePointerClient>;
|
||||
impl HandleEvent for RelativePointer {
|
||||
type Event = zwp_relative_pointer_v1::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: rp::client::zwp_relative_pointer_v1::Event => [
|
||||
RelativeMotion {
|
||||
utime_hi,
|
||||
utime_lo,
|
||||
dx,
|
||||
dy,
|
||||
dx_unaccel,
|
||||
dy_unaccel
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
845
src/server/mod.rs
Normal file
845
src/server/mod.rs
Normal file
|
|
@ -0,0 +1,845 @@
|
|||
mod dispatch;
|
||||
mod event;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use self::event::*;
|
||||
use super::FromServerState;
|
||||
use crate::clientside::*;
|
||||
use crate::xstate::{Atoms, WindowDims, WmHints, WmName, WmNormalHints};
|
||||
use crate::XConnection;
|
||||
use log::{debug, warn};
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use slotmap::{new_key_type, HopSlotMap, SparseSecondaryMap};
|
||||
use std::collections::HashMap;
|
||||
use std::os::fd::{AsFd, BorrowedFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use wayland_client::{protocol as client, Proxy};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
|
||||
relative_pointer::zv1::{
|
||||
self as rp, server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
},
|
||||
viewporter::server as s_vp,
|
||||
},
|
||||
xdg::{
|
||||
shell::client::{
|
||||
xdg_popup::XdgPopup,
|
||||
xdg_positioner::{Anchor, Gravity, XdgPositioner},
|
||||
xdg_surface::XdgSurface,
|
||||
xdg_toplevel::XdgToplevel,
|
||||
xdg_wm_base::XdgWmBase,
|
||||
},
|
||||
xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||
},
|
||||
};
|
||||
use wayland_server::{
|
||||
protocol::{
|
||||
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat,
|
||||
wl_shm::WlShm, wl_surface::WlSurface,
|
||||
},
|
||||
DisplayHandle, Resource, WEnum,
|
||||
};
|
||||
use wl_drm::{client::wl_drm::WlDrm as WlDrmClient, server::wl_drm::WlDrm as WlDrmServer};
|
||||
use xcb::x;
|
||||
|
||||
impl From<&x::CreateNotifyEvent> for WindowDims {
|
||||
fn from(value: &x::CreateNotifyEvent) -> Self {
|
||||
Self {
|
||||
x: value.x(),
|
||||
y: value.y(),
|
||||
width: value.width(),
|
||||
height: value.height(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Request<T> = <T as Resource>::Request;
|
||||
|
||||
/// Converts a WEnum from its client side version to its server side version
|
||||
fn convert_wenum<Client, Server>(wenum: WEnum<Client>) -> Server
|
||||
where
|
||||
u32: From<WEnum<Client>>,
|
||||
Server: TryFrom<u32>,
|
||||
<Server as TryFrom<u32>>::Error: std::fmt::Debug,
|
||||
{
|
||||
u32::from(wenum).try_into().unwrap()
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct WindowAttributes {
|
||||
pub override_redirect: bool,
|
||||
pub popup_for: Option<x::Window>,
|
||||
pub dims: WindowDims,
|
||||
pub size_hints: Option<WmNormalHints>,
|
||||
pub title: Option<WmName>,
|
||||
pub class: Option<String>,
|
||||
pub group: Option<x::Window>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WindowData {
|
||||
window: x::Window,
|
||||
surface_id: u32,
|
||||
surface_key: Option<ObjectKey>,
|
||||
mapped: bool,
|
||||
attrs: WindowAttributes,
|
||||
}
|
||||
|
||||
impl WindowData {
|
||||
fn new(
|
||||
window: x::Window,
|
||||
override_redirect: bool,
|
||||
dims: WindowDims,
|
||||
parent: Option<x::Window>,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
surface_id: 0,
|
||||
surface_key: None,
|
||||
mapped: false,
|
||||
attrs: WindowAttributes {
|
||||
override_redirect,
|
||||
dims,
|
||||
popup_for: parent,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SurfaceAttach {
|
||||
buffer: Option<client::wl_buffer::WlBuffer>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
pub struct SurfaceData {
|
||||
client: client::wl_surface::WlSurface,
|
||||
server: WlSurface,
|
||||
key: ObjectKey,
|
||||
frame_callback: Option<WlCallback>,
|
||||
attach: Option<SurfaceAttach>,
|
||||
role: Option<SurfaceRole>,
|
||||
}
|
||||
|
||||
impl SurfaceData {
|
||||
fn xdg(&self) -> Option<&XdgSurfaceData> {
|
||||
match self
|
||||
.role
|
||||
.as_ref()
|
||||
.expect("Tried to get XdgSurface for surface without role")
|
||||
{
|
||||
SurfaceRole::Toplevel(ref t) => t.as_ref().map(|t| &t.xdg),
|
||||
SurfaceRole::Popup(ref p) => p.as_ref().map(|p| &p.xdg),
|
||||
}
|
||||
}
|
||||
|
||||
fn xdg_mut(&mut self) -> Option<&mut XdgSurfaceData> {
|
||||
match self
|
||||
.role
|
||||
.as_mut()
|
||||
.expect("Tried to get XdgSurface for surface without role")
|
||||
{
|
||||
SurfaceRole::Toplevel(ref mut t) => t.as_mut().map(|t| &mut t.xdg),
|
||||
SurfaceRole::Popup(ref mut p) => p.as_mut().map(|p| &mut p.xdg),
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy_role(&mut self) {
|
||||
if let Some(role) = self.role.take() {
|
||||
match role {
|
||||
SurfaceRole::Toplevel(Some(t)) => {
|
||||
t.toplevel.destroy();
|
||||
t.xdg.surface.destroy();
|
||||
}
|
||||
SurfaceRole::Popup(Some(p)) => {
|
||||
p.positioner.destroy();
|
||||
p.popup.destroy();
|
||||
p.xdg.surface.destroy();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SurfaceRole {
|
||||
Toplevel(Option<ToplevelData>),
|
||||
Popup(Option<PopupData>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct XdgSurfaceData {
|
||||
surface: XdgSurface,
|
||||
configured: bool,
|
||||
pending: Option<PendingSurfaceState>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ToplevelData {
|
||||
toplevel: XdgToplevel,
|
||||
xdg: XdgSurfaceData,
|
||||
fullscreen: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PopupData {
|
||||
popup: XdgPopup,
|
||||
positioner: XdgPositioner,
|
||||
xdg: XdgSurfaceData,
|
||||
}
|
||||
|
||||
pub(crate) trait HandleEvent {
|
||||
type Event;
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>);
|
||||
}
|
||||
|
||||
macro_rules! enum_try_from {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$pub:vis enum $enum:ident {
|
||||
$( $variant:ident($ty:ty) ),+
|
||||
}
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
$pub enum $enum {
|
||||
$( $variant($ty) ),+
|
||||
}
|
||||
|
||||
$(
|
||||
impl TryFrom<$enum> for $ty {
|
||||
type Error = String;
|
||||
fn try_from(value: $enum) -> Result<Self, Self::Error> {
|
||||
enum_try_from!(@variant_match value $enum $variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a $enum> for &'a $ty {
|
||||
type Error = String;
|
||||
fn try_from(value: &'a $enum) -> Result<Self, Self::Error> {
|
||||
enum_try_from!(@variant_match value $enum $variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a mut $enum> for &'a mut $ty {
|
||||
type Error = String;
|
||||
fn try_from(value: &'a mut $enum) -> Result<Self, Self::Error> {
|
||||
enum_try_from!(@variant_match value $enum $variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$ty> for $enum {
|
||||
fn from(value: $ty) -> Self {
|
||||
$enum::$variant(value)
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
(@variant_match $value:ident $enum:ident $variant:ident) => {
|
||||
match $value {
|
||||
$enum::$variant(obj) => Ok(obj),
|
||||
other => Err(format!("wrong variant type: {}", std::any::type_name_of_val(&other)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement HandleEvent for our enum
|
||||
macro_rules! handle_event_enum {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$pub:vis enum $name:ident {
|
||||
$( $variant:ident($ty:ty) ),+
|
||||
}
|
||||
) => {
|
||||
enum_try_from! {
|
||||
$(#[$meta])*
|
||||
$pub enum $name {
|
||||
$( $variant($ty) ),+
|
||||
}
|
||||
}
|
||||
|
||||
paste::paste! {
|
||||
enum_try_from! {
|
||||
#[derive(Debug)]
|
||||
$pub enum [<$name Event>] {
|
||||
$( $variant(<$ty as HandleEvent>::Event) ),+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HandleEvent for $name {
|
||||
paste::paste! {
|
||||
type Event = [<$name Event>];
|
||||
}
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
match self {
|
||||
$(
|
||||
Self::$variant(v) => {
|
||||
let Self::Event::$variant(event) = event else {
|
||||
unreachable!();
|
||||
};
|
||||
v.handle_event(event, state)
|
||||
}
|
||||
),+
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle_event_enum! {
|
||||
|
||||
/// Objects that generate client side events that we will have to process.
|
||||
pub(crate) enum Object {
|
||||
Surface(SurfaceData),
|
||||
Buffer(Buffer),
|
||||
Seat(Seat),
|
||||
Pointer(Pointer),
|
||||
Keyboard(Keyboard),
|
||||
Output(Output),
|
||||
RelativePointer(RelativePointer),
|
||||
DmabufFeedback(DmabufFeedback),
|
||||
Drm(Drm),
|
||||
XdgOutput(XdgOutput),
|
||||
Touch(Touch)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct WrappedObject(Option<Object>);
|
||||
|
||||
impl<T> From<T> for WrappedObject
|
||||
where
|
||||
T: Into<Object>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(Some(value.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for WrappedObject
|
||||
where
|
||||
for<'a> &'a T: TryFrom<&'a Object, Error = String>,
|
||||
{
|
||||
fn as_ref(&self) -> &T {
|
||||
<&T>::try_from(self.0.as_ref().unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for WrappedObject
|
||||
where
|
||||
for<'a> &'a mut T: TryFrom<&'a mut Object, Error = String>,
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
<&mut T>::try_from(self.0.as_mut().unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
type ObjectMap = HopSlotMap<ObjectKey, WrappedObject>;
|
||||
trait ObjectMapExt {
|
||||
fn insert_from_other_objects<F, const N: usize>(&mut self, keys: [ObjectKey; N], insert_fn: F)
|
||||
where
|
||||
F: FnOnce([&Object; N], ObjectKey) -> Object;
|
||||
}
|
||||
|
||||
impl ObjectMapExt for ObjectMap {
|
||||
/// Insert an object into our map that needs some other values from our map as well
|
||||
fn insert_from_other_objects<F, const N: usize>(&mut self, keys: [ObjectKey; N], insert_fn: F)
|
||||
where
|
||||
F: FnOnce([&Object; N], ObjectKey) -> Object,
|
||||
{
|
||||
let objects = keys.each_ref().map(|key| self[*key].0.take().unwrap());
|
||||
let key = self.insert(WrappedObject(None));
|
||||
let obj = insert_fn(objects.each_ref(), key);
|
||||
let ret = self[key].0.replace(obj);
|
||||
debug_assert!(ret.is_none());
|
||||
for (object, key) in objects.into_iter().zip(keys.into_iter()) {
|
||||
let ret = self[key].0.replace(object);
|
||||
debug_assert!(ret.is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_key_type! {
|
||||
pub struct ObjectKey;
|
||||
}
|
||||
pub struct ServerState<C: XConnection> {
|
||||
pub atoms: Option<Atoms>,
|
||||
dh: DisplayHandle,
|
||||
clientside: ClientState,
|
||||
objects: ObjectMap,
|
||||
associated_windows: SparseSecondaryMap<ObjectKey, x::Window>,
|
||||
windows: HashMap<x::Window, WindowData>,
|
||||
|
||||
xdg_wm_base: XdgWmBase,
|
||||
qh: ClientQueueHandle,
|
||||
to_focus: Option<x::Window>,
|
||||
last_focused_toplevel: Option<x::Window>,
|
||||
connection: Option<C>,
|
||||
}
|
||||
|
||||
const XDG_WM_BASE_VERSION: u32 = 2;
|
||||
|
||||
impl<C: XConnection> ServerState<C> {
|
||||
pub fn new(dh: DisplayHandle, server_connection: Option<UnixStream>) -> Self {
|
||||
let mut clientside = ClientState::new(server_connection);
|
||||
let qh = clientside.qh.clone();
|
||||
|
||||
let xdg_pos = clientside
|
||||
.globals
|
||||
.new_globals
|
||||
.iter()
|
||||
.position(|g| g.interface == XdgWmBase::interface().name)
|
||||
.expect("Did not get an xdg_wm_base global");
|
||||
|
||||
let data = clientside.globals.new_globals.swap_remove(xdg_pos);
|
||||
|
||||
assert!(
|
||||
data.version >= XDG_WM_BASE_VERSION,
|
||||
"xdg_wm_base older than version {XDG_WM_BASE_VERSION}"
|
||||
);
|
||||
|
||||
let xdg_wm_base =
|
||||
clientside
|
||||
.registry
|
||||
.bind::<XdgWmBase, _, _>(data.name, XDG_WM_BASE_VERSION, &qh, ());
|
||||
|
||||
let mut ret = Self {
|
||||
windows: HashMap::new(),
|
||||
clientside,
|
||||
atoms: None,
|
||||
qh,
|
||||
dh,
|
||||
to_focus: None,
|
||||
last_focused_toplevel: None,
|
||||
connection: None,
|
||||
objects: Default::default(),
|
||||
associated_windows: Default::default(),
|
||||
xdg_wm_base,
|
||||
};
|
||||
ret.handle_new_globals();
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn clientside_fd(&self) -> BorrowedFd<'_> {
|
||||
self.clientside.queue.as_fd()
|
||||
}
|
||||
|
||||
pub fn connect(&mut self, connection: UnixStream) {
|
||||
self.dh
|
||||
.insert_client(connection, std::sync::Arc::new(()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn set_x_connection(&mut self, connection: C) {
|
||||
self.connection = Some(connection);
|
||||
}
|
||||
|
||||
fn handle_new_globals(&mut self) {
|
||||
let globals = std::mem::take(&mut self.clientside.globals.new_globals);
|
||||
for data in globals {
|
||||
macro_rules! server_global {
|
||||
($($global:ty),+) => {
|
||||
match data.interface {
|
||||
$(
|
||||
ref x if x == <$global>::interface().name => {
|
||||
self.dh.create_global::<Self, $global, GlobalData>(data.version, data);
|
||||
}
|
||||
)+
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server_global![
|
||||
WlCompositor,
|
||||
WlShm,
|
||||
WlSeat,
|
||||
WlOutput,
|
||||
ZwpRelativePointerManagerV1,
|
||||
WlDrmServer,
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
ZxdgOutputManagerV1,
|
||||
s_vp::wp_viewporter::WpViewporter
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
fn get_object_from_client_object<T, P: Proxy>(&self, proxy: &P) -> &T
|
||||
where
|
||||
for<'a> &'a T: TryFrom<&'a Object, Error = String>,
|
||||
Globals: wayland_client::Dispatch<P, ObjectKey>,
|
||||
{
|
||||
let key: ObjectKey = proxy.data().copied().unwrap();
|
||||
self.objects[key].as_ref()
|
||||
}
|
||||
|
||||
pub fn new_window(
|
||||
&mut self,
|
||||
window: x::Window,
|
||||
override_redirect: bool,
|
||||
dims: WindowDims,
|
||||
parent: Option<x::Window>,
|
||||
) {
|
||||
self.windows.insert(
|
||||
window,
|
||||
WindowData::new(window, override_redirect, dims, parent),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_win_title(&mut self, window: x::Window, name: WmName) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
|
||||
let new_title = match &mut win.attrs.title {
|
||||
Some(w) => {
|
||||
if matches!(w, WmName::NetWmName(_)) && matches!(name, WmName::WmName(_)) {
|
||||
debug!("skipping setting window name to {name:?} because a _NET_WM_NAME title is already set");
|
||||
None
|
||||
} else {
|
||||
*w = name;
|
||||
Some(w)
|
||||
}
|
||||
}
|
||||
None => Some(win.attrs.title.insert(name)),
|
||||
};
|
||||
|
||||
let Some(title) = new_title else {
|
||||
return;
|
||||
};
|
||||
if let Some(key) = win.surface_key {
|
||||
let surface: &SurfaceData = self.objects[key].as_ref();
|
||||
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
|
||||
data.toplevel.set_title(title.name().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_win_class(&mut self, window: x::Window, class: String) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
|
||||
let class = win.attrs.class.insert(class);
|
||||
if let Some(key) = win.surface_key {
|
||||
let surface: &SurfaceData = self.objects[key].as_ref();
|
||||
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
|
||||
data.toplevel.set_app_id(class.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_win_hints(&mut self, window: x::Window, hints: WmHints) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
win.attrs.group = hints.window_group;
|
||||
}
|
||||
|
||||
pub fn set_size_hints(&mut self, window: x::Window, hints: WmNormalHints) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
|
||||
if win.attrs.size_hints.is_none() || *win.attrs.size_hints.as_ref().unwrap() != hints {
|
||||
debug!("setting {window:?} hints {hints:?}");
|
||||
if let Some(surface) = win.surface_key {
|
||||
let surface: &SurfaceData = self.objects[surface].as_ref();
|
||||
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
|
||||
if let Some(min_size) = &hints.min_size {
|
||||
data.toplevel.set_min_size(min_size.width, min_size.height);
|
||||
}
|
||||
if let Some(max_size) = &hints.max_size {
|
||||
data.toplevel.set_max_size(max_size.width, max_size.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
win.attrs.size_hints = Some(hints);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn associate_window(&mut self, window: x::Window, surface_id: u32) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
win.surface_id = surface_id;
|
||||
|
||||
if let Some(key) = self
|
||||
.objects
|
||||
.iter_mut()
|
||||
.filter_map(|(key, obj)| {
|
||||
Some(key).zip(<&mut SurfaceData>::try_from(obj.0.as_mut().unwrap()).ok())
|
||||
})
|
||||
.find_map(|(key, surface)| {
|
||||
(surface_id == surface.server.id().protocol_id()).then_some(key)
|
||||
})
|
||||
{
|
||||
win.surface_key = Some(key);
|
||||
self.associated_windows.insert(key, window);
|
||||
debug!("associate {:?} with surface {surface_id}", window);
|
||||
if win.mapped {
|
||||
self.create_role_window(window, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reconfigure_window(&mut self, event: x::ConfigureNotifyEvent) {
|
||||
let win = self.windows.get_mut(&event.window()).unwrap();
|
||||
win.attrs.dims = WindowDims {
|
||||
x: event.x(),
|
||||
y: event.y(),
|
||||
width: event.width(),
|
||||
height: event.height(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn map_window(&mut self, window: x::Window) {
|
||||
debug!("mapping {window:?}");
|
||||
|
||||
let window = self.windows.get_mut(&window).unwrap();
|
||||
window.mapped = true;
|
||||
}
|
||||
|
||||
pub fn unmap_window(&mut self, window: x::Window) {
|
||||
let Some(win) = self.windows.get_mut(&window) else {
|
||||
return;
|
||||
};
|
||||
if !win.mapped {
|
||||
return;
|
||||
}
|
||||
debug!("unmapping {window:?}");
|
||||
|
||||
if matches!(self.last_focused_toplevel, Some(x) if x == window) {
|
||||
self.last_focused_toplevel.take();
|
||||
}
|
||||
win.mapped = false;
|
||||
|
||||
if let Some(key) = win.surface_key.take() {
|
||||
let surface: &mut SurfaceData = self.objects[key].as_mut();
|
||||
surface.destroy_role();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&mut self, window: x::Window, state: super::xstate::SetState) {
|
||||
let win = self.windows.get(&window).unwrap();
|
||||
let Some(key) = win.surface_key else {
|
||||
warn!("Tried to set window without surface fullscreen: {window:?}");
|
||||
return;
|
||||
};
|
||||
let surface: &mut SurfaceData = self.objects[key].as_mut();
|
||||
let Some(SurfaceRole::Toplevel(Some(ref toplevel))) = surface.role else {
|
||||
warn!("Tried to set an unmapped toplevel or non toplevel fullscreen: {window:?}");
|
||||
return;
|
||||
};
|
||||
|
||||
match state {
|
||||
crate::xstate::SetState::Add => toplevel.toplevel.set_fullscreen(None),
|
||||
crate::xstate::SetState::Remove => toplevel.toplevel.unset_fullscreen(),
|
||||
crate::xstate::SetState::Toggle => {
|
||||
if toplevel.fullscreen {
|
||||
toplevel.toplevel.unset_fullscreen()
|
||||
} else {
|
||||
toplevel.toplevel.set_fullscreen(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy_window(&mut self, window: x::Window) {
|
||||
let _ = self.windows.remove(&window);
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
if let Some(r) = self.clientside.queue.prepare_read() {
|
||||
let fd = r.connection_fd();
|
||||
let pollfd = PollFd::new(&fd, PollFlags::IN);
|
||||
if poll(&mut [pollfd], 0).unwrap() > 0 {
|
||||
let _ = r.read();
|
||||
}
|
||||
}
|
||||
self.clientside
|
||||
.queue
|
||||
.dispatch_pending(&mut self.clientside.globals)
|
||||
.unwrap();
|
||||
self.handle_clientside_events();
|
||||
self.clientside.queue.flush().unwrap();
|
||||
}
|
||||
|
||||
pub fn handle_clientside_events(&mut self) {
|
||||
self.handle_new_globals();
|
||||
|
||||
let client_events = std::mem::take(&mut self.clientside.globals.events);
|
||||
for (key, event) in client_events {
|
||||
let object = &mut self.objects[key];
|
||||
let mut object = object.0.take().unwrap();
|
||||
object.handle_event(event, self);
|
||||
let ret = self.objects[key].0.replace(object);
|
||||
debug_assert!(ret.is_none());
|
||||
}
|
||||
|
||||
{
|
||||
if let Some(win) = self.to_focus.take() {
|
||||
let data = C::ExtraData::create(self);
|
||||
let conn = self.connection.as_mut().unwrap();
|
||||
debug!("focusing window {win:?}");
|
||||
conn.focus_window(win, data);
|
||||
self.last_focused_toplevel = Some(win);
|
||||
}
|
||||
}
|
||||
|
||||
self.clientside.queue.flush().unwrap();
|
||||
}
|
||||
|
||||
fn create_role_window(&mut self, window: x::Window, surface_key: ObjectKey) {
|
||||
let surface: &SurfaceData = self.objects[surface_key].as_ref();
|
||||
let client = &surface.client;
|
||||
client.attach(None, 0, 0);
|
||||
client.commit();
|
||||
|
||||
let xdg_surface = self
|
||||
.xdg_wm_base
|
||||
.get_xdg_surface(client, &self.qh, surface_key);
|
||||
|
||||
let window_data = self.windows.get_mut(&window).unwrap();
|
||||
if window_data.attrs.override_redirect {
|
||||
// Override redirect is hard to convert to Wayland!
|
||||
// We will just make them be popups for the last focused toplevel.
|
||||
if let Some(win) = self.last_focused_toplevel {
|
||||
window_data.attrs.popup_for = Some(win)
|
||||
}
|
||||
}
|
||||
let window = self.windows.get(&window).unwrap();
|
||||
|
||||
let role = if let Some(parent) = window.attrs.popup_for {
|
||||
debug!(
|
||||
"creating popup ({:?}) {:?} {:?} {:?} {surface_key:?}",
|
||||
window.window,
|
||||
parent,
|
||||
window.attrs.dims,
|
||||
surface.client.id()
|
||||
);
|
||||
|
||||
let parent_window = self.windows.get(&parent).unwrap();
|
||||
let parent_surface: &SurfaceData =
|
||||
self.objects[parent_window.surface_key.unwrap()].as_ref();
|
||||
|
||||
let positioner = self.xdg_wm_base.create_positioner(&self.qh, ());
|
||||
positioner.set_size(window.attrs.dims.width as _, window.attrs.dims.height as _);
|
||||
positioner.set_offset(window.attrs.dims.x as i32, window.attrs.dims.y as i32);
|
||||
positioner.set_anchor(Anchor::TopLeft);
|
||||
positioner.set_gravity(Gravity::BottomRight);
|
||||
positioner.set_anchor_rect(
|
||||
0,
|
||||
0,
|
||||
parent_window.attrs.dims.width as _,
|
||||
parent_window.attrs.dims.height as _,
|
||||
);
|
||||
let popup = xdg_surface.get_popup(
|
||||
Some(&parent_surface.xdg().unwrap().surface),
|
||||
&positioner,
|
||||
&self.qh,
|
||||
surface_key,
|
||||
);
|
||||
let popup = PopupData {
|
||||
popup,
|
||||
positioner,
|
||||
xdg: XdgSurfaceData {
|
||||
surface: xdg_surface,
|
||||
configured: false,
|
||||
pending: None,
|
||||
},
|
||||
};
|
||||
SurfaceRole::Popup(Some(popup))
|
||||
} else {
|
||||
let data = self.create_toplevel(window, surface_key, xdg_surface);
|
||||
SurfaceRole::Toplevel(Some(data))
|
||||
};
|
||||
|
||||
let surface: &mut SurfaceData = self.objects[surface_key].as_mut();
|
||||
|
||||
let new_role_type = std::mem::discriminant(&role);
|
||||
let prev = surface.role.replace(role);
|
||||
if let Some(role) = prev {
|
||||
let old_role_type = std::mem::discriminant(&role);
|
||||
assert_eq!(
|
||||
new_role_type, old_role_type,
|
||||
"Surface for {:?} already had a role: {:?}",
|
||||
window.window, role
|
||||
);
|
||||
}
|
||||
|
||||
surface.client.commit();
|
||||
}
|
||||
|
||||
fn create_toplevel(
|
||||
&self,
|
||||
window: &WindowData,
|
||||
surface_key: ObjectKey,
|
||||
xdg: XdgSurface,
|
||||
) -> ToplevelData {
|
||||
debug!("creating toplevel for {:?}", window.window);
|
||||
|
||||
let toplevel = xdg.get_toplevel(&self.qh, surface_key);
|
||||
if let Some(hints) = &window.attrs.size_hints {
|
||||
if let Some(min) = &hints.min_size {
|
||||
toplevel.set_min_size(min.width, min.height);
|
||||
}
|
||||
if let Some(max) = &hints.max_size {
|
||||
toplevel.set_max_size(max.width, max.height);
|
||||
}
|
||||
}
|
||||
|
||||
let group = window.attrs.group.and_then(|win| self.windows.get(&win));
|
||||
if let Some(class) = window
|
||||
.attrs
|
||||
.class
|
||||
.as_ref()
|
||||
.or(group.and_then(|g| g.attrs.class.as_ref()))
|
||||
{
|
||||
toplevel.set_app_id(class.to_string());
|
||||
}
|
||||
if let Some(title) = window
|
||||
.attrs
|
||||
.title
|
||||
.as_ref()
|
||||
.or(group.and_then(|g| g.attrs.title.as_ref()))
|
||||
{
|
||||
toplevel.set_title(title.name().to_string());
|
||||
}
|
||||
|
||||
ToplevelData {
|
||||
xdg: XdgSurfaceData {
|
||||
surface: xdg,
|
||||
configured: false,
|
||||
pending: None,
|
||||
},
|
||||
toplevel,
|
||||
fullscreen: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_server_surface_from_client(&self, surface: client::wl_surface::WlSurface) -> &WlSurface {
|
||||
let key: &ObjectKey = surface.data().unwrap();
|
||||
let surface: &SurfaceData = self.objects[*key].as_ref();
|
||||
&surface.server
|
||||
}
|
||||
|
||||
fn get_client_surface_from_server(&self, surface: WlSurface) -> &client::wl_surface::WlSurface {
|
||||
let key: &ObjectKey = surface.data().unwrap();
|
||||
let surface: &SurfaceData = self.objects[*key].as_ref();
|
||||
&surface.client
|
||||
}
|
||||
|
||||
fn close_x_window(&mut self, window: x::Window) {
|
||||
debug!("sending close request to {window:?}");
|
||||
let data = C::ExtraData::create(self);
|
||||
self.connection.as_mut().unwrap().close_window(window, data);
|
||||
if self.last_focused_toplevel == Some(window) {
|
||||
self.last_focused_toplevel.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct PendingSurfaceState {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
947
src/server/tests.rs
Normal file
947
src/server/tests.rs
Normal file
|
|
@ -0,0 +1,947 @@
|
|||
use super::{ServerState, WindowDims};
|
||||
use crate::xstate::{SetState, WmName};
|
||||
use paste::paste;
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use std::collections::HashMap;
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wayland_client::{
|
||||
backend::{protocol::Message, Backend, ObjectData, ObjectId, WaylandError},
|
||||
protocol::{
|
||||
wl_buffer::WlBuffer,
|
||||
wl_compositor::WlCompositor,
|
||||
wl_display::WlDisplay,
|
||||
wl_registry::WlRegistry,
|
||||
wl_seat::WlSeat,
|
||||
wl_shm::{Format, WlShm},
|
||||
wl_shm_pool::WlShmPool,
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
Connection, Proxy, WEnum,
|
||||
};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
relative_pointer::zv1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
viewporter::client::wp_viewporter::WpViewporter,
|
||||
},
|
||||
xdg::{
|
||||
shell::server::{xdg_positioner, xdg_toplevel},
|
||||
xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||
},
|
||||
};
|
||||
use wayland_server::{protocol as s_proto, Display, Resource};
|
||||
use wl_drm::client::wl_drm::WlDrm;
|
||||
use xcb::x::Window;
|
||||
|
||||
use xcb::XidNew;
|
||||
|
||||
macro_rules! with_optional {
|
||||
(
|
||||
$( #[$attr:meta] )?
|
||||
struct $name:ident$(<$($lifetimes:lifetime),+>)? {
|
||||
$(
|
||||
$field:ident: $type:ty
|
||||
),+$(,)?
|
||||
}
|
||||
) => {
|
||||
$( #[$attr] )?
|
||||
struct $name$(<$($lifetimes),+>)? {
|
||||
$(
|
||||
$field: $type
|
||||
),+
|
||||
}
|
||||
|
||||
paste! {
|
||||
#[derive(Default)]
|
||||
struct [< $name Optional >] {
|
||||
$(
|
||||
$field: Option<$type>
|
||||
),+
|
||||
}
|
||||
}
|
||||
|
||||
paste! {
|
||||
impl From<[<$name Optional>]> for $name {
|
||||
fn from(opt: [<$name Optional>]) -> Self {
|
||||
Self {
|
||||
$(
|
||||
$field: opt.$field.expect(concat!("uninitialized field ", stringify!($field)))
|
||||
),+
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with_optional! {
|
||||
|
||||
struct Compositor {
|
||||
compositor: TestObject<WlCompositor>,
|
||||
shm: TestObject<WlShm>,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Compositor {
|
||||
fn create_surface(&self) -> (TestObject<WlBuffer>, TestObject<WlSurface>) {
|
||||
let fd = unsafe { BorrowedFd::borrow_raw(0) };
|
||||
let pool = TestObject::<WlShmPool>::from_request(
|
||||
&self.shm.obj,
|
||||
Req::<WlShm>::CreatePool { fd, size: 1024 },
|
||||
);
|
||||
let buffer = TestObject::<WlBuffer>::from_request(
|
||||
&pool.obj,
|
||||
Req::<WlShmPool>::CreateBuffer {
|
||||
offset: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
stride: 1,
|
||||
format: WEnum::Value(Format::Xrgb8888A8),
|
||||
},
|
||||
);
|
||||
let surface = TestObject::<WlSurface>::from_request(
|
||||
&self.compositor.obj,
|
||||
Req::<WlCompositor>::CreateSurface {},
|
||||
);
|
||||
surface
|
||||
.send_request(Req::<WlSurface>::Attach {
|
||||
buffer: Some(buffer.obj.clone()),
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
(buffer, surface)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct WindowData {
|
||||
mapped: bool,
|
||||
fullscreen: bool,
|
||||
dims: WindowDims,
|
||||
}
|
||||
struct FakeXConnection {
|
||||
root: Window,
|
||||
focused_window: Option<Window>,
|
||||
windows: HashMap<Window, WindowData>,
|
||||
}
|
||||
|
||||
impl FakeXConnection {
|
||||
#[track_caller]
|
||||
fn window(&mut self, window: Window) -> &mut WindowData {
|
||||
self.windows
|
||||
.get_mut(&window)
|
||||
.expect(&format!("Unknown window: {window:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FakeXConnection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
root: unsafe { Window::new(9001) },
|
||||
focused_window: None,
|
||||
windows: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::FromServerState<FakeXConnection> for () {
|
||||
fn create(_: &FakeServerState) -> Self {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
impl super::XConnection for FakeXConnection {
|
||||
type ExtraData = ();
|
||||
fn root_window(&self) -> Window {
|
||||
self.root
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn close_window(&mut self, window: Window, _: ()) {
|
||||
log::debug!("closing window {window:?}");
|
||||
self.window(window).mapped = false;
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn set_fullscreen(&mut self, window: xcb::x::Window, fullscreen: bool, _: ()) {
|
||||
self.window(window).fullscreen = fullscreen;
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn set_window_dims(&mut self, window: Window, state: super::PendingSurfaceState) {
|
||||
self.window(window).dims = WindowDims {
|
||||
x: state.x as _,
|
||||
y: state.y as _,
|
||||
width: state.width as _,
|
||||
height: state.height as _,
|
||||
};
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn focus_window(&mut self, window: Window, _: ()) {
|
||||
assert!(
|
||||
self.windows.contains_key(&window),
|
||||
"Unknown window: {window:?}"
|
||||
);
|
||||
self.focused_window = window.into();
|
||||
}
|
||||
}
|
||||
|
||||
type FakeServerState = ServerState<FakeXConnection>;
|
||||
|
||||
struct TestFixture {
|
||||
testwl: testwl::Server,
|
||||
exwayland: FakeServerState,
|
||||
/// Our connection to exwayland - i.e., where Xwayland sends requests to
|
||||
exwl_connection: Arc<Connection>,
|
||||
/// Exwayland's display - must dispatch this for our server state to advance
|
||||
exwl_display: Display<FakeServerState>,
|
||||
}
|
||||
|
||||
static INIT: std::sync::Once = std::sync::Once::new();
|
||||
|
||||
impl TestFixture {
|
||||
fn new() -> Self {
|
||||
INIT.call_once(|| {
|
||||
env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter_level(log::LevelFilter::Trace)
|
||||
.init()
|
||||
});
|
||||
|
||||
let (client_s, server_s) = UnixStream::pair().unwrap();
|
||||
let mut testwl = testwl::Server::new(true);
|
||||
let display = Display::<FakeServerState>::new().unwrap();
|
||||
testwl.connect(server_s);
|
||||
// Handle initial globals roundtrip setup requirement
|
||||
let thread = std::thread::spawn(move || {
|
||||
let mut pollfd = [PollFd::from_borrowed_fd(testwl.poll_fd(), PollFlags::IN)];
|
||||
if poll(&mut pollfd, 1000).unwrap() == 0 {
|
||||
panic!("Did not get events for testwl!");
|
||||
}
|
||||
testwl.dispatch();
|
||||
testwl
|
||||
});
|
||||
let mut exwayland = FakeServerState::new(display.handle(), Some(client_s));
|
||||
let testwl = thread.join().unwrap();
|
||||
|
||||
let (fake_client, ex_server) = UnixStream::pair().unwrap();
|
||||
exwayland.connect(ex_server);
|
||||
|
||||
exwayland.set_x_connection(FakeXConnection::default());
|
||||
let mut f = TestFixture {
|
||||
testwl,
|
||||
exwayland,
|
||||
exwl_connection: Connection::from_socket(fake_client).unwrap().into(),
|
||||
exwl_display: display,
|
||||
};
|
||||
f.run();
|
||||
f
|
||||
}
|
||||
|
||||
fn new_with_compositor() -> (Self, Compositor) {
|
||||
let mut f = Self::new();
|
||||
let compositor = f.compositor();
|
||||
(f, compositor)
|
||||
}
|
||||
|
||||
fn connection(&self) -> &FakeXConnection {
|
||||
self.exwayland.connection.as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn compositor(&mut self) -> Compositor {
|
||||
let mut ret = CompositorOptional::default();
|
||||
let wl_display = self.exwl_connection.display();
|
||||
|
||||
let registry =
|
||||
TestObject::<WlRegistry>::from_request(&wl_display, Req::<WlDisplay>::GetRegistry {});
|
||||
self.run();
|
||||
|
||||
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
|
||||
assert!(events.len() > 0);
|
||||
|
||||
let bind_req = |name, interface, version| Req::<WlRegistry>::Bind {
|
||||
name,
|
||||
id: (interface, version),
|
||||
};
|
||||
|
||||
for event in events {
|
||||
if let Ev::<WlRegistry>::Global {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
} = event
|
||||
{
|
||||
match interface {
|
||||
x if x == WlCompositor::interface().name => {
|
||||
ret.compositor = Some(TestObject::from_request(
|
||||
®istry.obj,
|
||||
bind_req(name, WlCompositor::interface(), version),
|
||||
));
|
||||
}
|
||||
x if x == WlShm::interface().name => {
|
||||
ret.shm = Some(TestObject::from_request(
|
||||
®istry.obj,
|
||||
bind_req(name, WlShm::interface(), version),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret.into()
|
||||
}
|
||||
|
||||
/// Cascade our requests/events through exwayland and testwl
|
||||
fn run(&mut self) {
|
||||
// Flush our requests to exwayland
|
||||
self.exwl_connection.flush().unwrap();
|
||||
|
||||
// Have exwayland dispatch our requests
|
||||
self.exwl_display
|
||||
.dispatch_clients(&mut self.exwayland)
|
||||
.unwrap();
|
||||
self.exwl_display.flush_clients().unwrap();
|
||||
|
||||
// Dispatch any clientside requests
|
||||
self.exwayland.run();
|
||||
|
||||
// Have testwl dispatch the clientside requests
|
||||
self.testwl.dispatch();
|
||||
|
||||
// Handle clientside events
|
||||
self.exwayland.handle_clientside_events();
|
||||
|
||||
self.testwl.dispatch();
|
||||
|
||||
// Get our events
|
||||
let res = self.exwl_connection.prepare_read().unwrap().read();
|
||||
if res.is_err()
|
||||
&& !matches!(res, Err(WaylandError::Io(ref e)) if e.kind() == std::io::ErrorKind::WouldBlock)
|
||||
{
|
||||
panic!("Read failed: {res:?}")
|
||||
}
|
||||
}
|
||||
|
||||
fn register_window(&mut self, window: Window, data: WindowData) {
|
||||
self.exwayland
|
||||
.connection
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.windows
|
||||
.insert(window, data);
|
||||
}
|
||||
|
||||
fn create_and_map_window(
|
||||
&mut self,
|
||||
comp: &Compositor,
|
||||
window: Window,
|
||||
override_redirect: bool,
|
||||
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
|
||||
let (_buffer, surface) = comp.create_surface();
|
||||
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false,
|
||||
};
|
||||
|
||||
let dims = data.dims;
|
||||
|
||||
self.register_window(window, data);
|
||||
self.exwayland
|
||||
.new_window(window, override_redirect, dims, None);
|
||||
self.exwayland.map_window(window);
|
||||
self.exwayland
|
||||
.associate_window(window, surface.id().protocol_id());
|
||||
|
||||
self.run();
|
||||
|
||||
let testwl_id = self
|
||||
.testwl
|
||||
.last_created_surface_id()
|
||||
.expect("Surface not created");
|
||||
|
||||
assert!(self.testwl.get_surface_data(testwl_id).is_some());
|
||||
|
||||
(surface, testwl_id)
|
||||
}
|
||||
|
||||
fn create_toplevel(
|
||||
&mut self,
|
||||
comp: &Compositor,
|
||||
window: Window,
|
||||
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
|
||||
let (_buffer, surface) = comp.create_surface();
|
||||
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false,
|
||||
};
|
||||
|
||||
let dims = data.dims;
|
||||
|
||||
self.register_window(window, data);
|
||||
self.exwayland.new_window(window, false, dims, None);
|
||||
self.exwayland.map_window(window);
|
||||
self.exwayland
|
||||
.associate_window(window, surface.id().protocol_id());
|
||||
|
||||
self.run();
|
||||
|
||||
let testwl_id = self
|
||||
.testwl
|
||||
.last_created_surface_id()
|
||||
.expect("Toplevel surface not created");
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(testwl_id).unwrap();
|
||||
assert!(
|
||||
surface_data.surface
|
||||
== self
|
||||
.testwl
|
||||
.get_object::<s_proto::wl_surface::WlSurface>(testwl_id)
|
||||
.unwrap()
|
||||
);
|
||||
assert!(surface_data.buffer.is_none());
|
||||
assert!(
|
||||
matches!(surface_data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
||||
"surface role: {:?}",
|
||||
surface_data.role
|
||||
);
|
||||
}
|
||||
|
||||
self.testwl
|
||||
.configure_toplevel(testwl_id, 100, 100, vec![xdg_toplevel::State::Activated]);
|
||||
self.run();
|
||||
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(testwl_id).unwrap();
|
||||
assert!(surface_data.buffer.is_some());
|
||||
}
|
||||
|
||||
let win_data = self.connection().windows.get(&window).map(|d| &d.dims);
|
||||
assert!(
|
||||
matches!(
|
||||
win_data,
|
||||
Some(&super::WindowDims {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100
|
||||
})
|
||||
),
|
||||
"Incorrect window geometry: {win_data:?}"
|
||||
);
|
||||
|
||||
(surface, testwl_id)
|
||||
}
|
||||
|
||||
fn create_popup(
|
||||
&mut self,
|
||||
comp: &Compositor,
|
||||
window: Window,
|
||||
parent_id: testwl::SurfaceId,
|
||||
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
|
||||
let (_, popup_surface) = comp.create_surface();
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
x: 10,
|
||||
y: 10,
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false,
|
||||
};
|
||||
let dims = data.dims;
|
||||
self.register_window(window, data);
|
||||
self.exwayland.new_window(window, true, dims, None);
|
||||
self.exwayland.map_window(window);
|
||||
self.exwayland
|
||||
.associate_window(window, popup_surface.id().protocol_id());
|
||||
self.run();
|
||||
let popup_id = self.testwl.last_created_surface_id().unwrap();
|
||||
assert_ne!(popup_id, parent_id);
|
||||
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(popup_id).unwrap();
|
||||
assert!(
|
||||
surface_data.surface
|
||||
== self
|
||||
.testwl
|
||||
.get_object::<s_proto::wl_surface::WlSurface>(popup_id)
|
||||
.unwrap()
|
||||
);
|
||||
assert!(surface_data.buffer.is_none());
|
||||
assert!(
|
||||
matches!(surface_data.role, Some(testwl::SurfaceRole::Popup(_))),
|
||||
"surface was not a popup (role: {:?})",
|
||||
surface_data.role
|
||||
);
|
||||
|
||||
let toplevel_xdg = &self
|
||||
.testwl
|
||||
.get_surface_data(parent_id)
|
||||
.unwrap()
|
||||
.xdg()
|
||||
.surface;
|
||||
assert_eq!(&surface_data.popup().parent, toplevel_xdg);
|
||||
|
||||
let pos = &surface_data.popup().positioner_state;
|
||||
assert_eq!(pos.size.as_ref().unwrap(), &testwl::Vec2 { x: 50, y: 50 });
|
||||
assert_eq!(
|
||||
pos.anchor_rect.as_ref().unwrap(),
|
||||
&testwl::Rect {
|
||||
size: testwl::Vec2 { x: 100, y: 100 },
|
||||
offset: testwl::Vec2::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
pos.offset,
|
||||
testwl::Vec2 {
|
||||
x: dims.x as _,
|
||||
y: dims.y as _
|
||||
}
|
||||
);
|
||||
assert_eq!(pos.anchor, xdg_positioner::Anchor::TopLeft);
|
||||
assert_eq!(pos.gravity, xdg_positioner::Gravity::BottomRight);
|
||||
}
|
||||
|
||||
self.testwl.configure_popup(popup_id);
|
||||
self.run();
|
||||
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(popup_id).unwrap();
|
||||
assert!(surface_data.buffer.is_some());
|
||||
}
|
||||
|
||||
(popup_surface, popup_id)
|
||||
}
|
||||
}
|
||||
|
||||
struct TestObjectData<T: Proxy> {
|
||||
events: Mutex<Vec<T::Event>>,
|
||||
_phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Proxy> Default for TestObjectData<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
events: Default::default(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Proxy + Send + Sync + 'static> ObjectData for TestObjectData<T>
|
||||
where
|
||||
T::Event: Send + Sync + std::fmt::Debug,
|
||||
{
|
||||
fn event(
|
||||
self: Arc<Self>,
|
||||
backend: &Backend,
|
||||
msg: Message<ObjectId, std::os::fd::OwnedFd>,
|
||||
) -> Option<Arc<dyn ObjectData>> {
|
||||
let connection = Connection::from_backend(backend.clone());
|
||||
let event = T::parse_event(&connection, msg).unwrap().1;
|
||||
self.events.lock().unwrap().push(event);
|
||||
None
|
||||
}
|
||||
|
||||
fn destroyed(&self, _: ObjectId) {}
|
||||
}
|
||||
|
||||
struct TestObject<T: Proxy> {
|
||||
obj: T,
|
||||
data: Arc<TestObjectData<T>>,
|
||||
}
|
||||
|
||||
impl<T: Proxy> std::ops::Deref for TestObject<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
&self.obj
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Proxy + Sync + Send + 'static> TestObject<T>
|
||||
where
|
||||
T::Event: Sync + Send + std::fmt::Debug,
|
||||
{
|
||||
fn from_request<P: Proxy>(object: &P, request: P::Request<'_>) -> Self {
|
||||
let data = Arc::<TestObjectData<T>>::default();
|
||||
let obj: T = P::send_constructor(object, request, data.clone()).unwrap();
|
||||
Self { obj, data }
|
||||
}
|
||||
}
|
||||
|
||||
type Req<'a, T> = <T as Proxy>::Request<'a>;
|
||||
type Ev<T> = <T as Proxy>::Event;
|
||||
|
||||
// TODO: tests to add
|
||||
// - destroy window before surface
|
||||
// - destroy surface before window
|
||||
// - destroy popup and reassociate with new surface
|
||||
// - reconfigure window (popup) before mapping
|
||||
// - associate window after surface is already created
|
||||
|
||||
// Matches Xwayland flow.
|
||||
#[test]
|
||||
fn toplevel_flow() {
|
||||
let (mut f, compositor) = TestFixture::new_with_compositor();
|
||||
|
||||
let window = unsafe { Window::new(1) };
|
||||
let (surface, testwl_id) = f.create_toplevel(&compositor, window);
|
||||
{
|
||||
let surface_data = f.testwl.get_surface_data(testwl_id).unwrap();
|
||||
assert!(surface_data.buffer.is_some());
|
||||
}
|
||||
|
||||
f.testwl.close_toplevel(testwl_id);
|
||||
f.run();
|
||||
|
||||
assert!(!f.exwayland.connection.as_ref().unwrap().windows[&window].mapped);
|
||||
|
||||
assert!(
|
||||
f.testwl.get_surface_data(testwl_id).is_some(),
|
||||
"Surface should still exist for closed toplevel"
|
||||
);
|
||||
assert!(surface.obj.is_alive());
|
||||
|
||||
// For some reason, we can get two UnmapNotify events
|
||||
// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4
|
||||
f.exwayland.unmap_window(window);
|
||||
f.exwayland.unmap_window(window);
|
||||
f.exwayland.destroy_window(window);
|
||||
surface.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(testwl_id).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn popup_flow_simple() {
|
||||
let (mut f, compositor) = TestFixture::new_with_compositor();
|
||||
|
||||
let win_toplevel = unsafe { Window::new(1) };
|
||||
let (_, toplevel_id) = f.create_toplevel(&compositor, win_toplevel);
|
||||
|
||||
let win_popup = unsafe { Window::new(2) };
|
||||
let (popup_surface, popup_id) = f.create_popup(&compositor, win_popup, toplevel_id);
|
||||
|
||||
f.exwayland.unmap_window(win_popup);
|
||||
f.exwayland.destroy_window(win_popup);
|
||||
popup_surface.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(popup_id).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pass_through_globals() {
|
||||
use wayland_client::protocol::wl_output::WlOutput;
|
||||
|
||||
let mut f = TestFixture::new();
|
||||
|
||||
const fn check<T: Proxy>() {}
|
||||
|
||||
macro_rules! globals_struct {
|
||||
($($field:ident),+) => {
|
||||
$( check::<$field>(); )+
|
||||
#[derive(Default)]
|
||||
#[allow(non_snake_case)]
|
||||
struct SupportedGlobals {
|
||||
$( $field: bool ),+
|
||||
}
|
||||
|
||||
impl SupportedGlobals {
|
||||
fn check_globals(&self) {
|
||||
$( assert!(self.$field, "Missing global {}", stringify!($field)); )+
|
||||
}
|
||||
|
||||
fn global_found(&mut self, interface: String) {
|
||||
match interface {
|
||||
$(
|
||||
x if x == $field::interface().name => {
|
||||
self.$field = true;
|
||||
}
|
||||
)+
|
||||
_ => panic!("Found an unhandled global: {interface}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New globals need to be added here and in testwl.
|
||||
globals_struct! {
|
||||
WlCompositor,
|
||||
WlShm,
|
||||
WlOutput,
|
||||
WlSeat,
|
||||
ZwpLinuxDmabufV1,
|
||||
ZwpRelativePointerManagerV1,
|
||||
ZxdgOutputManagerV1,
|
||||
WpViewporter,
|
||||
WlDrm
|
||||
}
|
||||
|
||||
let mut globals = SupportedGlobals::default();
|
||||
let display = f.exwl_connection.display();
|
||||
let registry =
|
||||
TestObject::<WlRegistry>::from_request(&display, Req::<WlDisplay>::GetRegistry {});
|
||||
f.run();
|
||||
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
|
||||
assert!(events.len() > 0);
|
||||
for event in events {
|
||||
let Ev::<WlRegistry>::Global { interface, .. } = event else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
globals.global_found(interface);
|
||||
}
|
||||
|
||||
globals.check_globals();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_activated_toplevel_is_focused() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win1 = unsafe { Window::new(1) };
|
||||
|
||||
let (_surface1, id1) = f.create_toplevel(&comp, win1);
|
||||
assert_eq!(
|
||||
f.connection().focused_window,
|
||||
Some(win1),
|
||||
"new toplevel's window is not focused"
|
||||
);
|
||||
|
||||
let win2 = unsafe { Window::new(2) };
|
||||
let _data2 = f.create_toplevel(&comp, win2);
|
||||
assert_eq!(
|
||||
f.connection().focused_window,
|
||||
Some(win2),
|
||||
"toplevel focus did not switch"
|
||||
);
|
||||
|
||||
f.testwl.configure_toplevel(id1, 100, 100, vec![]);
|
||||
f.run();
|
||||
assert_eq!(
|
||||
f.connection().focused_window,
|
||||
Some(win2),
|
||||
"toplevel focus did not stay the same"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn popup_window_changes_surface() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let t_win = unsafe { Window::new(1) };
|
||||
let (_, toplevel_id) = f.create_toplevel(&comp, t_win);
|
||||
|
||||
let win = unsafe { Window::new(2) };
|
||||
let (surface, old_id) = f.create_popup(&comp, win, toplevel_id);
|
||||
|
||||
f.exwayland.unmap_window(win);
|
||||
surface.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(old_id).is_none());
|
||||
|
||||
let (_, surface) = comp.create_surface();
|
||||
f.run();
|
||||
let id = f
|
||||
.testwl
|
||||
.last_created_surface_id()
|
||||
.expect("No surface created");
|
||||
|
||||
assert_ne!(old_id, id);
|
||||
assert!(f.testwl.get_surface_data(id).is_some());
|
||||
|
||||
f.exwayland.map_window(win);
|
||||
f.exwayland
|
||||
.associate_window(win, surface.id().protocol_id());
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data.popup().popup.is_alive());
|
||||
|
||||
f.testwl.configure_popup(id);
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data.popup().popup.is_alive());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn override_redirect_window_after_toplevel_close() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win1 = unsafe { Window::new(1) };
|
||||
let (obj, first) = f.create_toplevel(&comp, win1);
|
||||
f.testwl.close_toplevel(first);
|
||||
f.run();
|
||||
|
||||
f.exwayland.unmap_window(win1);
|
||||
f.exwayland.destroy_window(win1);
|
||||
obj.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(first).is_none());
|
||||
|
||||
let win2 = unsafe { Window::new(2) };
|
||||
let (_, second) = f.create_and_map_window(&comp, win2, true);
|
||||
let data = f.testwl.get_surface_data(second).unwrap();
|
||||
assert!(
|
||||
matches!(data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
||||
"wrong role: {:?}",
|
||||
data.role
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fullscreen() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win = unsafe { Window::new(1) };
|
||||
let (_, id) = f.create_toplevel(&comp, win);
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Add);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Remove);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(!data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Toggle);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Toggle);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(!data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_title_and_class() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win = unsafe { Window::new(1) };
|
||||
let (_, id) = f.create_toplevel(&comp, win);
|
||||
|
||||
f.exwayland
|
||||
.set_win_title(win, WmName::WmName("window".into()));
|
||||
f.exwayland.set_win_class(win, "class".into());
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert_eq!(data.toplevel().title, Some("window".into()));
|
||||
assert_eq!(data.toplevel().app_id, Some("class".into()));
|
||||
|
||||
f.exwayland
|
||||
.set_win_title(win, WmName::NetWmName("superwindow".into()));
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert_eq!(data.toplevel().title, Some("superwindow".into()));
|
||||
|
||||
f.exwayland
|
||||
.set_win_title(win, WmName::WmName("shwindow".into()));
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert_eq!(data.toplevel().title, Some("superwindow".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_group_properties() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let prop_win = unsafe { Window::new(1) };
|
||||
f.exwayland.new_window(
|
||||
prop_win,
|
||||
false,
|
||||
super::WindowDims {
|
||||
width: 1,
|
||||
height: 1,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
f.exwayland
|
||||
.set_win_title(prop_win, WmName::WmName("window".into()));
|
||||
f.exwayland.set_win_class(prop_win, "class".into());
|
||||
|
||||
let win = unsafe { Window::new(2) };
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
width: 50,
|
||||
height: 50,
|
||||
..Default::default()
|
||||
},
|
||||
fullscreen: false,
|
||||
};
|
||||
|
||||
let (_, surface) = comp.create_surface();
|
||||
let dims = data.dims;
|
||||
f.register_window(win, data);
|
||||
f.exwayland.new_window(win, false, dims, None);
|
||||
f.exwayland.set_win_hints(
|
||||
win,
|
||||
super::WmHints {
|
||||
window_group: Some(prop_win),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
f.exwayland.map_window(win);
|
||||
f.exwayland
|
||||
.associate_window(win, surface.id().protocol_id());
|
||||
f.run();
|
||||
|
||||
let id = f.testwl.last_created_surface_id().unwrap();
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
|
||||
assert_eq!(data.toplevel().title, Some("window".into()));
|
||||
assert_eq!(data.toplevel().app_id, Some("class".into()));
|
||||
}
|
||||
/// See Pointer::handle_event for an explanation.
|
||||
#[test]
|
||||
fn popup_pointer_motion_workaround() {}
|
||||
775
src/xstate.rs
Normal file
775
src/xstate.rs
Normal file
|
|
@ -0,0 +1,775 @@
|
|||
use crate::server::WindowAttributes;
|
||||
use bitflags::bitflags;
|
||||
use log::{debug, trace, warn};
|
||||
use std::ffi::CString;
|
||||
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||
use std::sync::Arc;
|
||||
use xcb::{x, Xid, XidNew};
|
||||
use xcb_util_cursor::{Cursor, CursorContext};
|
||||
|
||||
pub struct XState {
|
||||
pub connection: Arc<xcb::Connection>,
|
||||
root: x::Window,
|
||||
pub atoms: Atoms,
|
||||
}
|
||||
|
||||
/// Essentially a trait alias.
|
||||
trait PropertyResolver {
|
||||
type Output;
|
||||
fn resolve(self, reply: x::GetPropertyReply) -> Self::Output;
|
||||
}
|
||||
impl<T, Output> PropertyResolver for T
|
||||
where
|
||||
T: FnOnce(x::GetPropertyReply) -> Output,
|
||||
{
|
||||
type Output = Output;
|
||||
fn resolve(self, reply: x::GetPropertyReply) -> Self::Output {
|
||||
(self)(reply)
|
||||
}
|
||||
}
|
||||
|
||||
struct PropertyCookieWrapper<'a, F: PropertyResolver> {
|
||||
connection: &'a xcb::Connection,
|
||||
cookie: x::GetPropertyCookie,
|
||||
resolver: F,
|
||||
}
|
||||
|
||||
impl<F: PropertyResolver> PropertyCookieWrapper<'_, F> {
|
||||
/// Get the result from our property cookie.
|
||||
fn resolve(self) -> Option<F::Output> {
|
||||
let reply = self.connection.wait_for_reply(self.cookie).unwrap();
|
||||
if reply.r#type() == x::ATOM_NONE {
|
||||
None
|
||||
} else {
|
||||
Some(self.resolver.resolve(reply))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WmName {
|
||||
WmName(String),
|
||||
NetWmName(String),
|
||||
}
|
||||
|
||||
impl WmName {
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::WmName(n) => n,
|
||||
Self::NetWmName(n) => n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XState {
|
||||
pub fn new(fd: BorrowedFd) -> Self {
|
||||
let connection = Arc::new(xcb::Connection::connect_to_fd(fd.as_raw_fd(), None).unwrap());
|
||||
let setup = connection.get_setup();
|
||||
let screen = setup.roots().next().unwrap();
|
||||
let root = screen.root();
|
||||
|
||||
connection
|
||||
.send_and_check_request(&x::ChangeWindowAttributes {
|
||||
window: root,
|
||||
value_list: &[x::Cw::EventMask(
|
||||
x::EventMask::SUBSTRUCTURE_REDIRECT // To have Xwayland send us WL_SURFACE_ID
|
||||
| x::EventMask::SUBSTRUCTURE_NOTIFY // To get notified whenever new windows are created
|
||||
| x::EventMask::RESIZE_REDIRECT,
|
||||
)],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let atoms = Atoms::intern_all(&connection).unwrap();
|
||||
trace!("atoms: {atoms:#?}");
|
||||
|
||||
// This makes Xwayland spit out damage tracking
|
||||
connection
|
||||
.send_and_check_request(&xcb::composite::RedirectSubwindows {
|
||||
window: screen.root(),
|
||||
update: xcb::composite::Redirect::Manual,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Setup default cursor theme
|
||||
let ctx = CursorContext::new(&connection, screen).unwrap();
|
||||
let left_ptr = ctx.load_cursor(Cursor::LeftPtr);
|
||||
connection
|
||||
.send_and_check_request(&x::ChangeWindowAttributes {
|
||||
window: root,
|
||||
value_list: &[x::Cw::Cursor(left_ptr)],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut r = Self {
|
||||
connection,
|
||||
root,
|
||||
atoms,
|
||||
};
|
||||
r.create_ewmh_window();
|
||||
r
|
||||
}
|
||||
|
||||
fn set_root_property<P: x::PropEl>(&self, property: x::Atom, r#type: x::Atom, data: &[P]) {
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: self.root,
|
||||
property,
|
||||
r#type,
|
||||
data,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn create_ewmh_window(&mut self) {
|
||||
let window = self.connection.generate_id();
|
||||
self.connection
|
||||
.send_and_check_request(&x::CreateWindow {
|
||||
depth: 0,
|
||||
wid: window,
|
||||
parent: self.root,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
border_width: 0,
|
||||
class: x::WindowClass::InputOnly,
|
||||
visual: x::COPY_FROM_PARENT,
|
||||
value_list: &[],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.set_root_property(self.atoms.wm_check, x::ATOM_WINDOW, &[window]);
|
||||
self.set_root_property(self.atoms.active_win, x::ATOM_WINDOW, &[x::Window::none()]);
|
||||
self.set_root_property(self.atoms.supported, x::ATOM_ATOM, &[self.atoms.active_win]);
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
property: self.atoms.wm_check,
|
||||
r#type: x::ATOM_WINDOW,
|
||||
data: &[window],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
property: self.atoms.net_wm_name,
|
||||
r#type: x::ATOM_STRING,
|
||||
data: b"exwayland wm",
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn handle_events(&mut self, server_state: &mut super::RealServerState) {
|
||||
while let Some(event) = self.connection.poll_for_event().unwrap() {
|
||||
trace!("x11 event: {event:?}");
|
||||
match event {
|
||||
xcb::Event::X(x::Event::CreateNotify(e)) => {
|
||||
debug!("new window: {:?}", e);
|
||||
let parent = e.parent();
|
||||
let parent = if parent.is_none() || parent == self.root {
|
||||
None
|
||||
} else {
|
||||
Some(parent)
|
||||
};
|
||||
server_state.new_window(e.window(), e.override_redirect(), (&e).into(), parent);
|
||||
}
|
||||
xcb::Event::X(x::Event::ReparentNotify(e)) => {
|
||||
debug!("reparent event: {e:?}");
|
||||
if e.parent() == self.root {
|
||||
let attrs = self.get_window_attributes(e.window());
|
||||
server_state.new_window(
|
||||
e.window(),
|
||||
attrs.override_redirect,
|
||||
attrs.dims,
|
||||
None,
|
||||
);
|
||||
self.handle_window_attributes(server_state, e.window(), attrs);
|
||||
} else {
|
||||
debug!("destroying window since its parent is no longer root!");
|
||||
server_state.destroy_window(e.window());
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::MapRequest(e)) => {
|
||||
debug!("requested to map {:?}", e.window());
|
||||
self.connection
|
||||
.send_and_check_request(&x::MapWindow { window: e.window() })
|
||||
.unwrap();
|
||||
}
|
||||
xcb::Event::X(x::Event::MapNotify(e)) => {
|
||||
let attrs = self.get_window_attributes(e.window());
|
||||
self.handle_window_attributes(server_state, e.window(), attrs);
|
||||
server_state.map_window(e.window());
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeWindowAttributes {
|
||||
window: e.window(),
|
||||
value_list: &[x::Cw::EventMask(x::EventMask::PROPERTY_CHANGE)],
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
xcb::Event::X(x::Event::ConfigureNotify(e)) => {
|
||||
server_state.reconfigure_window(e);
|
||||
}
|
||||
xcb::Event::X(x::Event::UnmapNotify(e)) => {
|
||||
trace!("unmap event: {:?}", e.event());
|
||||
server_state.unmap_window(e.window());
|
||||
match self
|
||||
.connection
|
||||
.send_and_check_request(&x::ChangeWindowAttributes {
|
||||
window: e.window(),
|
||||
value_list: &[x::Cw::EventMask(x::EventMask::empty())],
|
||||
}) {
|
||||
// Window error may occur if the window has been destroyed,
|
||||
// which is fine
|
||||
Ok(_) | Err(xcb::ProtocolError::X(x::Error::Window(_), _)) => {}
|
||||
Err(other) => panic!("Error removing event mask from window: {other:?}"),
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::DestroyNotify(e)) => {
|
||||
debug!("destroying window {:?}", e.window());
|
||||
server_state.destroy_window(e.window());
|
||||
}
|
||||
xcb::Event::X(x::Event::PropertyNotify(e)) => {
|
||||
self.handle_property_change(e, server_state);
|
||||
}
|
||||
xcb::Event::X(x::Event::ConfigureRequest(e)) => {
|
||||
debug!("{:?} request: {:?}", e.window(), e.value_mask());
|
||||
let mut list = Vec::new();
|
||||
let mask = e.value_mask();
|
||||
|
||||
if mask.contains(x::ConfigWindowMask::X) {
|
||||
list.push(x::ConfigWindow::X(e.x().into()));
|
||||
}
|
||||
if mask.contains(x::ConfigWindowMask::Y) {
|
||||
list.push(x::ConfigWindow::Y(e.y().into()));
|
||||
}
|
||||
if mask.contains(x::ConfigWindowMask::WIDTH) {
|
||||
list.push(x::ConfigWindow::Width(e.width().into()));
|
||||
}
|
||||
if mask.contains(x::ConfigWindowMask::HEIGHT) {
|
||||
list.push(x::ConfigWindow::Height(e.height().into()));
|
||||
}
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ConfigureWindow {
|
||||
window: e.window(),
|
||||
value_list: &list,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
xcb::Event::X(x::Event::ClientMessage(e)) => match e.r#type() {
|
||||
x if x == self.atoms.wl_surface_id => {
|
||||
let x::ClientMessageData::Data32(data) = e.data() else {
|
||||
unreachable!();
|
||||
};
|
||||
let id: u32 = (data[0] as u64 | ((data[1] as u64) << 32)) as u32;
|
||||
server_state.associate_window(e.window(), id);
|
||||
}
|
||||
x if x == self.atoms.net_wm_state => {
|
||||
let x::ClientMessageData::Data32(data) = e.data() else {
|
||||
unreachable!();
|
||||
};
|
||||
let Ok(action) = SetState::try_from(data[0]) else {
|
||||
warn!("unknown action for _NET_WM_STATE: {}", data[0]);
|
||||
continue;
|
||||
};
|
||||
let prop1 = unsafe { x::Atom::new(data[1]) };
|
||||
let prop2 = unsafe { x::Atom::new(data[2]) };
|
||||
|
||||
trace!("_NET_WM_STATE ({action:?}) props: {prop1:?} {prop2:?}");
|
||||
|
||||
for prop in [prop1, prop2] {
|
||||
match prop {
|
||||
x if x == self.atoms.wm_fullscreen => {
|
||||
server_state.set_fullscreen(e.window(), action);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
t => warn!("unrecognized message: {t:?}"),
|
||||
},
|
||||
xcb::Event::X(x::Event::MappingNotify(_)) => {}
|
||||
other => {
|
||||
warn!("unhandled event: {other:?}");
|
||||
}
|
||||
}
|
||||
|
||||
server_state.run();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_window_attributes(&self, window: x::Window) -> WindowAttributes {
|
||||
let geometry = self.connection.send_request(&x::GetGeometry {
|
||||
drawable: x::Drawable::Window(window),
|
||||
});
|
||||
let attrs = self
|
||||
.connection
|
||||
.send_request(&x::GetWindowAttributes { window });
|
||||
|
||||
let name = self.get_net_wm_name(window);
|
||||
let class = self.get_wm_class(window);
|
||||
let wm_hints = self.get_wm_hints(window);
|
||||
let size_hints = self.get_wm_size_hints(window);
|
||||
|
||||
let geometry = self.connection.wait_for_reply(geometry).unwrap();
|
||||
let attrs = self.connection.wait_for_reply(attrs).unwrap();
|
||||
let title = name
|
||||
.resolve()
|
||||
.or_else(|| self.get_wm_name(window).resolve());
|
||||
let class = class.resolve();
|
||||
let wm_hints = wm_hints.resolve();
|
||||
let size_hints = size_hints.resolve();
|
||||
|
||||
WindowAttributes {
|
||||
override_redirect: attrs.override_redirect(),
|
||||
popup_for: None,
|
||||
dims: WindowDims {
|
||||
x: geometry.x(),
|
||||
y: geometry.y(),
|
||||
width: geometry.width(),
|
||||
height: geometry.height(),
|
||||
},
|
||||
title,
|
||||
class,
|
||||
group: wm_hints.map(|h| h.window_group).flatten(),
|
||||
size_hints,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_window_attributes(
|
||||
&self,
|
||||
server_state: &mut super::RealServerState,
|
||||
window: x::Window,
|
||||
attrs: WindowAttributes,
|
||||
) {
|
||||
if let Some(name) = attrs.title {
|
||||
debug!("setting {window:?} title to {name:?}");
|
||||
server_state.set_win_title(window, name);
|
||||
}
|
||||
if let Some(class) = attrs.class {
|
||||
debug!("setting {window:?} class to {class}");
|
||||
server_state.set_win_class(window, class);
|
||||
}
|
||||
if let Some(hints) = attrs.size_hints {
|
||||
debug!("{window:?} size hints: {hints:?}");
|
||||
server_state.set_size_hints(window, hints);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_property_cookie(
|
||||
&self,
|
||||
window: x::Window,
|
||||
property: x::Atom,
|
||||
ty: x::Atom,
|
||||
long_length: u32,
|
||||
) -> x::GetPropertyCookie {
|
||||
self.connection.send_request(&x::GetProperty {
|
||||
delete: false,
|
||||
window,
|
||||
property,
|
||||
r#type: ty,
|
||||
long_offset: 0,
|
||||
long_length,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_wm_class(
|
||||
&self,
|
||||
window: x::Window,
|
||||
) -> PropertyCookieWrapper<impl PropertyResolver<Output = String>> {
|
||||
let cookie = self.get_property_cookie(window, x::ATOM_WM_CLASS, x::ATOM_STRING, 256);
|
||||
let resolver = move |reply: x::GetPropertyReply| {
|
||||
let data: &[u8] = reply.value();
|
||||
// wm class is instance + class - ignore instance
|
||||
let class_start = data.iter().copied().position(|b| b == 0u8).unwrap() + 1;
|
||||
let data = data[class_start..].to_vec();
|
||||
let class = CString::from_vec_with_nul(data).unwrap();
|
||||
debug!("{:?} class: {class:?}", window);
|
||||
class.to_string_lossy().to_string()
|
||||
};
|
||||
PropertyCookieWrapper {
|
||||
connection: &self.connection,
|
||||
cookie,
|
||||
resolver,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_wm_name(
|
||||
&self,
|
||||
window: x::Window,
|
||||
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmName>> {
|
||||
let cookie = self.get_property_cookie(window, x::ATOM_WM_NAME, x::ATOM_STRING, 256);
|
||||
let resolver = |reply: x::GetPropertyReply| {
|
||||
let data: &[u8] = reply.value();
|
||||
WmName::WmName(String::from_utf8(data.to_vec()).unwrap())
|
||||
};
|
||||
|
||||
PropertyCookieWrapper {
|
||||
connection: &self.connection,
|
||||
cookie,
|
||||
resolver,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_net_wm_name(
|
||||
&self,
|
||||
window: x::Window,
|
||||
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmName>> {
|
||||
let cookie =
|
||||
self.get_property_cookie(window, self.atoms.net_wm_name, self.atoms.utf8_string, 256);
|
||||
let resolver = |reply: x::GetPropertyReply| {
|
||||
let data: &[u8] = reply.value();
|
||||
WmName::NetWmName(String::from_utf8(data.to_vec()).unwrap())
|
||||
};
|
||||
|
||||
PropertyCookieWrapper {
|
||||
connection: &self.connection,
|
||||
cookie,
|
||||
resolver,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_wm_hints(
|
||||
&self,
|
||||
window: x::Window,
|
||||
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmHints>> {
|
||||
let cookie = self.get_property_cookie(window, x::ATOM_WM_HINTS, x::ATOM_WM_HINTS, 9);
|
||||
let resolver = |reply: x::GetPropertyReply| {
|
||||
let data: &[u32] = reply.value();
|
||||
let hints = WmHints::from(data);
|
||||
debug!("wm hints: {hints:?}");
|
||||
hints
|
||||
};
|
||||
PropertyCookieWrapper {
|
||||
connection: &self.connection,
|
||||
cookie,
|
||||
resolver,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_wm_size_hints(
|
||||
&self,
|
||||
window: x::Window,
|
||||
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmNormalHints>> {
|
||||
let cookie =
|
||||
self.get_property_cookie(window, x::ATOM_WM_NORMAL_HINTS, x::ATOM_WM_SIZE_HINTS, 9);
|
||||
let resolver = |reply: x::GetPropertyReply| {
|
||||
let data: &[u32] = reply.value();
|
||||
WmNormalHints::from(data)
|
||||
};
|
||||
|
||||
PropertyCookieWrapper {
|
||||
connection: &self.connection,
|
||||
cookie,
|
||||
resolver,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_property_change(
|
||||
&self,
|
||||
event: x::PropertyNotifyEvent,
|
||||
server_state: &mut super::RealServerState,
|
||||
) {
|
||||
if event.state() != x::Property::NewValue {
|
||||
println!("ignoring non newvalue for property {:?}", event.atom());
|
||||
return;
|
||||
}
|
||||
|
||||
let window = event.window();
|
||||
let get_prop = |r#type, long_length| {
|
||||
self.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::GetProperty {
|
||||
window,
|
||||
property: event.atom(),
|
||||
r#type,
|
||||
long_offset: 0,
|
||||
long_length,
|
||||
delete: false,
|
||||
}))
|
||||
};
|
||||
|
||||
match event.atom() {
|
||||
x if x == x::ATOM_WM_HINTS => {
|
||||
let hints = self.get_wm_hints(window).resolve().unwrap();
|
||||
server_state.set_win_hints(window, hints);
|
||||
}
|
||||
x if x == x::ATOM_WM_NORMAL_HINTS => {
|
||||
let hints = self.get_wm_size_hints(window).resolve().unwrap();
|
||||
server_state.set_size_hints(window, hints);
|
||||
}
|
||||
x if x == x::ATOM_WM_NAME || x == self.atoms.net_wm_name => {
|
||||
let ty = if x == x::ATOM_WM_NAME {
|
||||
x::ATOM_STRING
|
||||
} else {
|
||||
self.atoms.utf8_string
|
||||
};
|
||||
let prop = get_prop(ty, 256).unwrap();
|
||||
let data: &[u8] = prop.value();
|
||||
let name = String::from_utf8(data.to_vec()).unwrap();
|
||||
debug!("{:?} named: {name}", window);
|
||||
let name = if x == x::ATOM_WM_NAME {
|
||||
WmName::WmName(name)
|
||||
} else {
|
||||
WmName::NetWmName(name)
|
||||
};
|
||||
server_state.set_win_title(window, name);
|
||||
}
|
||||
x if x == x::ATOM_WM_CLASS => {
|
||||
let class = self.get_wm_class(window).resolve().unwrap();
|
||||
server_state.set_win_class(window, class);
|
||||
}
|
||||
_ => {
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
let prop = self
|
||||
.connection
|
||||
.wait_for_reply(
|
||||
self.connection
|
||||
.send_request(&x::GetAtomName { atom: event.atom() }),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
debug!("changed property {:?} for {:?}", prop.name(), window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xcb::atoms_struct! {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Atoms {
|
||||
pub wl_surface_id => b"WL_SURFACE_ID" only_if_exists = false,
|
||||
pub wm_protocols => b"WM_PROTOCOLS" only_if_exists = false,
|
||||
pub wm_delete_window => b"WM_DELETE_WINDOW" only_if_exists = false,
|
||||
pub wm_transient_for => b"WM_TRANSIENT_FOR" only_if_exists = false,
|
||||
pub wm_check => b"_NET_SUPPORTING_WM_CHECK" only_if_exists = false,
|
||||
pub net_wm_name => b"_NET_WM_NAME" only_if_exists = false,
|
||||
pub wm_pid => b"_NET_WM_PID" only_if_exists = false,
|
||||
pub net_wm_state => b"_NET_WM_STATE" only_if_exists = false,
|
||||
pub wm_fullscreen => b"_NET_WM_STATE_FULLSCREEN" only_if_exists = false,
|
||||
pub active_win => b"_NET_ACTIVE_WINDOW" only_if_exists = false,
|
||||
pub client_list => b"_NET_CLIENT_LIST" only_if_exists = false,
|
||||
pub supported => b"_NET_SUPPORTED" only_if_exists = false,
|
||||
pub utf8_string => b"UTF8_STRING" only_if_exists = false,
|
||||
}
|
||||
}
|
||||
|
||||
xcb::atoms_struct! {
|
||||
pub struct WindowTypes {
|
||||
pub normal => b"_NET_WM_WINDOW_TYPE_NORMAL" only_if_exists = false,
|
||||
pub dialog => b"_NET_WM_WINDOW_TYPE_DIALOG" only_if_exists = false,
|
||||
pub splash => b"_NET_WM_WINDOW_TYPE_SPLASH" only_if_exists = false,
|
||||
pub menu => b"_NET_WM_WINDOW_TYPE_MENU" only_if_exists = false,
|
||||
pub utility => b"_NET_WM_WINDOW_TYPE_UTILITY" only_if_exists = false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct WindowDims {
|
||||
pub x: i16,
|
||||
pub y: i16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// From ICCCM spec.
|
||||
/// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.3
|
||||
pub struct WmSizeHintsFlags: u32 {
|
||||
const ProgramMinSize = 16;
|
||||
const ProgramMaxSize = 32;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.4
|
||||
pub struct WmHintsFlags: u32 {
|
||||
const Input = 1;
|
||||
const WindowGroup = 64;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct WinSize {
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub struct WmNormalHints {
|
||||
pub min_size: Option<WinSize>,
|
||||
pub max_size: Option<WinSize>,
|
||||
}
|
||||
|
||||
impl From<&[u32]> for WmNormalHints {
|
||||
fn from(value: &[u32]) -> Self {
|
||||
let mut ret = Self::default();
|
||||
let flags = WmSizeHintsFlags::from_bits_truncate(value[0]);
|
||||
|
||||
if flags.contains(WmSizeHintsFlags::ProgramMinSize) {
|
||||
ret.min_size = Some(WinSize {
|
||||
width: value[5] as _,
|
||||
height: value[6] as _,
|
||||
});
|
||||
}
|
||||
|
||||
if flags.contains(WmSizeHintsFlags::ProgramMaxSize) {
|
||||
ret.max_size = Some(WinSize {
|
||||
width: value[7] as _,
|
||||
height: value[8] as _,
|
||||
});
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub struct WmHints {
|
||||
pub input: Option<bool>,
|
||||
pub window_group: Option<x::Window>,
|
||||
}
|
||||
|
||||
impl From<&[u32]> for WmHints {
|
||||
fn from(value: &[u32]) -> Self {
|
||||
let mut ret = Self::default();
|
||||
let flags = WmHintsFlags::from_bits_truncate(value[0]);
|
||||
|
||||
if flags.contains(WmHintsFlags::WindowGroup) {
|
||||
let window = unsafe { x::Window::new(value[8]) };
|
||||
ret.window_group = Some(window);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SetState {
|
||||
Remove,
|
||||
Add,
|
||||
Toggle,
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for SetState {
|
||||
type Error = ();
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::Remove),
|
||||
1 => Ok(Self::Add),
|
||||
2 => Ok(Self::Toggle),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::XConnection for Arc<xcb::Connection> {
|
||||
type ExtraData = Atoms;
|
||||
|
||||
fn root_window(&self) -> x::Window {
|
||||
self.get_setup().roots().next().unwrap().root()
|
||||
}
|
||||
|
||||
fn set_window_dims(&mut self, window: x::Window, dims: crate::server::PendingSurfaceState) {
|
||||
trace!("reconfiguring window {window:?}");
|
||||
self.send_and_check_request(&x::ConfigureWindow {
|
||||
window,
|
||||
value_list: &[
|
||||
x::ConfigWindow::X(dims.x),
|
||||
x::ConfigWindow::Y(dims.y),
|
||||
x::ConfigWindow::Width(dims.width as _),
|
||||
x::ConfigWindow::Height(dims.height as _),
|
||||
],
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool, atoms: Self::ExtraData) {
|
||||
let data = if fullscreen {
|
||||
std::slice::from_ref(&atoms.wm_fullscreen)
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
self.send_and_check_request(&x::ChangeProperty::<x::Atom> {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
property: atoms.net_wm_state,
|
||||
r#type: x::ATOM_ATOM,
|
||||
data,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn focus_window(&mut self, window: x::Window, atoms: Self::ExtraData) {
|
||||
let prop = self
|
||||
.wait_for_reply(self.send_request(&x::GetProperty {
|
||||
delete: false,
|
||||
window,
|
||||
property: x::ATOM_WM_HINTS,
|
||||
r#type: x::ATOM_WM_HINTS,
|
||||
long_offset: 0,
|
||||
long_length: 8,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let fields: &[u32] = prop.value();
|
||||
let mut input = false;
|
||||
if !fields.is_empty() {
|
||||
let flags = fields[0];
|
||||
if (flags & 0x1) > 0 {
|
||||
input = fields[1] > 0;
|
||||
}
|
||||
}
|
||||
|
||||
if input {
|
||||
// might fail if window is not visible but who cares
|
||||
let _ = self.send_and_check_request(&x::SetInputFocus {
|
||||
focus: window,
|
||||
revert_to: x::InputFocus::None,
|
||||
time: x::CURRENT_TIME,
|
||||
});
|
||||
}
|
||||
|
||||
self.send_and_check_request(&x::ConfigureWindow {
|
||||
window,
|
||||
value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)],
|
||||
})
|
||||
.unwrap();
|
||||
self.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: self.root_window(),
|
||||
property: atoms.active_win,
|
||||
r#type: x::ATOM_WINDOW,
|
||||
data: &[window],
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn close_window(&mut self, window: x::Window, atoms: Self::ExtraData) {
|
||||
let data = [atoms.wm_delete_window.resource_id(), 0, 0, 0, 0];
|
||||
let event = &x::ClientMessageEvent::new(
|
||||
window,
|
||||
atoms.wm_protocols,
|
||||
x::ClientMessageData::Data32(data),
|
||||
);
|
||||
|
||||
self.send_and_check_request(&x::SendEvent {
|
||||
destination: x::SendEventDest::Window(window),
|
||||
propagate: false,
|
||||
event_mask: x::EventMask::empty(),
|
||||
event,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl super::FromServerState<Arc<xcb::Connection>> for Atoms {
|
||||
fn create(state: &super::RealServerState) -> Self {
|
||||
state.atoms.as_ref().unwrap().clone()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue