From ce728a0736617c69d1bf2dbdda74809e3583b88d Mon Sep 17 00:00:00 2001 From: jkds Date: Thu, 9 Jan 2025 18:57:32 +0100 Subject: [PATCH 01/16] wayland: partial impl of clipboard logic. Setting the clipboard works, but there's a segfault when the text is replaced (cancelled). Reading the clipboard data just hangs so it's commented out for now. --- src/native/linux_wayland.rs | 319 +++++++++++++++++- src/native/linux_wayland/libwayland_client.rs | 46 +++ 2 files changed, 358 insertions(+), 7 deletions(-) diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index 57289be8..1a17fc59 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -20,6 +20,19 @@ use crate::{ use std::collections::HashSet; +const WL_DATA_OFFER_RECEIVE: u32 = 1; +const WL_DATA_OFFER_DESTROY: u32 = 2; + +const WL_DATA_SOURCE_OFFER: u32 = 0; +const WL_DATA_SOURCE_DESTROY: u32 = 1; + +const WL_MARSHAL_FLAG_DESTROY: u32 = (1 << 0); + +const WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE: u32 = 0; +const WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE: u32 = 1; + +const WL_DATA_DEVICE_SET_SELECTION: u32 = 1; + fn wl_fixed_to_double(f: i32) -> f32 { (f as f32) / 256.0 } @@ -40,6 +53,8 @@ struct WaylandPayload { viewporter: *mut extensions::viewporter::wp_viewporter, shm: *mut wl_shm, seat: *mut wl_seat, + data_device_manager: *mut wl_data_device_manager, + data_device: *mut wl_data_device, xkb_context: *mut xkb_context, keymap: *mut xkb_keymap, xkb_state: *mut xkb_state, @@ -48,6 +63,7 @@ struct WaylandPayload { pointer: *mut wl_pointer, keyboard: *mut wl_keyboard, focused_window: *mut wl_surface, + keyb_serial: u32, //xkb_state: xkb::XkbState, decorations: Option, @@ -91,6 +107,24 @@ macro_rules! wl_request { }}; } +#[macro_export] +macro_rules! wl_request_flags { + ($libwayland:expr, $instance:expr, $request_name:expr, $interface:expr, $version:expr, $flags:expr) => { + wl_request_flags!($libwayland, $instance, $request_name, $interface, $version, $flags, ()) + }; + + ($libwayland:expr, $instance:expr, $request_name:expr, $interface:expr, $version:expr, $flags:expr, $($arg:expr),*) => {{ + ($libwayland.wl_proxy_marshal_flags)( + $instance as _, + $request_name, + $interface as *const _, + $version, + $flags, + $($arg,)* + ) + }}; +} + static mut SEAT_LISTENER: wl_seat_listener = wl_seat_listener { capabilities: Some(seat_handle_capabilities), name: Some(seat_handle_name), @@ -179,14 +213,15 @@ unsafe extern "C" fn keyboard_handle_keymap( display.xkb_state = (display.xkb.xkb_state_new)(display.keymap); } unsafe extern "C" fn keyboard_handle_enter( - _data: *mut ::core::ffi::c_void, + data: *mut ::core::ffi::c_void, _wl_keyboard: *mut wl_keyboard, - _serial: u32, + serial: u32, _surface: *mut wl_surface, _keys: *mut wl_array, ) { - // We can capture held keys when window is refocused. - // Ignore this for now. + let display: &mut WaylandPayload = &mut *(data as *mut _); + // Needed for setting the clipboard + display.keyb_serial = serial; } unsafe extern "C" fn keyboard_handle_leave( data: *mut ::core::ffi::c_void, @@ -436,6 +471,15 @@ unsafe extern "C" fn registry_add_object( data, ); } + "wl_data_device_manager" => { + display.data_device_manager = display.client.wl_registry_bind( + registry, + name, + display.client.wl_data_device_manager_interface, + 3, + ) as _; + assert!(!display.data_device_manager.is_null()); + } _ => {} } @@ -528,14 +572,242 @@ unsafe extern "C" fn xdg_wm_base_handle_ping( ); } -struct WaylandClipboard; +static mut DATA_DEVICE_LISTENER: wl_data_device_listener = wl_data_device_listener { + data_offer: Some(data_device_handle_data_offer), + enter: None, + leave: None, + motion: None, + drop: None, + selection: Some(data_device_handle_selection), +}; + +static mut DATA_OFFER_LISTENER: wl_data_offer_listener = wl_data_offer_listener { + offer: Some(data_offer_handle_offer), + source_actions: None, + action: None, +}; + +unsafe extern "C" fn data_offer_handle_offer( + _data: *mut ::core::ffi::c_void, + _offer: *mut wl_data_offer, + mime_type: *const ::core::ffi::c_char, +) { + let mime_type = std::ffi::CStr::from_ptr(mime_type).to_str().unwrap(); + println!("Clipboard receive mime: {mime_type}"); +} + +unsafe extern "C" fn data_device_handle_data_offer( + data: *mut ::core::ffi::c_void, + _data_device: *mut wl_data_device, + offer: *mut wl_data_offer, +) { + println!("data_device_handle_data_offer()"); + //let display: &mut WaylandPayload = &mut *(data as *mut _); + //(display.client.wl_proxy_add_listener)(offer as _, &DATA_OFFER_LISTENER as *const _ as _, data); +} + +unsafe extern "C" fn data_device_handle_selection( + data: *mut ::core::ffi::c_void, + data_device: *mut wl_data_device, + offer: *mut wl_data_offer, +) { + // An application has set the clipboard contents + + if offer.is_null() { + // Clipboard is empty + println!("Clipboard is empty"); + return; + } + + let display: &mut WaylandPayload = &mut *(data as *mut _); + + println!("Clipboard is set"); + /* + let mut fds: [i32; 2] = [0, 0]; + libc::pipe(fds.as_mut_ptr()); + libc::close(fds[1]); + + // wl_data_offer_receive(offer, "text/plain", fds[1]); + let mime = std::ffi::CString::new("text/plain;charset=utf-8").unwrap(); + let version = display.client.wl_proxy_get_version(offer); + wl_request_flags!( + display.client, + offer, + WL_DATA_OFFER_RECEIVE, + std::ptr::null::(), + version, + 0, + mime, + fds[1] + ); + + // To read in this thread, we would need: wl_display_roundtrip(display); + + let fd = fds[0]; + std::thread::spawn(move || { + let mut content = String::new(); + let mut buf = [0u8; 1024]; + loop { + let n = libc::read(fd, buf.as_mut_ptr() as *mut std::ffi::c_void, buf.len()); + if n <= 0 { + break; + } + assert!(n <= 1024); + content.push_str(std::str::from_utf8(&buf[..n as usize]).unwrap()); + } + libc::close(fd); + + /* + // wl_data_offer_destroy(offer); + wl_request_flags!( + display.client, + offer, + WL_DATA_OFFER_DESTROY, + std::ptr::null::(), + version, + WL_MARSHAL_FLAG_DESTROY + ); + */ + }); + */ +} + +static mut DATA_SOURCE_LISTENER: wl_data_source_listener = wl_data_source_listener { + target: None, + send: Some(data_source_handle_send), + cancelled: Some(data_source_handle_cancelled), + dnd_drop_performed: None, + dnd_finished: None, + action: None, +}; + +unsafe extern "C" fn data_source_handle_send( + data: *mut std::ffi::c_void, + source: *mut wl_data_source, + mime_type: *const std::ffi::c_char, + fd: i32, +) { + let text: &String = &*(data as *mut _); + + let mime_type = std::ffi::CStr::from_ptr(mime_type).to_str().unwrap(); + match mime_type { + "text/plain" => { + libc::write(fd, text.as_bytes().as_ptr() as *const _, text.len()); + } + _ => {} + } + libc::close(fd); +} + +unsafe extern "C" fn data_source_handle_cancelled( + data: *mut std::ffi::c_void, + source: *mut wl_data_source, +) { + println!("data_source_handle_cancelled()"); + let display: &mut WaylandPayload = &mut *(data as *mut _); + + //assert!(!source.is_null()); + //let proxy: *mut wl_proxy = source as *mut _; + //let version = (display.client.wl_proxy_get_version)(proxy); + let version = display.client.wl_proxy_get_version(source); + + let id = (display.client.wl_proxy_marshal_flags)( + source as _, + WL_DATA_SOURCE_DESTROY, + std::ptr::null::() as *const _, + 3, + WL_MARSHAL_FLAG_DESTROY, + ); + assert!(!id.is_null()); +} + +struct WaylandClipboard { + display: *mut WaylandPayload, + text: String, +} + +impl WaylandClipboard { + fn new(display: *mut WaylandPayload) -> Self { + Self { + display, + text: String::new(), + } + } +} + impl crate::native::Clipboard for WaylandClipboard { fn get(&mut self) -> Option { None } - fn set(&mut self, _data: &str) {} + fn set(&mut self, data: &str) { + println!("wayland set clip {data}"); + self.text.clear(); + self.text.push_str(data); + //self.text = data.to_string(); + + unsafe { + let display: &mut WaylandPayload = &mut *self.display; + + //let proxy: *mut wl_proxy = display.data_device_manager as _; + //assert!(!proxy.is_null()); + //let version = (display.client.wl_proxy_get_version)(proxy); + let version = display + .client + .wl_proxy_get_version(display.data_device_manager); + + // wl_data_device_manager_create_data_source + let source = wl_request_flags!( + display.client, + display.data_device_manager, + WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE, + display.client.wl_data_source_interface, + version, + 0, + std::ptr::null_mut::() + ); + assert!(!source.is_null()); + + // Setup a listener to receive wl_data_source events. + // wl_data_source_add_listener + (display.client.wl_proxy_add_listener)( + source, + &DATA_SOURCE_LISTENER as *const _ as _, + &self.text as *const _ as _, + ); + + // Advertise a few MIME types + // wl_data_source_offer(source, "text/plain"); + let mime = std::ffi::CString::new("text/plain;charset=utf-8").unwrap(); + let version = display.client.wl_proxy_get_version(source); + wl_request_flags!( + display.client, + source, + WL_DATA_SOURCE_OFFER, + std::ptr::null::(), + version, + 0, + mime.as_ptr() + ); + + // wl_data_device_set_selection + let version = display.client.wl_proxy_get_version(display.data_device); + wl_request_flags!( + display.client, + display.data_device, + WL_DATA_DEVICE_SET_SELECTION, + std::ptr::null::(), + version, + 0, + source, + display.keyb_serial + ); + } + } } +unsafe impl Send for WaylandClipboard {} +unsafe impl Sync for WaylandClipboard {} + pub fn run(conf: &crate::conf::Conf, f: &mut Option) -> Option<()> where F: 'static + FnOnce() -> Box, @@ -579,6 +851,8 @@ where viewporter: std::ptr::null_mut(), shm: std::ptr::null_mut(), seat: std::ptr::null_mut(), + data_device_manager: std::ptr::null_mut(), + data_device: std::ptr::null_mut(), xkb_context, keymap: std::ptr::null_mut(), xkb_state: std::ptr::null_mut(), @@ -586,13 +860,14 @@ where pointer: std::ptr::null_mut(), keyboard: std::ptr::null_mut(), focused_window: std::ptr::null_mut(), + keyb_serial: 0, decorations: None, event_handler: None, closed: false, }; let (tx, rx) = std::sync::mpsc::channel(); - let clipboard = Box::new(WaylandClipboard); + let clipboard = Box::new(WaylandClipboard::new(&mut display as *mut _)); crate::set_display(NativeDisplayData { ..NativeDisplayData::new(conf.window_width, conf.window_height, tx, clipboard) }); @@ -604,6 +879,36 @@ where ); (display.client.wl_display_roundtrip)(wdisplay); + assert!(!display.data_device_manager.is_null()); + assert!(!display.seat.is_null()); + display.data_device = wl_request_flags!( + display.client, + display.data_device_manager, + WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, + display.client.wl_data_device_interface, + display + .client + .wl_proxy_get_version(display.data_device_manager), + 0, + std::ptr::null::(), + display.seat + ) as _; + //display.data_device = wl_request_constructor!( + // display.client, + // display.data_device_manager, + // 1, + // display.client.wl_data_device_interface, + // display.seat + //) as _; + assert!(!display.data_device.is_null()); + + // wl_data_device_add_listener(data_device, &data_device_listener, NULL); + (display.client.wl_proxy_add_listener)( + display.data_device as _, + &DATA_DEVICE_LISTENER as *const _ as _, + &mut display as *mut _ as _, + ); + assert!(!display.compositor.is_null()); assert!(!display.xdg_wm_base.is_null()); assert!(!display.subcompositor.is_null()); diff --git a/src/native/linux_wayland/libwayland_client.rs b/src/native/linux_wayland/libwayland_client.rs index 601c53c7..7a8a91fc 100644 --- a/src/native/linux_wayland/libwayland_client.rs +++ b/src/native/linux_wayland/libwayland_client.rs @@ -68,6 +68,7 @@ pub const WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE: u32 = 0; pub const WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE: u32 = 1; pub const WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE_SINCE_VERSION: u32 = 1; pub const WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE_SINCE_VERSION: u32 = 1; +pub const WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: u32 = 1; pub const WL_SHELL_GET_SHELL_SURFACE: u32 = 0; pub const WL_SHELL_GET_SHELL_SURFACE_SINCE_VERSION: u32 = 1; pub const WL_SHELL_SURFACE_PONG: u32 = 0; @@ -382,6 +383,7 @@ pub type wl_pointer_button_state = c_uint; pub type wl_pointer_axis = c_uint; pub type wl_pointer_axis_source = c_uint; pub type wl_pointer_axis_relative_direction = c_uint; +pub type wl_data_device_manager_dnd_action = c_uint; wl_listener!( wl_registry_listener, @@ -449,6 +451,42 @@ wl_listener!( ), ); +wl_listener!( + wl_data_device_listener, + wl_data_device, + fn data_offer(id: *mut wl_data_offer), + fn enter( + serial: c_uint, + surface: *mut wl_surface, + x: wl_fixed_t, + y: wl_fixed_t, + id: *mut wl_data_offer, + ), + fn leave(), + fn motion(time: c_uint, x: wl_fixed_t, y: wl_fixed_t), + fn drop(), + fn selection(id: *mut wl_data_offer), +); + +wl_listener!( + wl_data_offer_listener, + wl_data_offer, + fn offer(mime_type: *const c_char), + fn source_actions(source_actions: wl_data_device_manager_dnd_action), + fn action(dnd_action: wl_data_device_manager_dnd_action), +); + +wl_listener!( + wl_data_source_listener, + wl_data_source, + fn target(mime_type: *const c_char), + fn send(mime_type: *const c_char, fd: c_int), + fn cancelled(), + fn dnd_drop_performed(), + fn dnd_finished(), + fn action(dnd_action: wl_data_device_manager_dnd_action), +); + crate::declare_module!( LibWaylandClient, "libwayland-client.so", @@ -491,10 +529,12 @@ crate::declare_module!( pub fn wl_proxy_get_queue(*mut wl_proxy) -> *mut wl_event_queue, pub fn wl_proxy_add_listener(*mut wl_proxy, *mut Option, *mut c_void) -> c_int, pub fn wl_proxy_destroy(*mut wl_proxy), + pub fn wl_proxy_get_version(*mut wl_proxy) -> c_uint, ... pub fn wl_proxy_marshal(*mut wl_proxy, c_uint, ...), pub fn wl_proxy_marshal_constructor(*mut wl_proxy, c_uint, *const wl_interface, ...) -> *mut wl_proxy, pub fn wl_proxy_marshal_constructor_versioned(*mut wl_proxy, c_uint, *const wl_interface, c_uint, ...) -> *mut wl_proxy, + pub fn wl_proxy_marshal_flags(*mut wl_proxy, c_uint, *const wl_interface, c_uint, c_uint, ...) -> *mut wl_proxy, ... ); @@ -518,4 +558,10 @@ impl LibWaylandClient { ); id as *mut _ } + + pub unsafe fn wl_proxy_get_version(&self, proxy: *mut T) -> u32 { + let proxy: *mut wl_proxy = proxy as _; + assert!(!proxy.is_null()); + (self.wl_proxy_get_version)(proxy) + } } From 163868e4fe16aaf7ce2e9abe26b7ff700ce10b53 Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Wed, 26 Feb 2025 18:48:21 +0000 Subject: [PATCH 02/16] native/linux_wayland: Refactor - Implement Wayland key repeat - Implement Wayland clipboard and drag_n_drop - Rename `Platform::linux_x11_wm_class` to `linux_wm_class` for both X11 and Wayland usage - Use `eglSwapInterval` --- src/conf.rs | 11 +- src/native/linux_wayland.rs | 720 +++++++++--------- src/native/linux_wayland/clipboard.rs | 172 +++++ src/native/linux_wayland/drag_n_drop.rs | 87 +++ src/native/linux_wayland/keycodes.rs | 1 + src/native/linux_wayland/libwayland_client.rs | 28 +- src/native/linux_x11.rs | 4 + src/native/linux_x11/libx11_ex.rs | 2 +- 8 files changed, 647 insertions(+), 378 deletions(-) create mode 100644 src/native/linux_wayland/clipboard.rs create mode 100644 src/native/linux_wayland/drag_n_drop.rs diff --git a/src/conf.rs b/src/conf.rs index 4ee6d03d..70cdfa65 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -146,11 +146,12 @@ pub struct Platform { /// When using Wayland, this controls whether to draw the default window decorations. pub wayland_use_fallback_decorations: bool, - /// Set the `WM_CLASS` window property on X11 + /// Set the `WM_CLASS` window property on X11 and the `app_id` on Wayland. This is used + /// by gnome to determine the window icon (together with an external `.desktop` file). // in fact `WM_CLASS` contains two strings "instance name" and "class name" // for most purposes they are the same so we just use class name for simplicity // https://unix.stackexchange.com/questions/494169/ - pub linux_x11_wm_class: &'static str, + pub linux_wm_class: &'static str, } impl Default for Platform { @@ -164,7 +165,7 @@ impl Default for Platform { swap_interval: None, framebuffer_alpha: false, wayland_use_fallback_decorations: true, - linux_x11_wm_class: "miniquad-application", + linux_wm_class: "miniquad-application", } } } @@ -203,8 +204,8 @@ pub struct Conf { /// - On macOS, Dock/title bar icon /// - TODO: Favicon on HTML5 /// - TODO: Taskbar/title bar icon on Linux (depends on WM) - /// - Note: on gnome (with X11), icon is determined using `WM_CLASS` (can be set under - /// `Platform`) and an external `.desktop` file + /// - Note: on gnome, icon is determined using `WM_CLASS` (can be set under [`Platform`]) and + /// an external `.desktop` file pub icon: Option, /// Platform-specific hints (e.g., context creation, driver settings). diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index 1a17fc59..478da4fd 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -4,7 +4,9 @@ mod libwayland_client; mod libwayland_egl; mod libxkbcommon; +mod clipboard; mod decorations; +mod drag_n_drop; mod extensions; mod keycodes; mod shm; @@ -18,20 +20,7 @@ use crate::{ native::{egl, NativeDisplayData, Request}, }; -use std::collections::HashSet; - -const WL_DATA_OFFER_RECEIVE: u32 = 1; -const WL_DATA_OFFER_DESTROY: u32 = 2; - -const WL_DATA_SOURCE_OFFER: u32 = 0; -const WL_DATA_SOURCE_DESTROY: u32 = 1; - -const WL_MARSHAL_FLAG_DESTROY: u32 = (1 << 0); - -const WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE: u32 = 0; -const WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE: u32 = 1; - -const WL_DATA_DEVICE_SET_SELECTION: u32 = 1; +use core::time::Duration; fn wl_fixed_to_double(f: i32) -> f32 { (f as f32) / 256.0 @@ -40,6 +29,8 @@ fn wl_fixed_to_double(f: i32) -> f32 { /// A thing to pass around within *void pointer of wayland's event handler struct WaylandPayload { client: LibWaylandClient, + display: *mut wl_display, + registry: *mut wl_registry, // this is libwayland-egl.so, a library with ~4 functions // not the libEGL.so(which will be loaded, but not here) egl: LibWaylandEgl, @@ -48,6 +39,7 @@ struct WaylandPayload { subcompositor: *mut wl_subcompositor, xdg_toplevel: *mut extensions::xdg_shell::xdg_toplevel, xdg_wm_base: *mut extensions::xdg_shell::xdg_wm_base, + xdg_surface: *mut extensions::xdg_shell::xdg_surface, surface: *mut wl_surface, decoration_manager: *mut extensions::xdg_decoration::zxdg_decoration_manager_v1, viewporter: *mut extensions::viewporter::wp_viewporter, @@ -63,14 +55,163 @@ struct WaylandPayload { pointer: *mut wl_pointer, keyboard: *mut wl_keyboard, focused_window: *mut wl_surface, - keyb_serial: u32, //xkb_state: xkb::XkbState, decorations: Option, + keyboard_context: KeyboardContext, + drag_n_drop: drag_n_drop::WaylandDnD, + update_requested: bool, event_handler: Option>, closed: bool, } +impl WaylandPayload { + /// block until a new event is available + // needs to combine both the Wayland events and the key repeat events + // the implementation is translated from glfw + unsafe fn block_on_new_event(&mut self) { + let mut fds = [ + libc::pollfd { + fd: (self.client.wl_display_get_fd)(self.display), + events: libc::POLLIN, + revents: 0, + }, + libc::pollfd { + fd: self.keyboard_context.timerfd, + events: libc::POLLIN, + revents: 0, + }, + ]; + (self.client.wl_display_flush)(self.display); + while (self.client.wl_display_prepare_read)(self.display) != 0 { + (self.client.wl_display_dispatch_pending)(self.display); + } + if !self.update_requested && libc::poll(fds.as_mut_ptr(), 2, i32::MAX) > 0 { + // if the Wayland display has events available + if fds[0].revents & libc::POLLIN == 1 { + (self.client.wl_display_read_events)(self.display); + (self.client.wl_display_dispatch_pending)(self.display); + } else { + (self.client.wl_display_cancel_read)(self.display); + } + // if key repeat takes place + if fds[1].revents & libc::POLLIN == 1 { + let mut count: [libc::size_t; 1] = [0]; + let n_bits = core::mem::size_of::(); + assert_eq!( + libc::read( + self.keyboard_context.timerfd, + count.as_mut_ptr() as _, + n_bits + ), + n_bits as _ + ); + if let Some(key) = self.keyboard_context.repeated_key { + for _ in 0..count[0] { + EVENTS.push(WaylandEvent::KeyboardKey { + key, + state: WaylandKeyState::Repeat, + }); + } + } + } + } else { + (self.client.wl_display_cancel_read)(self.display); + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum WaylandKeyState { + Released = 0, + Pressed = 1, + Repeat = 2, +} + +impl From for bool { + fn from(value: WaylandKeyState) -> bool { + match value { + WaylandKeyState::Released => false, + WaylandKeyState::Pressed | WaylandKeyState::Repeat => true, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum RepeatInfo { + Repeat { delay: Duration, gap: Duration }, + NoRepeat, +} + +impl Default for RepeatInfo { + // default value copied from winit + fn default() -> Self { + Self::Repeat { + delay: Duration::from_millis(200), + gap: Duration::from_millis(40), + } + } +} + +// key repeat in Wayland needs to be handled by the client +// `KeyboardContext` is mostly for tracking the currently repeated key +// Note that apparently `timerfd` is not unix compliant and only available on linux +struct KeyboardContext { + enter_serial: Option, + repeat_info: RepeatInfo, + repeated_key: Option, + timerfd: core::ffi::c_int, +} + +fn new_itimerspec() -> libc::itimerspec { + libc::itimerspec { + it_interval: libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }, + it_value: libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }, + } +} + +impl KeyboardContext { + fn new() -> Self { + Self { + enter_serial: None, + repeat_info: Default::default(), + repeated_key: None, + timerfd: unsafe { libc::timerfd_create(libc::CLOCK_MONOTONIC, libc::TFD_CLOEXEC) }, + } + } + fn key_down(&mut self, key: core::ffi::c_uint) { + let mut timer = new_itimerspec(); + match self.repeat_info { + RepeatInfo::Repeat { delay, gap } => { + self.repeated_key = Some(key); + timer.it_interval.tv_sec = gap.as_secs() as _; + timer.it_interval.tv_nsec = gap.subsec_nanos() as _; + timer.it_value.tv_sec = delay.as_secs() as _; + timer.it_value.tv_nsec = delay.subsec_nanos() as _; + } + RepeatInfo::NoRepeat => self.repeated_key = None, + } + unsafe { + libc::timerfd_settime(self.timerfd, 0, &timer, core::ptr::null_mut()); + } + } + fn key_up(&mut self, key: core::ffi::c_uint) { + if self.repeated_key == Some(key) { + self.repeated_key = None; + unsafe { + libc::timerfd_settime(self.timerfd, 0, &new_itimerspec(), core::ptr::null_mut()); + } + } + } +} + #[macro_export] macro_rules! wl_request_constructor { ($libwayland:expr, $instance:expr, $request_name:expr, $interface:expr) => { @@ -107,24 +248,6 @@ macro_rules! wl_request { }}; } -#[macro_export] -macro_rules! wl_request_flags { - ($libwayland:expr, $instance:expr, $request_name:expr, $interface:expr, $version:expr, $flags:expr) => { - wl_request_flags!($libwayland, $instance, $request_name, $interface, $version, $flags, ()) - }; - - ($libwayland:expr, $instance:expr, $request_name:expr, $interface:expr, $version:expr, $flags:expr, $($arg:expr),*) => {{ - ($libwayland.wl_proxy_marshal_flags)( - $instance as _, - $request_name, - $interface as *const _, - $version, - $flags, - $($arg,)* - ) - }}; -} - static mut SEAT_LISTENER: wl_seat_listener = wl_seat_listener { capabilities: Some(seat_handle_capabilities), name: Some(seat_handle_name), @@ -138,38 +261,46 @@ unsafe extern "C" fn seat_handle_capabilities( let display: &mut WaylandPayload = &mut *(data as *mut _); if caps & wl_seat_capability_WL_SEAT_CAPABILITY_POINTER != 0 { - // struct wl_pointer *pointer = wl_seat_get_pointer (seat); - let id: *mut wl_proxy = wl_request_constructor!( + display.pointer = wl_request_constructor!( display.client, seat, WL_SEAT_GET_POINTER, display.client.wl_pointer_interface ); - assert!(!id.is_null()); - // wl_pointer_add_listener (pointer, &pointer_listener, NULL); - (display.client.wl_proxy_add_listener)(id, &POINTER_LISTENER as *const _ as _, data); + assert!(!display.pointer.is_null()); + (display.client.wl_proxy_add_listener)( + display.pointer as _, + &POINTER_LISTENER as *const _ as _, + data, + ); } if caps & wl_seat_capability_WL_SEAT_CAPABILITY_KEYBOARD != 0 { - // struct wl_keyboard *keyboard = wl_seat_get_keyboard(seat); - let id: *mut wl_proxy = wl_request_constructor!( + display.keyboard = wl_request_constructor!( display.client, seat, WL_SEAT_GET_KEYBOARD, display.client.wl_keyboard_interface ); - assert!(!id.is_null()); - // wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL); - (display.client.wl_proxy_add_listener)(id, &KEYBOARD_LISTENER as *const _ as _, data); + assert!(!display.keyboard.is_null()); + (display.client.wl_proxy_add_listener)( + display.keyboard as _, + &KEYBOARD_LISTENER as *const _ as _, + data, + ); } } enum WaylandEvent { KeyboardLeave, - KeyboardKey(u32, bool), + KeyboardKey { + key: core::ffi::c_uint, + state: WaylandKeyState, + }, PointerMotion(f32, f32), PointerButton(MouseButton, bool), PointerAxis(f32, f32), + FilesDropped(String), } static mut EVENTS: Vec = Vec::new(); @@ -215,13 +346,13 @@ unsafe extern "C" fn keyboard_handle_keymap( unsafe extern "C" fn keyboard_handle_enter( data: *mut ::core::ffi::c_void, _wl_keyboard: *mut wl_keyboard, - serial: u32, + serial: ::core::ffi::c_uint, _surface: *mut wl_surface, _keys: *mut wl_array, ) { let display: &mut WaylandPayload = &mut *(data as *mut _); // Needed for setting the clipboard - display.keyb_serial = serial; + display.keyboard_context.enter_serial = Some(serial); } unsafe extern "C" fn keyboard_handle_leave( data: *mut ::core::ffi::c_void, @@ -232,17 +363,36 @@ unsafe extern "C" fn keyboard_handle_leave( // Clear modifiers let display: &mut WaylandPayload = &mut *(data as *mut _); (display.xkb.xkb_state_update_mask)(display.xkb_state, 0, 0, 0, 0, 0, 0); + // keyboard leave event must be handled here to stop key repeat, otherwise repeat events could + // be pushed into EVENTS before the leave event is handled by the `event_handler` + display.keyboard_context.repeated_key = None; + display.keyboard_context.enter_serial = None; EVENTS.push(WaylandEvent::KeyboardLeave); } unsafe extern "C" fn keyboard_handle_key( - _data: *mut ::core::ffi::c_void, + data: *mut ::core::ffi::c_void, _wl_keyboard: *mut wl_keyboard, _serial: u32, _time: u32, key: u32, - state: u32, + state: wl_keyboard_key_state, ) { - EVENTS.push(WaylandEvent::KeyboardKey(key, state == 1)); + let state = match state { + 0 => WaylandKeyState::Released, + 1 => WaylandKeyState::Pressed, + 2 => WaylandKeyState::Repeat, + _ => { + eprintln!("Unknown wl_keyboard::key_state"); + return; + } + }; + let display: &mut WaylandPayload = &mut *(data as *mut _); + // release event must be handled here to stop key repeat, otherwise repeat events could be + // pushed into EVENTS before the release event is handled by the `event_handler` + if state == WaylandKeyState::Released { + display.keyboard_context.key_up(key); + } + EVENTS.push(WaylandEvent::KeyboardKey { key, state }); } unsafe extern "C" fn keyboard_handle_modifiers( data: *mut ::core::ffi::c_void, @@ -265,11 +415,20 @@ unsafe extern "C" fn keyboard_handle_modifiers( ); } unsafe extern "C" fn keyboard_handle_repeat_info( - _data: *mut ::core::ffi::c_void, + data: *mut ::core::ffi::c_void, _wl_keyboard: *mut wl_keyboard, - _rate: i32, - _delay: i32, + rate: i32, + delay: i32, ) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + display.keyboard_context.repeat_info = if rate == 0 { + RepeatInfo::NoRepeat + } else { + RepeatInfo::Repeat { + delay: Duration::from_millis(delay as u64), + gap: Duration::from_micros(1_000_000 / rate as u64), + } + }; } static mut POINTER_LISTENER: wl_pointer_listener = wl_pointer_listener { @@ -417,6 +576,7 @@ unsafe extern "C" fn registry_add_object( display.client.wl_compositor_interface, 1, ) as _; + assert!(!display.compositor.is_null()); } "wl_subcompositor" => { display.subcompositor = display.client.wl_registry_bind( @@ -425,6 +585,7 @@ unsafe extern "C" fn registry_add_object( display.client.wl_subcompositor_interface, 1, ) as _; + assert!(!display.subcompositor.is_null()); } "xdg_wm_base" => { display.xdg_wm_base = display.client.wl_registry_bind( @@ -433,6 +594,7 @@ unsafe extern "C" fn registry_add_object( &extensions::xdg_shell::xdg_wm_base_interface, 1, ) as _; + assert!(!display.xdg_wm_base.is_null()); } "zxdg_decoration_manager" | "zxdg_decoration_manager_v1" => { display.decoration_manager = display.client.wl_registry_bind( @@ -465,6 +627,7 @@ unsafe extern "C" fn registry_add_object( display.client.wl_seat_interface, seat_version, ) as _; + assert!(!display.seat.is_null()); (display.client.wl_proxy_add_listener)( display.seat as _, &SEAT_LISTENER as *const _ as _, @@ -572,242 +735,40 @@ unsafe extern "C" fn xdg_wm_base_handle_ping( ); } -static mut DATA_DEVICE_LISTENER: wl_data_device_listener = wl_data_device_listener { - data_offer: Some(data_device_handle_data_offer), - enter: None, - leave: None, - motion: None, - drop: None, - selection: Some(data_device_handle_selection), -}; - static mut DATA_OFFER_LISTENER: wl_data_offer_listener = wl_data_offer_listener { offer: Some(data_offer_handle_offer), - source_actions: None, - action: None, + source_actions: Some(drag_n_drop::data_offer_handle_source_actions), + action: Some(data_offer_handle_action), }; unsafe extern "C" fn data_offer_handle_offer( _data: *mut ::core::ffi::c_void, - _offer: *mut wl_data_offer, - mime_type: *const ::core::ffi::c_char, + _data_offer: *mut wl_data_offer, + _mime_type: *const ::core::ffi::c_char, ) { - let mime_type = std::ffi::CStr::from_ptr(mime_type).to_str().unwrap(); - println!("Clipboard receive mime: {mime_type}"); } -unsafe extern "C" fn data_device_handle_data_offer( - data: *mut ::core::ffi::c_void, - _data_device: *mut wl_data_device, - offer: *mut wl_data_offer, +unsafe extern "C" fn data_offer_handle_action( + _data: *mut ::core::ffi::c_void, + _data_offer: *mut wl_data_offer, + _action: ::core::ffi::c_uint, ) { - println!("data_device_handle_data_offer()"); - //let display: &mut WaylandPayload = &mut *(data as *mut _); - //(display.client.wl_proxy_add_listener)(offer as _, &DATA_OFFER_LISTENER as *const _ as _, data); } -unsafe extern "C" fn data_device_handle_selection( +unsafe extern "C" fn data_device_handle_data_offer( data: *mut ::core::ffi::c_void, data_device: *mut wl_data_device, - offer: *mut wl_data_offer, -) { - // An application has set the clipboard contents - - if offer.is_null() { - // Clipboard is empty - println!("Clipboard is empty"); - return; - } - - let display: &mut WaylandPayload = &mut *(data as *mut _); - - println!("Clipboard is set"); - /* - let mut fds: [i32; 2] = [0, 0]; - libc::pipe(fds.as_mut_ptr()); - libc::close(fds[1]); - - // wl_data_offer_receive(offer, "text/plain", fds[1]); - let mime = std::ffi::CString::new("text/plain;charset=utf-8").unwrap(); - let version = display.client.wl_proxy_get_version(offer); - wl_request_flags!( - display.client, - offer, - WL_DATA_OFFER_RECEIVE, - std::ptr::null::(), - version, - 0, - mime, - fds[1] - ); - - // To read in this thread, we would need: wl_display_roundtrip(display); - - let fd = fds[0]; - std::thread::spawn(move || { - let mut content = String::new(); - let mut buf = [0u8; 1024]; - loop { - let n = libc::read(fd, buf.as_mut_ptr() as *mut std::ffi::c_void, buf.len()); - if n <= 0 { - break; - } - assert!(n <= 1024); - content.push_str(std::str::from_utf8(&buf[..n as usize]).unwrap()); - } - libc::close(fd); - - /* - // wl_data_offer_destroy(offer); - wl_request_flags!( - display.client, - offer, - WL_DATA_OFFER_DESTROY, - std::ptr::null::(), - version, - WL_MARSHAL_FLAG_DESTROY - ); - */ - }); - */ -} - -static mut DATA_SOURCE_LISTENER: wl_data_source_listener = wl_data_source_listener { - target: None, - send: Some(data_source_handle_send), - cancelled: Some(data_source_handle_cancelled), - dnd_drop_performed: None, - dnd_finished: None, - action: None, -}; - -unsafe extern "C" fn data_source_handle_send( - data: *mut std::ffi::c_void, - source: *mut wl_data_source, - mime_type: *const std::ffi::c_char, - fd: i32, + data_offer: *mut wl_data_offer, ) { - let text: &String = &*(data as *mut _); - - let mime_type = std::ffi::CStr::from_ptr(mime_type).to_str().unwrap(); - match mime_type { - "text/plain" => { - libc::write(fd, text.as_bytes().as_ptr() as *const _, text.len()); - } - _ => {} - } - libc::close(fd); -} - -unsafe extern "C" fn data_source_handle_cancelled( - data: *mut std::ffi::c_void, - source: *mut wl_data_source, -) { - println!("data_source_handle_cancelled()"); let display: &mut WaylandPayload = &mut *(data as *mut _); - - //assert!(!source.is_null()); - //let proxy: *mut wl_proxy = source as *mut _; - //let version = (display.client.wl_proxy_get_version)(proxy); - let version = display.client.wl_proxy_get_version(source); - - let id = (display.client.wl_proxy_marshal_flags)( - source as _, - WL_DATA_SOURCE_DESTROY, - std::ptr::null::() as *const _, - 3, - WL_MARSHAL_FLAG_DESTROY, + assert_eq!(data_device, display.data_device); + (display.client.wl_proxy_add_listener)( + data_offer as _, + &DATA_OFFER_LISTENER as *const _ as _, + data, ); - assert!(!id.is_null()); -} - -struct WaylandClipboard { - display: *mut WaylandPayload, - text: String, -} - -impl WaylandClipboard { - fn new(display: *mut WaylandPayload) -> Self { - Self { - display, - text: String::new(), - } - } -} - -impl crate::native::Clipboard for WaylandClipboard { - fn get(&mut self) -> Option { - None - } - fn set(&mut self, data: &str) { - println!("wayland set clip {data}"); - self.text.clear(); - self.text.push_str(data); - //self.text = data.to_string(); - - unsafe { - let display: &mut WaylandPayload = &mut *self.display; - - //let proxy: *mut wl_proxy = display.data_device_manager as _; - //assert!(!proxy.is_null()); - //let version = (display.client.wl_proxy_get_version)(proxy); - let version = display - .client - .wl_proxy_get_version(display.data_device_manager); - - // wl_data_device_manager_create_data_source - let source = wl_request_flags!( - display.client, - display.data_device_manager, - WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE, - display.client.wl_data_source_interface, - version, - 0, - std::ptr::null_mut::() - ); - assert!(!source.is_null()); - - // Setup a listener to receive wl_data_source events. - // wl_data_source_add_listener - (display.client.wl_proxy_add_listener)( - source, - &DATA_SOURCE_LISTENER as *const _ as _, - &self.text as *const _ as _, - ); - - // Advertise a few MIME types - // wl_data_source_offer(source, "text/plain"); - let mime = std::ffi::CString::new("text/plain;charset=utf-8").unwrap(); - let version = display.client.wl_proxy_get_version(source); - wl_request_flags!( - display.client, - source, - WL_DATA_SOURCE_OFFER, - std::ptr::null::(), - version, - 0, - mime.as_ptr() - ); - - // wl_data_device_set_selection - let version = display.client.wl_proxy_get_version(display.data_device); - wl_request_flags!( - display.client, - display.data_device, - WL_DATA_DEVICE_SET_SELECTION, - std::ptr::null::(), - version, - 0, - source, - display.keyb_serial - ); - } - } } -unsafe impl Send for WaylandClipboard {} -unsafe impl Sync for WaylandClipboard {} - pub fn run(conf: &crate::conf::Conf, f: &mut Option) -> Option<()> where F: 'static + FnOnce() -> Box, @@ -823,7 +784,7 @@ where return None; } - let registry: *mut wl_proxy = wl_request_constructor!( + let registry: *mut wl_registry = wl_request_constructor!( client, wdisplay, WL_DISPLAY_GET_REGISTRY, @@ -839,13 +800,16 @@ where let xkb_context = (xkb.xkb_context_new)(0); let mut display = WaylandPayload { - client: client.clone(), + client, + display: wdisplay, + registry, egl, xkb, compositor: std::ptr::null_mut(), subcompositor: std::ptr::null_mut(), xdg_toplevel: std::ptr::null_mut(), xdg_wm_base: std::ptr::null_mut(), + xdg_surface: std::ptr::null_mut(), surface: std::ptr::null_mut(), decoration_manager: std::ptr::null_mut(), viewporter: std::ptr::null_mut(), @@ -860,59 +824,27 @@ where pointer: std::ptr::null_mut(), keyboard: std::ptr::null_mut(), focused_window: std::ptr::null_mut(), - keyb_serial: 0, decorations: None, + keyboard_context: KeyboardContext::new(), + drag_n_drop: Default::default(), + update_requested: true, event_handler: None, closed: false, }; let (tx, rx) = std::sync::mpsc::channel(); - let clipboard = Box::new(WaylandClipboard::new(&mut display as *mut _)); + let clipboard = Box::new(clipboard::WaylandClipboard::new(&mut display as *mut _)); crate::set_display(NativeDisplayData { ..NativeDisplayData::new(conf.window_width, conf.window_height, tx, clipboard) }); (display.client.wl_proxy_add_listener)( - registry, + display.registry as _, ®istry_listener as *const _ as _, &mut display as *mut _ as _, ); - (display.client.wl_display_roundtrip)(wdisplay); - - assert!(!display.data_device_manager.is_null()); - assert!(!display.seat.is_null()); - display.data_device = wl_request_flags!( - display.client, - display.data_device_manager, - WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, - display.client.wl_data_device_interface, - display - .client - .wl_proxy_get_version(display.data_device_manager), - 0, - std::ptr::null::(), - display.seat - ) as _; - //display.data_device = wl_request_constructor!( - // display.client, - // display.data_device_manager, - // 1, - // display.client.wl_data_device_interface, - // display.seat - //) as _; - assert!(!display.data_device.is_null()); - - // wl_data_device_add_listener(data_device, &data_device_listener, NULL); - (display.client.wl_proxy_add_listener)( - display.data_device as _, - &DATA_DEVICE_LISTENER as *const _ as _, - &mut display as *mut _ as _, - ); + (display.client.wl_display_dispatch)(display.display); - assert!(!display.compositor.is_null()); - assert!(!display.xdg_wm_base.is_null()); - assert!(!display.subcompositor.is_null()); - assert!(!display.seat.is_null()); //assert!(!display.keymap.is_null()); //assert!(!display.xkb_state.is_null()); @@ -947,28 +879,28 @@ where ); assert!(!display.surface.is_null()); - let xdg_surface: *mut extensions::xdg_shell::xdg_surface = wl_request_constructor!( + display.xdg_surface = wl_request_constructor!( display.client, display.xdg_wm_base, extensions::xdg_shell::xdg_wm_base::get_xdg_surface, &extensions::xdg_shell::xdg_surface_interface, display.surface ); - assert!(!xdg_surface.is_null()); + assert!(!display.xdg_surface.is_null()); let xdg_surface_listener = extensions::xdg_shell::xdg_surface_listener { configure: Some(xdg_surface_handle_configure), }; (display.client.wl_proxy_add_listener)( - xdg_surface as _, + display.xdg_surface as _, &xdg_surface_listener as *const _ as _, &mut display as *mut _ as _, ); display.xdg_toplevel = wl_request_constructor!( display.client, - xdg_surface, + display.xdg_surface, extensions::xdg_shell::xdg_surface::get_toplevel, &extensions::xdg_shell::xdg_toplevel_interface ); @@ -994,8 +926,17 @@ where title.as_ptr() ); + let wm_class = std::ffi::CString::new(conf.platform.linux_wm_class).unwrap(); + + wl_request!( + display.client, + display.xdg_toplevel, + extensions::xdg_shell::xdg_toplevel::set_app_id, + wm_class.as_ptr() + ); + wl_request!(display.client, display.surface, WL_SURFACE_COMMIT); - (display.client.wl_display_roundtrip)(wdisplay); + (display.client.wl_display_dispatch)(display.display); display.egl_window = (display.egl.wl_egl_window_create)( display.surface as _, @@ -1018,6 +959,10 @@ where panic!("eglMakeCurrent failed"); } + if (libegl.eglSwapInterval)(egl_display, conf.platform.swap_interval.unwrap_or(1)) == 0 { + eprintln!("eglSwapInterval failed"); + } + // For some reason, setting fullscreen before egl_window is created leads // to segfault because wl_egl_window_create returns NULL. if conf.fullscreen { @@ -1067,91 +1012,106 @@ where alt: false, logo: false, }; - let mut repeated_keys: HashSet = HashSet::new(); let (mut last_mouse_x, mut last_mouse_y) = (0.0, 0.0); - while !display.closed { - (client.wl_display_dispatch_pending)(wdisplay); - - if let Some(ref mut event_handler) = display.event_handler { - for key in &repeated_keys { - let keysym = - (display.xkb.xkb_state_key_get_one_sym)(display.xkb_state, key + 8); - let keycode = keycodes::translate(keysym); + display.data_device = wl_request_constructor!( + display.client, + display.data_device_manager, + WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, + display.client.wl_data_device_interface, + display.seat + ) as _; + assert!(!display.data_device.is_null()); - event_handler.key_down_event(keycode, keymods, true); + let data_device_listener = wl_data_device_listener { + data_offer: Some(data_device_handle_data_offer), + enter: Some(drag_n_drop::data_device_handle_enter), + leave: Some(drag_n_drop::data_device_handle_leave), + motion: Some(drag_n_drop::data_device_handle_motion), + drop: Some(drag_n_drop::data_device_handle_drop), + selection: Some(clipboard::data_device_handle_selection), + }; + (display.client.wl_proxy_add_listener)( + display.data_device as _, + &data_device_listener as *const _ as _, + &mut display as *mut _ as _, + ); - let chr = keycodes::keysym_to_unicode(&mut display.xkb, keysym); - if chr > 0 { - if let Some(chr) = char::from_u32(chr as u32) { - event_handler.char_event(chr, keymods, true); + while !(display.closed || crate::native_display().try_lock().unwrap().quit_ordered) { + while let Ok(request) = rx.try_recv() { + match request { + Request::SetFullscreen(full) => { + if full { + wl_request!( + display.client, + display.xdg_toplevel, + extensions::xdg_shell::xdg_toplevel::set_fullscreen, + std::ptr::null_mut::<*mut wl_output>() + ); + } else { + wl_request!( + display.client, + display.xdg_toplevel, + extensions::xdg_shell::xdg_toplevel::unset_fullscreen + ); } } + Request::ScheduleUpdate => display.update_requested = true, + // TODO: implement the other events + _ => (), } + } - while let Ok(request) = rx.try_recv() { - #[allow(clippy::single_match)] - match request { - Request::SetFullscreen(full) => { - if full { - wl_request!( - display.client, - display.xdg_toplevel, - extensions::xdg_shell::xdg_toplevel::set_fullscreen, - std::ptr::null_mut::<*mut wl_output>() - ); - } else { - wl_request!( - display.client, - display.xdg_toplevel, - extensions::xdg_shell::xdg_toplevel::unset_fullscreen - ); - } - } - // TODO: implement the other events - _ => (), - } - } + display.block_on_new_event(); + if let Some(ref mut event_handler) = display.event_handler { for event in EVENTS.drain(..) { match event { WaylandEvent::KeyboardLeave => { - repeated_keys.clear(); keymods.shift = false; keymods.ctrl = false; keymods.logo = false; keymods.alt = false; } - WaylandEvent::KeyboardKey(key, state) => { + WaylandEvent::KeyboardKey { key, state } => { // https://wayland-book.com/seat/keyboard.html // To translate this to an XKB scancode, you must add 8 to the evdev scancode. let keysym = (display.xkb.xkb_state_key_get_one_sym)(display.xkb_state, key + 8); let keycode = keycodes::translate(keysym); + let should_repeat = + (display.xkb.xkb_keymap_key_repeats)(display.keymap, key + 8) == 1; match keycode { - KeyCode::LeftShift | KeyCode::RightShift => keymods.shift = state, + KeyCode::LeftShift | KeyCode::RightShift => { + keymods.shift = state.into() + } KeyCode::LeftControl | KeyCode::RightControl => { - keymods.ctrl = state + keymods.ctrl = state.into() + } + KeyCode::LeftAlt | KeyCode::RightAlt => keymods.alt = state.into(), + KeyCode::LeftSuper | KeyCode::RightSuper => { + keymods.logo = state.into() } - KeyCode::LeftAlt | KeyCode::RightAlt => keymods.alt = state, - KeyCode::LeftSuper | KeyCode::RightSuper => keymods.logo = state, _ => {} } - if state { - event_handler.key_down_event(keycode, keymods, false); - repeated_keys.insert(key); + if state.into() { + let repeat = matches!(state, WaylandKeyState::Repeat); + if !repeat && should_repeat { + display.keyboard_context.key_down(key); + } + + event_handler.key_down_event(keycode, keymods, repeat); let chr = keycodes::keysym_to_unicode(&mut display.xkb, keysym); if chr > 0 { if let Some(chr) = char::from_u32(chr as u32) { - event_handler.char_event(chr, keymods, false); + event_handler.char_event(chr, keymods, repeat); } } } else { event_handler.key_up_event(keycode, keymods); - repeated_keys.remove(&key); } } WaylandEvent::PointerMotion(x, y) => { @@ -1174,14 +1134,42 @@ where } } WaylandEvent::PointerAxis(x, y) => event_handler.mouse_wheel_event(x, y), + WaylandEvent::FilesDropped(filenames) => { + let mut d = crate::native_display().try_lock().unwrap(); + d.dropped_files = Default::default(); + for filename in filenames.lines() { + let path = std::path::PathBuf::from(filename); + if let Ok(bytes) = std::fs::read(&path) { + d.dropped_files.paths.push(path); + d.dropped_files.bytes.push(bytes); + } + } + // drop d since files_dropped_event is likely to need access to it + drop(d); + event_handler.files_dropped_event(); + } } } - event_handler.update(); - event_handler.draw(); - } + { + let d = crate::native_display().try_lock().unwrap(); + if d.quit_requested && !d.quit_ordered { + drop(d); + event_handler.quit_requested_event(); + let mut d = crate::native_display().try_lock().unwrap(); + if d.quit_requested { + d.quit_ordered = true + } + } + } - (libegl.eglSwapBuffers)(egl_display, egl_surface); + if !conf.platform.blocking_event_loop || display.update_requested { + display.update_requested = false; + event_handler.update(); + event_handler.draw(); + (libegl.eglSwapBuffers)(egl_display, egl_surface); + } + } } } diff --git a/src/native/linux_wayland/clipboard.rs b/src/native/linux_wayland/clipboard.rs new file mode 100644 index 00000000..22676447 --- /dev/null +++ b/src/native/linux_wayland/clipboard.rs @@ -0,0 +1,172 @@ +use super::*; +use crate::{wl_request, wl_request_constructor}; + +use core::ffi::{c_char, c_int, c_void}; + +// new clipboard content is available +// this could be fired at any time, so we just store the `data_offer` for later use +pub(super) unsafe extern "C" fn data_device_handle_selection( + data: *mut c_void, + data_device: *mut wl_data_device, + data_offer: *mut wl_data_offer, +) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + assert_eq!(data_device, display.data_device); + CLIPBOARD.get_mut().unwrap().data_offer = (!data_offer.is_null()).then_some(data_offer); +} + +use std::sync::OnceLock; +static mut CLIPBOARD: OnceLock = OnceLock::new(); + +// Contains the owned clipboard and the `data_source` (object in Wayland that indicates clipboard +// ownership). There is a static instance `CLIPBOARD` available globally, initialized when creating +// the `WaylandClipboard`. +#[derive(Debug)] +struct ClipboardContext { + display: *mut WaylandPayload, + content: String, + data_source: Option<*mut wl_data_source>, + data_offer: Option<*mut wl_data_offer>, +} + +impl ClipboardContext { + unsafe fn get_clipboard(&mut self, mime_type: &str) -> Option> { + self.data_offer.map(|data_offer| { + let display: &mut WaylandPayload = &mut *self.display; + let mime_type = std::ffi::CString::new(mime_type).unwrap(); + display + .client + .data_offer_receive(display.display, data_offer, mime_type.as_ptr()) + }) + } + + unsafe fn set(&mut self, data: &str) { + self.content.clear(); + self.content.push_str(data); + let display: &mut WaylandPayload = &mut *self.display; + // Wayland requires that only the window with focus can set the clipboard + if let Some(serial) = display.keyboard_context.enter_serial { + let data_source = self.new_data_source(); + // only support copying utf8 strings + let mime_type = std::ffi::CString::new("UTF8_STRING").unwrap(); + wl_request!( + display.client, + data_source, + WL_DATA_SOURCE_OFFER, + mime_type.as_ptr() + ); + wl_request!( + display.client, + display.data_device, + WL_DATA_DEVICE_SET_SELECTION, + data_source, + serial + ); + } + } + + unsafe fn respond_to_clipboard_request(&mut self, mime_type: &str, fd: c_int) { + #![allow(clippy::single_match)] + match mime_type { + "UTF8_STRING" => { + libc::write(fd, self.content.as_ptr() as _, self.content.len()); + } + _ => {} + } + libc::close(fd); + } + + unsafe fn destroy_data_source(&mut self) { + // since the data_source is constructed by us, we need to dispose of it properly + if let Some(data_source) = self.data_source { + let display: &mut WaylandPayload = &mut *self.display; + wl_request!(display.client, data_source, WL_DATA_SOURCE_DESTROY); + (display.client.wl_proxy_destroy)(data_source as _); + self.data_source = None; + } + } + + unsafe fn new_data_source(&mut self) -> *mut wl_data_source { + self.destroy_data_source(); + let display: &mut WaylandPayload = &mut *self.display; + let data_source: *mut wl_data_source = wl_request_constructor!( + display.client, + display.data_device_manager, + WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE, + display.client.wl_data_source_interface, + ); + assert!(!data_source.is_null()); + (display.client.wl_proxy_add_listener)( + data_source as _, + &DATA_SOURCE_LISTENER as *const _ as _, + self.display as *const _ as _, + ); + self.data_source = Some(data_source); + data_source + } +} + +static mut DATA_SOURCE_LISTENER: wl_data_source_listener = wl_data_source_listener { + target: None, + send: Some(data_source_handle_send), + cancelled: Some(data_source_handle_cancelled), + dnd_drop_performed: None, + dnd_finished: None, + action: None, +}; + +// some app (could be ourself) is requesting the owned clipboard +unsafe extern "C" fn data_source_handle_send( + _data: *mut c_void, + data_source: *mut wl_data_source, + mime_type: *const c_char, + fd: c_int, +) { + let mime_type = core::ffi::CStr::from_ptr(mime_type).to_str().unwrap(); + let ctx = CLIPBOARD.get_mut().unwrap(); + assert!(ctx.data_source == Some(data_source)); + ctx.respond_to_clipboard_request(mime_type, fd); +} + +// the owned clipboard has been replaced by some other app +unsafe extern "C" fn data_source_handle_cancelled( + _data: *mut c_void, + data_source: *mut wl_data_source, +) { + let ctx = CLIPBOARD.get_mut().unwrap(); + assert!(ctx.data_source == Some(data_source)); + ctx.destroy_data_source(); +} + +pub struct WaylandClipboard {} +unsafe impl Send for WaylandClipboard {} +unsafe impl Sync for WaylandClipboard {} + +impl WaylandClipboard { + pub(super) fn new(display: *mut WaylandPayload) -> Self { + // initialize the global context + unsafe { + CLIPBOARD + .set(ClipboardContext { + display, + content: String::new(), + data_source: None, + data_offer: None, + }) + .unwrap(); + } + WaylandClipboard {} + } +} + +impl crate::native::Clipboard for WaylandClipboard { + fn get(&mut self) -> Option { + let bytes = unsafe { CLIPBOARD.get_mut().unwrap().get_clipboard("UTF8_STRING")? }; + Some(std::str::from_utf8(&bytes).ok()?.to_string()) + } + fn set(&mut self, data: &str) { + unsafe { + CLIPBOARD.get_mut().unwrap().set(data); + } + } +} diff --git a/src/native/linux_wayland/drag_n_drop.rs b/src/native/linux_wayland/drag_n_drop.rs new file mode 100644 index 00000000..c4f4428a --- /dev/null +++ b/src/native/linux_wayland/drag_n_drop.rs @@ -0,0 +1,87 @@ +use super::*; +use crate::wl_request; + +#[derive(Default)] +pub struct WaylandDnD { + data_offer: Option<*mut wl_data_offer>, + enter_serial: Option, +} + +pub(super) unsafe extern "C" fn data_offer_handle_source_actions( + data: *mut ::core::ffi::c_void, + data_offer: *mut wl_data_offer, + actions: ::core::ffi::c_uint, +) { + if actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY == 1 { + let display: &mut WaylandPayload = &mut *(data as *mut _); + wl_request!( + display.client, + data_offer, + WL_DATA_OFFER_SET_ACTIONS, + WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, + WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY + ); + } +} + +pub(super) unsafe extern "C" fn data_device_handle_enter( + data: *mut ::core::ffi::c_void, + data_device: *mut wl_data_device, + serial: core::ffi::c_uint, + _surface: *mut wl_surface, + _surface_x: i32, + _surface_y: i32, + data_offer: *mut wl_data_offer, +) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + assert_eq!(data_device, display.data_device); + display.drag_n_drop.enter_serial = Some(serial); + display.drag_n_drop.data_offer = Some(data_offer); + // only accept utf8 strings + let mime_type = std::ffi::CString::new("UTF8_STRING").unwrap(); + wl_request!( + display.client, + data_offer, + WL_DATA_OFFER_ACCEPT, + serial, + mime_type.as_ptr() + ); +} + +pub(super) unsafe extern "C" fn data_device_handle_motion( + _data: *mut ::core::ffi::c_void, + _data_device: *mut wl_data_device, + _time: core::ffi::c_uint, + _surface_x: i32, + _surface_y: i32, +) { +} + +pub(super) unsafe extern "C" fn data_device_handle_leave( + data: *mut ::core::ffi::c_void, + data_device: *mut wl_data_device, +) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + assert_eq!(data_device, display.data_device); + display.drag_n_drop.enter_serial = None; + display.drag_n_drop.data_offer = None; +} + +pub(super) unsafe extern "C" fn data_device_handle_drop( + data: *mut ::core::ffi::c_void, + data_device: *mut wl_data_device, +) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + assert_eq!(data_device, display.data_device); + if let Some(data_offer) = display.drag_n_drop.data_offer { + let mime_type = std::ffi::CString::new("UTF8_STRING").unwrap(); + let bytes = + display + .client + .data_offer_receive(display.display, data_offer, mime_type.as_ptr()); + wl_request!(display.client, data_offer, WL_DATA_OFFER_FINISH); + if let Ok(filenames) = String::from_utf8(bytes) { + EVENTS.push(WaylandEvent::FilesDropped(filenames)); + } + } +} diff --git a/src/native/linux_wayland/keycodes.rs b/src/native/linux_wayland/keycodes.rs index 91c35c77..7b4dcf40 100644 --- a/src/native/linux_wayland/keycodes.rs +++ b/src/native/linux_wayland/keycodes.rs @@ -5,6 +5,7 @@ pub fn translate(keysym: u32) -> KeyCode { // See xkbcommon/xkbcommon-keysyms.h match keysym { 65307 => KeyCode::Escape, + 65056 => KeyCode::Tab, // LeftTab 65289 => KeyCode::Tab, 65505 => KeyCode::LeftShift, 65506 => KeyCode::RightShift, diff --git a/src/native/linux_wayland/libwayland_client.rs b/src/native/linux_wayland/libwayland_client.rs index 7a8a91fc..7991d2d9 100644 --- a/src/native/linux_wayland/libwayland_client.rs +++ b/src/native/linux_wayland/libwayland_client.rs @@ -554,14 +554,30 @@ impl LibWaylandClient { name, (*interface).name, version, - std::ptr::null_mut::(), ); id as *mut _ } - - pub unsafe fn wl_proxy_get_version(&self, proxy: *mut T) -> u32 { - let proxy: *mut wl_proxy = proxy as _; - assert!(!proxy.is_null()); - (self.wl_proxy_get_version)(proxy) + pub unsafe fn data_offer_receive( + &mut self, + display: *mut wl_display, + data_offer: *mut wl_data_offer, + mime_type: *const c_char, + ) -> Vec { + let mut fds: [c_int; 2] = [0; 2]; + assert_eq!(libc::pipe(fds.as_mut_ptr()), 0); + (self.wl_proxy_marshal)(data_offer as _, WL_DATA_OFFER_RECEIVE, mime_type, fds[1]); + libc::close(fds[1]); + (self.wl_display_roundtrip)(display); + let mut bytes = Vec::new(); + loop { + let mut buf = [0_u8; 1024]; + let n = libc::read(fds[0], buf.as_mut_ptr() as _, buf.len()); + if n <= 0 { + break; + } + bytes.extend_from_slice(&buf[..n as usize]); + } + libc::close(fds[0]); + bytes } } diff --git a/src/native/linux_x11.rs b/src/native/linux_x11.rs index db0d88fb..31c9605b 100644 --- a/src/native/linux_x11.rs +++ b/src/native/linux_x11.rs @@ -558,6 +558,10 @@ where panic!("eglMakeCurrent failed"); } + if (egl_lib.eglSwapInterval)(egl_display, conf.platform.swap_interval.unwrap_or(1)) == 0 { + eprintln!("eglSwapInterval failed"); + } + crate::native::gl::load_gl_funcs(|proc| { let name = std::ffi::CString::new(proc).unwrap(); (egl_lib.eglGetProcAddress)(name.as_ptr() as _) diff --git a/src/native/linux_x11/libx11_ex.rs b/src/native/linux_x11/libx11_ex.rs index 8f7e704e..7a859d86 100644 --- a/src/native/linux_x11/libx11_ex.rs +++ b/src/native/linux_x11/libx11_ex.rs @@ -235,7 +235,7 @@ impl LibX11 { (self.XFree)(hints as *mut libc::c_void); let class_hint = (self.XAllocClassHint)(); - let wm_class = std::ffi::CString::new(conf.platform.linux_x11_wm_class).unwrap(); + let wm_class = std::ffi::CString::new(conf.platform.linux_wm_class).unwrap(); (*class_hint).res_name = wm_class.as_ptr() as _; (*class_hint).res_class = wm_class.as_ptr() as _; (self.XSetClassHint)(display, window, class_hint); From 8c4db0536272a30e5938c7b9451aa12f3399fc5a Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Wed, 26 Feb 2025 19:14:04 +0000 Subject: [PATCH 03/16] native/linux_wayland: Refactor Remove `event_handler` from the payload and add `EVENTS` to the payload. Essentially we now maintain our own event queue. --- src/native/linux_wayland.rs | 338 ++++++++++++------------ src/native/linux_wayland/drag_n_drop.rs | 2 +- 2 files changed, 164 insertions(+), 176 deletions(-) diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index 478da4fd..c2ae1ff7 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -58,10 +58,10 @@ struct WaylandPayload { //xkb_state: xkb::XkbState, decorations: Option, + events: Vec, keyboard_context: KeyboardContext, drag_n_drop: drag_n_drop::WaylandDnD, update_requested: bool, - event_handler: Option>, closed: bool, } @@ -106,13 +106,13 @@ impl WaylandPayload { ), n_bits as _ ); - if let Some(key) = self.keyboard_context.repeated_key { - for _ in 0..count[0] { - EVENTS.push(WaylandEvent::KeyboardKey { - key, - state: WaylandKeyState::Repeat, - }); - } + for _ in 0..count[0] { + self.keyboard_context.generate_events( + &mut self.xkb, + self.xkb_state, + true, + &mut self.events, + ); } } } else { @@ -121,23 +121,6 @@ impl WaylandPayload { } } -#[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum WaylandKeyState { - Released = 0, - Pressed = 1, - Repeat = 2, -} - -impl From for bool { - fn from(value: WaylandKeyState) -> bool { - match value { - WaylandKeyState::Released => false, - WaylandKeyState::Pressed | WaylandKeyState::Repeat => true, - } - } -} - #[derive(Clone, Copy, PartialEq, Eq)] pub enum RepeatInfo { Repeat { delay: Duration, gap: Duration }, @@ -162,6 +145,7 @@ struct KeyboardContext { repeat_info: RepeatInfo, repeated_key: Option, timerfd: core::ffi::c_int, + keymods: KeyMods, } fn new_itimerspec() -> libc::itimerspec { @@ -184,9 +168,10 @@ impl KeyboardContext { repeat_info: Default::default(), repeated_key: None, timerfd: unsafe { libc::timerfd_create(libc::CLOCK_MONOTONIC, libc::TFD_CLOEXEC) }, + keymods: Default::default(), } } - fn key_down(&mut self, key: core::ffi::c_uint) { + fn track_key_down(&mut self, key: core::ffi::c_uint) { let mut timer = new_itimerspec(); match self.repeat_info { RepeatInfo::Repeat { delay, gap } => { @@ -202,7 +187,7 @@ impl KeyboardContext { libc::timerfd_settime(self.timerfd, 0, &timer, core::ptr::null_mut()); } } - fn key_up(&mut self, key: core::ffi::c_uint) { + fn track_key_up(&mut self, key: core::ffi::c_uint) { if self.repeated_key == Some(key) { self.repeated_key = None; unsafe { @@ -210,6 +195,25 @@ impl KeyboardContext { } } } + unsafe fn generate_events( + &self, + xkb: &mut LibXkbCommon, + xkb_state: *mut xkb_state, + repeat: bool, + events: &mut Vec, + ) { + if let Some(key) = self.repeated_key { + let keysym = (xkb.xkb_state_key_get_one_sym)(xkb_state, key + 8); + let keycode = keycodes::translate(keysym); + events.push(WaylandEvent::KeyDown(keycode, self.keymods, repeat)); + let chr = keycodes::keysym_to_unicode(xkb, keysym); + if chr > 0 { + if let Some(chr) = char::from_u32(chr as u32) { + events.push(WaylandEvent::Char(chr, self.keymods, repeat)); + } + } + } + } } #[macro_export] @@ -292,19 +296,16 @@ unsafe extern "C" fn seat_handle_capabilities( } enum WaylandEvent { - KeyboardLeave, - KeyboardKey { - key: core::ffi::c_uint, - state: WaylandKeyState, - }, + KeyDown(KeyCode, KeyMods, bool), + KeyUp(KeyCode, KeyMods), + Char(char, KeyMods, bool), PointerMotion(f32, f32), PointerButton(MouseButton, bool), PointerAxis(f32, f32), FilesDropped(String), + Resize(f32, f32), } -static mut EVENTS: Vec = Vec::new(); - static mut KEYBOARD_LISTENER: wl_keyboard_listener = wl_keyboard_listener { keymap: Some(keyboard_handle_keymap), enter: Some(keyboard_handle_enter), @@ -363,11 +364,12 @@ unsafe extern "C" fn keyboard_handle_leave( // Clear modifiers let display: &mut WaylandPayload = &mut *(data as *mut _); (display.xkb.xkb_state_update_mask)(display.xkb_state, 0, 0, 0, 0, 0, 0); - // keyboard leave event must be handled here to stop key repeat, otherwise repeat events could - // be pushed into EVENTS before the leave event is handled by the `event_handler` display.keyboard_context.repeated_key = None; display.keyboard_context.enter_serial = None; - EVENTS.push(WaylandEvent::KeyboardLeave); + display.keyboard_context.keymods.shift = false; + display.keyboard_context.keymods.ctrl = false; + display.keyboard_context.keymods.logo = false; + display.keyboard_context.keymods.alt = false; } unsafe extern "C" fn keyboard_handle_key( data: *mut ::core::ffi::c_void, @@ -377,22 +379,45 @@ unsafe extern "C" fn keyboard_handle_key( key: u32, state: wl_keyboard_key_state, ) { - let state = match state { - 0 => WaylandKeyState::Released, - 1 => WaylandKeyState::Pressed, - 2 => WaylandKeyState::Repeat, + use KeyCode::*; + let is_down = state == 1 || state == 2; + let display: &mut WaylandPayload = &mut *(data as *mut _); + // https://wayland-book.com/seat/keyboard.html + // To translate this to an XKB scancode, you must add 8 to the evdev scancode. + let keysym = (display.xkb.xkb_state_key_get_one_sym)(display.xkb_state, key + 8); + let should_repeat = (display.xkb.xkb_keymap_key_repeats)(display.keymap, key + 8) == 1; + let keycode = keycodes::translate(keysym); + match keycode { + LeftShift | RightShift => display.keyboard_context.keymods.shift = is_down, + LeftControl | RightControl => display.keyboard_context.keymods.ctrl = is_down, + LeftAlt | RightAlt => display.keyboard_context.keymods.alt = is_down, + LeftSuper | RightSuper => display.keyboard_context.keymods.logo = is_down, + _ => {} + } + match state { + 0 => { + display.keyboard_context.track_key_up(key); + display.events.push(WaylandEvent::KeyUp( + keycode, + display.keyboard_context.keymods, + )); + } + 1 | 2 => { + let repeat = state == 2; + if !repeat && should_repeat { + display.keyboard_context.track_key_down(key); + } + display.keyboard_context.generate_events( + &mut display.xkb, + display.xkb_state, + repeat, + &mut display.events, + ); + } _ => { eprintln!("Unknown wl_keyboard::key_state"); - return; } }; - let display: &mut WaylandPayload = &mut *(data as *mut _); - // release event must be handled here to stop key repeat, otherwise repeat events could be - // pushed into EVENTS before the release event is handled by the `event_handler` - if state == WaylandKeyState::Released { - display.keyboard_context.key_up(key); - } - EVENTS.push(WaylandEvent::KeyboardKey { key, state }); } unsafe extern "C" fn keyboard_handle_modifiers( data: *mut ::core::ffi::c_void, @@ -446,13 +471,15 @@ static mut POINTER_LISTENER: wl_pointer_listener = wl_pointer_listener { }; unsafe extern "C" fn pointer_handle_enter( - _data: *mut ::core::ffi::c_void, + data: *mut ::core::ffi::c_void, _wl_pointer: *mut wl_pointer, _serial: u32, - _surface: *mut wl_surface, + surface: *mut wl_surface, _surface_x: i32, _surface_y: i32, ) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + display.focused_window = surface; } unsafe extern "C" fn pointer_handle_leave( _data: *mut ::core::ffi::c_void, @@ -462,40 +489,49 @@ unsafe extern "C" fn pointer_handle_leave( ) { } unsafe extern "C" fn pointer_handle_motion( - _data: *mut ::core::ffi::c_void, + data: *mut ::core::ffi::c_void, _wl_pointer: *mut wl_pointer, _time: u32, surface_x: i32, surface_y: i32, ) { - // From wl_fixed_to_double(), it simply divides by 256 - let (x, y) = (wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)); - EVENTS.push(WaylandEvent::PointerMotion(x, y)); + let display: &mut WaylandPayload = &mut *(data as *mut _); + if display.focused_window == display.surface { + // From wl_fixed_to_double(), it simply divides by 256 + let (x, y) = (wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)); + display.events.push(WaylandEvent::PointerMotion(x, y)); + } } unsafe extern "C" fn pointer_handle_button( - _data: *mut ::core::ffi::c_void, + data: *mut ::core::ffi::c_void, _wl_pointer: *mut wl_pointer, _serial: u32, _time: u32, button: u32, state: u32, ) { - // The code is defined in the kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT - let button = match button { - 272 => MouseButton::Left, - 273 => MouseButton::Right, - 274 => MouseButton::Middle, - _ => MouseButton::Unknown, - }; - EVENTS.push(WaylandEvent::PointerButton(button, state == 1)); + let display: &mut WaylandPayload = &mut *(data as *mut _); + if display.focused_window == display.surface { + // The code is defined in the kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT + let button = match button { + 272 => MouseButton::Left, + 273 => MouseButton::Right, + 274 => MouseButton::Middle, + _ => MouseButton::Unknown, + }; + display + .events + .push(WaylandEvent::PointerButton(button, state == 1)); + } } unsafe extern "C" fn pointer_handle_axis( - _data: *mut ::core::ffi::c_void, + data: *mut ::core::ffi::c_void, _wl_pointer: *mut wl_pointer, _time: u32, axis: u32, value: i32, ) { + let display: &mut WaylandPayload = &mut *(data as *mut _); let mut value = wl_fixed_to_double(value); // Normalize the value to {-1, 0, 1} value /= value.abs(); @@ -505,10 +541,10 @@ unsafe extern "C" fn pointer_handle_axis( // Vertical scroll // Wayland defines the direction differently to miniquad so lets flip it value = -value; - EVENTS.push(WaylandEvent::PointerAxis(0.0, value)); + display.events.push(WaylandEvent::PointerAxis(0.0, value)); } else if axis == 1 { // Horizontal scroll - EVENTS.push(WaylandEvent::PointerAxis(value, 0.0)); + display.events.push(WaylandEvent::PointerAxis(value, 0.0)); } } unsafe extern "C" fn pointer_handle_frame( @@ -713,9 +749,9 @@ unsafe extern "C" fn xdg_toplevel_handle_configure( decorations.resize(&mut payload.client, width, height); } - if let Some(ref mut event_handler) = payload.event_handler { - event_handler.resize_event(width as _, height as _); - } + payload + .events + .push(WaylandEvent::Resize(width as _, height as _)); } } @@ -825,10 +861,10 @@ where keyboard: std::ptr::null_mut(), focused_window: std::ptr::null_mut(), decorations: None, + events: Vec::new(), keyboard_context: KeyboardContext::new(), drag_n_drop: Default::default(), update_requested: true, - event_handler: None, closed: false, }; @@ -1003,15 +1039,8 @@ where )); } - let event_handler = (f.take().unwrap())(); - display.event_handler = Some(event_handler); + let mut event_handler = (f.take().unwrap())(); - let mut keymods = KeyMods { - shift: false, - ctrl: false, - alt: false, - logo: false, - }; let (mut last_mouse_x, mut last_mouse_y) = (0.0, 0.0); display.data_device = wl_request_constructor!( @@ -1064,111 +1093,70 @@ where display.block_on_new_event(); - if let Some(ref mut event_handler) = display.event_handler { - for event in EVENTS.drain(..) { - match event { - WaylandEvent::KeyboardLeave => { - keymods.shift = false; - keymods.ctrl = false; - keymods.logo = false; - keymods.alt = false; - } - WaylandEvent::KeyboardKey { key, state } => { - // https://wayland-book.com/seat/keyboard.html - // To translate this to an XKB scancode, you must add 8 to the evdev scancode. - let keysym = - (display.xkb.xkb_state_key_get_one_sym)(display.xkb_state, key + 8); - let keycode = keycodes::translate(keysym); - let should_repeat = - (display.xkb.xkb_keymap_key_repeats)(display.keymap, key + 8) == 1; - - match keycode { - KeyCode::LeftShift | KeyCode::RightShift => { - keymods.shift = state.into() - } - KeyCode::LeftControl | KeyCode::RightControl => { - keymods.ctrl = state.into() - } - KeyCode::LeftAlt | KeyCode::RightAlt => keymods.alt = state.into(), - KeyCode::LeftSuper | KeyCode::RightSuper => { - keymods.logo = state.into() - } - _ => {} - } - - if state.into() { - let repeat = matches!(state, WaylandKeyState::Repeat); - if !repeat && should_repeat { - display.keyboard_context.key_down(key); - } - - event_handler.key_down_event(keycode, keymods, repeat); - - let chr = keycodes::keysym_to_unicode(&mut display.xkb, keysym); - if chr > 0 { - if let Some(chr) = char::from_u32(chr as u32) { - event_handler.char_event(chr, keymods, repeat); - } - } - } else { - event_handler.key_up_event(keycode, keymods); - } - } - WaylandEvent::PointerMotion(x, y) => { - event_handler.mouse_motion_event(x, y); - (last_mouse_x, last_mouse_y) = (x, y); - } - WaylandEvent::PointerButton(button, state) => { - if state { - event_handler.mouse_button_down_event( - button, - last_mouse_x, - last_mouse_y, - ); - } else { - event_handler.mouse_button_up_event( - button, - last_mouse_x, - last_mouse_y, - ); - } + for event in display.events.drain(..) { + match event { + WaylandEvent::KeyDown(keycode, keymods, repeat) => { + event_handler.key_down_event(keycode, keymods, repeat) + } + WaylandEvent::KeyUp(keycode, keymods) => { + event_handler.key_up_event(keycode, keymods) + } + WaylandEvent::Char(chr, keymods, repeat) => { + event_handler.char_event(chr, keymods, repeat) + } + WaylandEvent::PointerMotion(x, y) => { + event_handler.mouse_motion_event(x, y); + (last_mouse_x, last_mouse_y) = (x, y); + } + WaylandEvent::PointerButton(button, state) => { + if state { + event_handler.mouse_button_down_event( + button, + last_mouse_x, + last_mouse_y, + ); + } else { + event_handler.mouse_button_up_event(button, last_mouse_x, last_mouse_y); } - WaylandEvent::PointerAxis(x, y) => event_handler.mouse_wheel_event(x, y), - WaylandEvent::FilesDropped(filenames) => { - let mut d = crate::native_display().try_lock().unwrap(); - d.dropped_files = Default::default(); - for filename in filenames.lines() { - let path = std::path::PathBuf::from(filename); - if let Ok(bytes) = std::fs::read(&path) { - d.dropped_files.paths.push(path); - d.dropped_files.bytes.push(bytes); - } + } + WaylandEvent::PointerAxis(x, y) => event_handler.mouse_wheel_event(x, y), + WaylandEvent::Resize(width, height) => { + event_handler.resize_event(width, height) + } + WaylandEvent::FilesDropped(filenames) => { + let mut d = crate::native_display().try_lock().unwrap(); + d.dropped_files = Default::default(); + for filename in filenames.lines() { + let path = std::path::PathBuf::from(filename); + if let Ok(bytes) = std::fs::read(&path) { + d.dropped_files.paths.push(path); + d.dropped_files.bytes.push(bytes); } - // drop d since files_dropped_event is likely to need access to it - drop(d); - event_handler.files_dropped_event(); } + // drop d since files_dropped_event is likely to need access to it + drop(d); + event_handler.files_dropped_event(); } } + } - { - let d = crate::native_display().try_lock().unwrap(); - if d.quit_requested && !d.quit_ordered { - drop(d); - event_handler.quit_requested_event(); - let mut d = crate::native_display().try_lock().unwrap(); - if d.quit_requested { - d.quit_ordered = true - } + { + let d = crate::native_display().try_lock().unwrap(); + if d.quit_requested && !d.quit_ordered { + drop(d); + event_handler.quit_requested_event(); + let mut d = crate::native_display().try_lock().unwrap(); + if d.quit_requested { + d.quit_ordered = true } } + } - if !conf.platform.blocking_event_loop || display.update_requested { - display.update_requested = false; - event_handler.update(); - event_handler.draw(); - (libegl.eglSwapBuffers)(egl_display, egl_surface); - } + if !conf.platform.blocking_event_loop || display.update_requested { + display.update_requested = false; + event_handler.update(); + event_handler.draw(); + (libegl.eglSwapBuffers)(egl_display, egl_surface); } } } diff --git a/src/native/linux_wayland/drag_n_drop.rs b/src/native/linux_wayland/drag_n_drop.rs index c4f4428a..ff6c21a1 100644 --- a/src/native/linux_wayland/drag_n_drop.rs +++ b/src/native/linux_wayland/drag_n_drop.rs @@ -81,7 +81,7 @@ pub(super) unsafe extern "C" fn data_device_handle_drop( .data_offer_receive(display.display, data_offer, mime_type.as_ptr()); wl_request!(display.client, data_offer, WL_DATA_OFFER_FINISH); if let Ok(filenames) = String::from_utf8(bytes) { - EVENTS.push(WaylandEvent::FilesDropped(filenames)); + display.events.push(WaylandEvent::FilesDropped(filenames)); } } } From f4461bce602de32bb3d34a5dfc566904f695ae38 Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Wed, 26 Feb 2025 19:34:38 +0000 Subject: [PATCH 04/16] native/linux_wayland: Use libdecor --- src/native/linux_wayland.rs | 234 +++------- src/native/linux_wayland/decorations.rs | 398 ++++++++++-------- src/native/linux_wayland/extensions.rs | 1 + .../linux_wayland/extensions/libdecor.rs | 87 ++++ 4 files changed, 369 insertions(+), 351 deletions(-) create mode 100644 src/native/linux_wayland/extensions/libdecor.rs diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index c2ae1ff7..4adeea6a 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -39,7 +39,6 @@ struct WaylandPayload { subcompositor: *mut wl_subcompositor, xdg_toplevel: *mut extensions::xdg_shell::xdg_toplevel, xdg_wm_base: *mut extensions::xdg_shell::xdg_wm_base, - xdg_surface: *mut extensions::xdg_shell::xdg_surface, surface: *mut wl_surface, decoration_manager: *mut extensions::xdg_decoration::zxdg_decoration_manager_v1, viewporter: *mut extensions::viewporter::wp_viewporter, @@ -62,7 +61,6 @@ struct WaylandPayload { keyboard_context: KeyboardContext, drag_n_drop: drag_n_drop::WaylandDnD, update_requested: bool, - closed: bool, } impl WaylandPayload { @@ -691,70 +689,6 @@ unsafe extern "C" fn registry_remove_object( ) { } -unsafe extern "C" fn xdg_surface_handle_configure( - data: *mut std::ffi::c_void, - xdg_surface: *mut extensions::xdg_shell::xdg_surface, - serial: u32, -) { - assert!(!data.is_null()); - let payload: &mut WaylandPayload = &mut *(data as *mut _); - - wl_request!( - payload.client, - xdg_surface, - extensions::xdg_shell::xdg_surface::ack_configure, - serial - ); - wl_request!(payload.client, payload.surface, WL_SURFACE_COMMIT) -} - -unsafe extern "C" fn xdg_toplevel_handle_close( - data: *mut std::ffi::c_void, - _xdg_toplevel: *mut extensions::xdg_shell::xdg_toplevel, -) { - assert!(!data.is_null()); - let payload: &mut WaylandPayload = &mut *(data as *mut _); - payload.closed = true; -} - -unsafe extern "C" fn xdg_toplevel_handle_configure( - data: *mut std::ffi::c_void, - _toplevel: *mut extensions::xdg_shell::xdg_toplevel, - width: i32, - height: i32, - _states: *mut wl_array, -) { - assert!(!data.is_null()); - let payload: &mut WaylandPayload = &mut *(data as *mut _); - let mut d = crate::native_display().lock().unwrap(); - - if width != 0 && height != 0 { - let (egl_w, egl_h) = if payload.decorations.is_some() { - // Otherwise window will resize iteself on sway - // I have no idea why - ( - width - decorations::Decorations::WIDTH * 2, - height - decorations::Decorations::BAR_HEIGHT - decorations::Decorations::WIDTH, - ) - } else { - (width, height) - }; - (payload.egl.wl_egl_window_resize)(payload.egl_window, egl_w, egl_h, 0, 0); - - d.screen_width = width; - d.screen_height = height; - drop(d); - - if let Some(ref decorations) = payload.decorations { - decorations.resize(&mut payload.client, width, height); - } - - payload - .events - .push(WaylandEvent::Resize(width as _, height as _)); - } -} - unsafe extern "C" fn xdg_wm_base_handle_ping( data: *mut std::ffi::c_void, toplevel: *mut extensions::xdg_shell::xdg_wm_base, @@ -845,7 +779,6 @@ where subcompositor: std::ptr::null_mut(), xdg_toplevel: std::ptr::null_mut(), xdg_wm_base: std::ptr::null_mut(), - xdg_surface: std::ptr::null_mut(), surface: std::ptr::null_mut(), decoration_manager: std::ptr::null_mut(), viewporter: std::ptr::null_mut(), @@ -865,7 +798,6 @@ where keyboard_context: KeyboardContext::new(), drag_n_drop: Default::default(), update_requested: true, - closed: false, }; let (tx, rx) = std::sync::mpsc::channel(); @@ -881,6 +813,28 @@ where ); (display.client.wl_display_dispatch)(display.display); + display.data_device = wl_request_constructor!( + display.client, + display.data_device_manager, + WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, + display.client.wl_data_device_interface, + display.seat + ) as _; + assert!(!display.data_device.is_null()); + + let data_device_listener = wl_data_device_listener { + data_offer: Some(data_device_handle_data_offer), + enter: Some(drag_n_drop::data_device_handle_enter), + leave: Some(drag_n_drop::data_device_handle_leave), + motion: Some(drag_n_drop::data_device_handle_motion), + drop: Some(drag_n_drop::data_device_handle_drop), + selection: Some(clipboard::data_device_handle_selection), + }; + (display.client.wl_proxy_add_listener)( + display.data_device as _, + &data_device_listener as *const _ as _, + &mut display as *mut _ as _, + ); //assert!(!display.keymap.is_null()); //assert!(!display.xkb_state.is_null()); @@ -894,10 +848,6 @@ where &mut display as *mut _ as _, ); - if display.decoration_manager.is_null() && conf.platform.wayland_use_fallback_decorations { - eprintln!("Decoration manager not found, will draw fallback decorations"); - } - let mut libegl = egl::LibEgl::try_load().ok()?; let (context, config, egl_display) = egl::create_egl_context( &mut libegl, @@ -915,65 +865,6 @@ where ); assert!(!display.surface.is_null()); - display.xdg_surface = wl_request_constructor!( - display.client, - display.xdg_wm_base, - extensions::xdg_shell::xdg_wm_base::get_xdg_surface, - &extensions::xdg_shell::xdg_surface_interface, - display.surface - ); - assert!(!display.xdg_surface.is_null()); - - let xdg_surface_listener = extensions::xdg_shell::xdg_surface_listener { - configure: Some(xdg_surface_handle_configure), - }; - - (display.client.wl_proxy_add_listener)( - display.xdg_surface as _, - &xdg_surface_listener as *const _ as _, - &mut display as *mut _ as _, - ); - - display.xdg_toplevel = wl_request_constructor!( - display.client, - display.xdg_surface, - extensions::xdg_shell::xdg_surface::get_toplevel, - &extensions::xdg_shell::xdg_toplevel_interface - ); - assert!(!display.xdg_toplevel.is_null()); - - let xdg_toplevel_listener = extensions::xdg_shell::xdg_toplevel_listener { - configure: Some(xdg_toplevel_handle_configure), - close: Some(xdg_toplevel_handle_close), - }; - - (display.client.wl_proxy_add_listener)( - display.xdg_toplevel as _, - &xdg_toplevel_listener as *const _ as _, - &mut display as *mut _ as _, - ); - - let title = std::ffi::CString::new(conf.window_title.as_str()).unwrap(); - - wl_request!( - display.client, - display.xdg_toplevel, - extensions::xdg_shell::xdg_toplevel::set_title, - title.as_ptr() - ); - - let wm_class = std::ffi::CString::new(conf.platform.linux_wm_class).unwrap(); - - wl_request!( - display.client, - display.xdg_toplevel, - extensions::xdg_shell::xdg_toplevel::set_app_id, - wm_class.as_ptr() - ); - - wl_request!(display.client, display.surface, WL_SURFACE_COMMIT); - (display.client.wl_display_dispatch)(display.display); - display.egl_window = (display.egl.wl_egl_window_create)( display.surface as _, conf.window_width as _, @@ -999,74 +890,49 @@ where eprintln!("eglSwapInterval failed"); } - // For some reason, setting fullscreen before egl_window is created leads - // to segfault because wl_egl_window_create returns NULL. - if conf.fullscreen { - wl_request!( - display.client, - display.xdg_toplevel, - extensions::xdg_shell::xdg_toplevel::set_fullscreen, - std::ptr::null_mut::<*mut wl_output>() - ) - } - crate::native::gl::load_gl_funcs(|proc| { let name = std::ffi::CString::new(proc).unwrap(); (libegl.eglGetProcAddress)(name.as_ptr() as _) }); - if !display.decoration_manager.is_null() { - let server_decoration: *mut extensions::xdg_decoration::zxdg_toplevel_decoration_v1 = wl_request_constructor!( - display.client, - display.decoration_manager, - extensions::xdg_decoration::zxdg_decoration_manager_v1::get_toplevel_decoration, - &extensions::xdg_decoration::zxdg_toplevel_decoration_v1_interface, - display.xdg_toplevel + display.decorations = decorations::Decorations::new(&mut display); + assert!(!display.xdg_toplevel.is_null()); + + if let Some(ref mut decorations) = display.decorations { + decorations.set_title( + &mut display.client, + display.xdg_toplevel, + conf.window_title.as_str(), ); - assert!(!server_decoration.is_null()); + } + + let wm_class = std::ffi::CString::new(conf.platform.linux_wm_class).unwrap(); + wl_request!( + display.client, + display.xdg_toplevel, + extensions::xdg_shell::xdg_toplevel::set_app_id, + wm_class.as_ptr() + ); + // For some reason, setting fullscreen before egl_window is created leads + // to segfault because wl_egl_window_create returns NULL. + if conf.fullscreen { wl_request!( display.client, - server_decoration, - extensions::xdg_decoration::zxdg_toplevel_decoration_v1::set_mode, - extensions::xdg_decoration::ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE - ); - } else if conf.platform.wayland_use_fallback_decorations { - display.decorations = Some(decorations::Decorations::new( - &mut display, - conf.window_width, - conf.window_height, - )); + display.xdg_toplevel, + extensions::xdg_shell::xdg_toplevel::set_fullscreen, + ) } + wl_request!(display.client, display.surface, WL_SURFACE_COMMIT); + (display.client.wl_display_dispatch)(display.display); + (display.client.wl_display_dispatch)(display.display); + let mut event_handler = (f.take().unwrap())(); let (mut last_mouse_x, mut last_mouse_y) = (0.0, 0.0); - display.data_device = wl_request_constructor!( - display.client, - display.data_device_manager, - WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, - display.client.wl_data_device_interface, - display.seat - ) as _; - assert!(!display.data_device.is_null()); - - let data_device_listener = wl_data_device_listener { - data_offer: Some(data_device_handle_data_offer), - enter: Some(drag_n_drop::data_device_handle_enter), - leave: Some(drag_n_drop::data_device_handle_leave), - motion: Some(drag_n_drop::data_device_handle_motion), - drop: Some(drag_n_drop::data_device_handle_drop), - selection: Some(clipboard::data_device_handle_selection), - }; - (display.client.wl_proxy_add_listener)( - display.data_device as _, - &data_device_listener as *const _ as _, - &mut display as *mut _ as _, - ); - - while !(display.closed || crate::native_display().try_lock().unwrap().quit_ordered) { + while !crate::native_display().try_lock().unwrap().quit_ordered { while let Ok(request) = rx.try_recv() { match request { Request::SetFullscreen(full) => { diff --git a/src/native/linux_wayland/decorations.rs b/src/native/linux_wayland/decorations.rs index c42e2ef3..8410668d 100644 --- a/src/native/linux_wayland/decorations.rs +++ b/src/native/linux_wayland/decorations.rs @@ -1,196 +1,260 @@ //! Window decorations(borders, titlebards, close/minimize buttons) are optional -//! Most importantly they are not implemented on Gnome. -//! Gnome suggest linking with GTK and do lots of gnome-specific things to -//! get decorations working... +//! Most importantly they are not implemented on Gnome so we need to do some +//! extra work. //! -//! So this module is drawing some sort of a window border, just for GNOME -//! looks horrible, doesn't fit OS theme at all, but better than nothing +//! We use `libdecor` which is also used by `xwayland` so we will get the same +//! decorations as the X11 backend. If it's not available then no decorations +//! will be drawn at all. #![allow(static_mut_refs)] -use crate::{ - native::linux_wayland::{ - extensions::viewporter::{wp_viewport, wp_viewport_interface, wp_viewporter}, - libwayland_client::*, - shm, WaylandPayload, - }, - wl_request, wl_request_constructor, -}; - -pub struct Decoration { - pub surface: *mut wl_surface, - pub subsurface: *mut wl_subsurface, - pub viewport: *mut wp_viewport, -} +use super::*; +use crate::{wl_request, wl_request_constructor}; +use core::ffi::{c_char, c_int, c_void}; +use extensions::libdecor::*; +use extensions::xdg_shell::*; -pub(crate) struct Decorations { - buffer: *mut wl_buffer, - pub top_decoration: Decoration, - pub bottom_decoration: Decoration, - pub left_decoration: Decoration, - pub right_decoration: Decoration, +/// Window decorations, whether they should be done by the compositor or by us. +/// In the later case we use `libdecor` so it needs to be loaded. +/// If it's not available then no decorations will be drawn at all. +pub(super) enum Decorations { + Server, + Client { + libdecor: LibDecor, + context: *mut libdecor, + frame: *mut libdecor_frame, + }, } -#[allow(clippy::too_many_arguments)] -unsafe fn create_decoration( - display: &mut WaylandPayload, - compositor: *mut wl_compositor, - subcompositor: *mut wl_subcompositor, - parent: *mut wl_surface, - buffer: *mut wl_buffer, - x: i32, - y: i32, - w: i32, - h: i32, -) -> Decoration { - let surface = wl_request_constructor!( +// If we use client decorations, `libdecor` will handle the creation for us. +// So this is used for either server or no decorations. +unsafe fn create_xdg_toplevel(display: &mut WaylandPayload) { + let xdg_surface: *mut xdg_surface = wl_request_constructor!( display.client, - compositor, - WL_COMPOSITOR_CREATE_SURFACE, - display.client.wl_surface_interface, + display.xdg_wm_base, + extensions::xdg_shell::xdg_wm_base::get_xdg_surface, + &xdg_surface_interface, + display.surface ); - - let subsurface = wl_request_constructor!( - display.client, - subcompositor, - WL_SUBCOMPOSITOR_GET_SUBSURFACE, - display.client.wl_subsurface_interface, - surface, - parent + assert!(!xdg_surface.is_null()); + (display.client.wl_proxy_add_listener)( + xdg_surface as _, + &XDG_SURFACE_LISTENER as *const _ as _, + display as *mut _ as _, ); - wl_request!(display.client, subsurface, WL_SUBSURFACE_SET_POSITION, x, y); - - let viewport = wl_request_constructor!( + display.xdg_toplevel = wl_request_constructor!( display.client, - display.viewporter, - wp_viewporter::get_viewport, - &wp_viewport_interface, - surface + xdg_surface, + extensions::xdg_shell::xdg_surface::get_toplevel, + &extensions::xdg_shell::xdg_toplevel_interface + ); + assert!(!display.xdg_toplevel.is_null()); + (display.client.wl_proxy_add_listener)( + display.xdg_toplevel as _, + &XDG_TOPLEVEL_LISTENER as *const _ as _, + display as *mut _ as _, ); - - wl_request!(display.client, viewport, wp_viewport::set_destination, w, h); - wl_request!(display.client, surface, WL_SURFACE_ATTACH, buffer, 0, 0); - wl_request!(display.client, surface, WL_SURFACE_COMMIT); - - Decoration { - surface, - subsurface, - viewport, - } } impl Decorations { - pub const WIDTH: i32 = 2; - pub const BAR_HEIGHT: i32 = 15; - - pub(super) unsafe fn new(display: &mut WaylandPayload, width: i32, height: i32) -> Decorations { - let buffer = shm::create_shm_buffer( - &mut display.client, - display.shm, - 1, - 1, - &[200, 200, 200, 255], - ); + pub(super) fn new(display: &mut WaylandPayload) -> Option { + unsafe { + if display.decoration_manager.is_null() { + Decorations::try_client(display) + } else { + Some(Decorations::server(display)) + } + } + } - Decorations { - buffer, - top_decoration: create_decoration( - display, - display.compositor, - display.subcompositor, - display.surface, - buffer, - -Self::WIDTH, - -Self::BAR_HEIGHT, - width + Self::WIDTH * Self::WIDTH, - Self::BAR_HEIGHT, - ), - left_decoration: create_decoration( - display, - display.compositor, - display.subcompositor, - display.surface, - buffer, - -Self::WIDTH, - -Self::BAR_HEIGHT, - Self::WIDTH, - height + Self::BAR_HEIGHT, - ), - right_decoration: create_decoration( - display, - display.compositor, - display.subcompositor, - display.surface, - buffer, - width, - -Self::BAR_HEIGHT, - Self::WIDTH, - height + Self::BAR_HEIGHT, - ), - bottom_decoration: create_decoration( - display, - display.compositor, - display.subcompositor, - display.surface, - buffer, - -Self::WIDTH, - height, - width + Self::WIDTH, - Self::WIDTH, - ), + pub(super) unsafe fn set_title( + &mut self, + client: &mut LibWaylandClient, + xdg_toplevel: *mut xdg_toplevel, + title: &str, + ) { + let title = std::ffi::CString::new(title).unwrap(); + match self { + Decorations::Server => { + wl_request!( + client, + xdg_toplevel, + extensions::xdg_shell::xdg_toplevel::set_title, + title.as_ptr() + ); + } + Decorations::Client { + libdecor, frame, .. + } => { + (libdecor.libdecor_frame_set_title)(*frame, title.as_ptr()); + } } } - pub unsafe fn resize(&self, client: &mut LibWaylandClient, width: i32, height: i32) { - wl_request!( - client, - self.top_decoration.viewport, - wp_viewport::set_destination, - width, - Self::BAR_HEIGHT - ); - wl_request!(client, self.top_decoration.surface, WL_SURFACE_COMMIT); + unsafe fn server(display: &mut WaylandPayload) -> Self { + create_xdg_toplevel(display); - wl_request!( - client, - self.left_decoration.viewport, - wp_viewport::set_destination, - Self::WIDTH, - height + let server_decoration: *mut extensions::xdg_decoration::zxdg_toplevel_decoration_v1 = wl_request_constructor!( + display.client, + display.decoration_manager, + extensions::xdg_decoration::zxdg_decoration_manager_v1::get_toplevel_decoration, + &extensions::xdg_decoration::zxdg_toplevel_decoration_v1_interface, + display.xdg_toplevel ); - wl_request!(client, self.left_decoration.surface, WL_SURFACE_COMMIT); + assert!(!server_decoration.is_null()); wl_request!( - client, - self.right_decoration.subsurface, - WL_SUBSURFACE_SET_POSITION, - width - Self::WIDTH * 2, - -Self::BAR_HEIGHT - ); - wl_request!( - client, - self.right_decoration.viewport, - wp_viewport::set_destination, - Self::WIDTH, - height + display.client, + server_decoration, + extensions::xdg_decoration::zxdg_toplevel_decoration_v1::set_mode, + extensions::xdg_decoration::ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE ); - wl_request!(client, self.right_decoration.surface, WL_SURFACE_COMMIT); + Decorations::Server + } - wl_request!( - client, - self.bottom_decoration.subsurface, - WL_SUBSURFACE_SET_POSITION, - 0, - height - Self::BAR_HEIGHT - Self::WIDTH - ); - wl_request!( - client, - self.bottom_decoration.viewport, - wp_viewport::set_destination, - width - Self::WIDTH * 2, - Self::WIDTH - ); - wl_request!(client, self.bottom_decoration.surface, WL_SURFACE_COMMIT); + unsafe fn try_client(display: &mut WaylandPayload) -> Option { + if let Ok(libdecor) = LibDecor::try_load() { + let context = (libdecor.libdecor_new)(display.display, &mut LIBDECOR_INTERFACE as _); + let frame = (libdecor.libdecor_decorate)( + context, + display.surface, + &mut LIBDECOR_FRAME_INTERFACE as _, + display as *mut _ as _, + ); + (libdecor.libdecor_frame_map)(frame); + display.xdg_toplevel = (libdecor.libdecor_frame_get_xdg_toplevel)(frame); + assert!(!display.xdg_toplevel.is_null()); + Some(Decorations::Client { + libdecor, + context, + frame, + }) + } else { + // If we can't load `libdecor` we just create the `xdg_toplevel` and return `None` + create_xdg_toplevel(display); + None + } + } + + fn libdecor(&mut self) -> Option<&mut LibDecor> { + if let Decorations::Client { libdecor, .. } = self { + Some(libdecor) + } else { + None + } + } +} + +unsafe extern "C" fn xdg_surface_handle_configure( + data: *mut std::ffi::c_void, + xdg_surface: *mut extensions::xdg_shell::xdg_surface, + serial: u32, +) { + assert!(!data.is_null()); + let payload: &mut WaylandPayload = &mut *(data as *mut _); + + wl_request!( + payload.client, + xdg_surface, + extensions::xdg_shell::xdg_surface::ack_configure, + serial + ); + wl_request!(payload.client, payload.surface, WL_SURFACE_COMMIT) +} + +unsafe extern "C" fn handle_configure(data: *mut std::ffi::c_void, width: i32, height: i32) { + assert!(!data.is_null()); + let payload: &mut WaylandPayload = &mut *(data as *mut _); + + if width != 0 && height != 0 { + (payload.egl.wl_egl_window_resize)(payload.egl_window, width, height, 0, 0); + + let mut d = crate::native_display().lock().unwrap(); + d.screen_width = width; + d.screen_height = height; + drop(d); + + payload + .events + .push(WaylandEvent::Resize(width as _, height as _)); } } + +unsafe extern "C" fn xdg_toplevel_handle_configure( + data: *mut std::ffi::c_void, + _toplevel: *mut extensions::xdg_shell::xdg_toplevel, + width: i32, + height: i32, + _states: *mut wl_array, +) { + handle_configure(data, width, height); +} + +unsafe extern "C" fn libdecor_handle_frame_configure( + frame: *mut libdecor_frame, + configuration: *mut libdecor_configuration, + data: *mut c_void, +) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + let libdecor = display.decorations.as_mut().unwrap().libdecor().unwrap(); + + let mut width: c_int = 0; + let mut height: c_int = 0; + + if (libdecor.libdecor_configuration_get_content_size)( + configuration, + frame, + &mut width, + &mut height, + ) == 0 + { + // libdecor failed to retrieve the new dimension, so we use the old value + let d = crate::native_display().lock().unwrap(); + width = d.screen_width; + height = d.screen_height; + drop(d); + } + let state = (libdecor.libdecor_state_new)(width, height); + (libdecor.libdecor_frame_commit)(frame, state, configuration); + (libdecor.libdecor_state_free)(state); + + handle_configure(data, width, height); +} + +unsafe extern "C" fn xdg_toplevel_handle_close( + _data: *mut std::ffi::c_void, + _xdg_toplevel: *mut extensions::xdg_shell::xdg_toplevel, +) { + crate::native_display().try_lock().unwrap().quit_requested = true; +} + +unsafe extern "C" fn libdecor_handle_frame_close(_frame: *mut libdecor_frame, _data: *mut c_void) { + crate::native_display().try_lock().unwrap().quit_requested = true; +} + +unsafe extern "C" fn libdecor_handle_frame_commit(_frame: *mut libdecor_frame, _data: *mut c_void) { +} +unsafe extern "C" fn libdecor_handle_error( + _context: *mut libdecor, + _error: *mut libdecor_error, + message: *const c_char, +) { + let message = core::ffi::CStr::from_ptr(message).to_str().unwrap(); + eprintln!("{}", message); +} +static mut LIBDECOR_INTERFACE: libdecor_interface = libdecor_interface { + error: libdecor_handle_error, +}; +static mut LIBDECOR_FRAME_INTERFACE: libdecor_frame_interface = libdecor_frame_interface { + frame_configure: libdecor_handle_frame_configure, + frame_close: libdecor_handle_frame_close, + frame_commit: libdecor_handle_frame_commit, +}; +static mut XDG_TOPLEVEL_LISTENER: xdg_toplevel_listener = xdg_toplevel_listener { + configure: Some(xdg_toplevel_handle_configure), + close: Some(xdg_toplevel_handle_close), +}; +static mut XDG_SURFACE_LISTENER: xdg_surface_listener = xdg_surface_listener { + configure: Some(xdg_surface_handle_configure), +}; diff --git a/src/native/linux_wayland/extensions.rs b/src/native/linux_wayland/extensions.rs index a5105582..a8e014a1 100644 --- a/src/native/linux_wayland/extensions.rs +++ b/src/native/linux_wayland/extensions.rs @@ -1,5 +1,6 @@ #![allow(unused_variables, dead_code, non_upper_case_globals, static_mut_refs)] +pub mod libdecor; pub mod viewporter; pub mod xdg_decoration; pub mod xdg_shell; diff --git a/src/native/linux_wayland/extensions/libdecor.rs b/src/native/linux_wayland/extensions/libdecor.rs new file mode 100644 index 00000000..28eacef4 --- /dev/null +++ b/src/native/linux_wayland/extensions/libdecor.rs @@ -0,0 +1,87 @@ +#![allow(non_camel_case_types, dead_code)] + +use super::super::libwayland_client::*; +use super::xdg_shell::*; +use crate::declare_module; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct libdecor { + _unused: [u8; 0], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct libdecor_frame { + _unused: [u8; 0], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct libdecor_configuration { + _unused: [u8; 0], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct libdecor_state { + _unused: [u8; 0], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct libdecor_error { + _unused: [u8; 0], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct libdecor_interface { + pub error: unsafe extern "C" fn(*mut libdecor, *mut libdecor_error, *const c_char), +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct libdecor_frame_interface { + pub frame_configure: + unsafe extern "C" fn(*mut libdecor_frame, *mut libdecor_configuration, *mut c_void), + pub frame_close: unsafe extern "C" fn(*mut libdecor_frame, *mut c_void), + pub frame_commit: unsafe extern "C" fn(*mut libdecor_frame, *mut c_void), +} + +use core::ffi::{c_char, c_int, c_void}; + +declare_module! { + LibDecor, + "libdecor-0.so", + "libdecor-0.so.0", + ... + ... + pub fn libdecor_new(*mut wl_display, *mut libdecor_interface) -> *mut libdecor, + pub fn libdecor_decorate( + *mut libdecor, + *mut wl_surface, + *mut libdecor_frame_interface, + *mut c_void + ) -> *mut libdecor_frame, + pub fn libdecor_frame_set_app_id(*mut libdecor_frame, *const c_char), + pub fn libdecor_frame_set_title(*mut libdecor_frame, *const c_char), + pub fn libdecor_frame_map(*mut libdecor_frame), + pub fn libdecor_state_new(c_int, c_int) -> *mut libdecor_state, + pub fn libdecor_frame_commit( + *mut libdecor_frame, + *mut libdecor_state, + *mut libdecor_configuration, + ), + pub fn libdecor_state_free(*mut libdecor_state), + pub fn libdecor_configuration_get_content_size( + *mut libdecor_configuration, + *mut libdecor_frame, + *mut c_int, + *mut c_int, + ) -> c_int, + pub fn libdecor_frame_get_xdg_surface(*mut libdecor_frame) -> *mut xdg_surface, + pub fn libdecor_frame_get_xdg_toplevel(*mut libdecor_frame) -> *mut xdg_toplevel, + ... + ... +} From e5ce7be8dd931296af282b4039a16304f94d375d Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:38:04 +0000 Subject: [PATCH 05/16] native/linux_wayland: Fix key repeat --- src/native/linux_wayland.rs | 92 ++++++++++++---------- src/native/linux_wayland/keycodes.rs | 23 ++++-- src/native/linux_wayland/libxkbcommon.rs | 97 +++++++++++++++++++++++- 3 files changed, 160 insertions(+), 52 deletions(-) diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index 4adeea6a..4043d292 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -47,14 +47,13 @@ struct WaylandPayload { data_device_manager: *mut wl_data_device_manager, data_device: *mut wl_data_device, xkb_context: *mut xkb_context, - keymap: *mut xkb_keymap, xkb_state: *mut xkb_state, + keymap: XkbKeymap, egl_window: *mut wl_egl_window, pointer: *mut wl_pointer, keyboard: *mut wl_keyboard, focused_window: *mut wl_surface, - //xkb_state: xkb::XkbState, decorations: Option, events: Vec, @@ -105,10 +104,10 @@ impl WaylandPayload { n_bits as _ ); for _ in 0..count[0] { - self.keyboard_context.generate_events( + self.keyboard_context.generate_key_repeat_events( &mut self.xkb, + self.keymap.xkb_keymap, self.xkb_state, - true, &mut self.events, ); } @@ -141,6 +140,7 @@ impl Default for RepeatInfo { struct KeyboardContext { enter_serial: Option, repeat_info: RepeatInfo, + /// This is the actual key being sent by Wayland, not `keysym` or Miniquad `Keycode` repeated_key: Option, timerfd: core::ffi::c_int, keymods: KeyMods, @@ -193,22 +193,37 @@ impl KeyboardContext { } } } - unsafe fn generate_events( + unsafe fn generate_key_repeat_events( &self, - xkb: &mut LibXkbCommon, + libxkb: &mut LibXkbCommon, + xkb_keymap: *mut xkb_keymap, xkb_state: *mut xkb_state, - repeat: bool, events: &mut Vec, ) { if let Some(key) = self.repeated_key { - let keysym = (xkb.xkb_state_key_get_one_sym)(xkb_state, key + 8); - let keycode = keycodes::translate(keysym); - events.push(WaylandEvent::KeyDown(keycode, self.keymods, repeat)); - let chr = keycodes::keysym_to_unicode(xkb, keysym); - if chr > 0 { - if let Some(chr) = char::from_u32(chr as u32) { - events.push(WaylandEvent::Char(chr, self.keymods, repeat)); - } + self.generate_key_events(libxkb, xkb_keymap, xkb_state, key, true, events) + } + } + unsafe fn generate_key_events( + &self, + libxkb: &mut LibXkbCommon, + xkb_keymap: *mut xkb_keymap, + xkb_state: *mut xkb_state, + key: core::ffi::c_uint, + repeat: bool, + events: &mut Vec, + ) { + // The keycodes in Miniquad are obtained without modifiers + let keysym = libxkb.keymap_key_get_sym_without_mod(xkb_keymap, key + 8); + let keycode = keycodes::translate_keysym(keysym); + events.push(WaylandEvent::KeyDown(keycode, self.keymods, repeat)); + + // To obtain the underlying character, we do need to provide the modifiers + let keysym = (libxkb.xkb_state_key_get_one_sym)(xkb_state, key + 8); + let chr = (libxkb.xkb_keysym_to_utf32)(keysym); + if chr > 0 { + if let Some(chr) = char::from_u32(chr) { + events.push(WaylandEvent::Char(chr, self.keymods, repeat)); } } } @@ -330,8 +345,8 @@ unsafe extern "C" fn keyboard_handle_keymap( 0, ); assert!(map_shm != libc::MAP_FAILED); - (display.xkb.xkb_keymap_unref)(display.keymap); - display.keymap = (display.xkb.xkb_keymap_new_from_string)( + (display.xkb.xkb_keymap_unref)(display.keymap.xkb_keymap); + display.keymap.xkb_keymap = (display.xkb.xkb_keymap_new_from_string)( display.xkb_context, map_shm as *mut libc::FILE, 1, @@ -339,8 +354,9 @@ unsafe extern "C" fn keyboard_handle_keymap( ); libc::munmap(map_shm, size as usize); libc::close(fd); + display.keymap.cache_mod_indices(&mut display.xkb); (display.xkb.xkb_state_unref)(display.xkb_state); - display.xkb_state = (display.xkb.xkb_state_new)(display.keymap); + display.xkb_state = (display.xkb.xkb_state_new)(display.keymap.xkb_keymap); } unsafe extern "C" fn keyboard_handle_enter( data: *mut ::core::ffi::c_void, @@ -362,12 +378,9 @@ unsafe extern "C" fn keyboard_handle_leave( // Clear modifiers let display: &mut WaylandPayload = &mut *(data as *mut _); (display.xkb.xkb_state_update_mask)(display.xkb_state, 0, 0, 0, 0, 0, 0); + display.keyboard_context.keymods = KeyMods::default(); display.keyboard_context.repeated_key = None; display.keyboard_context.enter_serial = None; - display.keyboard_context.keymods.shift = false; - display.keyboard_context.keymods.ctrl = false; - display.keyboard_context.keymods.logo = false; - display.keyboard_context.keymods.alt = false; } unsafe extern "C" fn keyboard_handle_key( data: *mut ::core::ffi::c_void, @@ -377,37 +390,32 @@ unsafe extern "C" fn keyboard_handle_key( key: u32, state: wl_keyboard_key_state, ) { - use KeyCode::*; - let is_down = state == 1 || state == 2; let display: &mut WaylandPayload = &mut *(data as *mut _); + let libxkb = &mut display.xkb; + let xkb_keymap = display.keymap.xkb_keymap; + let xkb_state = display.xkb_state; // https://wayland-book.com/seat/keyboard.html // To translate this to an XKB scancode, you must add 8 to the evdev scancode. - let keysym = (display.xkb.xkb_state_key_get_one_sym)(display.xkb_state, key + 8); - let should_repeat = (display.xkb.xkb_keymap_key_repeats)(display.keymap, key + 8) == 1; - let keycode = keycodes::translate(keysym); - match keycode { - LeftShift | RightShift => display.keyboard_context.keymods.shift = is_down, - LeftControl | RightControl => display.keyboard_context.keymods.ctrl = is_down, - LeftAlt | RightAlt => display.keyboard_context.keymods.alt = is_down, - LeftSuper | RightSuper => display.keyboard_context.keymods.logo = is_down, - _ => {} - } + let keysym = libxkb.keymap_key_get_sym_without_mod(xkb_keymap, key + 8); + let keycode = keycodes::translate_keysym(keysym); + let keymods = display.keymap.get_keymods(libxkb, xkb_state); + display.keyboard_context.keymods = keymods; match state { 0 => { display.keyboard_context.track_key_up(key); - display.events.push(WaylandEvent::KeyUp( - keycode, - display.keyboard_context.keymods, - )); + display.events.push(WaylandEvent::KeyUp(keycode, keymods)); } 1 | 2 => { let repeat = state == 2; + let should_repeat = (libxkb.xkb_keymap_key_repeats)(xkb_keymap, key + 8) == 1; if !repeat && should_repeat { display.keyboard_context.track_key_down(key); } - display.keyboard_context.generate_events( - &mut display.xkb, - display.xkb_state, + display.keyboard_context.generate_key_events( + libxkb, + xkb_keymap, + xkb_state, + key, repeat, &mut display.events, ); @@ -787,7 +795,7 @@ where data_device_manager: std::ptr::null_mut(), data_device: std::ptr::null_mut(), xkb_context, - keymap: std::ptr::null_mut(), + keymap: Default::default(), xkb_state: std::ptr::null_mut(), egl_window: std::ptr::null_mut(), pointer: std::ptr::null_mut(), diff --git a/src/native/linux_wayland/keycodes.rs b/src/native/linux_wayland/keycodes.rs index 7b4dcf40..02d4ab5d 100644 --- a/src/native/linux_wayland/keycodes.rs +++ b/src/native/linux_wayland/keycodes.rs @@ -1,7 +1,22 @@ +//! There are quite a few different notions for keycodes. And most of them are `u32` so it does get +//! very confusing... +//! Basically +//! - Wayland server sends a scancode `key` of type `c_uint` +//! - `key + 8` becomes a `xkb` scancode of type `xkb_keycode_t` +//! - We feed this to `xkb` to get a `keysym` of type `xkb_keysym_t` +//! - The `keysym` can be modifier-dependent: `Shift + Key1` can be translated to either `Key1` +//! (without modifier) or `Exclam` (with modifier) +//! - We then feed the `keysym` to `translate_keysym` to get a Miniquad `Keycode` +//! +//! Note that the default Miniquad behavior is without modifier; there is not even a Keycode for +//! `Exclam`. So we must provide the unmodified `keysym` or we will get a `Keycode::Unknown`. +//! +//! On the other hand, the modified `keysym` is useful when we want to translate it into the +//! underlying character. use crate::event::KeyCode; -use crate::native::linux_wayland::libxkbcommon::LibXkbCommon; +use crate::native::linux_wayland::libxkbcommon::xkb_keysym_t; -pub fn translate(keysym: u32) -> KeyCode { +pub fn translate_keysym(keysym: xkb_keysym_t) -> KeyCode { // See xkbcommon/xkbcommon-keysyms.h match keysym { 65307 => KeyCode::Escape, @@ -127,7 +142,3 @@ pub fn translate(keysym: u32) -> KeyCode { _ => KeyCode::Unknown, } } - -pub unsafe extern "C" fn keysym_to_unicode(libxkbcommon: &mut LibXkbCommon, keysym: u32) -> i32 { - (libxkbcommon.xkb_keysym_to_utf32)(keysym) as i32 -} diff --git a/src/native/linux_wayland/libxkbcommon.rs b/src/native/linux_wayland/libxkbcommon.rs index 350e448f..fba6b47d 100644 --- a/src/native/linux_wayland/libxkbcommon.rs +++ b/src/native/linux_wayland/libxkbcommon.rs @@ -16,7 +16,16 @@ pub struct xkb_state { _unused: [u8; 0], } -use core::ffi::{c_int, c_uint}; +pub const XKB_STATE_MODS_EFFECTIVE: c_int = 1 << 3; +pub const XKB_MOD_NAME_SHIFT: &str = "Shift"; +pub const XKB_MOD_NAME_CTRL: &str = "Control"; +pub const XKB_MOD_NAME_ALT: &str = "Mod1"; +pub const XKB_MOD_NAME_LOGO: &str = "Mod4"; + +use core::ffi::{c_char, c_int, c_uint}; +pub type xkb_keycode_t = c_uint; +pub type xkb_keysym_t = c_uint; +pub type xkb_mod_index_t = c_uint; crate::declare_module!( LibXkbCommon, "libxkbcommon.so", @@ -29,12 +38,92 @@ crate::declare_module!( pub fn xkb_context_unref(*mut xkb_context), pub fn xkb_keymap_new_from_string(*mut xkb_context, *mut libc::FILE, c_int, c_int) -> *mut xkb_keymap, pub fn xkb_keymap_unref(*mut xkb_keymap), - pub fn xkb_keymap_key_repeats(*mut xkb_keymap, c_uint) -> c_int, + pub fn xkb_keymap_key_repeats(*mut xkb_keymap, xkb_keycode_t) -> c_int, pub fn xkb_state_new(*mut xkb_keymap) -> *mut xkb_state, pub fn xkb_state_unref(*mut xkb_state), - pub fn xkb_state_key_get_one_sym(*mut xkb_state, c_uint) -> c_uint, + pub fn xkb_state_key_get_one_sym(*mut xkb_state, xkb_keycode_t) -> xkb_keysym_t, + pub fn xkb_keymap_mod_get_index(*mut xkb_keymap, *const c_char) -> xkb_mod_index_t, + pub fn xkb_state_mod_index_is_active(*mut xkb_state, xkb_mod_index_t, c_int) -> c_int, pub fn xkb_state_update_mask(*mut xkb_state, c_uint, c_uint, c_uint, c_uint, c_uint, c_uint) -> c_int, - pub fn xkb_keysym_to_utf32(c_uint) -> c_uint, + pub fn xkb_keysym_to_utf32(xkb_keysym_t) -> c_uint, ... ... ); + +impl LibXkbCommon { + // The keycodes in Miniquad are obtained without modifiers (for example, `Shift + Key1` is + // translated to `Key1` and not `Exclam`) + pub unsafe fn keymap_key_get_sym_without_mod( + &mut self, + keymap: *mut xkb_keymap, + keycode: xkb_keycode_t, + ) -> xkb_keysym_t { + let xkb_state = (self.xkb_state_new)(keymap); + let keysym = (self.xkb_state_key_get_one_sym)(xkb_state, keycode); + (self.xkb_state_unref)(xkb_state); + keysym + } +} + +pub mod libxkbcommon_ex { + use super::*; + use crate::KeyMods; + + /// In `xkb` the modifier indices are tied to a particular `xkb_keymap` and not hardcoded. + #[derive(Copy, Clone)] + pub struct XkbKeymap { + pub xkb_keymap: *mut xkb_keymap, + shift: xkb_mod_index_t, + ctrl: xkb_mod_index_t, + alt: xkb_mod_index_t, + logo: xkb_mod_index_t, + } + + impl Default for XkbKeymap { + fn default() -> Self { + XkbKeymap { + xkb_keymap: std::ptr::null_mut(), + shift: 0, + ctrl: 0, + alt: 0, + logo: 0, + } + } + } + + impl XkbKeymap { + pub unsafe fn cache_mod_indices(&mut self, libxkb: &mut LibXkbCommon) { + let shift = std::ffi::CString::new(XKB_MOD_NAME_SHIFT).unwrap(); + self.shift = (libxkb.xkb_keymap_mod_get_index)(self.xkb_keymap, shift.as_ptr()); + let ctrl = std::ffi::CString::new(XKB_MOD_NAME_CTRL).unwrap(); + self.ctrl = (libxkb.xkb_keymap_mod_get_index)(self.xkb_keymap, ctrl.as_ptr()); + let alt = std::ffi::CString::new(XKB_MOD_NAME_ALT).unwrap(); + self.alt = (libxkb.xkb_keymap_mod_get_index)(self.xkb_keymap, alt.as_ptr()); + let logo = std::ffi::CString::new(XKB_MOD_NAME_LOGO).unwrap(); + self.logo = (libxkb.xkb_keymap_mod_get_index)(self.xkb_keymap, logo.as_ptr()); + } + pub unsafe fn get_keymods( + &self, + libxkb: &mut LibXkbCommon, + xkb_state: *mut xkb_state, + ) -> KeyMods { + let mut mods = KeyMods::default(); + let is_active = libxkb.xkb_state_mod_index_is_active; + if (is_active)(xkb_state, self.shift, XKB_STATE_MODS_EFFECTIVE) == 1 { + mods.shift = true; + } + if (is_active)(xkb_state, self.ctrl, XKB_STATE_MODS_EFFECTIVE) == 1 { + mods.ctrl = true; + } + if (is_active)(xkb_state, self.alt, XKB_STATE_MODS_EFFECTIVE) == 1 { + mods.alt = true; + } + if (is_active)(xkb_state, self.logo, XKB_STATE_MODS_EFFECTIVE) == 1 { + mods.logo = true; + } + mods + } + } +} + +pub use libxkbcommon_ex::XkbKeymap; From 1751fb02ce5f33649c7b8663fdc3c9e9709dacf2 Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:38:13 +0000 Subject: [PATCH 06/16] native/linux_wayland: Refactor - Make the event handlers of listeners not `Option` anymore. - Let `wl_listener` macro write all the dummy event handlers - High-dpi support - Construct all the `wl_proxy` in `registry_add_object` (except the `wl_data_device`) --- src/native/linux_wayland.rs | 253 ++++++------------ src/native/linux_wayland/clipboard.rs | 11 +- src/native/linux_wayland/decorations.rs | 30 ++- src/native/linux_wayland/drag_n_drop.rs | 9 - src/native/linux_wayland/extensions.rs | 2 +- .../linux_wayland/extensions/libdecor.rs | 6 +- .../linux_wayland/extensions/xdg_shell.rs | 49 ++-- src/native/linux_wayland/libwayland_client.rs | 68 ++++- 8 files changed, 197 insertions(+), 231 deletions(-) diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index 4043d292..e585618d 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -36,12 +36,10 @@ struct WaylandPayload { egl: LibWaylandEgl, xkb: LibXkbCommon, compositor: *mut wl_compositor, - subcompositor: *mut wl_subcompositor, xdg_toplevel: *mut extensions::xdg_shell::xdg_toplevel, xdg_wm_base: *mut extensions::xdg_shell::xdg_wm_base, surface: *mut wl_surface, decoration_manager: *mut extensions::xdg_decoration::zxdg_decoration_manager_v1, - viewporter: *mut extensions::viewporter::wp_viewporter, shm: *mut wl_shm, seat: *mut wl_seat, data_device_manager: *mut wl_data_device_manager, @@ -265,10 +263,14 @@ macro_rules! wl_request { }}; } -static mut SEAT_LISTENER: wl_seat_listener = wl_seat_listener { - capabilities: Some(seat_handle_capabilities), - name: Some(seat_handle_name), -}; +static mut SEAT_LISTENER: wl_seat_listener = wl_seat_listener::dummy(); +static mut KEYBOARD_LISTENER: wl_keyboard_listener = wl_keyboard_listener::dummy(); +static mut POINTER_LISTENER: wl_pointer_listener = wl_pointer_listener::dummy(); +static mut OUTPUT_LISTENER: wl_output_listener = wl_output_listener::dummy(); +static mut DATA_DEVICE_LISTENER: wl_data_device_listener = wl_data_device_listener::dummy(); +static mut DATA_OFFER_LISTENER: wl_data_offer_listener = wl_data_offer_listener::dummy(); +static mut XDG_WM_BASE_LISTENER: extensions::xdg_shell::xdg_wm_base_listener = + extensions::xdg_shell::xdg_wm_base_listener::dummy(); unsafe extern "C" fn seat_handle_capabilities( data: *mut std::ffi::c_void, @@ -285,6 +287,10 @@ unsafe extern "C" fn seat_handle_capabilities( display.client.wl_pointer_interface ); assert!(!display.pointer.is_null()); + POINTER_LISTENER.enter = pointer_handle_enter; + POINTER_LISTENER.axis = pointer_handle_axis; + POINTER_LISTENER.motion = pointer_handle_motion; + POINTER_LISTENER.button = pointer_handle_button; (display.client.wl_proxy_add_listener)( display.pointer as _, &POINTER_LISTENER as *const _ as _, @@ -300,6 +306,12 @@ unsafe extern "C" fn seat_handle_capabilities( display.client.wl_keyboard_interface ); assert!(!display.keyboard.is_null()); + KEYBOARD_LISTENER.enter = keyboard_handle_enter; + KEYBOARD_LISTENER.keymap = keyboard_handle_keymap; + KEYBOARD_LISTENER.repeat_info = keyboard_handle_repeat_info; + KEYBOARD_LISTENER.key = keyboard_handle_key; + KEYBOARD_LISTENER.modifiers = keyboard_handle_modifiers; + KEYBOARD_LISTENER.leave = keyboard_handle_leave; (display.client.wl_proxy_add_listener)( display.keyboard as _, &KEYBOARD_LISTENER as *const _ as _, @@ -319,15 +331,6 @@ enum WaylandEvent { Resize(f32, f32), } -static mut KEYBOARD_LISTENER: wl_keyboard_listener = wl_keyboard_listener { - keymap: Some(keyboard_handle_keymap), - enter: Some(keyboard_handle_enter), - leave: Some(keyboard_handle_leave), - key: Some(keyboard_handle_key), - modifiers: Some(keyboard_handle_modifiers), - repeat_info: Some(keyboard_handle_repeat_info), -}; - unsafe extern "C" fn keyboard_handle_keymap( data: *mut ::core::ffi::c_void, _wl_keyboard: *mut wl_keyboard, @@ -462,20 +465,6 @@ unsafe extern "C" fn keyboard_handle_repeat_info( }; } -static mut POINTER_LISTENER: wl_pointer_listener = wl_pointer_listener { - enter: Some(pointer_handle_enter), - leave: Some(pointer_handle_leave), - motion: Some(pointer_handle_motion), - button: Some(pointer_handle_button), - axis: Some(pointer_handle_axis), - frame: Some(pointer_handle_frame), - axis_source: Some(pointer_handle_axis_source), - axis_stop: Some(pointer_handle_axis_stop), - axis_discrete: Some(pointer_handle_axis_discrete), - axis_value120: Some(pointer_handle_axis_value120), - axis_relative_direction: Some(pointer_handle_axis_relative_direction), -}; - unsafe extern "C" fn pointer_handle_enter( data: *mut ::core::ffi::c_void, _wl_pointer: *mut wl_pointer, @@ -487,13 +476,6 @@ unsafe extern "C" fn pointer_handle_enter( let display: &mut WaylandPayload = &mut *(data as *mut _); display.focused_window = surface; } -unsafe extern "C" fn pointer_handle_leave( - _data: *mut ::core::ffi::c_void, - _wl_pointer: *mut wl_pointer, - _serial: u32, - _surface: *mut wl_surface, -) { -} unsafe extern "C" fn pointer_handle_motion( data: *mut ::core::ffi::c_void, _wl_pointer: *mut wl_pointer, @@ -504,7 +486,9 @@ unsafe extern "C" fn pointer_handle_motion( let display: &mut WaylandPayload = &mut *(data as *mut _); if display.focused_window == display.surface { // From wl_fixed_to_double(), it simply divides by 256 - let (x, y) = (wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)); + let d = crate::native_display().lock().unwrap(); + let x = wl_fixed_to_double(surface_x) * d.dpi_scale; + let y = wl_fixed_to_double(surface_y) * d.dpi_scale; display.events.push(WaylandEvent::PointerMotion(x, y)); } } @@ -553,51 +537,23 @@ unsafe extern "C" fn pointer_handle_axis( display.events.push(WaylandEvent::PointerAxis(value, 0.0)); } } -unsafe extern "C" fn pointer_handle_frame( - _data: *mut ::core::ffi::c_void, - _wl_pointer: *mut wl_pointer, -) { -} -unsafe extern "C" fn pointer_handle_axis_source( - _data: *mut ::core::ffi::c_void, - _wl_pointer: *mut wl_pointer, - _axis_source: u32, -) { -} -unsafe extern "C" fn pointer_handle_axis_stop( - _data: *mut ::core::ffi::c_void, - _wl_pointer: *mut wl_pointer, - _time: u32, - _axis: u32, -) { -} -unsafe extern "C" fn pointer_handle_axis_discrete( - _data: *mut ::core::ffi::c_void, - _wl_pointer: *mut wl_pointer, - _axis: u32, - _discrete: i32, -) { -} -unsafe extern "C" fn pointer_handle_axis_value120( - _data: *mut ::core::ffi::c_void, - _wl_pointer: *mut wl_pointer, - _axis: u32, - _value120: i32, -) { -} -unsafe extern "C" fn pointer_handle_axis_relative_direction( - _data: *mut ::core::ffi::c_void, - _wl_pointer: *mut wl_pointer, - _axis: u32, - _direction: u32, -) { -} -extern "C" fn seat_handle_name( - _data: *mut std::ffi::c_void, - _seat: *mut wl_seat, - _name: *const ::core::ffi::c_char, +unsafe extern "C" fn output_handle_scale( + data: *mut std::ffi::c_void, + _: *mut wl_output, + factor: core::ffi::c_int, ) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + let mut d = crate::native_display().try_lock().unwrap(); + if d.high_dpi { + d.dpi_scale = factor as _; + wl_request!( + display.client, + display.surface, + WL_SURFACE_SET_BUFFER_SCALE, + factor + ); + } } unsafe extern "C" fn registry_add_object( @@ -611,23 +567,36 @@ unsafe extern "C" fn registry_add_object( let interface = std::ffi::CStr::from_ptr(interface).to_str().unwrap(); match interface { - "wl_compositor" => { - display.compositor = display.client.wl_registry_bind( + "wl_output" => { + let wl_output: *mut wl_output = display.client.wl_registry_bind( registry, name, - display.client.wl_compositor_interface, - 1, + display.client.wl_output_interface, + 3.min(version), ) as _; - assert!(!display.compositor.is_null()); + assert!(!wl_output.is_null()); + OUTPUT_LISTENER.scale = output_handle_scale; + (display.client.wl_proxy_add_listener)( + wl_output as _, + &OUTPUT_LISTENER as *const _ as _, + display as *mut _ as _, + ); } - "wl_subcompositor" => { - display.subcompositor = display.client.wl_registry_bind( + "wl_compositor" => { + display.compositor = display.client.wl_registry_bind( registry, name, - display.client.wl_subcompositor_interface, - 1, + display.client.wl_compositor_interface, + 3.min(version), ) as _; - assert!(!display.subcompositor.is_null()); + assert!(!display.compositor.is_null()); + display.surface = wl_request_constructor!( + display.client, + display.compositor, + WL_COMPOSITOR_CREATE_SURFACE, + display.client.wl_surface_interface + ); + assert!(!display.surface.is_null()); } "xdg_wm_base" => { display.xdg_wm_base = display.client.wl_registry_bind( @@ -637,6 +606,12 @@ unsafe extern "C" fn registry_add_object( 1, ) as _; assert!(!display.xdg_wm_base.is_null()); + XDG_WM_BASE_LISTENER.ping = xdg_wm_base_handle_ping; + (display.client.wl_proxy_add_listener)( + display.xdg_wm_base as _, + &XDG_WM_BASE_LISTENER as *const _ as _, + display as *mut _ as _, + ); } "zxdg_decoration_manager" | "zxdg_decoration_manager_v1" => { display.decoration_manager = display.client.wl_registry_bind( @@ -646,14 +621,6 @@ unsafe extern "C" fn registry_add_object( 1, ) as _; } - "wp_viewporter" => { - display.viewporter = display.client.wl_registry_bind( - registry, - name, - &extensions::viewporter::wp_viewporter_interface, - 1, - ) as _; - } "wl_shm" => { display.shm = display @@ -670,6 +637,7 @@ unsafe extern "C" fn registry_add_object( seat_version, ) as _; assert!(!display.seat.is_null()); + SEAT_LISTENER.capabilities = seat_handle_capabilities; (display.client.wl_proxy_add_listener)( display.seat as _, &SEAT_LISTENER as *const _ as _, @@ -690,13 +658,6 @@ unsafe extern "C" fn registry_add_object( } } -unsafe extern "C" fn registry_remove_object( - _data: *mut std::ffi::c_void, - _registry: *mut wl_registry, - _name: u32, -) { -} - unsafe extern "C" fn xdg_wm_base_handle_ping( data: *mut std::ffi::c_void, toplevel: *mut extensions::xdg_shell::xdg_wm_base, @@ -713,31 +674,12 @@ unsafe extern "C" fn xdg_wm_base_handle_ping( ); } -static mut DATA_OFFER_LISTENER: wl_data_offer_listener = wl_data_offer_listener { - offer: Some(data_offer_handle_offer), - source_actions: Some(drag_n_drop::data_offer_handle_source_actions), - action: Some(data_offer_handle_action), -}; - -unsafe extern "C" fn data_offer_handle_offer( - _data: *mut ::core::ffi::c_void, - _data_offer: *mut wl_data_offer, - _mime_type: *const ::core::ffi::c_char, -) { -} - -unsafe extern "C" fn data_offer_handle_action( - _data: *mut ::core::ffi::c_void, - _data_offer: *mut wl_data_offer, - _action: ::core::ffi::c_uint, -) { -} - unsafe extern "C" fn data_device_handle_data_offer( data: *mut ::core::ffi::c_void, data_device: *mut wl_data_device, data_offer: *mut wl_data_offer, ) { + DATA_OFFER_LISTENER.source_actions = drag_n_drop::data_offer_handle_source_actions; let display: &mut WaylandPayload = &mut *(data as *mut _); assert_eq!(data_device, display.data_device); (display.client.wl_proxy_add_listener)( @@ -770,11 +712,6 @@ where ); assert!(!registry.is_null()); - let registry_listener = wl_registry_listener { - global: Some(registry_add_object), - global_remove: Some(registry_remove_object), - }; - let xkb_context = (xkb.xkb_context_new)(0); let mut display = WaylandPayload { @@ -784,12 +721,10 @@ where egl, xkb, compositor: std::ptr::null_mut(), - subcompositor: std::ptr::null_mut(), xdg_toplevel: std::ptr::null_mut(), xdg_wm_base: std::ptr::null_mut(), surface: std::ptr::null_mut(), decoration_manager: std::ptr::null_mut(), - viewporter: std::ptr::null_mut(), shm: std::ptr::null_mut(), seat: std::ptr::null_mut(), data_device_manager: std::ptr::null_mut(), @@ -808,12 +743,8 @@ where update_requested: true, }; - let (tx, rx) = std::sync::mpsc::channel(); - let clipboard = Box::new(clipboard::WaylandClipboard::new(&mut display as *mut _)); - crate::set_display(NativeDisplayData { - ..NativeDisplayData::new(conf.window_width, conf.window_height, tx, clipboard) - }); - + let mut registry_listener = wl_registry_listener::dummy(); + registry_listener.global = registry_add_object; (display.client.wl_proxy_add_listener)( display.registry as _, ®istry_listener as *const _ as _, @@ -821,6 +752,8 @@ where ); (display.client.wl_display_dispatch)(display.display); + // Construct the `wl_data_device` here, as it needs both the `wl_seat` and the + // `wl_data_device_manager`, and they may be constructed in different orders display.data_device = wl_request_constructor!( display.client, display.data_device_manager, @@ -829,33 +762,28 @@ where display.seat ) as _; assert!(!display.data_device.is_null()); - - let data_device_listener = wl_data_device_listener { - data_offer: Some(data_device_handle_data_offer), - enter: Some(drag_n_drop::data_device_handle_enter), - leave: Some(drag_n_drop::data_device_handle_leave), - motion: Some(drag_n_drop::data_device_handle_motion), - drop: Some(drag_n_drop::data_device_handle_drop), - selection: Some(clipboard::data_device_handle_selection), - }; + DATA_DEVICE_LISTENER.data_offer = data_device_handle_data_offer; + DATA_DEVICE_LISTENER.enter = drag_n_drop::data_device_handle_enter; + DATA_DEVICE_LISTENER.leave = drag_n_drop::data_device_handle_leave; + DATA_DEVICE_LISTENER.drop = drag_n_drop::data_device_handle_drop; + DATA_DEVICE_LISTENER.selection = clipboard::data_device_handle_selection; (display.client.wl_proxy_add_listener)( display.data_device as _, - &data_device_listener as *const _ as _, + &DATA_DEVICE_LISTENER as *const _ as _, &mut display as *mut _ as _, ); + + let (tx, rx) = std::sync::mpsc::channel(); + let clipboard = Box::new(clipboard::WaylandClipboard::new(&mut display as *mut _)); + crate::set_display(NativeDisplayData { + high_dpi: conf.high_dpi, + dpi_scale: 1., + blocking_event_loop: conf.platform.blocking_event_loop, + ..NativeDisplayData::new(conf.window_width, conf.window_height, tx, clipboard) + }); //assert!(!display.keymap.is_null()); //assert!(!display.xkb_state.is_null()); - let xdg_wm_base_listener = extensions::xdg_shell::xdg_wm_base_listener { - ping: Some(xdg_wm_base_handle_ping), - }; - - (display.client.wl_proxy_add_listener)( - display.xdg_wm_base as _, - &xdg_wm_base_listener as *const _ as _, - &mut display as *mut _ as _, - ); - let mut libegl = egl::LibEgl::try_load().ok()?; let (context, config, egl_display) = egl::create_egl_context( &mut libegl, @@ -865,14 +793,6 @@ where ) .unwrap(); - display.surface = wl_request_constructor!( - display.client, - display.compositor, - WL_COMPOSITOR_CREATE_SURFACE, - display.client.wl_surface_interface - ); - assert!(!display.surface.is_null()); - display.egl_window = (display.egl.wl_egl_window_create)( display.surface as _, conf.window_width as _, @@ -949,7 +869,6 @@ where display.client, display.xdg_toplevel, extensions::xdg_shell::xdg_toplevel::set_fullscreen, - std::ptr::null_mut::<*mut wl_output>() ); } else { wl_request!( diff --git a/src/native/linux_wayland/clipboard.rs b/src/native/linux_wayland/clipboard.rs index 22676447..7f1e7a28 100644 --- a/src/native/linux_wayland/clipboard.rs +++ b/src/native/linux_wayland/clipboard.rs @@ -96,6 +96,8 @@ impl ClipboardContext { display.client.wl_data_source_interface, ); assert!(!data_source.is_null()); + DATA_SOURCE_LISTENER.send = data_source_handle_send; + DATA_SOURCE_LISTENER.cancelled = data_source_handle_cancelled; (display.client.wl_proxy_add_listener)( data_source as _, &DATA_SOURCE_LISTENER as *const _ as _, @@ -106,14 +108,7 @@ impl ClipboardContext { } } -static mut DATA_SOURCE_LISTENER: wl_data_source_listener = wl_data_source_listener { - target: None, - send: Some(data_source_handle_send), - cancelled: Some(data_source_handle_cancelled), - dnd_drop_performed: None, - dnd_finished: None, - action: None, -}; +static mut DATA_SOURCE_LISTENER: wl_data_source_listener = wl_data_source_listener::dummy(); // some app (could be ourself) is requesting the owned clipboard unsafe extern "C" fn data_source_handle_send( diff --git a/src/native/linux_wayland/decorations.rs b/src/native/linux_wayland/decorations.rs index 8410668d..451df69a 100644 --- a/src/native/linux_wayland/decorations.rs +++ b/src/native/linux_wayland/decorations.rs @@ -168,16 +168,18 @@ unsafe extern "C" fn handle_configure(data: *mut std::ffi::c_void, width: i32, h let payload: &mut WaylandPayload = &mut *(data as *mut _); if width != 0 && height != 0 { - (payload.egl.wl_egl_window_resize)(payload.egl_window, width, height, 0, 0); - let mut d = crate::native_display().lock().unwrap(); - d.screen_width = width; - d.screen_height = height; + let screen_width = ((width as f32) * d.dpi_scale) as i32; + let screen_height = ((height as f32) * d.dpi_scale) as i32; + // screen_width / screen_height are the actual numbers of pixels + d.screen_width = screen_width; + d.screen_height = screen_height; drop(d); + (payload.egl.wl_egl_window_resize)(payload.egl_window, screen_width, screen_height, 0, 0); payload .events - .push(WaylandEvent::Resize(width as _, height as _)); + .push(WaylandEvent::Resize(screen_width as _, screen_height as _)); } } @@ -191,7 +193,7 @@ unsafe extern "C" fn xdg_toplevel_handle_configure( handle_configure(data, width, height); } -unsafe extern "C" fn libdecor_handle_frame_configure( +unsafe extern "C" fn libdecor_frame_handle_configure( frame: *mut libdecor_frame, configuration: *mut libdecor_configuration, data: *mut c_void, @@ -229,11 +231,11 @@ unsafe extern "C" fn xdg_toplevel_handle_close( crate::native_display().try_lock().unwrap().quit_requested = true; } -unsafe extern "C" fn libdecor_handle_frame_close(_frame: *mut libdecor_frame, _data: *mut c_void) { +unsafe extern "C" fn libdecor_frame_handle_close(_frame: *mut libdecor_frame, _data: *mut c_void) { crate::native_display().try_lock().unwrap().quit_requested = true; } -unsafe extern "C" fn libdecor_handle_frame_commit(_frame: *mut libdecor_frame, _data: *mut c_void) { +unsafe extern "C" fn libdecor_frame_handle_commit(_frame: *mut libdecor_frame, _data: *mut c_void) { } unsafe extern "C" fn libdecor_handle_error( _context: *mut libdecor, @@ -247,14 +249,14 @@ static mut LIBDECOR_INTERFACE: libdecor_interface = libdecor_interface { error: libdecor_handle_error, }; static mut LIBDECOR_FRAME_INTERFACE: libdecor_frame_interface = libdecor_frame_interface { - frame_configure: libdecor_handle_frame_configure, - frame_close: libdecor_handle_frame_close, - frame_commit: libdecor_handle_frame_commit, + configure: libdecor_frame_handle_configure, + close: libdecor_frame_handle_close, + commit: libdecor_frame_handle_commit, }; static mut XDG_TOPLEVEL_LISTENER: xdg_toplevel_listener = xdg_toplevel_listener { - configure: Some(xdg_toplevel_handle_configure), - close: Some(xdg_toplevel_handle_close), + configure: xdg_toplevel_handle_configure, + close: xdg_toplevel_handle_close, }; static mut XDG_SURFACE_LISTENER: xdg_surface_listener = xdg_surface_listener { - configure: Some(xdg_surface_handle_configure), + configure: xdg_surface_handle_configure, }; diff --git a/src/native/linux_wayland/drag_n_drop.rs b/src/native/linux_wayland/drag_n_drop.rs index ff6c21a1..ad8e16e4 100644 --- a/src/native/linux_wayland/drag_n_drop.rs +++ b/src/native/linux_wayland/drag_n_drop.rs @@ -48,15 +48,6 @@ pub(super) unsafe extern "C" fn data_device_handle_enter( ); } -pub(super) unsafe extern "C" fn data_device_handle_motion( - _data: *mut ::core::ffi::c_void, - _data_device: *mut wl_data_device, - _time: core::ffi::c_uint, - _surface_x: i32, - _surface_y: i32, -) { -} - pub(super) unsafe extern "C" fn data_device_handle_leave( data: *mut ::core::ffi::c_void, data_device: *mut wl_data_device, diff --git a/src/native/linux_wayland/extensions.rs b/src/native/linux_wayland/extensions.rs index a8e014a1..08cdf347 100644 --- a/src/native/linux_wayland/extensions.rs +++ b/src/native/linux_wayland/extensions.rs @@ -1,7 +1,7 @@ #![allow(unused_variables, dead_code, non_upper_case_globals, static_mut_refs)] pub mod libdecor; -pub mod viewporter; +// pub mod viewporter; pub mod xdg_decoration; pub mod xdg_shell; diff --git a/src/native/linux_wayland/extensions/libdecor.rs b/src/native/linux_wayland/extensions/libdecor.rs index 28eacef4..1ebbd381 100644 --- a/src/native/linux_wayland/extensions/libdecor.rs +++ b/src/native/linux_wayland/extensions/libdecor.rs @@ -43,10 +43,10 @@ pub struct libdecor_interface { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct libdecor_frame_interface { - pub frame_configure: + pub configure: unsafe extern "C" fn(*mut libdecor_frame, *mut libdecor_configuration, *mut c_void), - pub frame_close: unsafe extern "C" fn(*mut libdecor_frame, *mut c_void), - pub frame_commit: unsafe extern "C" fn(*mut libdecor_frame, *mut c_void), + pub close: unsafe extern "C" fn(*mut libdecor_frame, *mut c_void), + pub commit: unsafe extern "C" fn(*mut libdecor_frame, *mut c_void), } use core::ffi::{c_char, c_int, c_void}; diff --git a/src/native/linux_wayland/extensions/xdg_shell.rs b/src/native/linux_wayland/extensions/xdg_shell.rs index e261ce54..2764a636 100644 --- a/src/native/linux_wayland/extensions/xdg_shell.rs +++ b/src/native/linux_wayland/extensions/xdg_shell.rs @@ -87,31 +87,28 @@ wayland_interface!( [("configure", "iiii"), ("popup_done", "")] ); -#[derive(Copy, Clone)] -#[repr(C)] -pub(crate) struct xdg_wm_base_listener { - pub ping: - Option ()>, -} +crate::wl_listener!( + xdg_wm_base_listener, + xdg_wm_base, + xdg_wm_base_dummy, + fn ping(serial: core::ffi::c_uint), +); -#[derive(Copy, Clone)] -#[repr(C)] -pub(crate) struct xdg_surface_listener { - pub configure: - Option ()>, -} +crate::wl_listener!( + xdg_surface_listener, + xdg_surface, + xdg_surface_dummy, + fn configure(serial: core::ffi::c_uint), +); -#[derive(Copy, Clone)] -#[repr(C)] -pub(crate) struct xdg_toplevel_listener { - pub configure: Option< - unsafe extern "C" fn( - _: *mut std::ffi::c_void, - _: *mut xdg_toplevel, - _: i32, - _: i32, - _: *mut wl_array, - ) -> (), - >, - pub close: Option ()>, -} +crate::wl_listener!( + xdg_toplevel_listener, + xdg_toplevel, + xdg_toplevel_dummy, + fn configure( + width: core::ffi::c_int, + height: core::ffi::c_int, + states: *mut wl_array, + ), + fn close(), +); diff --git a/src/native/linux_wayland/libwayland_client.rs b/src/native/linux_wayland/libwayland_client.rs index 7991d2d9..26e84e77 100644 --- a/src/native/linux_wayland/libwayland_client.rs +++ b/src/native/linux_wayland/libwayland_client.rs @@ -360,13 +360,27 @@ pub struct wl_interface { #[macro_export] macro_rules! wl_listener { - ($name_listener:ident, $name:ident, - $(fn $event_name:ident($($arg_name:ident: $arg_ty:ty),*$(,)?)),*$(,)? + ($name_listener:ident, $name:ident, $name_dummy:ident, + $(fn $event:ident($($arg_name:ident: $arg_ty:ty),*$(,)?)),*$(,)? ) => { #[repr(C)] #[derive(Debug, Copy, Clone)] + /// Wayland event listener pub struct $name_listener { - $(pub $event_name: Option),* + $(pub $event: unsafe extern "C" fn(data: *mut core::ffi::c_void, $name: *mut $name, $($arg_name: $arg_ty),*)),* + } + /// Implementation for the dummy event handlers + mod $name_dummy { + use super::*; + $(pub unsafe extern "C" fn $event(_: *mut core::ffi::c_void, _: *mut $name, $(_: $arg_ty),*) {})* + } + impl $name_listener { + /// Create a listener with dummy event handlers + pub const fn dummy() -> Self { + Self { + $($event: $name_dummy::$event),* + } + } } }; } @@ -377,6 +391,9 @@ pub type wl_seat_capability = ::core::ffi::c_uint; use core::ffi::{c_char, c_int, c_uint, c_void}; +pub type wl_output_subpixel = c_int; +pub type wl_output_transform = c_int; +pub type wl_output_mode = c_uint; pub type wl_keyboard_keymap_format = c_uint; pub type wl_keyboard_key_state = c_uint; pub type wl_pointer_button_state = c_uint; @@ -388,6 +405,7 @@ pub type wl_data_device_manager_dnd_action = c_uint; wl_listener!( wl_registry_listener, wl_registry, + wl_registry_dummy, fn global(name: c_uint, interface: *const c_char, version: c_uint), fn global_remove(name: c_uint), ); @@ -395,19 +413,58 @@ wl_listener!( wl_listener!( wl_callback_listener, wl_callback, + wl_callback_dummy, fn done(callback_data: c_uint), ); wl_listener!( wl_seat_listener, wl_seat, + wl_seat_dummy, fn capabilities(capabilities: wl_seat_capability), fn name(name: *const c_char), ); +wl_listener!( + wl_output_listener, + wl_output, + wl_output_dummy, + fn geometry( + x: c_int, + y: c_int, + physical_width: c_int, + physical_height: c_int, + subpixel: wl_output_subpixel, + make: *const c_char, + model: *const c_char, + transform: wl_output_transform, + ), + fn mode( + flags: wl_output_mode, + width: c_int, + height: c_int, + refresh: c_int, + ), + fn done(), + fn scale(factor: c_int), + fn name(name: *const c_char), + fn description(description: *const c_char), +); + +wl_listener!( + wl_surface_listener, + wl_surface, + wl_surface_dummy, + fn enter(output: *mut wl_output), + fn leave(output: *mut wl_output), + fn preferred_buffer_scale(factor: c_int), + fn preferred_buffer_transform(transform: wl_output_transform), +); + wl_listener!( wl_keyboard_listener, wl_keyboard, + wl_keyboard_dummy, fn keymap(format: wl_keyboard_keymap_format, fd: c_int, size: c_uint), fn enter(serial: c_uint, surface: *mut wl_surface, keys: *mut wl_array), fn leave(serial: c_uint, surface: *mut wl_surface), @@ -425,6 +482,7 @@ wl_listener!( wl_listener!( wl_pointer_listener, wl_pointer, + wl_pointer_dummy, fn enter( serial: c_uint, surface: *mut wl_surface, @@ -454,6 +512,7 @@ wl_listener!( wl_listener!( wl_data_device_listener, wl_data_device, + wl_data_device_dummy, fn data_offer(id: *mut wl_data_offer), fn enter( serial: c_uint, @@ -471,6 +530,7 @@ wl_listener!( wl_listener!( wl_data_offer_listener, wl_data_offer, + wl_data_offer_dummy, fn offer(mime_type: *const c_char), fn source_actions(source_actions: wl_data_device_manager_dnd_action), fn action(dnd_action: wl_data_device_manager_dnd_action), @@ -479,6 +539,7 @@ wl_listener!( wl_listener!( wl_data_source_listener, wl_data_source, + wl_data_source_dummy, fn target(mime_type: *const c_char), fn send(mime_type: *const c_char, fd: c_int), fn cancelled(), @@ -501,6 +562,7 @@ crate::declare_module!( pub wl_seat_interface: *mut wl_interface, pub wl_shm_interface: *mut wl_interface, pub wl_shm_pool_interface: *mut wl_interface, + pub wl_output_interface: *mut wl_interface, pub wl_keyboard_interface: *mut wl_interface, pub wl_pointer_interface: *mut wl_interface, pub wl_data_device_manager_interface: *mut wl_interface, From 254e02bff27157fe0ace0df489a7102e3937c543 Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:57:12 +0000 Subject: [PATCH 07/16] native/linux_wayland: Refactor pointer - Add new struct `PointerContext` - Support cursor shape and cursor grab - Move the `wl_request` macros to `libwayland_client.rs` --- src/native/linux_wayland.rs | 376 +++++++++++++----- src/native/linux_wayland/extensions.rs | 1 + src/native/linux_wayland/extensions/cursor.rs | 127 ++++++ src/native/linux_wayland/libwayland_client.rs | 36 ++ 4 files changed, 448 insertions(+), 92 deletions(-) create mode 100644 src/native/linux_wayland/extensions/cursor.rs diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index e585618d..f45aa710 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -11,6 +11,7 @@ mod extensions; mod keycodes; mod shm; +use crate::{wl_request, wl_request_constructor}; use libwayland_client::*; use libwayland_egl::*; use libxkbcommon::*; @@ -39,7 +40,6 @@ struct WaylandPayload { xdg_toplevel: *mut extensions::xdg_shell::xdg_toplevel, xdg_wm_base: *mut extensions::xdg_shell::xdg_wm_base, surface: *mut wl_surface, - decoration_manager: *mut extensions::xdg_decoration::zxdg_decoration_manager_v1, shm: *mut wl_shm, seat: *mut wl_seat, data_device_manager: *mut wl_data_device_manager, @@ -49,9 +49,10 @@ struct WaylandPayload { keymap: XkbKeymap, egl_window: *mut wl_egl_window, - pointer: *mut wl_pointer, + pointer_context: PointerContext, keyboard: *mut wl_keyboard, focused_window: *mut wl_surface, + decoration_manager: *mut extensions::xdg_decoration::zxdg_decoration_manager_v1, decorations: Option, events: Vec, @@ -114,6 +115,55 @@ impl WaylandPayload { (self.client.wl_display_cancel_read)(self.display); } } + unsafe fn init_data_device(&mut self) { + self.data_device = wl_request_constructor!( + self.client, + self.data_device_manager, + WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, + self.client.wl_data_device_interface, + self.seat + ); + assert!(!self.data_device.is_null()); + DATA_DEVICE_LISTENER.data_offer = data_device_handle_data_offer; + DATA_DEVICE_LISTENER.enter = drag_n_drop::data_device_handle_enter; + DATA_DEVICE_LISTENER.leave = drag_n_drop::data_device_handle_leave; + DATA_DEVICE_LISTENER.drop = drag_n_drop::data_device_handle_drop; + DATA_DEVICE_LISTENER.selection = clipboard::data_device_handle_selection; + (self.client.wl_proxy_add_listener)( + self.data_device as _, + &DATA_DEVICE_LISTENER as *const _ as _, + self as *mut _ as _, + ); + } + unsafe fn init_pointer_context(&mut self) { + if !self.pointer_context.cursor_shape_manager.is_null() { + self.pointer_context.cursor_shape_device = wl_request_constructor!( + self.client, + self.pointer_context.cursor_shape_manager, + extensions::cursor::CURSOR_SHAPE_MANAGER_GET_POINTER, + &extensions::cursor::wp_cursor_shape_device_v1_interface, + self.pointer_context.pointer + ); + assert!(!self.pointer_context.cursor_shape_device.is_null()); + } else { + eprintln!("Wayland compositor does not support cursor shape"); + } + } + unsafe fn set_fullscreen(&mut self, full: bool) { + if full { + wl_request!( + self.client, + self.xdg_toplevel, + extensions::xdg_shell::xdg_toplevel::set_fullscreen, + ); + } else { + wl_request!( + self.client, + self.xdg_toplevel, + extensions::xdg_shell::xdg_toplevel::unset_fullscreen + ); + } + } } #[derive(Clone, Copy, PartialEq, Eq)] @@ -227,40 +277,142 @@ impl KeyboardContext { } } -#[macro_export] -macro_rules! wl_request_constructor { - ($libwayland:expr, $instance:expr, $request_name:expr, $interface:expr) => { - wl_request_constructor!($libwayland, $instance, $request_name, $interface, ()) - }; - - ($libwayland:expr, $instance:expr, $request_name:expr, $interface:expr, $($arg:expr),*) => {{ - let id: *mut wl_proxy; - - id = ($libwayland.wl_proxy_marshal_constructor)( - $instance as _, - $request_name, - $interface as _, - std::ptr::null_mut::(), - $($arg,)* - ); - - id as *mut _ - }}; +struct PointerContext { + pointer: *mut wl_pointer, + enter_serial: Option, + position: (f32, f32), + /// Wayland does not remember what cursor icon a window has; if the cursor leaves and comes + /// back, it will not be reset to what icon it had unless we keep track of it. + cursor_icon: Option, + /// Wayland requires that only the window with focus can set the cursor. So if we don't have + /// the focus yet, we queue the cursor icon and apply it once we regain focus. + queued_cursor_icon: Option>, + cursor_shape_manager: *mut extensions::cursor::wp_cursor_shape_manager_v1, + cursor_shape_device: *mut extensions::cursor::wp_cursor_shape_device_v1, + pointer_constraints: *mut extensions::cursor::zwp_pointer_constraints_v1, + locked_pointer: *mut extensions::cursor::zwp_locked_pointer_v1, + relative_pointer_manager: *mut extensions::cursor::zwp_relative_pointer_manager_v1, + relative_pointer: *mut extensions::cursor::zwp_relative_pointer_v1, } +impl PointerContext { + fn new() -> Self { + Self { + pointer: std::ptr::null_mut(), + enter_serial: None, + position: (0., 0.), + cursor_icon: Some(crate::CursorIcon::Default), + queued_cursor_icon: None, + cursor_shape_manager: std::ptr::null_mut(), + cursor_shape_device: std::ptr::null_mut(), + pointer_constraints: std::ptr::null_mut(), + locked_pointer: std::ptr::null_mut(), + relative_pointer_manager: std::ptr::null_mut(), + relative_pointer: std::ptr::null_mut(), + } + } + unsafe fn set_cursor_with_serial( + &mut self, + client: &mut LibWaylandClient, + icon: Option, + serial: core::ffi::c_uint, + ) { + self.cursor_icon = icon; + if let Some(icon) = icon { + if !self.cursor_shape_device.is_null() { + wl_request!( + client, + self.cursor_shape_device, + extensions::cursor::CURSOR_SHAPE_DEVICE_SET_SHAPE, + serial, + extensions::cursor::translate_cursor(icon) + ); + } + } else { + wl_request!( + client, + self.pointer, + WL_POINTER_SET_CURSOR, + serial, + std::ptr::null_mut::(), + 0, + 0 + ); + } + } + fn handle_enter(&mut self, client: &mut LibWaylandClient, serial: core::ffi::c_uint) { + self.enter_serial = Some(serial); + let change = self.queued_cursor_icon.take().unwrap_or(self.cursor_icon); + unsafe { + self.set_cursor_with_serial(client, change, serial); + } + } + /// Change the cursor to the given icon (or hide it if `None` is passed) + /// If the window currently does not have focus, the change will be queued and applied once the + /// window regains focus + fn set_cursor(&mut self, client: &mut LibWaylandClient, icon: Option) { + if let Some(serial) = self.enter_serial { + unsafe { + self.set_cursor_with_serial(client, icon, serial); + } + } else { + self.queued_cursor_icon = Some(icon); + } + } + unsafe fn set_grab(&mut self, data: *mut std::ffi::c_void, grab: bool) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + if grab { + if self.locked_pointer.is_null() { + if !self.pointer_constraints.is_null() { + self.locked_pointer = wl_request_constructor!( + display.client, + self.pointer_constraints, + extensions::cursor::POINTER_CONSTRAINTS_LOCK_POINTER, + &extensions::cursor::zwp_locked_pointer_v1_interface, + display.surface, + self.pointer, + std::ptr::null_mut::(), + extensions::cursor::zwp_pointer_constraints_v1_lifetime_PERSISTENT + ); + assert!(!self.locked_pointer.is_null()); + } else { + eprintln!("Wayland compositor does not support locked pointer"); + } + } -#[macro_export] -macro_rules! wl_request { - ($libwayland:expr, $instance:expr, $request_name:expr) => { - wl_request!($libwayland, $instance, $request_name, ()) - }; - - ($libwayland:expr, $instance:expr, $request_name:expr, $($arg:expr),*) => {{ - ($libwayland.wl_proxy_marshal)( - $instance as _, - $request_name, - $($arg,)* - ) - }}; + if self.relative_pointer.is_null() { + if !self.relative_pointer_manager.is_null() { + self.relative_pointer = wl_request_constructor!( + display.client, + self.relative_pointer_manager, + extensions::cursor::RELATIVE_POINTER_MANAGER_GET_RELATIVE_POINTER, + &extensions::cursor::zwp_relative_pointer_v1_interface, + self.pointer + ); + assert!(!self.relative_pointer.is_null()); + (RELATIVE_POINTER_LISTENER.relative_motion) = + relative_pointer_handle_relative_motion; + (display.client.wl_proxy_add_listener)( + self.relative_pointer as _, + &RELATIVE_POINTER_LISTENER as *const _ as _, + data, + ); + } else { + eprintln!("Wayland compositor does not support relative pointer"); + } + } + } else { + if !self.locked_pointer.is_null() { + wl_request!(display.client, self.locked_pointer, 0); + (display.client.wl_proxy_destroy)(self.locked_pointer as _); + self.locked_pointer = std::ptr::null_mut(); + } + if !self.relative_pointer.is_null() { + wl_request!(display.client, self.relative_pointer, 0); + (display.client.wl_proxy_destroy)(self.relative_pointer as _); + self.relative_pointer = std::ptr::null_mut(); + } + } + } } static mut SEAT_LISTENER: wl_seat_listener = wl_seat_listener::dummy(); @@ -271,6 +423,8 @@ static mut DATA_DEVICE_LISTENER: wl_data_device_listener = wl_data_device_listen static mut DATA_OFFER_LISTENER: wl_data_offer_listener = wl_data_offer_listener::dummy(); static mut XDG_WM_BASE_LISTENER: extensions::xdg_shell::xdg_wm_base_listener = extensions::xdg_shell::xdg_wm_base_listener::dummy(); +static mut RELATIVE_POINTER_LISTENER: extensions::cursor::zwp_relative_pointer_v1_listener = + extensions::cursor::zwp_relative_pointer_v1_listener::dummy(); unsafe extern "C" fn seat_handle_capabilities( data: *mut std::ffi::c_void, @@ -280,19 +434,20 @@ unsafe extern "C" fn seat_handle_capabilities( let display: &mut WaylandPayload = &mut *(data as *mut _); if caps & wl_seat_capability_WL_SEAT_CAPABILITY_POINTER != 0 { - display.pointer = wl_request_constructor!( + display.pointer_context.pointer = wl_request_constructor!( display.client, seat, WL_SEAT_GET_POINTER, display.client.wl_pointer_interface ); - assert!(!display.pointer.is_null()); + assert!(!display.pointer_context.pointer.is_null()); POINTER_LISTENER.enter = pointer_handle_enter; POINTER_LISTENER.axis = pointer_handle_axis; POINTER_LISTENER.motion = pointer_handle_motion; POINTER_LISTENER.button = pointer_handle_button; + POINTER_LISTENER.leave = pointer_handle_leave; (display.client.wl_proxy_add_listener)( - display.pointer as _, + display.pointer_context.pointer as _, &POINTER_LISTENER as *const _ as _, data, ); @@ -325,6 +480,7 @@ enum WaylandEvent { KeyUp(KeyCode, KeyMods), Char(char, KeyMods, bool), PointerMotion(f32, f32), + RawMotion(f32, f32), PointerButton(MouseButton, bool), PointerAxis(f32, f32), FilesDropped(String), @@ -468,14 +624,28 @@ unsafe extern "C" fn keyboard_handle_repeat_info( unsafe extern "C" fn pointer_handle_enter( data: *mut ::core::ffi::c_void, _wl_pointer: *mut wl_pointer, - _serial: u32, + serial: u32, surface: *mut wl_surface, _surface_x: i32, _surface_y: i32, ) { let display: &mut WaylandPayload = &mut *(data as *mut _); display.focused_window = surface; + display + .pointer_context + .handle_enter(&mut display.client, serial); } + +unsafe extern "C" fn pointer_handle_leave( + data: *mut ::core::ffi::c_void, + _wl_pointer: *mut wl_pointer, + _serial: u32, + _surface: *mut wl_surface, +) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + display.pointer_context.enter_serial = None; +} + unsafe extern "C" fn pointer_handle_motion( data: *mut ::core::ffi::c_void, _wl_pointer: *mut wl_pointer, @@ -489,6 +659,7 @@ unsafe extern "C" fn pointer_handle_motion( let d = crate::native_display().lock().unwrap(); let x = wl_fixed_to_double(surface_x) * d.dpi_scale; let y = wl_fixed_to_double(surface_y) * d.dpi_scale; + display.pointer_context.position = (x, y); display.events.push(WaylandEvent::PointerMotion(x, y)); } } @@ -538,9 +709,28 @@ unsafe extern "C" fn pointer_handle_axis( } } +unsafe extern "C" fn relative_pointer_handle_relative_motion( + data: *mut ::core::ffi::c_void, + _relative_pointer: *mut extensions::cursor::zwp_relative_pointer_v1, + _utime_hi: core::ffi::c_uint, + _utime_lo: core::ffi::c_uint, + dx: wl_fixed_t, + dy: wl_fixed_t, + _dx_unaccel: wl_fixed_t, + _dy_unaccel: wl_fixed_t, +) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + if display.focused_window == display.surface { + // From wl_fixed_to_double(), it simply divides by 256 + let dx = wl_fixed_to_double(dx); + let dy = wl_fixed_to_double(dy); + display.events.push(WaylandEvent::RawMotion(dx, dy)); + } +} + unsafe extern "C" fn output_handle_scale( data: *mut std::ffi::c_void, - _: *mut wl_output, + _output: *mut wl_output, factor: core::ffi::c_int, ) { let display: &mut WaylandPayload = &mut *(data as *mut _); @@ -621,6 +811,30 @@ unsafe extern "C" fn registry_add_object( 1, ) as _; } + "wp_cursor_shape_manager_v1" => { + display.pointer_context.cursor_shape_manager = display.client.wl_registry_bind( + registry, + name, + &extensions::cursor::wp_cursor_shape_manager_v1_interface as _, + 1, + ) as _; + } + "zwp_pointer_constraints_v1" => { + display.pointer_context.pointer_constraints = display.client.wl_registry_bind( + registry, + name, + &extensions::cursor::zwp_pointer_constraints_v1_interface as _, + 1, + ) as _; + } + "zwp_relative_pointer_manager_v1" => { + display.pointer_context.relative_pointer_manager = display.client.wl_registry_bind( + registry, + name, + &extensions::cursor::zwp_relative_pointer_manager_v1_interface as _, + 1, + ) as _; + } "wl_shm" => { display.shm = display @@ -724,7 +938,6 @@ where xdg_toplevel: std::ptr::null_mut(), xdg_wm_base: std::ptr::null_mut(), surface: std::ptr::null_mut(), - decoration_manager: std::ptr::null_mut(), shm: std::ptr::null_mut(), seat: std::ptr::null_mut(), data_device_manager: std::ptr::null_mut(), @@ -733,11 +946,12 @@ where keymap: Default::default(), xkb_state: std::ptr::null_mut(), egl_window: std::ptr::null_mut(), - pointer: std::ptr::null_mut(), keyboard: std::ptr::null_mut(), focused_window: std::ptr::null_mut(), + decoration_manager: std::ptr::null_mut(), decorations: None, events: Vec::new(), + pointer_context: PointerContext::new(), keyboard_context: KeyboardContext::new(), drag_n_drop: Default::default(), update_requested: true, @@ -750,28 +964,6 @@ where ®istry_listener as *const _ as _, &mut display as *mut _ as _, ); - (display.client.wl_display_dispatch)(display.display); - - // Construct the `wl_data_device` here, as it needs both the `wl_seat` and the - // `wl_data_device_manager`, and they may be constructed in different orders - display.data_device = wl_request_constructor!( - display.client, - display.data_device_manager, - WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, - display.client.wl_data_device_interface, - display.seat - ) as _; - assert!(!display.data_device.is_null()); - DATA_DEVICE_LISTENER.data_offer = data_device_handle_data_offer; - DATA_DEVICE_LISTENER.enter = drag_n_drop::data_device_handle_enter; - DATA_DEVICE_LISTENER.leave = drag_n_drop::data_device_handle_leave; - DATA_DEVICE_LISTENER.drop = drag_n_drop::data_device_handle_drop; - DATA_DEVICE_LISTENER.selection = clipboard::data_device_handle_selection; - (display.client.wl_proxy_add_listener)( - display.data_device as _, - &DATA_DEVICE_LISTENER as *const _ as _, - &mut display as *mut _ as _, - ); let (tx, rx) = std::sync::mpsc::channel(); let clipboard = Box::new(clipboard::WaylandClipboard::new(&mut display as *mut _)); @@ -781,8 +973,12 @@ where blocking_event_loop: conf.platform.blocking_event_loop, ..NativeDisplayData::new(conf.window_width, conf.window_height, tx, clipboard) }); - //assert!(!display.keymap.is_null()); - //assert!(!display.xkb_state.is_null()); + + (display.client.wl_display_dispatch)(display.display); + (display.client.wl_display_dispatch)(display.display); + + display.init_data_device(); + display.init_pointer_context(); let mut libegl = egl::LibEgl::try_load().ok()?; let (context, config, egl_display) = egl::create_egl_context( @@ -845,11 +1041,7 @@ where // For some reason, setting fullscreen before egl_window is created leads // to segfault because wl_egl_window_create returns NULL. if conf.fullscreen { - wl_request!( - display.client, - display.xdg_toplevel, - extensions::xdg_shell::xdg_toplevel::set_fullscreen, - ) + display.set_fullscreen(true); } wl_request!(display.client, display.surface, WL_SURFACE_COMMIT); @@ -858,27 +1050,28 @@ where let mut event_handler = (f.take().unwrap())(); - let (mut last_mouse_x, mut last_mouse_y) = (0.0, 0.0); - while !crate::native_display().try_lock().unwrap().quit_ordered { while let Ok(request) = rx.try_recv() { match request { Request::SetFullscreen(full) => { - if full { - wl_request!( - display.client, - display.xdg_toplevel, - extensions::xdg_shell::xdg_toplevel::set_fullscreen, - ); - } else { - wl_request!( - display.client, - display.xdg_toplevel, - extensions::xdg_shell::xdg_toplevel::unset_fullscreen - ); - } + display.set_fullscreen(full); } Request::ScheduleUpdate => display.update_requested = true, + Request::SetMouseCursor(icon) => { + display + .pointer_context + .set_cursor(&mut display.client, Some(icon)); + } + Request::SetCursorGrab(grab) => { + let payload = &mut display as *mut _ as _; + display.pointer_context.set_grab(payload, grab); + } + Request::ShowMouse(show) => { + display.pointer_context.set_cursor( + &mut display.client, + show.then_some(crate::CursorIcon::Default), + ); + } // TODO: implement the other events _ => (), } @@ -899,17 +1092,16 @@ where } WaylandEvent::PointerMotion(x, y) => { event_handler.mouse_motion_event(x, y); - (last_mouse_x, last_mouse_y) = (x, y); + } + WaylandEvent::RawMotion(dx, dy) => { + event_handler.raw_mouse_motion(dx, dy); } WaylandEvent::PointerButton(button, state) => { + let (x, y) = display.pointer_context.position; if state { - event_handler.mouse_button_down_event( - button, - last_mouse_x, - last_mouse_y, - ); + event_handler.mouse_button_down_event(button, x, y); } else { - event_handler.mouse_button_up_event(button, last_mouse_x, last_mouse_y); + event_handler.mouse_button_up_event(button, x, y); } } WaylandEvent::PointerAxis(x, y) => event_handler.mouse_wheel_event(x, y), diff --git a/src/native/linux_wayland/extensions.rs b/src/native/linux_wayland/extensions.rs index 08cdf347..5cfe852e 100644 --- a/src/native/linux_wayland/extensions.rs +++ b/src/native/linux_wayland/extensions.rs @@ -1,5 +1,6 @@ #![allow(unused_variables, dead_code, non_upper_case_globals, static_mut_refs)] +pub mod cursor; pub mod libdecor; // pub mod viewporter; pub mod xdg_decoration; diff --git a/src/native/linux_wayland/extensions/cursor.rs b/src/native/linux_wayland/extensions/cursor.rs new file mode 100644 index 00000000..d547ae7f --- /dev/null +++ b/src/native/linux_wayland/extensions/cursor.rs @@ -0,0 +1,127 @@ +use super::super::libwayland_client::{wl_fixed_t, wl_interface, wl_message}; +use crate::wayland_interface; + +pub const CURSOR_SHAPE_MANAGER_GET_POINTER: u32 = 1; +pub const CURSOR_SHAPE_DEVICE_SET_SHAPE: u32 = 1; +pub const RELATIVE_POINTER_MANAGER_GET_RELATIVE_POINTER: u32 = 1; +pub const POINTER_CONSTRAINTS_LOCK_POINTER: u32 = 1; +pub const zwp_pointer_constraints_v1_lifetime_ONESHOT: u32 = 1; +pub const zwp_pointer_constraints_v1_lifetime_PERSISTENT: u32 = 2; + +#[rustfmt::skip] +wayland_interface!( + wp_cursor_shape_manager_v1_interface, + wp_cursor_shape_manager_v1, + 1, + [ + (destroy, "", ()), + (get_pointer, "no", (wp_cursor_shape_device_v1_interface)), + (get_tablet_tool_v2, "no", (wp_cursor_shape_device_v1_interface)) + ], + [] +); + +#[rustfmt::skip] +wayland_interface!( + wp_cursor_shape_device_v1_interface, + wp_cursor_shape_device_v1, + 1, + [ + (destroy, "", ()), + (set_shape, "uu", ()) + ], + [] +); + +#[rustfmt::skip] +wayland_interface!( + zwp_relative_pointer_manager_v1_interface, + zwp_relative_pointer_manager_v1, + 1, + [ + (destroy, "", ()), + (get_relative_pointer, "no", (zwp_relative_pointer_v1_interface)) + ], + [] +); + +#[rustfmt::skip] +wayland_interface!( + zwp_pointer_constraints_v1_interface, + zwp_pointer_constraints_v1, + 1, + [ + (destroy, "", ()), + (lock_pointer, "noo?ou", (zwp_locked_pointer_v1_interface)), + (confine_pointer, "noo?ou", (zwp_confined_pointer_v1_interface)) + ], + [] +); + +#[rustfmt::skip] +wayland_interface!( + zwp_locked_pointer_v1_interface, + zwp_locked_pointer_v1, + 1, + [ + (destroy, "", ()), + (set_cursor_position_hint, "ff", ()), + (set_region, "?o", ()) + ], + [("locked", ""), ("unlocked", "")] +); + +#[rustfmt::skip] +wayland_interface!( + zwp_confined_pointer_v1_interface, + zwp_confined_pointer_v1, + 1, + [ + (destroy, "", ()), + (set_region, "?o", ()) + ], + [("confined", ""), ("unconfined", "")] +); + +#[rustfmt::skip] +wayland_interface!( + zwp_relative_pointer_v1_interface, + zwp_relative_pointer_v1, + 1, + [ + (destroy, "", ()) + ], + [("relative_motion", "uuffff")] +); + +crate::wl_listener!( + zwp_relative_pointer_v1_listener, + zwp_relative_pointer_v1, + zwp_relative_pointer_v1_dummy, + fn relative_motion( + utime_hi: core::ffi::c_uint, + utime_lo: core::ffi::c_uint, + dx: wl_fixed_t, + dy: wl_fixed_t, + dx_unaccel: wl_fixed_t, + dy_unaccel: wl_fixed_t, + ), +); + +pub fn translate_cursor(icon: crate::CursorIcon) -> core::ffi::c_uint { + // https://wayland.app/protocols/cursor-shape-v1#wp_cursor_shape_device_v1:enum:shape + match icon { + crate::CursorIcon::Default => 1, + crate::CursorIcon::Help => 3, + crate::CursorIcon::Pointer => 4, + crate::CursorIcon::Wait => 6, + crate::CursorIcon::Crosshair => 8, + crate::CursorIcon::Text => 9, + crate::CursorIcon::Move => 13, + crate::CursorIcon::NotAllowed => 15, + crate::CursorIcon::EWResize => 26, + crate::CursorIcon::NSResize => 27, + crate::CursorIcon::NESWResize => 28, + crate::CursorIcon::NWSEResize => 29, + } +} diff --git a/src/native/linux_wayland/libwayland_client.rs b/src/native/linux_wayland/libwayland_client.rs index 26e84e77..d45e0463 100644 --- a/src/native/linux_wayland/libwayland_client.rs +++ b/src/native/linux_wayland/libwayland_client.rs @@ -643,3 +643,39 @@ impl LibWaylandClient { bytes } } + +#[macro_export] +macro_rules! wl_request_constructor { + ($libwayland:expr, $instance:expr, $request_name:expr, $interface:expr) => { + wl_request_constructor!($libwayland, $instance, $request_name, $interface, ()) + }; + + ($libwayland:expr, $instance:expr, $request_name:expr, $interface:expr, $($arg:expr),*) => {{ + let id: *mut wl_proxy; + + id = ($libwayland.wl_proxy_marshal_constructor)( + $instance as _, + $request_name, + $interface as _, + std::ptr::null_mut::(), + $($arg,)* + ); + + id as *mut _ + }}; +} + +#[macro_export] +macro_rules! wl_request { + ($libwayland:expr, $instance:expr, $request_name:expr) => { + wl_request!($libwayland, $instance, $request_name, ()) + }; + + ($libwayland:expr, $instance:expr, $request_name:expr, $($arg:expr),*) => {{ + ($libwayland.wl_proxy_marshal)( + $instance as _, + $request_name, + $($arg,)* + ) + }}; +} From 0da26c0d93402a203ad492cc9c0c1201b20ddce0 Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Fri, 28 Feb 2025 12:28:50 +0000 Subject: [PATCH 08/16] native/linux_wayland: Allow no decorations - Make `wayland_use_fallback_decorations` deprecated --- src/conf.rs | 5 ++++- src/native/linux_wayland.rs | 19 ++++++++-------- src/native/linux_wayland/decorations.rs | 29 +++++++++++++++---------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/conf.rs b/src/conf.rs index 70cdfa65..ce3d8af7 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -143,7 +143,9 @@ pub struct Platform { /// - TODO: Document(and check) what does it actually mean on android. Transparent window? pub framebuffer_alpha: bool, - /// When using Wayland, this controls whether to draw the default window decorations. + /// On Wayland, the decorations are either drawn by the server or via `libdecor`. If neither is + /// available then no decorations will be drawn. + #[deprecated] pub wayland_use_fallback_decorations: bool, /// Set the `WM_CLASS` window property on X11 and the `app_id` on Wayland. This is used @@ -156,6 +158,7 @@ pub struct Platform { impl Default for Platform { fn default() -> Platform { + #[allow(deprecated)] Platform { linux_x11_gl: LinuxX11Gl::default(), linux_backend: LinuxBackend::default(), diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index f45aa710..af2c9b60 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -53,7 +53,7 @@ struct WaylandPayload { keyboard: *mut wl_keyboard, focused_window: *mut wl_surface, decoration_manager: *mut extensions::xdg_decoration::zxdg_decoration_manager_v1, - decorations: Option, + decorations: decorations::Decorations, events: Vec, keyboard_context: KeyboardContext, @@ -949,7 +949,7 @@ where keyboard: std::ptr::null_mut(), focused_window: std::ptr::null_mut(), decoration_manager: std::ptr::null_mut(), - decorations: None, + decorations: decorations::Decorations::None, events: Vec::new(), pointer_context: PointerContext::new(), keyboard_context: KeyboardContext::new(), @@ -1019,16 +1019,15 @@ where (libegl.eglGetProcAddress)(name.as_ptr() as _) }); - display.decorations = decorations::Decorations::new(&mut display); + let borderless = false; + display.decorations = decorations::Decorations::new(&mut display, borderless); assert!(!display.xdg_toplevel.is_null()); - if let Some(ref mut decorations) = display.decorations { - decorations.set_title( - &mut display.client, - display.xdg_toplevel, - conf.window_title.as_str(), - ); - } + display.decorations.set_title( + &mut display.client, + display.xdg_toplevel, + conf.window_title.as_str(), + ); let wm_class = std::ffi::CString::new(conf.platform.linux_wm_class).unwrap(); wl_request!( diff --git a/src/native/linux_wayland/decorations.rs b/src/native/linux_wayland/decorations.rs index 451df69a..0621baeb 100644 --- a/src/native/linux_wayland/decorations.rs +++ b/src/native/linux_wayland/decorations.rs @@ -18,6 +18,7 @@ use extensions::xdg_shell::*; /// In the later case we use `libdecor` so it needs to be loaded. /// If it's not available then no decorations will be drawn at all. pub(super) enum Decorations { + None, Server, Client { libdecor: LibDecor, @@ -58,12 +59,14 @@ unsafe fn create_xdg_toplevel(display: &mut WaylandPayload) { } impl Decorations { - pub(super) fn new(display: &mut WaylandPayload) -> Option { + pub(super) fn new(display: &mut WaylandPayload, borderless: bool) -> Self { unsafe { - if display.decoration_manager.is_null() { + if borderless { + Decorations::none(display) + } else if display.decoration_manager.is_null() { Decorations::try_client(display) } else { - Some(Decorations::server(display)) + Decorations::server(display) } } } @@ -76,7 +79,7 @@ impl Decorations { ) { let title = std::ffi::CString::new(title).unwrap(); match self { - Decorations::Server => { + Decorations::None | Decorations::Server => { wl_request!( client, xdg_toplevel, @@ -92,6 +95,11 @@ impl Decorations { } } + unsafe fn none(display: &mut WaylandPayload) -> Self { + create_xdg_toplevel(display); + Decorations::None + } + unsafe fn server(display: &mut WaylandPayload) -> Self { create_xdg_toplevel(display); @@ -113,7 +121,7 @@ impl Decorations { Decorations::Server } - unsafe fn try_client(display: &mut WaylandPayload) -> Option { + unsafe fn try_client(display: &mut WaylandPayload) -> Self { if let Ok(libdecor) = LibDecor::try_load() { let context = (libdecor.libdecor_new)(display.display, &mut LIBDECOR_INTERFACE as _); let frame = (libdecor.libdecor_decorate)( @@ -124,16 +132,13 @@ impl Decorations { ); (libdecor.libdecor_frame_map)(frame); display.xdg_toplevel = (libdecor.libdecor_frame_get_xdg_toplevel)(frame); - assert!(!display.xdg_toplevel.is_null()); - Some(Decorations::Client { + Decorations::Client { libdecor, context, frame, - }) + } } else { - // If we can't load `libdecor` we just create the `xdg_toplevel` and return `None` - create_xdg_toplevel(display); - None + Decorations::none(display) } } @@ -199,7 +204,7 @@ unsafe extern "C" fn libdecor_frame_handle_configure( data: *mut c_void, ) { let display: &mut WaylandPayload = &mut *(data as *mut _); - let libdecor = display.decorations.as_mut().unwrap().libdecor().unwrap(); + let libdecor = display.decorations.libdecor().unwrap(); let mut width: c_int = 0; let mut height: c_int = 0; From d1d0bfa59bb68d12dace464fdd0f9defe01c9ea0 Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Fri, 28 Feb 2025 12:46:16 +0000 Subject: [PATCH 09/16] native/linux_wayland: Stop normalizing mouse wheel - There is no reason to normalize the mouse wheel value to {-1,0,1}: it's not done on other platforms, and this just makes the input very imprecise --- src/native/linux_wayland.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index af2c9b60..249c800b 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -694,9 +694,6 @@ unsafe extern "C" fn pointer_handle_axis( ) { let display: &mut WaylandPayload = &mut *(data as *mut _); let mut value = wl_fixed_to_double(value); - // Normalize the value to {-1, 0, 1} - value /= value.abs(); - // https://wayland-book.com/seat/pointer.html if axis == 0 { // Vertical scroll From bb89ec02ef4c783c3c56915f4dd8cb0ad25a7a32 Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:00:25 +0000 Subject: [PATCH 10/16] native/linux_wayland: Fix key repeat and blocking --- src/native/linux_wayland.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index 249c800b..07ea08d8 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -62,10 +62,11 @@ struct WaylandPayload { } impl WaylandPayload { - /// block until a new event is available + /// Poll new events, `blocking` specifies whether it should block until a new event is + /// available // needs to combine both the Wayland events and the key repeat events // the implementation is translated from glfw - unsafe fn block_on_new_event(&mut self) { + unsafe fn poll_new_event(&mut self, blocking: bool) { let mut fds = [ libc::pollfd { fd: (self.client.wl_display_get_fd)(self.display), @@ -82,7 +83,7 @@ impl WaylandPayload { while (self.client.wl_display_prepare_read)(self.display) != 0 { (self.client.wl_display_dispatch_pending)(self.display); } - if !self.update_requested && libc::poll(fds.as_mut_ptr(), 2, i32::MAX) > 0 { + if libc::poll(fds.as_mut_ptr(), 2, if blocking { i32::MAX } else { 0 }) > 0 { // if the Wayland display has events available if fds[0].revents & libc::POLLIN == 1 { (self.client.wl_display_read_events)(self.display); @@ -1073,7 +1074,10 @@ where } } - display.block_on_new_event(); + // If `blocking_event_loop` is set but an update is requested, we should still poll the + // new events but continue without blocking + let blocking = conf.platform.blocking_event_loop && !display.update_requested; + display.poll_new_event(blocking); for event in display.events.drain(..) { match event { From 71742322650c447c29581a628516b0e7df8d4177 Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:01:25 +0000 Subject: [PATCH 11/16] native/linux_wayland: Restore fallback decorations --- src/conf.rs | 4 +- src/native/linux_wayland.rs | 27 ++- src/native/linux_wayland/decorations.rs | 222 +++++++++++++++++++++++- src/native/linux_wayland/extensions.rs | 2 +- 4 files changed, 245 insertions(+), 10 deletions(-) diff --git a/src/conf.rs b/src/conf.rs index ce3d8af7..a1110619 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -144,8 +144,7 @@ pub struct Platform { pub framebuffer_alpha: bool, /// On Wayland, the decorations are either drawn by the server or via `libdecor`. If neither is - /// available then no decorations will be drawn. - #[deprecated] + /// available, then this flag controls whether fallback window decorations should be used. pub wayland_use_fallback_decorations: bool, /// Set the `WM_CLASS` window property on X11 and the `app_id` on Wayland. This is used @@ -158,7 +157,6 @@ pub struct Platform { impl Default for Platform { fn default() -> Platform { - #[allow(deprecated)] Platform { linux_x11_gl: LinuxX11Gl::default(), linux_backend: LinuxBackend::default(), diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index 07ea08d8..24a11a6a 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -37,9 +37,11 @@ struct WaylandPayload { egl: LibWaylandEgl, xkb: LibXkbCommon, compositor: *mut wl_compositor, + subcompositor: *mut wl_subcompositor, xdg_toplevel: *mut extensions::xdg_shell::xdg_toplevel, xdg_wm_base: *mut extensions::xdg_shell::xdg_wm_base, surface: *mut wl_surface, + viewporter: *mut extensions::viewporter::wp_viewporter, shm: *mut wl_shm, seat: *mut wl_seat, data_device_manager: *mut wl_data_device_manager, @@ -786,6 +788,15 @@ unsafe extern "C" fn registry_add_object( ); assert!(!display.surface.is_null()); } + "wl_subcompositor" => { + display.subcompositor = display.client.wl_registry_bind( + registry, + name, + display.client.wl_subcompositor_interface, + 1, + ) as _; + assert!(!display.subcompositor.is_null()); + } "xdg_wm_base" => { display.xdg_wm_base = display.client.wl_registry_bind( registry, @@ -809,6 +820,14 @@ unsafe extern "C" fn registry_add_object( 1, ) as _; } + "wp_viewporter" => { + display.viewporter = display.client.wl_registry_bind( + registry, + name, + &extensions::viewporter::wp_viewporter_interface, + 1, + ) as _; + } "wp_cursor_shape_manager_v1" => { display.pointer_context.cursor_shape_manager = display.client.wl_registry_bind( registry, @@ -933,9 +952,11 @@ where egl, xkb, compositor: std::ptr::null_mut(), + subcompositor: std::ptr::null_mut(), xdg_toplevel: std::ptr::null_mut(), xdg_wm_base: std::ptr::null_mut(), surface: std::ptr::null_mut(), + viewporter: std::ptr::null_mut(), shm: std::ptr::null_mut(), seat: std::ptr::null_mut(), data_device_manager: std::ptr::null_mut(), @@ -1018,7 +1039,11 @@ where }); let borderless = false; - display.decorations = decorations::Decorations::new(&mut display, borderless); + display.decorations = decorations::Decorations::new( + &mut display, + borderless, + conf.platform.wayland_use_fallback_decorations, + ); assert!(!display.xdg_toplevel.is_null()); display.decorations.set_title( diff --git a/src/native/linux_wayland/decorations.rs b/src/native/linux_wayland/decorations.rs index 0621baeb..fdd53f9b 100644 --- a/src/native/linux_wayland/decorations.rs +++ b/src/native/linux_wayland/decorations.rs @@ -25,6 +25,7 @@ pub(super) enum Decorations { context: *mut libdecor, frame: *mut libdecor_frame, }, + Fallback(fallback::Decorations), } // If we use client decorations, `libdecor` will handle the creation for us. @@ -59,12 +60,12 @@ unsafe fn create_xdg_toplevel(display: &mut WaylandPayload) { } impl Decorations { - pub(super) fn new(display: &mut WaylandPayload, borderless: bool) -> Self { + pub(super) fn new(display: &mut WaylandPayload, borderless: bool, use_fallback: bool) -> Self { unsafe { if borderless { Decorations::none(display) } else if display.decoration_manager.is_null() { - Decorations::try_client(display) + Decorations::try_client(display, use_fallback) } else { Decorations::server(display) } @@ -79,7 +80,7 @@ impl Decorations { ) { let title = std::ffi::CString::new(title).unwrap(); match self { - Decorations::None | Decorations::Server => { + Decorations::None | Decorations::Server | Decorations::Fallback(..) => { wl_request!( client, xdg_toplevel, @@ -100,6 +101,13 @@ impl Decorations { Decorations::None } + unsafe fn fallback(display: &mut WaylandPayload) -> Self { + create_xdg_toplevel(display); + let d = crate::native_display().lock().unwrap(); + let decorations = fallback::Decorations::new(display, d.screen_width, d.screen_height); + Decorations::Fallback(decorations) + } + unsafe fn server(display: &mut WaylandPayload) -> Self { create_xdg_toplevel(display); @@ -121,7 +129,7 @@ impl Decorations { Decorations::Server } - unsafe fn try_client(display: &mut WaylandPayload) -> Self { + unsafe fn try_client(display: &mut WaylandPayload, use_fallback: bool) -> Self { if let Ok(libdecor) = LibDecor::try_load() { let context = (libdecor.libdecor_new)(display.display, &mut LIBDECOR_INTERFACE as _); let frame = (libdecor.libdecor_decorate)( @@ -137,6 +145,8 @@ impl Decorations { context, frame, } + } else if use_fallback { + Decorations::fallback(display) } else { Decorations::none(display) } @@ -181,7 +191,14 @@ unsafe extern "C" fn handle_configure(data: *mut std::ffi::c_void, width: i32, h d.screen_height = screen_height; drop(d); - (payload.egl.wl_egl_window_resize)(payload.egl_window, screen_width, screen_height, 0, 0); + let mut window_width = screen_width; + let mut window_height = screen_height; + if let Decorations::Fallback(fallback) = &payload.decorations { + window_width -= fallback::Decorations::WIDTH * 2; + window_height -= fallback::Decorations::BAR_HEIGHT + fallback::Decorations::WIDTH; + fallback.resize(&mut payload.client, width, height); + } + (payload.egl.wl_egl_window_resize)(payload.egl_window, window_width, window_height, 0, 0); payload .events .push(WaylandEvent::Resize(screen_width as _, screen_height as _)); @@ -265,3 +282,198 @@ static mut XDG_TOPLEVEL_LISTENER: xdg_toplevel_listener = xdg_toplevel_listener static mut XDG_SURFACE_LISTENER: xdg_surface_listener = xdg_surface_listener { configure: xdg_surface_handle_configure, }; + +/// This module is drawing some sort of a window border, just for GNOME +/// looks horrible, doesn't fit OS theme at all, but better than nothing +mod fallback { + use crate::{ + native::linux_wayland::{ + extensions::viewporter::{wp_viewport, wp_viewport_interface, wp_viewporter}, + libwayland_client::*, + shm, WaylandPayload, + }, + wl_request, wl_request_constructor, + }; + + pub struct Decoration { + pub surface: *mut wl_surface, + pub subsurface: *mut wl_subsurface, + pub viewport: *mut wp_viewport, + } + + pub(crate) struct Decorations { + buffer: *mut wl_buffer, + pub top_decoration: Decoration, + pub bottom_decoration: Decoration, + pub left_decoration: Decoration, + pub right_decoration: Decoration, + } + + #[allow(clippy::too_many_arguments)] + unsafe fn create_decoration( + display: &mut WaylandPayload, + compositor: *mut wl_compositor, + subcompositor: *mut wl_subcompositor, + parent: *mut wl_surface, + buffer: *mut wl_buffer, + x: i32, + y: i32, + w: i32, + h: i32, + ) -> Decoration { + let surface = wl_request_constructor!( + display.client, + compositor, + WL_COMPOSITOR_CREATE_SURFACE, + display.client.wl_surface_interface, + ); + + let subsurface = wl_request_constructor!( + display.client, + subcompositor, + WL_SUBCOMPOSITOR_GET_SUBSURFACE, + display.client.wl_subsurface_interface, + surface, + parent + ); + + wl_request!(display.client, subsurface, WL_SUBSURFACE_SET_POSITION, x, y); + + let viewport = wl_request_constructor!( + display.client, + display.viewporter, + wp_viewporter::get_viewport, + &wp_viewport_interface, + surface + ); + + wl_request!(display.client, viewport, wp_viewport::set_destination, w, h); + wl_request!(display.client, surface, WL_SURFACE_ATTACH, buffer, 0, 0); + wl_request!(display.client, surface, WL_SURFACE_COMMIT); + + Decoration { + surface, + subsurface, + viewport, + } + } + + impl Decorations { + pub const WIDTH: i32 = 2; + pub const BAR_HEIGHT: i32 = 15; + + pub(super) unsafe fn new( + display: &mut WaylandPayload, + width: i32, + height: i32, + ) -> Decorations { + let buffer = shm::create_shm_buffer( + &mut display.client, + display.shm, + 1, + 1, + &[200, 200, 200, 255], + ); + + Decorations { + buffer, + top_decoration: create_decoration( + display, + display.compositor, + display.subcompositor, + display.surface, + buffer, + -Self::WIDTH, + -Self::BAR_HEIGHT, + width + Self::WIDTH * Self::WIDTH, + Self::BAR_HEIGHT, + ), + left_decoration: create_decoration( + display, + display.compositor, + display.subcompositor, + display.surface, + buffer, + -Self::WIDTH, + -Self::BAR_HEIGHT, + Self::WIDTH, + height + Self::BAR_HEIGHT, + ), + right_decoration: create_decoration( + display, + display.compositor, + display.subcompositor, + display.surface, + buffer, + width, + -Self::BAR_HEIGHT, + Self::WIDTH, + height + Self::BAR_HEIGHT, + ), + bottom_decoration: create_decoration( + display, + display.compositor, + display.subcompositor, + display.surface, + buffer, + -Self::WIDTH, + height, + width + Self::WIDTH, + Self::WIDTH, + ), + } + } + + pub unsafe fn resize(&self, client: &mut LibWaylandClient, width: i32, height: i32) { + wl_request!( + client, + self.top_decoration.viewport, + wp_viewport::set_destination, + width, + Self::BAR_HEIGHT + ); + wl_request!(client, self.top_decoration.surface, WL_SURFACE_COMMIT); + + wl_request!( + client, + self.left_decoration.viewport, + wp_viewport::set_destination, + Self::WIDTH, + height + ); + wl_request!(client, self.left_decoration.surface, WL_SURFACE_COMMIT); + + wl_request!( + client, + self.right_decoration.subsurface, + WL_SUBSURFACE_SET_POSITION, + width - Self::WIDTH * 2, + -Self::BAR_HEIGHT + ); + wl_request!( + client, + self.right_decoration.viewport, + wp_viewport::set_destination, + Self::WIDTH, + height + ); + wl_request!(client, self.right_decoration.surface, WL_SURFACE_COMMIT); + + wl_request!( + client, + self.bottom_decoration.subsurface, + WL_SUBSURFACE_SET_POSITION, + 0, + height - Self::BAR_HEIGHT - Self::WIDTH + ); + wl_request!( + client, + self.bottom_decoration.viewport, + wp_viewport::set_destination, + width - Self::WIDTH * 2, + Self::WIDTH + ); + wl_request!(client, self.bottom_decoration.surface, WL_SURFACE_COMMIT); + } + } +} diff --git a/src/native/linux_wayland/extensions.rs b/src/native/linux_wayland/extensions.rs index 5cfe852e..1bf8b293 100644 --- a/src/native/linux_wayland/extensions.rs +++ b/src/native/linux_wayland/extensions.rs @@ -2,7 +2,7 @@ pub mod cursor; pub mod libdecor; -// pub mod viewporter; +pub mod viewporter; pub mod xdg_decoration; pub mod xdg_shell; From ffe3798c2f3d39599bd28c730f5c3a6e833cc27b Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Sat, 1 Mar 2025 15:27:00 +0000 Subject: [PATCH 12/16] native/linux_wayland: Add touch support --- src/native/linux_wayland.rs | 104 ++++++++++++++++++ src/native/linux_wayland/libwayland_client.rs | 21 ++++ 2 files changed, 125 insertions(+) diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index 24a11a6a..ce376d9d 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -22,6 +22,7 @@ use crate::{ }; use core::time::Duration; +use std::collections::HashMap; fn wl_fixed_to_double(f: i32) -> f32 { (f as f32) / 256.0 @@ -53,6 +54,8 @@ struct WaylandPayload { egl_window: *mut wl_egl_window, pointer_context: PointerContext, keyboard: *mut wl_keyboard, + touch: *mut wl_touch, + touch_positions: HashMap, focused_window: *mut wl_surface, decoration_manager: *mut extensions::xdg_decoration::zxdg_decoration_manager_v1, decorations: decorations::Decorations, @@ -421,6 +424,7 @@ impl PointerContext { static mut SEAT_LISTENER: wl_seat_listener = wl_seat_listener::dummy(); static mut KEYBOARD_LISTENER: wl_keyboard_listener = wl_keyboard_listener::dummy(); static mut POINTER_LISTENER: wl_pointer_listener = wl_pointer_listener::dummy(); +static mut TOUCH_LISTENER: wl_touch_listener = wl_touch_listener::dummy(); static mut OUTPUT_LISTENER: wl_output_listener = wl_output_listener::dummy(); static mut DATA_DEVICE_LISTENER: wl_data_device_listener = wl_data_device_listener::dummy(); static mut DATA_OFFER_LISTENER: wl_data_offer_listener = wl_data_offer_listener::dummy(); @@ -476,6 +480,25 @@ unsafe extern "C" fn seat_handle_capabilities( data, ); } + + if caps & wl_seat_capability_WL_SEAT_CAPABILITY_TOUCH != 0 { + display.touch = wl_request_constructor!( + display.client, + seat, + WL_SEAT_GET_TOUCH, + display.client.wl_touch_interface + ); + assert!(!display.touch.is_null()); + TOUCH_LISTENER.down = touch_handle_down; + TOUCH_LISTENER.up = touch_handle_up; + TOUCH_LISTENER.motion = touch_handle_motion; + TOUCH_LISTENER.cancel = touch_handle_cancel; + (display.client.wl_proxy_add_listener)( + display.touch as _, + &TOUCH_LISTENER as *const _ as _, + data, + ); + } } enum WaylandEvent { @@ -486,6 +509,7 @@ enum WaylandEvent { RawMotion(f32, f32), PointerButton(MouseButton, bool), PointerAxis(f32, f32), + Touch(crate::TouchPhase, u64, f32, f32), FilesDropped(String), Resize(f32, f32), } @@ -746,6 +770,81 @@ unsafe extern "C" fn output_handle_scale( } } +unsafe extern "C" fn touch_handle_down( + data: *mut std::ffi::c_void, + _touch: *mut wl_touch, + _serial: core::ffi::c_uint, + _time: core::ffi::c_uint, + surface: *mut wl_surface, + id: core::ffi::c_int, + x: wl_fixed_t, + y: wl_fixed_t, +) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + display.focused_window = surface; + if display.focused_window == display.surface { + let d = crate::native_display().lock().unwrap(); + let x = wl_fixed_to_double(x) * d.dpi_scale; + let y = wl_fixed_to_double(y) * d.dpi_scale; + display.touch_positions.insert(id, (x, y)); + display.events.push(WaylandEvent::Touch( + crate::TouchPhase::Started, + id as _, + x, + y, + )); + } +} + +unsafe extern "C" fn touch_handle_motion( + data: *mut std::ffi::c_void, + _touch: *mut wl_touch, + _time: core::ffi::c_uint, + id: core::ffi::c_int, + x: wl_fixed_t, + y: wl_fixed_t, +) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + if display.focused_window == display.surface { + let d = crate::native_display().lock().unwrap(); + let x = wl_fixed_to_double(x) * d.dpi_scale; + let y = wl_fixed_to_double(y) * d.dpi_scale; + display.touch_positions.insert(id, (x, y)); + display + .events + .push(WaylandEvent::Touch(crate::TouchPhase::Moved, id as _, x, y)); + } +} + +unsafe extern "C" fn touch_handle_up( + data: *mut std::ffi::c_void, + _touch: *mut wl_touch, + _serial: core::ffi::c_uint, + _time: core::ffi::c_uint, + id: core::ffi::c_int, +) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + if display.focused_window == display.surface { + if let Some((x, y)) = display.touch_positions.remove(&id) { + display + .events + .push(WaylandEvent::Touch(crate::TouchPhase::Ended, id as _, x, y)); + } + } +} + +unsafe extern "C" fn touch_handle_cancel(data: *mut std::ffi::c_void, _touch: *mut wl_touch) { + let display: &mut WaylandPayload = &mut *(data as *mut _); + for (id, (x, y)) in display.touch_positions.drain() { + display.events.push(WaylandEvent::Touch( + crate::TouchPhase::Cancelled, + id as _, + x, + y, + )); + } +} + unsafe extern "C" fn registry_add_object( data: *mut std::ffi::c_void, registry: *mut wl_registry, @@ -966,6 +1065,8 @@ where xkb_state: std::ptr::null_mut(), egl_window: std::ptr::null_mut(), keyboard: std::ptr::null_mut(), + touch: std::ptr::null_mut(), + touch_positions: HashMap::new(), focused_window: std::ptr::null_mut(), decoration_manager: std::ptr::null_mut(), decorations: decorations::Decorations::None, @@ -1130,6 +1231,9 @@ where } } WaylandEvent::PointerAxis(x, y) => event_handler.mouse_wheel_event(x, y), + WaylandEvent::Touch(phase, id, x, y) => { + event_handler.touch_event(phase, id, x, y) + } WaylandEvent::Resize(width, height) => { event_handler.resize_event(width, height) } diff --git a/src/native/linux_wayland/libwayland_client.rs b/src/native/linux_wayland/libwayland_client.rs index d45e0463..addc6c9a 100644 --- a/src/native/linux_wayland/libwayland_client.rs +++ b/src/native/linux_wayland/libwayland_client.rs @@ -509,6 +509,26 @@ wl_listener!( ), ); +wl_listener!( + wl_touch_listener, + wl_touch, + wl_touch_dummy, + fn down( + serial: c_uint, + time: c_uint, + surface: *mut wl_surface, + id: c_int, + x: wl_fixed_t, + y: wl_fixed_t, + ), + fn up(serial: c_uint, time: c_uint, id: c_int), + fn motion(time: c_uint, id: c_int, x: wl_fixed_t, y: wl_fixed_t), + fn frame(), + fn cancel(), + fn shape(id: c_int, major: wl_fixed_t, minor: wl_fixed_t), + fn orientation(id: c_int, orientation: wl_fixed_t), +); + wl_listener!( wl_data_device_listener, wl_data_device, @@ -565,6 +585,7 @@ crate::declare_module!( pub wl_output_interface: *mut wl_interface, pub wl_keyboard_interface: *mut wl_interface, pub wl_pointer_interface: *mut wl_interface, + pub wl_touch_interface: *mut wl_interface, pub wl_data_device_manager_interface: *mut wl_interface, pub wl_data_device_interface: *mut wl_interface, pub wl_data_source_interface: *mut wl_interface, From 3394dac25e65eb040d5eb28e7c4095a708be3d5e Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Sat, 1 Mar 2025 18:55:39 +0000 Subject: [PATCH 13/16] native/linux_wayland: Emit "restore/minimized" --- src/native/linux_wayland.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index ce376d9d..40a97c3d 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -512,6 +512,8 @@ enum WaylandEvent { Touch(crate::TouchPhase, u64, f32, f32), FilesDropped(String), Resize(f32, f32), + WindowMinimized, + WindowRestored, } unsafe extern "C" fn keyboard_handle_keymap( @@ -554,6 +556,7 @@ unsafe extern "C" fn keyboard_handle_enter( let display: &mut WaylandPayload = &mut *(data as *mut _); // Needed for setting the clipboard display.keyboard_context.enter_serial = Some(serial); + display.events.push(WaylandEvent::WindowRestored); } unsafe extern "C" fn keyboard_handle_leave( data: *mut ::core::ffi::c_void, @@ -567,6 +570,7 @@ unsafe extern "C" fn keyboard_handle_leave( display.keyboard_context.keymods = KeyMods::default(); display.keyboard_context.repeated_key = None; display.keyboard_context.enter_serial = None; + display.events.push(WaylandEvent::WindowMinimized); } unsafe extern "C" fn keyboard_handle_key( data: *mut ::core::ffi::c_void, @@ -1237,6 +1241,8 @@ where WaylandEvent::Resize(width, height) => { event_handler.resize_event(width, height) } + WaylandEvent::WindowMinimized => event_handler.window_minimized_event(), + WaylandEvent::WindowRestored => event_handler.window_restored_event(), WaylandEvent::FilesDropped(filenames) => { let mut d = crate::native_display().try_lock().unwrap(); d.dropped_files = Default::default(); From 0199be8009cae0f0c5db08d781c692e119611359 Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Sun, 2 Mar 2025 22:18:11 +0000 Subject: [PATCH 14/16] native/linux_wayland: Fix some bugs - Fullscreen - High-dpi - Drag-n-drop "premature finish error" --- src/native/linux_wayland.rs | 19 ++++++++------ src/native/linux_wayland/clipboard.rs | 2 +- src/native/linux_wayland/decorations.rs | 25 ++++++++++++------- src/native/linux_wayland/drag_n_drop.rs | 17 +++++++++---- src/native/linux_wayland/libwayland_client.rs | 11 ++++---- 5 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index 40a97c3d..106668c4 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -161,6 +161,7 @@ impl WaylandPayload { self.client, self.xdg_toplevel, extensions::xdg_shell::xdg_toplevel::set_fullscreen, + std::ptr::null_mut::() ); } else { wl_request!( @@ -764,6 +765,9 @@ unsafe extern "C" fn output_handle_scale( let display: &mut WaylandPayload = &mut *(data as *mut _); let mut d = crate::native_display().try_lock().unwrap(); if d.high_dpi { + let dpi_scale = d.dpi_scale as i32; + d.screen_width = d.screen_width / dpi_scale * factor; + d.screen_height = d.screen_height / dpi_scale * factor; d.dpi_scale = factor as _; wl_request!( display.client, @@ -1113,11 +1117,14 @@ where ) .unwrap(); - display.egl_window = (display.egl.wl_egl_window_create)( - display.surface as _, - conf.window_width as _, - conf.window_height as _, - ); + { + let d = crate::native_display().try_lock().unwrap(); + display.egl_window = (display.egl.wl_egl_window_create)( + display.surface as _, + d.screen_width, + d.screen_height, + ); + } let egl_surface = (libegl.eglCreateWindowSurface)( egl_display, @@ -1165,8 +1172,6 @@ where wm_class.as_ptr() ); - // For some reason, setting fullscreen before egl_window is created leads - // to segfault because wl_egl_window_create returns NULL. if conf.fullscreen { display.set_fullscreen(true); } diff --git a/src/native/linux_wayland/clipboard.rs b/src/native/linux_wayland/clipboard.rs index 7f1e7a28..f49c9a97 100644 --- a/src/native/linux_wayland/clipboard.rs +++ b/src/native/linux_wayland/clipboard.rs @@ -37,7 +37,7 @@ impl ClipboardContext { display .client .data_offer_receive(display.display, data_offer, mime_type.as_ptr()) - }) + })? } unsafe fn set(&mut self, data: &str) { diff --git a/src/native/linux_wayland/decorations.rs b/src/native/linux_wayland/decorations.rs index fdd53f9b..9327ac7b 100644 --- a/src/native/linux_wayland/decorations.rs +++ b/src/native/linux_wayland/decorations.rs @@ -104,7 +104,12 @@ impl Decorations { unsafe fn fallback(display: &mut WaylandPayload) -> Self { create_xdg_toplevel(display); let d = crate::native_display().lock().unwrap(); - let decorations = fallback::Decorations::new(display, d.screen_width, d.screen_height); + let dpi_scale = d.dpi_scale as i32; + let decorations = fallback::Decorations::new( + display, + d.screen_width / dpi_scale, + d.screen_height / dpi_scale, + ); Decorations::Fallback(decorations) } @@ -184,8 +189,10 @@ unsafe extern "C" fn handle_configure(data: *mut std::ffi::c_void, width: i32, h if width != 0 && height != 0 { let mut d = crate::native_display().lock().unwrap(); - let screen_width = ((width as f32) * d.dpi_scale) as i32; - let screen_height = ((height as f32) * d.dpi_scale) as i32; + // Currently non-integer scales are not supported + let dpi_scale = d.dpi_scale as i32; + let screen_width = width * dpi_scale; + let screen_height = height * dpi_scale; // screen_width / screen_height are the actual numbers of pixels d.screen_width = screen_width; d.screen_height = screen_height; @@ -194,8 +201,9 @@ unsafe extern "C" fn handle_configure(data: *mut std::ffi::c_void, width: i32, h let mut window_width = screen_width; let mut window_height = screen_height; if let Decorations::Fallback(fallback) = &payload.decorations { - window_width -= fallback::Decorations::WIDTH * 2; - window_height -= fallback::Decorations::BAR_HEIGHT + fallback::Decorations::WIDTH; + window_width -= fallback::Decorations::WIDTH * 2 * dpi_scale; + window_height -= + (fallback::Decorations::BAR_HEIGHT + fallback::Decorations::WIDTH) * dpi_scale; fallback.resize(&mut payload.client, width, height); } (payload.egl.wl_egl_window_resize)(payload.egl_window, window_width, window_height, 0, 0); @@ -233,11 +241,10 @@ unsafe extern "C" fn libdecor_frame_handle_configure( &mut height, ) == 0 { - // libdecor failed to retrieve the new dimension, so we use the old value let d = crate::native_display().lock().unwrap(); - width = d.screen_width; - height = d.screen_height; - drop(d); + let dpi_scale = d.dpi_scale as i32; + width = d.screen_width / dpi_scale; + height = d.screen_height / dpi_scale; } let state = (libdecor.libdecor_state_new)(width, height); (libdecor.libdecor_frame_commit)(frame, state, configuration); diff --git a/src/native/linux_wayland/drag_n_drop.rs b/src/native/linux_wayland/drag_n_drop.rs index ad8e16e4..42975720 100644 --- a/src/native/linux_wayland/drag_n_drop.rs +++ b/src/native/linux_wayland/drag_n_drop.rs @@ -66,13 +66,20 @@ pub(super) unsafe extern "C" fn data_device_handle_drop( assert_eq!(data_device, display.data_device); if let Some(data_offer) = display.drag_n_drop.data_offer { let mime_type = std::ffi::CString::new("UTF8_STRING").unwrap(); - let bytes = + if let Some(bytes) = display .client - .data_offer_receive(display.display, data_offer, mime_type.as_ptr()); - wl_request!(display.client, data_offer, WL_DATA_OFFER_FINISH); - if let Ok(filenames) = String::from_utf8(bytes) { - display.events.push(WaylandEvent::FilesDropped(filenames)); + .data_offer_receive(display.display, data_offer, mime_type.as_ptr()) + { + // Doing `data_offer.finish` here sometimes causes "premature finish error" + // No idea why so we just delete the data_offer directly + // wl_request!(display.client, data_offer, WL_DATA_OFFER_FINISH); + wl_request!(display.client, data_offer, WL_DATA_OFFER_DESTROY); + (display.client.wl_proxy_destroy)(data_offer as _); + display.drag_n_drop.data_offer = None; + if let Ok(filenames) = String::from_utf8(bytes) { + display.events.push(WaylandEvent::FilesDropped(filenames)); + } } } } diff --git a/src/native/linux_wayland/libwayland_client.rs b/src/native/linux_wayland/libwayland_client.rs index addc6c9a..5ba484a4 100644 --- a/src/native/linux_wayland/libwayland_client.rs +++ b/src/native/linux_wayland/libwayland_client.rs @@ -645,7 +645,7 @@ impl LibWaylandClient { display: *mut wl_display, data_offer: *mut wl_data_offer, mime_type: *const c_char, - ) -> Vec { + ) -> Option> { let mut fds: [c_int; 2] = [0; 2]; assert_eq!(libc::pipe(fds.as_mut_ptr()), 0); (self.wl_proxy_marshal)(data_offer as _, WL_DATA_OFFER_RECEIVE, mime_type, fds[1]); @@ -655,13 +655,14 @@ impl LibWaylandClient { loop { let mut buf = [0_u8; 1024]; let n = libc::read(fds[0], buf.as_mut_ptr() as _, buf.len()); - if n <= 0 { - break; + match n { + n if n > 0 => bytes.extend_from_slice(&buf[..n as usize]), + 0 => break, + _ => return None, } - bytes.extend_from_slice(&buf[..n as usize]); } libc::close(fds[0]); - bytes + Some(bytes) } } From c1c42ab764e13c1a90e4fb8e1e2fe26acbda7883 Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Wed, 5 Mar 2025 19:02:29 +0000 Subject: [PATCH 15/16] native/linux_wayland: Improve resizing performance --- src/native/linux_wayland/decorations.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/native/linux_wayland/decorations.rs b/src/native/linux_wayland/decorations.rs index 9327ac7b..af684a14 100644 --- a/src/native/linux_wayland/decorations.rs +++ b/src/native/linux_wayland/decorations.rs @@ -207,9 +207,18 @@ unsafe extern "C" fn handle_configure(data: *mut std::ffi::c_void, width: i32, h fallback.resize(&mut payload.client, width, height); } (payload.egl.wl_egl_window_resize)(payload.egl_window, window_width, window_height, 0, 0); - payload - .events - .push(WaylandEvent::Resize(screen_width as _, screen_height as _)); + // The compositor can send multiple resizing configure during a single frame, and we + // probably don't want to fire the resize event for every one of them + // So if we still have a Resize event in the queue, instead of pushing a new one, we batch + // them by modifying the dimension + if let Some(WaylandEvent::Resize(width, height)) = payload.events.last_mut() { + *width = screen_width as _; + *height = screen_height as _; + } else { + payload + .events + .push(WaylandEvent::Resize(screen_width as _, screen_height as _)); + } } } From 0d2f4fc17fd58f7a76dcfc2813076b7dfd65739e Mon Sep 17 00:00:00 2001 From: bolphen <111203697+bolphen@users.noreply.github.com> Date: Wed, 5 Mar 2025 19:28:28 +0000 Subject: [PATCH 16/16] native/linux_wayland: Change setting for CSD --- src/conf.rs | 24 ++++++++++++++++---- src/native/linux_wayland.rs | 8 ++----- src/native/linux_wayland/decorations.rs | 30 ++++++++++++++----------- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/conf.rs b/src/conf.rs index a1110619..09a4244a 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -101,6 +101,22 @@ pub enum WebGLVersion { WebGL2, } +/// On Wayland, specify how to draw client-side decoration (CSD) if server-side decoration (SSD) is +/// not supported (e.g., on GNOME). +/// +/// Defaults to ServerWithLibDecorFallback +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub enum WaylandDecorations { + /// If SSD is not supported, will try to load `libdecor` to draw CSD. This is the default + /// choice. + #[default] + ServerWithLibDecorFallback, + /// If SSD is not supported, draw a light gray border. + ServerWithMiniquadFallback, + /// If SSD is not supported, no CSD will be drawn. + ServerOnly, +} + /// Platform-specific settings. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Platform { @@ -143,9 +159,9 @@ pub struct Platform { /// - TODO: Document(and check) what does it actually mean on android. Transparent window? pub framebuffer_alpha: bool, - /// On Wayland, the decorations are either drawn by the server or via `libdecor`. If neither is - /// available, then this flag controls whether fallback window decorations should be used. - pub wayland_use_fallback_decorations: bool, + /// On Wayland, specifies how to draw client-side decoration (CSD) if server-side decoration (SSD) is + /// not supported (e.g., on GNOME). + pub wayland_decorations: WaylandDecorations, /// Set the `WM_CLASS` window property on X11 and the `app_id` on Wayland. This is used /// by gnome to determine the window icon (together with an external `.desktop` file). @@ -165,7 +181,7 @@ impl Default for Platform { blocking_event_loop: false, swap_interval: None, framebuffer_alpha: false, - wayland_use_fallback_decorations: true, + wayland_decorations: WaylandDecorations::default(), linux_wm_class: "miniquad-application", } } diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index 106668c4..79293865 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -1150,12 +1150,8 @@ where (libegl.eglGetProcAddress)(name.as_ptr() as _) }); - let borderless = false; - display.decorations = decorations::Decorations::new( - &mut display, - borderless, - conf.platform.wayland_use_fallback_decorations, - ); + display.decorations = + decorations::Decorations::new(&mut display, conf.platform.wayland_decorations); assert!(!display.xdg_toplevel.is_null()); display.decorations.set_title( diff --git a/src/native/linux_wayland/decorations.rs b/src/native/linux_wayland/decorations.rs index af684a14..6789d03f 100644 --- a/src/native/linux_wayland/decorations.rs +++ b/src/native/linux_wayland/decorations.rs @@ -20,7 +20,7 @@ use extensions::xdg_shell::*; pub(super) enum Decorations { None, Server, - Client { + LibDecor { libdecor: LibDecor, context: *mut libdecor, frame: *mut libdecor_frame, @@ -60,14 +60,20 @@ unsafe fn create_xdg_toplevel(display: &mut WaylandPayload) { } impl Decorations { - pub(super) fn new(display: &mut WaylandPayload, borderless: bool, use_fallback: bool) -> Self { + pub(super) fn new( + display: &mut WaylandPayload, + fallback: crate::conf::WaylandDecorations, + ) -> Self { + use crate::conf::WaylandDecorations::*; unsafe { - if borderless { - Decorations::none(display) - } else if display.decoration_manager.is_null() { - Decorations::try_client(display, use_fallback) - } else { + if !display.decoration_manager.is_null() { Decorations::server(display) + } else { + match fallback { + ServerOnly => Decorations::none(display), + ServerWithLibDecorFallback => Decorations::try_libdecor(display), + ServerWithMiniquadFallback => Decorations::fallback(display), + } } } } @@ -88,7 +94,7 @@ impl Decorations { title.as_ptr() ); } - Decorations::Client { + Decorations::LibDecor { libdecor, frame, .. } => { (libdecor.libdecor_frame_set_title)(*frame, title.as_ptr()); @@ -134,7 +140,7 @@ impl Decorations { Decorations::Server } - unsafe fn try_client(display: &mut WaylandPayload, use_fallback: bool) -> Self { + unsafe fn try_libdecor(display: &mut WaylandPayload) -> Self { if let Ok(libdecor) = LibDecor::try_load() { let context = (libdecor.libdecor_new)(display.display, &mut LIBDECOR_INTERFACE as _); let frame = (libdecor.libdecor_decorate)( @@ -145,20 +151,18 @@ impl Decorations { ); (libdecor.libdecor_frame_map)(frame); display.xdg_toplevel = (libdecor.libdecor_frame_get_xdg_toplevel)(frame); - Decorations::Client { + Decorations::LibDecor { libdecor, context, frame, } - } else if use_fallback { - Decorations::fallback(display) } else { Decorations::none(display) } } fn libdecor(&mut self) -> Option<&mut LibDecor> { - if let Decorations::Client { libdecor, .. } = self { + if let Decorations::LibDecor { libdecor, .. } = self { Some(libdecor) } else { None