use core::ffi::{c_char, c_int, c_uint, c_void};
use core::ptr;
use crcbl_core::SurfaceTarget;
use super::ffi::{self, WlArgument, WlDisplay, WlMessage, WlProxy};
use super::protocol::virtual_keyboard::{zwp_virtual_keyboard_manager_v1, zwp_virtual_keyboard_v1};
use super::protocol::virtual_pointer::{zwlr_virtual_pointer_manager_v1, zwlr_virtual_pointer_v1};
use super::protocol::wayland::{
wl_data_device, wl_data_device_manager, wl_data_source, wl_pointer, wl_registry, wl_seat,
wl_shm, wl_shm_pool, wl_surface,
};
use super::{RawEvent, WaylandShell};
use crate::{PhysicalSize, Shell, ShellError, WindowId};
mod input_method;
pub use input_method::InputMethod;
unsafe extern "C" {
fn memfd_create(name: *const c_char, flags: c_uint) -> c_int;
fn ftruncate(fd: c_int, length: i64) -> c_int;
fn write(fd: c_int, buf: *const c_void, count: usize) -> isize;
fn close(fd: c_int) -> c_int;
fn shutdown(fd: c_int, how: c_int) -> c_int;
}
const SHUT_RDWR: c_int = 2;
struct ShmBinding {
registry: *mut WlProxy,
shm: *mut WlProxy,
}
unsafe extern "C" fn bind_shm(
user_data: *const c_void,
_target: *mut c_void,
opcode: u32,
_message: *const WlMessage,
args: *mut WlArgument,
) -> c_int {
let state = unsafe { &mut *user_data.cast::<ShmBinding>().cast_mut() };
let event = unsafe { wl_registry::decode_event(opcode, &*args.cast_const()) };
if let Some(wl_registry::Event::Global {
name,
interface,
version,
}) = event
&& interface == wl_shm::NAME
&& state.shm.is_null()
{
state.shm =
unsafe { wl_registry::bind(state.registry, name, &wl_shm::INTERFACE, version.min(1)) };
}
0
}
unsafe extern "C" fn ignore(
_user_data: *const c_void,
_target: *mut c_void,
_opcode: u32,
_message: *const WlMessage,
_args: *mut WlArgument,
) -> c_int {
0
}
pub fn map_window(shell: &dyn Shell, window: WindowId) -> Result<(), ShellError> {
let SurfaceTarget::Wayland {
display, surface, ..
} = shell.surface_target(window)?
else {
return Err(ShellError::Backend(
"not a Wayland window; this helper is only for the Wayland backend".to_string(),
));
};
let size = shell
.window_state(window)?
.size()
.ok_or_else(|| ShellError::Backend("map_window before the first configure".to_string()))?;
let lib = ffi::load().map_err(|detail| ShellError::Backend(detail.to_string()))?;
let display: *mut WlDisplay = display.as_ptr().cast();
let surface: *mut WlProxy = surface.as_ptr().cast();
let mut binding = Box::new(ShmBinding {
registry: ptr::null_mut(),
shm: ptr::null_mut(),
});
unsafe {
binding.registry =
super::protocol::wayland::wl_display::get_registry(ffi::display_as_proxy(display));
if binding.registry.is_null() {
return Err(ShellError::Backend("wl_display.get_registry".to_string()));
}
(lib.proxy_add_dispatcher)(
binding.registry,
bind_shm,
ptr::from_mut(binding.as_mut()).cast(),
ptr::null_mut(),
);
if (lib.display_roundtrip)(display) < 0 || (lib.display_roundtrip)(display) < 0 {
(lib.proxy_destroy)(binding.registry);
return Err(ShellError::Backend("roundtrip failed".to_string()));
}
}
let shm = binding.shm;
unsafe { (lib.proxy_destroy)(binding.registry) };
if shm.is_null() {
return Err(ShellError::Backend(
"the compositor does not advertise wl_shm".to_string(),
));
}
unsafe { (lib.proxy_add_dispatcher)(shm, ignore, ptr::null(), ptr::null_mut()) };
let result = attach_shm_buffer(lib, display, surface, size, shm);
unsafe { (lib.proxy_destroy)(shm) };
result
}
fn attach_shm_buffer(
lib: &'static ffi::Lib,
display: *mut WlDisplay,
surface: *mut WlProxy,
size: crate::PhysicalSize,
shm: *mut WlProxy,
) -> Result<(), ShellError> {
let stride = i64::from(size.width) * 4;
let length = stride * i64::from(size.height);
let fd = unsafe { memfd_create(c"crcbl-e2e".as_ptr(), 0) };
if fd < 0 {
return Err(ShellError::Backend("memfd_create failed".to_string()));
}
if unsafe { ftruncate(fd, length) } != 0 {
unsafe { close(fd) };
return Err(ShellError::Backend("ftruncate failed".to_string()));
}
#[expect(
clippy::cast_possible_truncation,
reason = "the sizes come from a compositor configure and are far below i32::MAX"
)]
unsafe {
let pool = wl_shm::create_pool(shm, fd, length as i32);
(lib.proxy_add_dispatcher)(pool, ignore, ptr::null(), ptr::null_mut());
let buffer = wl_shm_pool::create_buffer(
pool,
0,
size.width as i32,
size.height as i32,
stride as i32,
wl_shm::format::ARGB8888,
);
(lib.proxy_add_dispatcher)(buffer, ignore, ptr::null(), ptr::null_mut());
wl_surface::attach(surface, buffer, 0, 0);
wl_surface::damage(surface, 0, 0, size.width as i32, size.height as i32);
wl_surface::commit(surface);
(lib.display_flush)(display);
close(fd);
}
Ok(())
}
pub fn roundtrip(shell: &dyn Shell, window: WindowId) -> Result<(), ShellError> {
let SurfaceTarget::Wayland { display, .. } = shell.surface_target(window)? else {
return Err(ShellError::Backend(
"not a Wayland window; this helper is only for the Wayland backend".to_string(),
));
};
let lib = ffi::load().map_err(|detail| ShellError::Backend(detail.to_string()))?;
let display: *mut WlDisplay = display.as_ptr().cast();
if unsafe { (lib.display_roundtrip)(display) } < 0 {
return Err(ShellError::Backend("roundtrip failed".to_string()));
}
Ok(())
}
pub fn read_without_dispatching(
shell: &dyn Shell,
window: WindowId,
timeout_ms: c_int,
) -> Result<bool, ShellError> {
let SurfaceTarget::Wayland { display, .. } = shell.surface_target(window)? else {
return Err(ShellError::Backend(
"not a Wayland window; this helper is only for the Wayland backend".to_string(),
));
};
let lib = ffi::load().map_err(|detail| ShellError::Backend(detail.to_string()))?;
let display: *mut WlDisplay = display.as_ptr().cast();
unsafe {
if (lib.display_prepare_read)(display) != 0 {
return Ok(false);
}
let fd = (lib.display_get_fd)(display);
if !ffi::poll_readable_with(fd, &[], timeout_ms) {
(lib.display_cancel_read)(display);
return Ok(false);
}
if (lib.display_read_events)(display) < 0 {
return Err(ShellError::Backend(
"wl_display_read_events failed".to_string(),
));
}
}
Ok(true)
}
pub fn sever(shell: &dyn Shell, window: WindowId) -> Result<(), ShellError> {
let SurfaceTarget::Wayland { display, .. } = shell.surface_target(window)? else {
return Err(ShellError::Backend(
"not a Wayland window; this helper is only for the Wayland backend".to_string(),
));
};
let lib = ffi::load().map_err(|detail| ShellError::Backend(detail.to_string()))?;
let display: *mut WlDisplay = display.as_ptr().cast();
let severed = unsafe { shutdown((lib.display_get_fd)(display), SHUT_RDWR) };
if severed != 0 {
return Err(ShellError::Backend(format!(
"shutdown failed: {}",
std::io::Error::last_os_error()
)));
}
Ok(())
}
pub const MAX_PENDING_CLIPBOARD_WRITES: usize = super::fd::Writes::MAX_PENDING_WRITES;
pub const TEST_KEYMAP: &str = include_str!("e2e-keymap.xkb");
struct InputGlobals {
registry: *mut WlProxy,
seat: *mut WlProxy,
keyboard_manager: *mut WlProxy,
pointer_manager: *mut WlProxy,
}
unsafe extern "C" fn bind_input(
user_data: *const c_void,
_target: *mut c_void,
opcode: u32,
_message: *const WlMessage,
args: *mut WlArgument,
) -> c_int {
let state = unsafe { &mut *user_data.cast::<InputGlobals>().cast_mut() };
let event = unsafe { wl_registry::decode_event(opcode, &*args.cast_const()) };
let Some(wl_registry::Event::Global {
name,
interface,
version,
}) = event
else {
return 0;
};
unsafe {
if interface == wl_seat::NAME && state.seat.is_null() {
state.seat = wl_registry::bind(state.registry, name, &wl_seat::INTERFACE, 1);
} else if interface == zwp_virtual_keyboard_manager_v1::NAME
&& state.keyboard_manager.is_null()
{
state.keyboard_manager = wl_registry::bind(
state.registry,
name,
&zwp_virtual_keyboard_manager_v1::INTERFACE,
1,
);
} else if interface == zwlr_virtual_pointer_manager_v1::NAME
&& state.pointer_manager.is_null()
{
state.pointer_manager = wl_registry::bind(
state.registry,
name,
&zwlr_virtual_pointer_manager_v1::INTERFACE,
version.min(2),
);
}
}
0
}
pub struct VirtualInput {
lib: &'static ffi::Lib,
display: *mut WlDisplay,
globals: Box<InputGlobals>,
keyboard: *mut WlProxy,
pointer: *mut WlProxy,
depressed: core::cell::Cell<u32>,
locked: core::cell::Cell<u32>,
}
impl core::fmt::Debug for VirtualInput {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("VirtualInput")
.field("keyboard", &!self.keyboard.is_null())
.field("pointer", &!self.pointer.is_null())
.finish()
}
}
impl VirtualInput {
pub fn attach(shell: &dyn Shell, window: WindowId) -> Result<Self, ShellError> {
let SurfaceTarget::Wayland { display, .. } = shell.surface_target(window)? else {
return Err(ShellError::Backend(
"not a Wayland window; this helper is only for the Wayland backend".to_string(),
));
};
let lib = ffi::load().map_err(|detail| ShellError::Backend(detail.to_string()))?;
let display: *mut WlDisplay = display.as_ptr().cast();
let mut globals = Box::new(InputGlobals {
registry: ptr::null_mut(),
seat: ptr::null_mut(),
keyboard_manager: ptr::null_mut(),
pointer_manager: ptr::null_mut(),
});
unsafe {
globals.registry =
super::protocol::wayland::wl_display::get_registry(ffi::display_as_proxy(display));
if globals.registry.is_null() {
return Err(ShellError::Backend("wl_display.get_registry".to_string()));
}
(lib.proxy_add_dispatcher)(
globals.registry,
bind_input,
ptr::from_mut(globals.as_mut()).cast(),
ptr::null_mut(),
);
if (lib.display_roundtrip)(display) < 0 {
return Err(ShellError::Backend("roundtrip failed".to_string()));
}
}
if globals.seat.is_null() {
return Err(ShellError::Backend(
"the compositor has no wl_seat".to_string(),
));
}
unsafe { (lib.proxy_add_dispatcher)(globals.seat, ignore, ptr::null(), ptr::null_mut()) };
let mut input = Self {
lib,
display,
globals,
keyboard: ptr::null_mut(),
pointer: ptr::null_mut(),
depressed: core::cell::Cell::new(0),
locked: core::cell::Cell::new(0),
};
input.attach_keyboard()?;
input.attach_pointer()?;
unsafe { (lib.display_roundtrip)(display) };
Ok(input)
}
fn attach_keyboard(&mut self) -> Result<(), ShellError> {
if self.globals.keyboard_manager.is_null() {
return Err(ShellError::Backend(
"the compositor has no zwp_virtual_keyboard_manager_v1".to_string(),
));
}
let bytes = TEST_KEYMAP.as_bytes();
let size = bytes.len() + 1;
let fd = unsafe { memfd_create(c"crcbl-e2e-keymap".as_ptr(), 0) };
if fd < 0 {
return Err(ShellError::Backend("memfd_create failed".to_string()));
}
let written = unsafe {
let head = write(fd, bytes.as_ptr().cast(), bytes.len());
let nul = 0u8;
let tail = write(fd, ptr::from_ref(&nul).cast(), 1);
head.max(0) + tail.max(0)
};
if usize::try_from(written).unwrap_or(0) != size {
unsafe { close(fd) };
return Err(ShellError::Backend("short write of the keymap".to_string()));
}
unsafe {
self.keyboard = zwp_virtual_keyboard_manager_v1::create_virtual_keyboard(
self.globals.keyboard_manager,
self.globals.seat,
);
(self.lib.proxy_add_dispatcher)(self.keyboard, ignore, ptr::null(), ptr::null_mut());
zwp_virtual_keyboard_v1::keymap(
self.keyboard,
1, fd,
u32::try_from(size).unwrap_or(u32::MAX),
);
(self.lib.display_flush)(self.display);
close(fd);
}
Ok(())
}
fn attach_pointer(&mut self) -> Result<(), ShellError> {
if self.globals.pointer_manager.is_null() {
return Err(ShellError::Backend(
"the compositor has no zwlr_virtual_pointer_manager_v1".to_string(),
));
}
unsafe {
self.pointer = zwlr_virtual_pointer_manager_v1::create_virtual_pointer(
self.globals.pointer_manager,
self.globals.seat,
);
(self.lib.proxy_add_dispatcher)(self.pointer, ignore, ptr::null(), ptr::null_mut());
(self.lib.display_flush)(self.display);
}
Ok(())
}
fn now(&self) -> u32 {
#[expect(
clippy::cast_possible_truncation,
reason = "the compositor's own timestamps are 32-bit milliseconds too"
)]
let millis = (ffi::monotonic_nanos() / 1_000_000) as u32;
millis
}
fn flush(&self) {
unsafe { (self.lib.display_flush)(self.display) };
}
pub fn key(&self, evdev: u32, pressed: bool) {
let bit = match evdev {
42 | 54 => Some(1 << 0), 29 => Some(1 << 2), 56 => Some(1 << 3), _ => None,
};
if let Some(bit) = bit {
let mask = self.depressed.get();
self.depressed
.set(if pressed { mask | bit } else { mask & !bit });
} else if evdev == 58 && pressed {
self.locked.set(self.locked.get() ^ (1 << 1));
}
unsafe {
zwp_virtual_keyboard_v1::key(self.keyboard, self.now(), evdev, u32::from(pressed));
zwp_virtual_keyboard_v1::modifiers(
self.keyboard,
self.depressed.get(),
0,
self.locked.get(),
0,
);
}
self.flush();
}
pub fn tap(&self, evdev: u32) {
self.key(evdev, true);
self.key(evdev, false);
}
pub fn sync(&self) {
unsafe { (self.lib.display_roundtrip)(self.display) };
}
pub fn move_to(&self, x: u32, y: u32, extent: PhysicalSize) {
unsafe {
zwlr_virtual_pointer_v1::motion_absolute(
self.pointer,
self.now(),
x,
y,
extent.width,
extent.height,
);
zwlr_virtual_pointer_v1::frame(self.pointer);
}
self.flush();
}
pub fn move_by(&self, dx: f64, dy: f64) {
unsafe {
zwlr_virtual_pointer_v1::motion(self.pointer, self.now(), to_fixed(dx), to_fixed(dy));
zwlr_virtual_pointer_v1::frame(self.pointer);
}
self.flush();
}
pub fn button(&self, evdev: u32, pressed: bool) {
unsafe {
zwlr_virtual_pointer_v1::button(self.pointer, self.now(), evdev, u32::from(pressed));
zwlr_virtual_pointer_v1::frame(self.pointer);
}
self.flush();
}
pub fn touchpad_scroll(&self, pixels: f64) {
let time = self.now();
unsafe {
zwlr_virtual_pointer_v1::axis_source(self.pointer, 1);
zwlr_virtual_pointer_v1::axis(self.pointer, time, 0, to_fixed(pixels));
zwlr_virtual_pointer_v1::frame(self.pointer);
}
self.flush();
}
pub fn wheel(&self, detents: i32) {
let time = self.now();
let value = to_fixed(f64::from(detents) * 15.0);
unsafe {
zwlr_virtual_pointer_v1::axis_source(self.pointer, 0);
zwlr_virtual_pointer_v1::axis_discrete(self.pointer, time, 0, value, detents);
zwlr_virtual_pointer_v1::frame(self.pointer);
}
self.flush();
}
}
impl Drop for VirtualInput {
fn drop(&mut self) {
unsafe {
if !self.pointer.is_null() {
zwlr_virtual_pointer_v1::destroy(self.pointer);
}
if !self.keyboard.is_null() {
zwp_virtual_keyboard_v1::destroy(self.keyboard);
}
if !self.globals.seat.is_null() {
(self.lib.proxy_destroy)(self.globals.seat);
}
if !self.globals.keyboard_manager.is_null() {
(self.lib.proxy_destroy)(self.globals.keyboard_manager);
}
if !self.globals.pointer_manager.is_null() {
zwlr_virtual_pointer_manager_v1::destroy(self.globals.pointer_manager);
}
(self.lib.proxy_destroy)(self.globals.registry);
(self.lib.display_flush)(self.display);
}
}
}
pub struct DragSource {
lib: &'static ffi::Lib,
display: *mut WlDisplay,
state: Box<DragState>,
}
struct DragState {
registry: *mut WlProxy,
seat: *mut WlProxy,
manager: *mut WlProxy,
device: *mut WlProxy,
pointer: *mut WlProxy,
source: *mut WlProxy,
press_serial: core::cell::Cell<u32>,
payload: core::cell::RefCell<Vec<u8>>,
sent: core::cell::Cell<bool>,
cancelled: core::cell::Cell<bool>,
}
impl core::fmt::Debug for DragSource {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DragSource")
.field("press_serial", &self.state.press_serial.get())
.field("sent", &self.state.sent.get())
.finish()
}
}
unsafe extern "C" fn bind_drag(
user_data: *const c_void,
_target: *mut c_void,
opcode: u32,
_message: *const WlMessage,
args: *mut WlArgument,
) -> c_int {
let state = unsafe { &mut *user_data.cast::<DragState>().cast_mut() };
let event = unsafe { wl_registry::decode_event(opcode, &*args.cast_const()) };
let Some(wl_registry::Event::Global {
name,
interface,
version,
}) = event
else {
return 0;
};
unsafe {
if interface == wl_seat::NAME && state.seat.is_null() {
state.seat = wl_registry::bind(state.registry, name, &wl_seat::INTERFACE, 1);
} else if interface == wl_data_device_manager::NAME && state.manager.is_null() {
state.manager = wl_registry::bind(
state.registry,
name,
&wl_data_device_manager::INTERFACE,
version.min(3),
);
}
}
0
}
unsafe extern "C" fn drag_dispatch(
user_data: *const c_void,
target: *mut c_void,
opcode: u32,
_message: *const WlMessage,
args: *mut WlArgument,
) -> c_int {
let state = unsafe { &mut *user_data.cast::<DragState>().cast_mut() };
let args = unsafe { &*args.cast_const() };
if target == state.pointer.cast() {
if let Some(wl_pointer::Event::Button {
serial,
state: down,
..
}) = unsafe { wl_pointer::decode_event(opcode, args) }
&& down == wl_pointer::button_state::PRESSED
{
state.press_serial.set(serial);
}
return 0;
}
if target == state.source.cast() {
match unsafe { wl_data_source::decode_event(opcode, args) } {
Some(wl_data_source::Event::Send { fd, .. }) => {
let payload = state.payload.borrow();
unsafe {
write(fd, payload.as_ptr().cast(), payload.len());
close(fd);
}
state.sent.set(true);
}
Some(wl_data_source::Event::Cancelled) => state.cancelled.set(true),
_ => {}
}
}
0
}
impl DragSource {
pub fn attach(shell: &dyn Shell, window: WindowId) -> Result<Self, ShellError> {
let SurfaceTarget::Wayland { display, .. } = shell.surface_target(window)? else {
return Err(ShellError::Backend(
"not a Wayland window; this helper is only for the Wayland backend".to_string(),
));
};
let lib = ffi::load().map_err(|detail| ShellError::Backend(detail.to_string()))?;
let display: *mut WlDisplay = display.as_ptr().cast();
let mut state = Box::new(DragState {
registry: ptr::null_mut(),
seat: ptr::null_mut(),
manager: ptr::null_mut(),
device: ptr::null_mut(),
pointer: ptr::null_mut(),
source: ptr::null_mut(),
press_serial: core::cell::Cell::new(0),
payload: core::cell::RefCell::new(Vec::new()),
sent: core::cell::Cell::new(false),
cancelled: core::cell::Cell::new(false),
});
unsafe {
state.registry =
super::protocol::wayland::wl_display::get_registry(ffi::display_as_proxy(display));
if state.registry.is_null() {
return Err(ShellError::Backend("wl_display.get_registry".to_string()));
}
(lib.proxy_add_dispatcher)(
state.registry,
bind_drag,
ptr::from_mut(state.as_mut()).cast(),
ptr::null_mut(),
);
if (lib.display_roundtrip)(display) < 0 {
return Err(ShellError::Backend("roundtrip failed".to_string()));
}
}
if state.seat.is_null() || state.manager.is_null() {
return Err(ShellError::Backend(
"the compositor has no wl_seat or no wl_data_device_manager".to_string(),
));
}
unsafe {
(lib.proxy_add_dispatcher)(state.seat, ignore, ptr::null(), ptr::null_mut());
state.pointer = wl_seat::get_pointer(state.seat);
state.device = wl_data_device_manager::get_data_device(state.manager, state.seat);
let data = ptr::from_mut(state.as_mut()).cast::<c_void>();
(lib.proxy_add_dispatcher)(state.pointer, drag_dispatch, data, ptr::null_mut());
(lib.proxy_add_dispatcher)(state.device, ignore, ptr::null(), ptr::null_mut());
(lib.display_roundtrip)(display);
}
Ok(Self {
lib,
display,
state,
})
}
#[must_use]
pub fn press_serial(&self) -> u32 {
self.state.press_serial.get()
}
#[must_use]
pub fn was_asked_for_data(&self) -> bool {
self.state.sent.get()
}
#[must_use]
pub fn was_cancelled(&self) -> bool {
self.state.cancelled.get()
}
pub fn start(
&mut self,
shell: &dyn Shell,
window: WindowId,
mime: &core::ffi::CStr,
payload: &[u8],
) -> Result<(), ShellError> {
let SurfaceTarget::Wayland { surface, .. } = shell.surface_target(window)? else {
return Err(ShellError::Backend("not a Wayland window".to_string()));
};
let serial = self.state.press_serial.get();
if serial == 0 {
return Err(ShellError::Backend(
"no pointer button has been pressed, so there is no implicit grab to drag from"
.to_string(),
));
}
self.state.payload.replace(payload.to_vec());
let origin: *mut WlProxy = surface.as_ptr().cast();
unsafe {
self.state.source = wl_data_device_manager::create_data_source(self.state.manager);
if self.state.source.is_null() {
return Err(ShellError::Backend("create_data_source failed".to_string()));
}
let data = ptr::from_mut(self.state.as_mut()).cast::<c_void>();
(self.lib.proxy_add_dispatcher)(
self.state.source,
drag_dispatch,
data,
ptr::null_mut(),
);
wl_data_source::offer(self.state.source, mime);
if ffi::proxy_version(self.state.source) >= 3 {
wl_data_source::set_actions(self.state.source, 1 );
}
wl_data_device::start_drag(
self.state.device,
self.state.source,
origin,
ptr::null_mut(),
serial,
);
(self.lib.display_flush)(self.display);
}
Ok(())
}
}
impl Drop for DragSource {
fn drop(&mut self) {
unsafe {
if !self.state.source.is_null() {
wl_data_source::destroy(self.state.source);
}
if !self.state.device.is_null() {
if ffi::proxy_version(self.state.device) >= 2 {
wl_data_device::release(self.state.device);
} else {
(self.lib.proxy_destroy)(self.state.device);
}
}
if !self.state.pointer.is_null() {
(self.lib.proxy_destroy)(self.state.pointer);
}
if !self.state.manager.is_null() {
(self.lib.proxy_destroy)(self.state.manager);
}
if !self.state.seat.is_null() {
(self.lib.proxy_destroy)(self.state.seat);
}
(self.lib.proxy_destroy)(self.state.registry);
(self.lib.display_flush)(self.display);
}
}
}
pub struct GlobalHotplug {
shell: WaylandShell,
}
impl core::fmt::Debug for GlobalHotplug {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("GlobalHotplug")
.field("shell", &self.shell)
.finish()
}
}
impl GlobalHotplug {
pub fn open() -> Result<Self, ShellError> {
Ok(Self {
shell: WaylandShell::open()?,
})
}
pub fn shell(&mut self) -> &mut dyn Shell {
&mut self.shell
}
pub fn withdraw(&mut self, interface: &str) -> Option<(u32, u32)> {
let (name, slot) = self
.shell
.singletons
.iter()
.copied()
.find(|(_, slot)| slot.interface().to_bytes() == interface.as_bytes())?;
let proxy = *slot.slot(&mut self.shell.conn);
let version = unsafe { ffi::proxy_version(proxy) };
self.shell
.conn
.sink()
.events
.push(RawEvent::GlobalRemove { name });
Some((name, version))
}
pub fn readvertise(&mut self, name: u32, interface: &str, version: u32) {
self.shell.conn.sink().events.push(RawEvent::Global {
name,
interface: interface.to_string(),
version,
});
}
}
#[expect(
clippy::cast_possible_truncation,
reason = "test-only input amounts are single-digit pixels"
)]
fn to_fixed(value: f64) -> i32 {
(value * 256.0).round() as i32
}