From 3d85af04bebeedbd5f974a0ec14884988fd58fda Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 29 Sep 2020 00:11:43 +0300 Subject: [PATCH] Update SCTK to 0.11.0 * Update SCTK to 0.11.0 Updates smithay-client-toolkit to 0.11.0. The major highlight of that updated, is update of wayland-rs to 0.27.0. Switching to wayland-cursor, instead of using libwayland-cursor. It also fixes the following bugs: - Disabled repeat rate not being handled. - Decoration buttons not working after tty switch. - Scaling not being applied on output reenable. - Crash when `XCURSOR_SIZE` is `0`. - Pointer getting created in some cases without pointer capability. - On kwin, fix space between window and decorations on startup. - Incorrect size event when entering fullscreen when using client side decorations. - Client side decorations not being hided properly in fullscreen. - Size tracking between fullscreen/tiled state changes. - Repeat rate triggering multiple times from slow callback handler. - Resizable attribute not being applied properly on startup. - Not working IME Besides those fixes it also adds a bunch of missing virtual key codes, implements proper cursor grabbing, adds right click on decorations to open application menu, disabled maximize button for non-resizeable window, and fall back for cursor icon to similar ones, if the requested is missing. It also adds new methods to a `Theme` trait, such as: - `title_font(&self) -> Option<(String, f32)>` - The font for a title. - `title_color(&self, window_active: bool) -> [u8; 4]` - The color of the text in the title. Fixes #1680. Fixes #1678. Fixes #1676. Fixes #1646. Fixes #1614. Fixes #1601. Fixes #1533. Fixes #1509. Fixes #952. Fixes #947. --- CHANGELOG.md | 17 + Cargo.toml | 6 +- src/platform/desktop.rs | 6 +- src/platform/unix.rs | 139 +- src/platform_impl/linux/mod.rs | 21 +- src/platform_impl/linux/wayland/env.rs | 149 +++ src/platform_impl/linux/wayland/event_loop.rs | 1134 ----------------- .../linux/wayland/event_loop/mod.rs | 539 ++++++++ .../linux/wayland/event_loop/proxy.rs | 32 + .../linux/wayland/event_loop/sink.rs | 36 + .../linux/wayland/event_loop/state.rs | 25 + src/platform_impl/linux/wayland/keyboard.rs | 396 ------ src/platform_impl/linux/wayland/mod.rs | 28 +- src/platform_impl/linux/wayland/output.rs | 240 ++++ src/platform_impl/linux/wayland/pointer.rs | 290 ----- .../linux/wayland/seat/keyboard/handlers.rs | 151 +++ .../linux/wayland/seat/keyboard/keymap.rs | 188 +++ .../linux/wayland/seat/keyboard/mod.rs | 105 ++ src/platform_impl/linux/wayland/seat/mod.rs | 208 +++ .../linux/wayland/seat/pointer/data.rs | 74 ++ .../linux/wayland/seat/pointer/handlers.rs | 297 +++++ .../linux/wayland/seat/pointer/mod.rs | 242 ++++ .../linux/wayland/seat/text_input/handlers.rs | 78 ++ .../linux/wayland/seat/text_input/mod.rs | 66 + .../linux/wayland/seat/touch/handlers.rs | 122 ++ .../linux/wayland/seat/touch/mod.rs | 78 ++ src/platform_impl/linux/wayland/touch.rs | 132 -- src/platform_impl/linux/wayland/window.rs | 569 --------- src/platform_impl/linux/wayland/window/mod.rs | 656 ++++++++++ .../linux/wayland/window/shim.rs | 388 ++++++ src/window.rs | 9 +- 31 files changed, 3791 insertions(+), 2630 deletions(-) create mode 100644 src/platform_impl/linux/wayland/env.rs delete mode 100644 src/platform_impl/linux/wayland/event_loop.rs create mode 100644 src/platform_impl/linux/wayland/event_loop/mod.rs create mode 100644 src/platform_impl/linux/wayland/event_loop/proxy.rs create mode 100644 src/platform_impl/linux/wayland/event_loop/sink.rs create mode 100644 src/platform_impl/linux/wayland/event_loop/state.rs delete mode 100644 src/platform_impl/linux/wayland/keyboard.rs create mode 100644 src/platform_impl/linux/wayland/output.rs delete mode 100644 src/platform_impl/linux/wayland/pointer.rs create mode 100644 src/platform_impl/linux/wayland/seat/keyboard/handlers.rs create mode 100644 src/platform_impl/linux/wayland/seat/keyboard/keymap.rs create mode 100644 src/platform_impl/linux/wayland/seat/keyboard/mod.rs create mode 100644 src/platform_impl/linux/wayland/seat/mod.rs create mode 100644 src/platform_impl/linux/wayland/seat/pointer/data.rs create mode 100644 src/platform_impl/linux/wayland/seat/pointer/handlers.rs create mode 100644 src/platform_impl/linux/wayland/seat/pointer/mod.rs create mode 100644 src/platform_impl/linux/wayland/seat/text_input/handlers.rs create mode 100644 src/platform_impl/linux/wayland/seat/text_input/mod.rs create mode 100644 src/platform_impl/linux/wayland/seat/touch/handlers.rs create mode 100644 src/platform_impl/linux/wayland/seat/touch/mod.rs delete mode 100644 src/platform_impl/linux/wayland/touch.rs delete mode 100644 src/platform_impl/linux/wayland/window.rs create mode 100644 src/platform_impl/linux/wayland/window/mod.rs create mode 100644 src/platform_impl/linux/wayland/window/shim.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index da2e8e34..5dc3fd18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,23 @@ - On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called. - **Breaking:** `Fullscreen` enum now uses `Borderless(Option)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor. - On MacOS, fix `WindowEvent::Moved` ignoring the scale factor. +- On Wayland, add missing virtual keycodes. +- On Wayland, implement proper `set_cursor_grab`. +- On Wayland, the cursor will use similar icons if the requested one isn't available. +- On Wayland, right clicking on client side decorations will request application menu. +- On Wayland, fix tracking of window size after state changes. +- On Wayland, fix client side decorations not being hidden properly in fullscreen. +- On Wayland, fix incorrect size event when entering fullscreen with client side decorations. +- On Wayland, fix `resizable` attribute not being applied properly on startup. +- On Wayland, fix disabled repeat rate not being handled. +- On Wayland, fix decoration buttons not working after tty switch. +- On Wayland, fix scaling not being applied on output re-enable. +- On Wayland, fix crash when `XCURSOR_SIZE` is `0`. +- On Wayland, fix pointer getting created in some cases without pointer capability. +- On Wayland, on kwin, fix space between window and decorations on startup. +- **Breaking:** On Wayland, `Theme` trait was reworked. +- On Wayland, disable maximize button for non-resizable window. +- On Wayland, added support for `set_ime_position`. # 0.22.2 (2020-05-16) diff --git a/Cargo.toml b/Cargo.toml index 6f9056b4..b10c4b8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ default = ["x11", "wayland"] web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"] stdweb = ["std_web", "instant/stdweb"] x11 = ["x11-dl"] -wayland = ["wayland-client", "smithay-client-toolkit"] +wayland = ["wayland-client", "sctk"] [dependencies] instant = "0.1" @@ -81,10 +81,10 @@ features = [ ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] -wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] , optional = true } +wayland-client = { version = "0.27", features = [ "dlopen"] , optional = true } +sctk = { package = "smithay-client-toolkit", version = "0.11", optional = true } mio = "0.6" mio-extras = "2.0" -smithay-client-toolkit = { version = "^0.6.6", optional = true } x11-dl = { version = "2.18.5", optional = true } percent-encoding = "2.0" diff --git a/src/platform/desktop.rs b/src/platform/desktop.rs index abf58b29..a49d71c3 100644 --- a/src/platform/desktop.rs +++ b/src/platform/desktop.rs @@ -2,7 +2,11 @@ target_os = "windows", target_os = "macos", target_os = "android", - target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" ))] use crate::{ diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 13346a86..02472dd8 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -1,12 +1,15 @@ -#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] +#![cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] use std::os::raw; #[cfg(feature = "x11")] use std::{ptr, sync::Arc}; -#[cfg(feature = "wayland")] -use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme}; - use crate::{ event_loop::{EventLoop, EventLoopWindowTarget}, monitor::MonitorHandle, @@ -328,7 +331,7 @@ impl WindowExtUnix for Window { #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void> { match self.window { - LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _), + LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _), #[cfg(feature = "x11")] _ => None, } @@ -338,7 +341,7 @@ impl WindowExtUnix for Window { #[cfg(feature = "wayland")] fn set_wayland_theme(&self, theme: T) { match self.window { - LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)), + LinuxWindow::Wayland(ref w) => w.set_theme(theme), #[cfg(feature = "x11")] _ => {} } @@ -466,83 +469,50 @@ impl MonitorHandleExtUnix for MonitorHandle { } } -/// Wrapper for implementing SCTK's theme trait. +/// A theme for a Wayland's client side decorations. #[cfg(feature = "wayland")] -struct WaylandTheme(T); - pub trait Theme: Send + 'static { - /// Primary color of the scheme. - fn primary_color(&self, window_active: bool) -> [u8; 4]; + /// Title bar color. + fn element_color(&self, element: Element, window_active: bool) -> ARGBColor; - /// Secondary color of the scheme. - fn secondary_color(&self, window_active: bool) -> [u8; 4]; + /// Color for a given button part. + fn button_color( + &self, + button: Button, + state: ButtonState, + foreground: bool, + window_active: bool, + ) -> ARGBColor; - /// Color for the close button. - fn close_button_color(&self, status: ButtonState) -> [u8; 4]; - - /// Icon color for the close button, defaults to the secondary color. - #[allow(unused_variables)] - fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] { - self.secondary_color(true) - } - - /// Background color for the maximize button. - fn maximize_button_color(&self, status: ButtonState) -> [u8; 4]; - - /// Icon color for the maximize button, defaults to the secondary color. - #[allow(unused_variables)] - fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] { - self.secondary_color(true) - } - - /// Background color for the minimize button. - fn minimize_button_color(&self, status: ButtonState) -> [u8; 4]; - - /// Icon color for the minimize button, defaults to the secondary color. - #[allow(unused_variables)] - fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] { - self.secondary_color(true) + /// Font name and the size for the title bar. + /// + /// By default the font is `sans-serif` at the size of 11. + /// + /// Returning `None` means that title won't be drawn. + fn font(&self) -> Option<(String, f32)> { + // Not having any title isn't something desirable for the users, so setting it to + // something generic. + Some((String::from("sans-serif"), 11.)) } } +/// A button on Wayland's client side decorations. #[cfg(feature = "wayland")] -impl SCTKTheme for WaylandTheme { - fn get_primary_color(&self, active: bool) -> [u8; 4] { - self.0.primary_color(active) - } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Button { + /// Button that maximizes the window. + Maximize, - fn get_secondary_color(&self, active: bool) -> [u8; 4] { - self.0.secondary_color(active) - } + /// Button that minimizes the window. + Minimize, - fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0.close_button_color(ButtonState::from_sctk(status)) - } - - fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0 - .close_button_icon_color(ButtonState::from_sctk(status)) - } - - fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0.maximize_button_color(ButtonState::from_sctk(status)) - } - - fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0 - .maximize_button_icon_color(ButtonState::from_sctk(status)) - } - - fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0.minimize_button_color(ButtonState::from_sctk(status)) - } - - fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0 - .minimize_button_icon_color(ButtonState::from_sctk(status)) - } + /// Button that closes the window. + Close, } +/// A button state of the button on Wayland's client side decorations. +#[cfg(feature = "wayland")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ButtonState { /// Button is being hovered over by pointer. Hovered, @@ -553,12 +523,23 @@ pub enum ButtonState { } #[cfg(feature = "wayland")] -impl ButtonState { - fn from_sctk(button_state: SCTKButtonState) -> Self { - match button_state { - SCTKButtonState::Hovered => Self::Hovered, - SCTKButtonState::Idle => Self::Idle, - SCTKButtonState::Disabled => Self::Disabled, - } - } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Element { + /// Bar itself. + Bar, + + /// Separator between window and title bar. + Separator, + + /// Title bar text. + Text, +} + +#[cfg(feature = "wayland")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ARGBColor { + pub a: u8, + pub r: u8, + pub g: u8, + pub b: u8, } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 43fd8451..7bc91bde 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -9,6 +9,8 @@ #[cfg(all(not(feature = "x11"), not(feature = "wayland")))] compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); +#[cfg(feature = "wayland")] +use std::error::Error; use std::{collections::VecDeque, env, fmt}; #[cfg(feature = "x11")] use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc}; @@ -16,8 +18,6 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc}; #[cfg(feature = "x11")] use parking_lot::Mutex; use raw_window_handle::RawWindowHandle; -#[cfg(feature = "wayland")] -use smithay_client_toolkit::reexports::client::ConnectError; #[cfg(feature = "x11")] pub use self::x11::XNotSupported; @@ -108,6 +108,8 @@ pub enum OsError { XError(XError), #[cfg(feature = "x11")] XMisc(&'static str), + #[cfg(feature = "wayland")] + WaylandMisc(&'static str), } impl fmt::Display for OsError { @@ -117,6 +119,8 @@ impl fmt::Display for OsError { OsError::XError(ref e) => _f.pad(&e.description), #[cfg(feature = "x11")] OsError::XMisc(ref e) => _f.pad(e), + #[cfg(feature = "wayland")] + OsError::WaylandMisc(ref e) => _f.pad(e), } } } @@ -410,13 +414,8 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, _position: Position) { - match self { - #[cfg(feature = "x11")] - &Window::X(ref w) => w.set_ime_position(_position), - #[cfg(feature = "wayland")] - _ => (), - } + pub fn set_ime_position(&self, position: Position) { + x11_or_wayland!(match self; Window(w) => w.set_ime_position(position)) } #[inline] @@ -597,14 +596,14 @@ impl EventLoop { } #[cfg(feature = "wayland")] - pub fn new_wayland() -> Result, ConnectError> { + pub fn new_wayland() -> Result, Box> { assert_is_main_thread("new_wayland_any_thread"); EventLoop::new_wayland_any_thread() } #[cfg(feature = "wayland")] - pub fn new_wayland_any_thread() -> Result, ConnectError> { + pub fn new_wayland_any_thread() -> Result, Box> { wayland::EventLoop::new().map(EventLoop::Wayland) } diff --git a/src/platform_impl/linux/wayland/env.rs b/src/platform_impl/linux/wayland/env.rs new file mode 100644 index 00000000..1cb2745b --- /dev/null +++ b/src/platform_impl/linux/wayland/env.rs @@ -0,0 +1,149 @@ +//! SCTK environment setup. + +use sctk::reexports::client::protocol::wl_compositor::WlCompositor; +use sctk::reexports::client::protocol::wl_output::WlOutput; +use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6; +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1; +use sctk::reexports::client::protocol::wl_shell::WlShell; +use sctk::reexports::client::protocol::wl_subcompositor::WlSubcompositor; +use sctk::reexports::client::{Attached, DispatchData}; +use sctk::reexports::client::protocol::wl_shm::WlShm; +use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase; +use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1; +use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; + +use sctk::environment::{Environment, SimpleGlobal}; +use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener}; +use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener}; +use sctk::shell::{Shell, ShellHandler, ShellHandling}; +use sctk::shm::ShmHandler; + +/// Set of extra features that are supported by the compositor. +#[derive(Debug, Clone, Copy)] +pub struct WindowingFeatures { + cursor_grab: bool, +} + +impl WindowingFeatures { + /// Create `WindowingFeatures` based on the presented interfaces. + pub fn new(env: &Environment) -> Self { + let cursor_grab = env.get_global::().is_some(); + Self { cursor_grab } + } + + pub fn cursor_grab(&self) -> bool { + self.cursor_grab + } +} + +sctk::environment!(WinitEnv, + singles = [ + WlShm => shm, + WlCompositor => compositor, + WlSubcompositor => subcompositor, + WlShell => shell, + XdgWmBase => shell, + ZxdgShellV6 => shell, + ZxdgDecorationManagerV1 => decoration_manager, + ZwpRelativePointerManagerV1 => relative_pointer_manager, + ZwpPointerConstraintsV1 => pointer_constraints, + ZwpTextInputManagerV3 => text_input_manager, + ], + multis = [ + WlSeat => seats, + WlOutput => outputs, + ] +); + +/// The environment that we utilize. +pub struct WinitEnv { + seats: SeatHandler, + + outputs: OutputHandler, + + shm: ShmHandler, + + compositor: SimpleGlobal, + + subcompositor: SimpleGlobal, + + shell: ShellHandler, + + relative_pointer_manager: SimpleGlobal, + + pointer_constraints: SimpleGlobal, + + text_input_manager: SimpleGlobal, + + decoration_manager: SimpleGlobal, +} + +impl WinitEnv { + pub fn new() -> Self { + // Output tracking for available_monitors, etc. + let outputs = OutputHandler::new(); + + // Keyboard/Pointer/Touch input. + let seats = SeatHandler::new(); + + // Essential globals. + let shm = ShmHandler::new(); + let compositor = SimpleGlobal::new(); + let subcompositor = SimpleGlobal::new(); + + // Gracefully handle shell picking, since SCTK automatically supports multiple + // backends. + let shell = ShellHandler::new(); + + // Server side decorations. + let decoration_manager = SimpleGlobal::new(); + + // Device events for pointer. + let relative_pointer_manager = SimpleGlobal::new(); + + // Pointer grab functionality. + let pointer_constraints = SimpleGlobal::new(); + + // IME handling. + let text_input_manager = SimpleGlobal::new(); + + Self { + seats, + outputs, + shm, + compositor, + subcompositor, + shell, + decoration_manager, + relative_pointer_manager, + pointer_constraints, + text_input_manager, + } + } +} + +impl ShellHandling for WinitEnv { + fn get_shell(&self) -> Option { + self.shell.get_shell() + } +} + +impl SeatHandling for WinitEnv { + fn listen, &SeatData, DispatchData<'_>) + 'static>( + &mut self, + f: F, + ) -> SeatListener { + self.seats.listen(f) + } +} + +impl OutputHandling for WinitEnv { + fn listen) + 'static>( + &mut self, + f: F, + ) -> OutputStatusListener { + self.outputs.listen(f) + } +} diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs deleted file mode 100644 index 4dd66f12..00000000 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ /dev/null @@ -1,1134 +0,0 @@ -use std::{ - cell::RefCell, - collections::VecDeque, - fmt, - io::ErrorKind, - rc::Rc, - sync::{Arc, Mutex}, - time::{Duration, Instant}, -}; - -use mio::{Events, Poll, PollOpt, Ready, Token}; - -use mio_extras::channel::{channel, Receiver, SendError, Sender}; - -use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{ - zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::ZwpPointerConstraintsV1, -}; -use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{ - zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, - zwp_relative_pointer_v1::ZwpRelativePointerV1, -}; - -use smithay_client_toolkit::pointer::{AutoPointer, AutoThemer}; -use smithay_client_toolkit::reexports::client::protocol::{ - wl_compositor::WlCompositor, wl_shm::WlShm, wl_surface::WlSurface, -}; - -use crate::{ - dpi::{LogicalSize, PhysicalPosition, PhysicalSize}, - event::{ - DeviceEvent, DeviceId as RootDeviceId, Event, ModifiersState, StartCause, WindowEvent, - }, - event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform_impl::platform::{ - sticky_exit_callback, DeviceId as PlatformDeviceId, MonitorHandle as PlatformMonitorHandle, - VideoMode as PlatformVideoMode, WindowId as PlatformWindowId, - }, - window::{CursorIcon, WindowId as RootWindowId}, -}; - -use super::{ - window::{DecorationsAction, WindowStore}, - DeviceId, WindowId, -}; - -use smithay_client_toolkit::{ - output::OutputMgr, - reexports::client::{ - protocol::{wl_keyboard, wl_output, wl_pointer, wl_registry, wl_seat, wl_touch}, - ConnectError, Display, EventQueue, GlobalEvent, - }, - Environment, -}; - -const KBD_TOKEN: Token = Token(0); -const USER_TOKEN: Token = Token(1); -const EVQ_TOKEN: Token = Token(2); - -#[derive(Clone)] -pub struct EventsSink { - sender: Sender>, -} - -impl EventsSink { - pub fn new(sender: Sender>) -> EventsSink { - EventsSink { sender } - } - - pub fn send_event(&self, event: Event<'static, ()>) { - self.sender.send(event).unwrap() - } - - pub fn send_device_event(&self, event: DeviceEvent, device_id: DeviceId) { - self.send_event(Event::DeviceEvent { - event, - device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)), - }); - } - - pub fn send_window_event(&self, event: WindowEvent<'static>, window_id: WindowId) { - self.send_event(Event::WindowEvent { - event, - window_id: RootWindowId(PlatformWindowId::Wayland(window_id)), - }); - } -} - -pub struct CursorManager { - pointer_constraints_proxy: Arc>>, - auto_themer: Option, - pointers: Vec, - locked_pointers: Vec, - cursor_visible: bool, - current_cursor: CursorIcon, - scale_factor: u32, -} - -impl CursorManager { - fn new(constraints: Arc>>) -> CursorManager { - CursorManager { - pointer_constraints_proxy: constraints, - auto_themer: None, - pointers: Vec::new(), - locked_pointers: Vec::new(), - cursor_visible: true, - current_cursor: CursorIcon::default(), - scale_factor: 1, - } - } - - fn register_pointer(&mut self, pointer: wl_pointer::WlPointer) { - let auto_themer = self - .auto_themer - .as_ref() - .expect("AutoThemer not initialized. Server did not advertise shm or compositor?"); - self.pointers.push(auto_themer.theme_pointer(pointer)); - } - - fn set_auto_themer(&mut self, auto_themer: AutoThemer) { - self.auto_themer = Some(auto_themer); - } - - pub fn set_cursor_visible(&mut self, visible: bool) { - if !visible { - for pointer in self.pointers.iter() { - (**pointer).set_cursor(0, None, 0, 0); - } - } else { - self.set_cursor_icon_impl(self.current_cursor); - } - self.cursor_visible = visible; - } - - /// A helper function to restore cursor styles on PtrEvent::Enter. - pub fn reload_cursor_style(&mut self) { - if !self.cursor_visible { - self.set_cursor_visible(false); - } else { - self.set_cursor_icon_impl(self.current_cursor); - } - } - - pub fn set_cursor_icon(&mut self, cursor: CursorIcon) { - if cursor != self.current_cursor { - self.current_cursor = cursor; - if self.cursor_visible { - self.set_cursor_icon_impl(cursor); - } - } - } - - pub fn update_scale_factor(&mut self, scale: u32) { - self.scale_factor = scale; - self.reload_cursor_style(); - } - - fn set_cursor_icon_impl(&mut self, cursor: CursorIcon) { - let cursor = match cursor { - CursorIcon::Alias => "link", - CursorIcon::Arrow => "arrow", - CursorIcon::Cell => "plus", - CursorIcon::Copy => "copy", - CursorIcon::Crosshair => "crosshair", - CursorIcon::Default => "left_ptr", - CursorIcon::Hand => "hand", - CursorIcon::Help => "question_arrow", - CursorIcon::Move => "move", - CursorIcon::Grab => "grab", - CursorIcon::Grabbing => "grabbing", - CursorIcon::Progress => "progress", - CursorIcon::AllScroll => "all-scroll", - CursorIcon::ContextMenu => "context-menu", - - CursorIcon::NoDrop => "no-drop", - CursorIcon::NotAllowed => "crossed_circle", - - // Resize cursors - CursorIcon::EResize => "right_side", - CursorIcon::NResize => "top_side", - CursorIcon::NeResize => "top_right_corner", - CursorIcon::NwResize => "top_left_corner", - CursorIcon::SResize => "bottom_side", - CursorIcon::SeResize => "bottom_right_corner", - CursorIcon::SwResize => "bottom_left_corner", - CursorIcon::WResize => "left_side", - CursorIcon::EwResize => "h_double_arrow", - CursorIcon::NsResize => "v_double_arrow", - CursorIcon::NwseResize => "bd_double_arrow", - CursorIcon::NeswResize => "fd_double_arrow", - CursorIcon::ColResize => "h_double_arrow", - CursorIcon::RowResize => "v_double_arrow", - - CursorIcon::Text => "text", - CursorIcon::VerticalText => "vertical-text", - - CursorIcon::Wait => "watch", - - CursorIcon::ZoomIn => "zoom-in", - CursorIcon::ZoomOut => "zoom-out", - }; - - for pointer in self.pointers.iter() { - // Ignore erros, since we don't want to fail hard in case we can't find a proper cursor - // in a given theme. - let _ = pointer.set_cursor_with_scale(cursor, self.scale_factor, None); - } - } - - // This function can only be called from a thread on which `pointer_constraints_proxy` event - // queue is located, so calling it directly from a Window doesn't work well, in case - // you've sent your window to another thread, so we need to pass cursor grab updates to - // the event loop and call this function from there. - fn grab_pointer(&mut self, surface: Option<&WlSurface>) { - for locked_pointer in self.locked_pointers.drain(..) { - locked_pointer.destroy(); - } - - if let Some(surface) = surface { - for pointer in self.pointers.iter() { - let locked_pointer = self - .pointer_constraints_proxy - .try_lock() - .unwrap() - .as_ref() - .and_then(|pointer_constraints| { - super::pointer::implement_locked_pointer( - surface, - &**pointer, - pointer_constraints, - ) - .ok() - }); - - if let Some(locked_pointer) = locked_pointer { - self.locked_pointers.push(locked_pointer); - } - } - } - } -} - -pub struct EventLoop { - // Poll instance - poll: Poll, - // The wayland display - pub display: Arc, - // The cursor manager - cursor_manager: Arc>, - kbd_channel: Receiver>, - user_channel: Receiver, - user_sender: Sender, - window_target: RootELW, -} - -// A handle that can be sent across threads and used to wake up the `EventLoop`. -// -// We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs. -pub struct EventLoopProxy { - user_sender: Sender, -} - -pub struct EventLoopWindowTarget { - // the event queue - pub evq: RefCell, - // The window store - pub store: Arc>, - // The cursor manager - pub cursor_manager: Arc>, - // The env - pub env: Environment, - // A cleanup switch to prune dead windows - pub cleanup_needed: Arc>, - // The wayland display - pub display: Arc, - // The list of seats - pub seats: Arc>>, - // The output manager - pub outputs: OutputMgr, - _marker: ::std::marker::PhantomData, -} - -impl Clone for EventLoopProxy { - fn clone(&self) -> Self { - EventLoopProxy { - user_sender: self.user_sender.clone(), - } - } -} - -impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self.user_sender.send(event).map_err(|e| { - EventLoopClosed(if let SendError::Disconnected(x) = e { - x - } else { - unreachable!() - }) - }) - } -} - -impl EventLoop { - pub fn new() -> Result, ConnectError> { - let (display, mut event_queue) = Display::connect_to_env()?; - - let display = Arc::new(display); - let store = Arc::new(Mutex::new(WindowStore::new())); - let seats = Arc::new(Mutex::new(Vec::new())); - - let poll = Poll::new().unwrap(); - - let (kbd_sender, kbd_channel) = channel(); - - let sink = EventsSink::new(kbd_sender); - - poll.register(&kbd_channel, KBD_TOKEN, Ready::readable(), PollOpt::level()) - .unwrap(); - - let pointer_constraints_proxy = Arc::new(Mutex::new(None)); - - let mut seat_manager = SeatManager { - sink, - store: store.clone(), - seats: seats.clone(), - relative_pointer_manager_proxy: Rc::new(RefCell::new(None)), - pointer_constraints_proxy: pointer_constraints_proxy.clone(), - cursor_manager: Arc::new(Mutex::new(CursorManager::new(pointer_constraints_proxy))), - }; - - let cursor_manager = seat_manager.cursor_manager.clone(); - let cursor_manager_clone = cursor_manager.clone(); - - let shm_cell = Rc::new(RefCell::new(None)); - let compositor_cell = Rc::new(RefCell::new(None)); - - let env = Environment::from_display_with_cb( - &display, - &mut event_queue, - move |event, registry| match event { - GlobalEvent::New { - id, - ref interface, - version, - } => { - if interface == "zwp_relative_pointer_manager_v1" { - let relative_pointer_manager_proxy = registry - .bind(version, id, move |pointer_manager| { - pointer_manager.implement_closure(|_, _| (), ()) - }) - .unwrap(); - - *seat_manager - .relative_pointer_manager_proxy - .try_borrow_mut() - .unwrap() = Some(relative_pointer_manager_proxy); - } - if interface == "zwp_pointer_constraints_v1" { - let pointer_constraints_proxy = registry - .bind(version, id, move |pointer_constraints| { - pointer_constraints.implement_closure(|_, _| (), ()) - }) - .unwrap(); - - *seat_manager.pointer_constraints_proxy.lock().unwrap() = - Some(pointer_constraints_proxy); - } - if interface == "wl_shm" { - let shm: WlShm = registry - .bind(version, id, move |shm| shm.implement_closure(|_, _| (), ())) - .unwrap(); - - (*shm_cell.borrow_mut()) = Some(shm); - } - if interface == "wl_compositor" { - let compositor: WlCompositor = registry - .bind(version, id, move |compositor| { - compositor.implement_closure(|_, _| (), ()) - }) - .unwrap(); - (*compositor_cell.borrow_mut()) = Some(compositor); - } - - if compositor_cell.borrow().is_some() && shm_cell.borrow().is_some() { - let compositor = compositor_cell.borrow_mut().take().unwrap(); - let shm = shm_cell.borrow_mut().take().unwrap(); - let auto_themer = AutoThemer::init(None, compositor, &shm); - cursor_manager_clone - .lock() - .unwrap() - .set_auto_themer(auto_themer); - } - - if interface == "wl_seat" { - seat_manager.add_seat(id, version, registry) - } - } - GlobalEvent::Removed { id, ref interface } => { - if interface == "wl_seat" { - seat_manager.remove_seat(id) - } - } - }, - ) - .unwrap(); - - poll.register(&event_queue, EVQ_TOKEN, Ready::readable(), PollOpt::level()) - .unwrap(); - - let (user_sender, user_channel) = channel(); - - poll.register( - &user_channel, - USER_TOKEN, - Ready::readable(), - PollOpt::level(), - ) - .unwrap(); - - let cursor_manager_clone = cursor_manager.clone(); - let outputs = env.outputs.clone(); - Ok(EventLoop { - poll, - display: display.clone(), - user_sender, - user_channel, - kbd_channel, - cursor_manager, - window_target: RootELW { - p: crate::platform_impl::EventLoopWindowTarget::Wayland(EventLoopWindowTarget { - evq: RefCell::new(event_queue), - store, - env, - cursor_manager: cursor_manager_clone, - cleanup_needed: Arc::new(Mutex::new(false)), - seats, - display, - outputs, - _marker: ::std::marker::PhantomData, - }), - _marker: ::std::marker::PhantomData, - }, - }) - } - - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy { - user_sender: self.user_sender.clone(), - } - } - - pub fn run(mut self, callback: F) -> ! - where - F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - self.run_return(callback); - std::process::exit(0); - } - - pub fn run_return(&mut self, mut callback: F) - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - // send pending events to the server - self.display.flush().expect("Wayland connection lost."); - - let mut control_flow = ControlFlow::default(); - let mut events = Events::with_capacity(8); - - callback( - Event::NewEvents(StartCause::Init), - &self.window_target, - &mut control_flow, - ); - - loop { - // Read events from the event queue - { - let mut evq = get_target(&self.window_target).evq.borrow_mut(); - - evq.dispatch_pending() - .expect("failed to dispatch wayland events"); - - if let Some(read) = evq.prepare_read() { - if let Err(e) = read.read_events() { - if e.kind() != ErrorKind::WouldBlock { - panic!("failed to read wayland events: {}", e); - } - } - - evq.dispatch_pending() - .expect("failed to dispatch wayland events"); - } - } - - self.post_dispatch_triggers(&mut callback, &mut control_flow); - - while let Ok(event) = self.kbd_channel.try_recv() { - let event = event.map_nonuser_event().unwrap(); - sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); - } - - while let Ok(event) = self.user_channel.try_recv() { - sticky_exit_callback( - Event::UserEvent(event), - &self.window_target, - &mut control_flow, - &mut callback, - ); - } - - // send Events cleared - { - sticky_exit_callback( - Event::MainEventsCleared, - &self.window_target, - &mut control_flow, - &mut callback, - ); - } - - // handle request-redraw - { - self.redraw_triggers(|wid, window_target| { - sticky_exit_callback( - Event::RedrawRequested(crate::window::WindowId( - crate::platform_impl::WindowId::Wayland(wid), - )), - window_target, - &mut control_flow, - &mut callback, - ); - }); - } - - // send RedrawEventsCleared - { - sticky_exit_callback( - Event::RedrawEventsCleared, - &self.window_target, - &mut control_flow, - &mut callback, - ); - } - - // send pending events to the server - self.display.flush().expect("Wayland connection lost."); - - // During the run of the user callback, some other code monitoring and reading the - // wayland socket may have been run (mesa for example does this with vsync), if that - // is the case, some events may have been enqueued in our event queue. - // - // If some messages are there, the event loop needs to behave as if it was instantly - // woken up by messages arriving from the wayland socket, to avoid getting stuck. - let instant_wakeup = { - let window_target = match self.window_target.p { - crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, - #[cfg(feature = "x11")] - _ => unreachable!(), - }; - let dispatched = window_target - .evq - .borrow_mut() - .dispatch_pending() - .expect("Wayland connection lost."); - dispatched > 0 - }; - - match control_flow { - ControlFlow::Exit => break, - ControlFlow::Poll => { - // non-blocking dispatch - self.poll - .poll(&mut events, Some(Duration::from_millis(0))) - .unwrap(); - events.clear(); - - callback( - Event::NewEvents(StartCause::Poll), - &self.window_target, - &mut control_flow, - ); - } - ControlFlow::Wait => { - if !instant_wakeup { - self.poll.poll(&mut events, None).unwrap(); - events.clear(); - } - - callback( - Event::NewEvents(StartCause::WaitCancelled { - start: Instant::now(), - requested_resume: None, - }), - &self.window_target, - &mut control_flow, - ); - } - ControlFlow::WaitUntil(deadline) => { - let start = Instant::now(); - // compute the blocking duration - let duration = if deadline > start && !instant_wakeup { - deadline - start - } else { - Duration::from_millis(0) - }; - self.poll.poll(&mut events, Some(duration)).unwrap(); - events.clear(); - - let now = Instant::now(); - if now < deadline { - callback( - Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: Some(deadline), - }), - &self.window_target, - &mut control_flow, - ); - } else { - callback( - Event::NewEvents(StartCause::ResumeTimeReached { - start, - requested_resume: deadline, - }), - &self.window_target, - &mut control_flow, - ); - } - } - } - } - - callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); - } - - pub fn window_target(&self) -> &RootELW { - &self.window_target - } -} - -impl EventLoopWindowTarget { - pub fn display(&self) -> &Display { - &*self.display - } - - pub fn available_monitors(&self) -> VecDeque { - available_monitors(&self.outputs) - } - - pub fn primary_monitor(&self) -> Option { - // Wayland doesn't have a notion of primary monitor. - None - } -} - -/* - * Private EventLoop Internals - */ - -impl EventLoop { - fn redraw_triggers(&mut self, mut callback: F) - where - F: FnMut(WindowId, &RootELW), - { - let window_target = match self.window_target.p { - crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, - #[cfg(feature = "x11")] - _ => unreachable!(), - }; - window_target.store.lock().unwrap().for_each_redraw_trigger( - |refresh, frame_refresh, wid, frame| { - if let Some(frame) = frame { - let mut frame = frame.lock().unwrap(); - - if frame_refresh { - frame.refresh(); - if !refresh { - frame.surface().commit() - } - } - } - if refresh { - callback(wid, &self.window_target); - } - }, - ) - } - - fn post_dispatch_triggers(&mut self, mut callback: F, control_flow: &mut ControlFlow) - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - let window_target = match self.window_target.p { - crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, - #[cfg(feature = "x11")] - _ => unreachable!(), - }; - - let mut callback = |event: Event<'_, T>| { - sticky_exit_callback(event, &self.window_target, control_flow, &mut callback); - }; - - // prune possible dead windows - { - let mut cleanup_needed = window_target.cleanup_needed.lock().unwrap(); - if *cleanup_needed { - let pruned = window_target.store.lock().unwrap().cleanup(); - *cleanup_needed = false; - for wid in pruned { - callback(Event::WindowEvent { - window_id: crate::window::WindowId( - crate::platform_impl::WindowId::Wayland(wid), - ), - event: WindowEvent::Destroyed, - }); - } - } - } - // process pending resize/refresh - window_target.store.lock().unwrap().for_each(|window| { - let window_id = - crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid)); - - // Update window logical .size field (for callbacks using .inner_size) - let (old_logical_size, mut logical_size) = { - let mut window_size = window.size.lock().unwrap(); - let old_logical_size = *window_size; - *window_size = window.new_size.unwrap_or(old_logical_size); - (old_logical_size, *window_size) - }; - - if let Some(scale_factor) = window.new_scale_factor { - // Update cursor scale factor - self.cursor_manager - .lock() - .unwrap() - .update_scale_factor(scale_factor as u32); - let new_logical_size = { - let scale_factor = scale_factor as f64; - let mut physical_size = - LogicalSize::::from(logical_size).to_physical(scale_factor); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size: &mut physical_size, - }, - }); - physical_size.to_logical::(scale_factor).into() - }; - // Update size if changed by callback - if new_logical_size != logical_size { - logical_size = new_logical_size; - *window.size.lock().unwrap() = logical_size.into(); - } - } - - if window.new_size.is_some() || window.new_scale_factor.is_some() { - if let Some(frame) = window.frame { - let mut frame = frame.lock().unwrap(); - // Update decorations state - match window.decorations_action { - Some(DecorationsAction::Hide) => frame.set_decorate(false), - Some(DecorationsAction::Show) => frame.set_decorate(true), - None => (), - } - - // mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case - // it overlaps mutter's `bounding box`, so we can't avoid this resize call, - // which calls `set_geometry` under the hood, for now. - let (w, h) = logical_size; - frame.resize(w, h); - frame.refresh(); - } - // Don't send resize event downstream if the new logical size and scale is identical to the - // current one - if logical_size != old_logical_size || window.new_scale_factor.is_some() { - let physical_size = LogicalSize::::from(logical_size).to_physical( - window.new_scale_factor.unwrap_or(window.prev_scale_factor) as f64, - ); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Resized(physical_size), - }); - } - } - - if window.closed { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::CloseRequested, - }); - } - - if let Some(grab_cursor) = window.grab_cursor { - let surface = if grab_cursor { - Some(window.surface) - } else { - None - }; - self.cursor_manager.lock().unwrap().grab_pointer(surface); - } - }) - } -} - -fn get_target(target: &RootELW) -> &EventLoopWindowTarget { - match target.p { - crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, - #[cfg(feature = "x11")] - _ => unreachable!(), - } -} - -/* - * Wayland protocol implementations - */ - -struct SeatManager { - sink: EventsSink, - store: Arc>, - seats: Arc>>, - relative_pointer_manager_proxy: Rc>>, - pointer_constraints_proxy: Arc>>, - cursor_manager: Arc>, -} - -impl SeatManager { - fn add_seat(&mut self, id: u32, version: u32, registry: wl_registry::WlRegistry) { - use std::cmp::min; - - let mut seat_data = SeatData { - sink: self.sink.clone(), - store: self.store.clone(), - pointer: None, - relative_pointer: None, - relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.clone(), - keyboard: None, - touch: None, - modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())), - cursor_manager: self.cursor_manager.clone(), - }; - let seat = registry - .bind(min(version, 5), id, move |seat| { - seat.implement_closure(move |event, seat| seat_data.receive(event, seat), ()) - }) - .unwrap(); - self.store.lock().unwrap().new_seat(&seat); - self.seats.lock().unwrap().push((id, seat)); - } - - fn remove_seat(&mut self, id: u32) { - let mut seats = self.seats.lock().unwrap(); - if let Some(idx) = seats.iter().position(|&(i, _)| i == id) { - let (_, seat) = seats.swap_remove(idx); - if seat.as_ref().version() >= 5 { - seat.release(); - } - } - } -} - -struct SeatData { - sink: EventsSink, - store: Arc>, - pointer: Option, - relative_pointer: Option, - relative_pointer_manager_proxy: Rc>>, - keyboard: Option, - touch: Option, - modifiers_tracker: Arc>, - cursor_manager: Arc>, -} - -impl SeatData { - fn receive(&mut self, evt: wl_seat::Event, seat: wl_seat::WlSeat) { - match evt { - wl_seat::Event::Name { .. } => (), - wl_seat::Event::Capabilities { capabilities } => { - // create pointer if applicable - if capabilities.contains(wl_seat::Capability::Pointer) && self.pointer.is_none() { - self.pointer = Some(super::pointer::implement_pointer( - &seat, - self.sink.clone(), - self.store.clone(), - self.modifiers_tracker.clone(), - self.cursor_manager.clone(), - )); - - self.cursor_manager - .lock() - .unwrap() - .register_pointer(self.pointer.as_ref().unwrap().clone()); - - self.relative_pointer = self - .relative_pointer_manager_proxy - .try_borrow() - .unwrap() - .as_ref() - .and_then(|manager| { - super::pointer::implement_relative_pointer( - self.sink.clone(), - self.pointer.as_ref().unwrap(), - manager, - ) - .ok() - }) - } - // destroy pointer if applicable - if !capabilities.contains(wl_seat::Capability::Pointer) { - if let Some(pointer) = self.pointer.take() { - if pointer.as_ref().version() >= 3 { - pointer.release(); - } - } - } - // create keyboard if applicable - if capabilities.contains(wl_seat::Capability::Keyboard) && self.keyboard.is_none() { - self.keyboard = Some(super::keyboard::init_keyboard( - &seat, - self.sink.clone(), - self.modifiers_tracker.clone(), - )) - } - // destroy keyboard if applicable - if !capabilities.contains(wl_seat::Capability::Keyboard) { - if let Some(kbd) = self.keyboard.take() { - if kbd.as_ref().version() >= 3 { - kbd.release(); - } - } - } - // create touch if applicable - if capabilities.contains(wl_seat::Capability::Touch) && self.touch.is_none() { - self.touch = Some(super::touch::implement_touch( - &seat, - self.sink.clone(), - self.store.clone(), - )) - } - // destroy touch if applicable - if !capabilities.contains(wl_seat::Capability::Touch) { - if let Some(touch) = self.touch.take() { - if touch.as_ref().version() >= 3 { - touch.release(); - } - } - } - } - _ => unreachable!(), - } - } -} - -impl Drop for SeatData { - fn drop(&mut self) { - if let Some(pointer) = self.pointer.take() { - if pointer.as_ref().version() >= 3 { - pointer.release(); - } - } - if let Some(kbd) = self.keyboard.take() { - if kbd.as_ref().version() >= 3 { - kbd.release(); - } - } - if let Some(touch) = self.touch.take() { - if touch.as_ref().version() >= 3 { - touch.release(); - } - } - } -} - -/* - * Monitor stuff - */ - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct VideoMode { - pub(crate) size: (u32, u32), - pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, - pub(crate) monitor: MonitorHandle, -} - -impl VideoMode { - #[inline] - pub fn size(&self) -> PhysicalSize { - self.size.into() - } - - #[inline] - pub fn bit_depth(&self) -> u16 { - self.bit_depth - } - - #[inline] - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate - } - - #[inline] - pub fn monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(self.monitor.clone()), - } - } -} - -#[derive(Clone)] -pub struct MonitorHandle { - pub(crate) proxy: wl_output::WlOutput, - pub(crate) mgr: OutputMgr, -} - -impl PartialEq for MonitorHandle { - fn eq(&self, other: &Self) -> bool { - self.native_identifier() == other.native_identifier() - } -} - -impl Eq for MonitorHandle {} - -impl PartialOrd for MonitorHandle { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(&other)) - } -} - -impl Ord for MonitorHandle { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.native_identifier().cmp(&other.native_identifier()) - } -} - -impl std::hash::Hash for MonitorHandle { - fn hash(&self, state: &mut H) { - self.native_identifier().hash(state); - } -} - -impl fmt::Debug for MonitorHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[derive(Debug)] - struct MonitorHandle { - name: Option, - native_identifier: u32, - size: PhysicalSize, - position: PhysicalPosition, - scale_factor: i32, - } - - let monitor_id_proxy = MonitorHandle { - name: self.name(), - native_identifier: self.native_identifier(), - size: self.size(), - position: self.position(), - scale_factor: self.scale_factor(), - }; - - monitor_id_proxy.fmt(f) - } -} - -impl MonitorHandle { - pub fn name(&self) -> Option { - self.mgr.with_info(&self.proxy, |_, info| { - format!("{} ({})", info.model, info.make) - }) - } - - #[inline] - pub fn native_identifier(&self) -> u32 { - self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0) - } - - pub fn size(&self) -> PhysicalSize { - match self.mgr.with_info(&self.proxy, |_, info| { - info.modes - .iter() - .find(|m| m.is_current) - .map(|m| m.dimensions) - }) { - Some(Some((w, h))) => (w as u32, h as u32), - _ => (0, 0), - } - .into() - } - - pub fn position(&self) -> PhysicalPosition { - self.mgr - .with_info(&self.proxy, |_, info| info.location) - .unwrap_or((0, 0)) - .into() - } - - #[inline] - pub fn scale_factor(&self) -> i32 { - self.mgr - .with_info(&self.proxy, |_, info| info.scale_factor) - .unwrap_or(1) - } - - #[inline] - pub fn video_modes(&self) -> impl Iterator { - let monitor = self.clone(); - - self.mgr - .with_info(&self.proxy, |_, info| info.modes.clone()) - .unwrap_or(vec![]) - .into_iter() - .map(move |x| RootVideoMode { - video_mode: PlatformVideoMode::Wayland(VideoMode { - size: (x.dimensions.0 as u32, x.dimensions.1 as u32), - refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16, - bit_depth: 32, - monitor: monitor.clone(), - }), - }) - } -} - -pub fn available_monitors(outputs: &OutputMgr) -> VecDeque { - outputs.with_all(|list| { - list.iter() - .map(|&(_, ref proxy, _)| MonitorHandle { - proxy: proxy.clone(), - mgr: outputs.clone(), - }) - .collect() - }) -} diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs new file mode 100644 index 00000000..9e7cdde3 --- /dev/null +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -0,0 +1,539 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::error::Error; +use std::process; +use std::rc::Rc; +use std::time::{Duration, Instant}; + +use sctk::reexports::client::protocol::wl_compositor::WlCompositor; +use sctk::reexports::client::protocol::wl_shm::WlShm; +use sctk::reexports::client::Display; + +use sctk::reexports::calloop; + +use sctk::environment::Environment; +use sctk::seat::pointer::{ThemeManager, ThemeSpec}; +use sctk::WaylandSource; + +use crate::event::{Event, StartCause, WindowEvent}; +use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget}; +use crate::platform_impl::platform::sticky_exit_callback; + +use super::env::{WindowingFeatures, WinitEnv}; +use super::output::OutputManager; +use super::seat::SeatManager; +use super::window::shim::{self, WindowUpdate}; +use super::{DeviceId, WindowId}; + +mod proxy; +mod sink; +mod state; + +pub use proxy::EventLoopProxy; +pub use state::WinitState; + +use sink::EventSink; + +pub struct EventLoopWindowTarget { + /// Wayland display. + pub display: Display, + + /// Environment to handle object creation, etc. + pub env: Environment, + + /// Event loop handle. + pub event_loop_handle: calloop::LoopHandle, + + /// Output manager. + pub output_manager: OutputManager, + + /// State that we share across callbacks. + pub state: RefCell, + + /// Wayland source. + pub wayland_source: Rc>, + + /// A proxy to wake up event loop. + pub event_loop_awakener: calloop::ping::Ping, + + /// The available windowing features. + pub windowing_features: WindowingFeatures, + + /// Theme manager to manage cursors. + /// + /// It's being shared amoung all windows to avoid loading + /// multiple similar themes. + pub theme_manager: ThemeManager, + + _marker: std::marker::PhantomData, +} + +pub struct EventLoop { + /// Event loop. + event_loop: calloop::EventLoop, + + /// Wayland display. + display: Display, + + /// Pending user events. + pending_user_events: Rc>>, + + /// Sender of user events. + user_events_sender: calloop::channel::Sender, + + /// Wayland source of events. + wayland_source: Rc>, + + /// Window target. + window_target: RootEventLoopWindowTarget, + + /// Output manager. + _seat_manager: SeatManager, +} + +impl EventLoop { + pub fn new() -> Result, Box> { + // Connect to wayland server and setup event queue. + let display = Display::connect_to_env()?; + let mut event_queue = display.create_event_queue(); + let display_proxy = display.attach(event_queue.token()); + + // Setup environment. + let env = Environment::init(&display_proxy, WinitEnv::new()); + + // Issue 2 sync roundtrips to initialize environment. + event_queue.sync_roundtrip(&mut (), |_, _, _| unreachable!())?; + event_queue.sync_roundtrip(&mut (), |_, _, _| unreachable!())?; + + // Create event loop. + let event_loop = calloop::EventLoop::::new()?; + // Build windowing features. + let windowing_features = WindowingFeatures::new(&env); + + // Create a theme manager. + let compositor = env.require_global::(); + let shm = env.require_global::(); + let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm); + + // Setup theme seat and output managers. + let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone()); + let output_manager = OutputManager::new(&env); + + // A source of events that we plug into our event loop. + let wayland_source = WaylandSource::new(event_queue).quick_insert(event_loop.handle())?; + let wayland_source = Rc::new(wayland_source); + + // A source of user events. + let pending_user_events = Rc::new(RefCell::new(Vec::new())); + let pending_user_events_clone = pending_user_events.clone(); + let (user_events_sender, user_events_channel) = calloop::channel::channel(); + + // User events channel. + event_loop + .handle() + .insert_source(user_events_channel, move |event, _, _| { + if let calloop::channel::Event::Msg(msg) = event { + pending_user_events_clone.borrow_mut().push(msg); + } + })?; + + // An event's loop awakener to wake up for window events from winit's windows. + let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?; + + // Handler of window requests. + event_loop.handle().insert_source( + event_loop_awakener_source, + move |_, _, winit_state| { + shim::handle_window_requests(winit_state); + }, + )?; + + let event_loop_handle = event_loop.handle(); + let window_map = HashMap::new(); + let event_sink = EventSink::new(); + let window_updates = HashMap::new(); + + // Create event loop window target. + let event_loop_window_target = EventLoopWindowTarget { + display: display.clone(), + env, + state: RefCell::new(WinitState { + window_map, + event_sink, + window_updates, + }), + event_loop_handle, + output_manager, + event_loop_awakener, + wayland_source: wayland_source.clone(), + windowing_features, + theme_manager, + _marker: std::marker::PhantomData, + }; + + // Create event loop itself. + let event_loop = Self { + event_loop, + display, + pending_user_events, + wayland_source, + _seat_manager: seat_manager, + user_events_sender, + window_target: RootEventLoopWindowTarget { + p: crate::platform_impl::EventLoopWindowTarget::Wayland(event_loop_window_target), + _marker: std::marker::PhantomData, + }, + }; + + Ok(event_loop) + } + + pub fn run(mut self, callback: F) -> ! + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow) + 'static, + { + self.run_return(callback); + process::exit(0) + } + + pub fn run_return(&mut self, mut callback: F) + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + // Send pending events to the server. + let _ = self.display.flush(); + + let mut control_flow = ControlFlow::default(); + + let pending_user_events = self.pending_user_events.clone(); + + callback( + Event::NewEvents(StartCause::Init), + &self.window_target, + &mut control_flow, + ); + + let mut window_updates: Vec<(WindowId, WindowUpdate)> = Vec::new(); + let mut event_sink_back_buffer = Vec::new(); + + // NOTE We break on errors from dispatches, since if we've got protocol error + // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not + // really an option. Instead we inform that the event loop got destroyed. We may + // communicate an error that something was terminated, but winit doesn't provide us + // with an API to do that via some event. + loop { + // Handle pending user events. We don't need back buffer, since we can't dispatch + // user events indirectly via callback to the user. + for user_event in pending_user_events.borrow_mut().drain(..) { + sticky_exit_callback( + Event::UserEvent(user_event), + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + + // Process 'new' pending updates. + self.with_state(|state| { + window_updates.clear(); + window_updates.extend( + state + .window_updates + .iter_mut() + .map(|(wid, window_update)| (*wid, window_update.take())), + ); + }); + + for (window_id, window_update) in window_updates.iter_mut() { + if let Some(scale_factor) = window_update.scale_factor.map(|f| f as f64) { + let mut physical_size = self.with_state(|state| { + let window_handle = state.window_map.get(&window_id).unwrap(); + let mut size = window_handle.size.lock().unwrap(); + + // Update the new logical size if it was changed. + let window_size = window_update.size.unwrap_or(*size); + *size = window_size; + + window_size.to_physical(scale_factor) + }); + + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId( + crate::platform_impl::WindowId::Wayland(*window_id), + ), + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut physical_size, + }, + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); + + // We don't update size on a window handle since we'll do that later + // when handling size update. + let new_logical_size = physical_size.to_logical(scale_factor); + window_update.size = Some(new_logical_size); + } + + if let Some(size) = window_update.size.take() { + let physical_size = self.with_state(|state| { + let window_handle = state.window_map.get_mut(&window_id).unwrap(); + let mut window_size = window_handle.size.lock().unwrap(); + + // Always issue resize event on scale factor change. + let physical_size = + if window_update.scale_factor.is_none() && *window_size == size { + // The size hasn't changed, don't inform downstream about that. + None + } else { + *window_size = size; + let scale_factor = + sctk::get_surface_scale_factor(&window_handle.window.surface()); + let physical_size = size.to_physical(scale_factor as f64); + Some(physical_size) + }; + + // We still perform all of those resize related logic even if the size + // hasn't changed, since GNOME relies on `set_geometry` calls after + // configures. + window_handle.window.resize(size.width, size.height); + window_handle.window.refresh(); + + // Mark that refresh isn't required, since we've done it right now. + window_update.refresh_frame = false; + + physical_size + }); + + if let Some(physical_size) = physical_size { + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId( + crate::platform_impl::WindowId::Wayland(*window_id), + ), + event: WindowEvent::Resized(physical_size), + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + } + + if window_update.close_window { + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId( + crate::platform_impl::WindowId::Wayland(*window_id), + ), + event: WindowEvent::CloseRequested, + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + } + + // The purpose of the back buffer and that swap is to not hold borrow_mut when + // we're doing callback to the user, since we can double borrow if the user decides + // to create a window in one of those callbacks. + self.with_state(|state| { + std::mem::swap( + &mut event_sink_back_buffer, + &mut state.event_sink.window_events, + ) + }); + + // Handle pending window events. + for event in event_sink_back_buffer.drain(..) { + let event = event.map_nonuser_event().unwrap(); + sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + } + + // Send events cleared. + sticky_exit_callback( + Event::MainEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); + + // Handle RedrawRequested events. + for (window_id, window_update) in window_updates.iter() { + // Handle refresh of the frame. + if window_update.refresh_frame { + self.with_state(|state| { + let window_handle = state.window_map.get_mut(&window_id).unwrap(); + window_handle.window.refresh(); + if !window_update.redraw_requested { + window_handle.window.surface().commit(); + } + }); + } + + // Handle redraw request. + if window_update.redraw_requested { + sticky_exit_callback( + Event::RedrawRequested(crate::window::WindowId( + crate::platform_impl::WindowId::Wayland(*window_id), + )), + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + } + + // Send RedrawEventCleared. + sticky_exit_callback( + Event::RedrawEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); + + // Send pending events to the server. + let _ = self.display.flush(); + + // During the run of the user callback, some other code monitoring and reading the + // Wayland socket may have been run (mesa for example does this with vsync), if that + // is the case, some events may have been enqueued in our event queue. + // + // If some messages are there, the event loop needs to behave as if it was instantly + // woken up by messages arriving from the Wayland socket, to avoid delaying the + // dispatch of these events until we're woken up again. + let instant_wakeup = { + let handle = self.event_loop.handle(); + let source = self.wayland_source.clone(); + let dispatched = handle.with_source(&source, |wayland_source| { + let queue = wayland_source.queue(); + self.with_state(|state| { + queue.dispatch_pending(state, |_, _, _| unimplemented!()) + }) + }); + + if let Ok(dispatched) = dispatched { + dispatched > 0 + } else { + break; + } + }; + + match control_flow { + ControlFlow::Exit => break, + ControlFlow::Poll => { + // Non-blocking dispatch. + let timeout = Duration::from_millis(0); + if self.loop_dispatch(Some(timeout)).is_err() { + break; + } + + callback( + Event::NewEvents(StartCause::Poll), + &self.window_target, + &mut control_flow, + ); + } + ControlFlow::Wait => { + let timeout = if instant_wakeup { + Some(Duration::from_millis(0)) + } else { + None + }; + + if self.loop_dispatch(timeout).is_err() { + break; + } + + callback( + Event::NewEvents(StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, + }), + &self.window_target, + &mut control_flow, + ); + } + ControlFlow::WaitUntil(deadline) => { + let start = Instant::now(); + + // Compute the amount of time we'll block for. + let duration = if deadline > start && !instant_wakeup { + deadline - start + } else { + Duration::from_millis(0) + }; + + if self.loop_dispatch(Some(duration)).is_err() { + break; + } + + let now = Instant::now(); + + if now < deadline { + callback( + Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: Some(deadline), + }), + &self.window_target, + &mut control_flow, + ) + } else { + callback( + Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume: deadline, + }), + &self.window_target, + &mut control_flow, + ) + } + } + } + } + + callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); + } + + #[inline] + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy::new(self.user_events_sender.clone()) + } + + #[inline] + pub fn window_target(&self) -> &RootEventLoopWindowTarget { + &self.window_target + } + + fn with_state U>(&mut self, f: F) -> U { + let state = match &mut self.window_target.p { + crate::platform_impl::EventLoopWindowTarget::Wayland(ref mut window_target) => { + window_target.state.get_mut() + } + #[cfg(feature = "x11")] + _ => unreachable!(), + }; + + f(state) + } + + fn loop_dispatch>>( + &mut self, + timeout: D, + ) -> std::io::Result<()> { + let mut state = match &mut self.window_target.p { + crate::platform_impl::EventLoopWindowTarget::Wayland(ref mut window_target) => { + window_target.state.get_mut() + } + #[cfg(feature = "x11")] + _ => unreachable!(), + }; + + self.event_loop.dispatch(timeout, &mut state) + } +} diff --git a/src/platform_impl/linux/wayland/event_loop/proxy.rs b/src/platform_impl/linux/wayland/event_loop/proxy.rs new file mode 100644 index 00000000..dad64ef2 --- /dev/null +++ b/src/platform_impl/linux/wayland/event_loop/proxy.rs @@ -0,0 +1,32 @@ +//! An event loop proxy. + +use std::sync::mpsc::SendError; + +use sctk::reexports::calloop::channel::Sender; + +use crate::event_loop::EventLoopClosed; + +/// A handle that can be sent across the threads and used to wake up the `EventLoop`. +pub struct EventLoopProxy { + user_events_sender: Sender, +} + +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + EventLoopProxy { + user_events_sender: self.user_events_sender.clone(), + } + } +} + +impl EventLoopProxy { + pub fn new(user_events_sender: Sender) -> Self { + Self { user_events_sender } + } + + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.user_events_sender + .send(event) + .map_err(|SendError(error)| EventLoopClosed(error)) + } +} diff --git a/src/platform_impl/linux/wayland/event_loop/sink.rs b/src/platform_impl/linux/wayland/event_loop/sink.rs new file mode 100644 index 00000000..303ab826 --- /dev/null +++ b/src/platform_impl/linux/wayland/event_loop/sink.rs @@ -0,0 +1,36 @@ +//! An event loop's sink to deliver events from the Wayland event callbacks. + +use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent}; +use crate::platform_impl::platform::{DeviceId as PlatformDeviceId, WindowId as PlatformWindowId}; +use crate::window::WindowId as RootWindowId; + +use super::{DeviceId, WindowId}; + +/// An event loop's sink to deliver events from the Wayland event callbacks +/// to the winit's user. +#[derive(Default)] +pub struct EventSink { + pub window_events: Vec>, +} + +impl EventSink { + pub fn new() -> Self { + Default::default() + } + + /// Add new device event to a queue. + pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) { + self.window_events.push(Event::DeviceEvent { + event, + device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)), + }); + } + + /// Add new window event to a queue. + pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) { + self.window_events.push(Event::WindowEvent { + event, + window_id: RootWindowId(PlatformWindowId::Wayland(window_id)), + }); + } +} diff --git a/src/platform_impl/linux/wayland/event_loop/state.rs b/src/platform_impl/linux/wayland/event_loop/state.rs new file mode 100644 index 00000000..7aad9ecc --- /dev/null +++ b/src/platform_impl/linux/wayland/event_loop/state.rs @@ -0,0 +1,25 @@ +//! A state that we pass around in a dispatch. + +use std::collections::HashMap; + +use super::EventSink; +use crate::platform_impl::wayland::window::shim::{WindowHandle, WindowUpdate}; +use crate::platform_impl::wayland::WindowId; + +/// Wrapper to carry winit's state. +pub struct WinitState { + /// A sink for window and device events that is being filled during dispatching + /// event loop and forwarded downstream afterwards. + pub event_sink: EventSink, + + /// Window updates, which are coming from SCTK or the compositor, which require + /// calling back to the winit's downstream. They are handled right in the event loop, + /// unlike the ones coming from buffers on the `WindowHandle`'s. + pub window_updates: HashMap, + + /// Window map containing all SCTK windows. Since those windows aren't allowed + /// to be sent to other threads, they live on the event loop's thread + /// and requests from winit's windows are being forwarded to them either via + /// `WindowUpdate` or buffer on the associated with it `WindowHandle`. + pub window_map: HashMap, +} diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs deleted file mode 100644 index 7c58e7c4..00000000 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ /dev/null @@ -1,396 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use super::{event_loop::EventsSink, make_wid, DeviceId}; -use smithay_client_toolkit::{ - keyboard::{ - self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind, - }, - reexports::client::protocol::{wl_keyboard, wl_seat}, -}; - -use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent}; - -pub fn init_keyboard( - seat: &wl_seat::WlSeat, - sink: EventsSink, - modifiers_tracker: Arc>, -) -> wl_keyboard::WlKeyboard { - // { variables to be captured by the closures - let target = Arc::new(Mutex::new(None)); - let my_sink = sink.clone(); - let repeat_sink = sink.clone(); - let repeat_target = target.clone(); - let my_modifiers = modifiers_tracker.clone(); - // } - let ret = map_keyboard_auto_with_repeat( - seat, - KeyRepeatKind::System, - move |evt: KbEvent<'_>, _| { - match evt { - KbEvent::Enter { surface, .. } => { - let wid = make_wid(&surface); - my_sink.send_window_event(WindowEvent::Focused(true), wid); - *target.lock().unwrap() = Some(wid); - - let modifiers = *modifiers_tracker.lock().unwrap(); - - if !modifiers.is_empty() { - my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid); - } - } - KbEvent::Leave { surface, .. } => { - let wid = make_wid(&surface); - let modifiers = *modifiers_tracker.lock().unwrap(); - - if !modifiers.is_empty() { - my_sink.send_window_event( - WindowEvent::ModifiersChanged(ModifiersState::empty()), - wid, - ); - } - - my_sink.send_window_event(WindowEvent::Focused(false), wid); - *target.lock().unwrap() = None; - } - KbEvent::Key { - rawkey, - keysym, - state, - utf8, - .. - } => { - if let Some(wid) = *target.lock().unwrap() { - let state = match state { - wl_keyboard::KeyState::Pressed => ElementState::Pressed, - wl_keyboard::KeyState::Released => ElementState::Released, - _ => unreachable!(), - }; - let vkcode = key_to_vkey(rawkey, keysym); - my_sink.send_window_event( - #[allow(deprecated)] - WindowEvent::KeyboardInput { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - input: KeyboardInput { - state, - scancode: rawkey, - virtual_keycode: vkcode, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - is_synthetic: false, - }, - wid, - ); - // send char event only on key press, not release - if let ElementState::Released = state { - return; - } - if let Some(txt) = utf8 { - for chr in txt.chars() { - my_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid); - } - } - } - } - KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ } - KbEvent::Modifiers { - modifiers: event_modifiers, - } => { - let modifiers = ModifiersState::from_wayland(event_modifiers); - - *modifiers_tracker.lock().unwrap() = modifiers; - - if let Some(wid) = *target.lock().unwrap() { - my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid); - } - } - } - }, - move |repeat_event: KeyRepeatEvent, _| { - if let Some(wid) = *repeat_target.lock().unwrap() { - let state = ElementState::Pressed; - let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym); - repeat_sink.send_window_event( - #[allow(deprecated)] - WindowEvent::KeyboardInput { - device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( - DeviceId, - )), - input: KeyboardInput { - state, - scancode: repeat_event.rawkey, - virtual_keycode: vkcode, - modifiers: my_modifiers.lock().unwrap().clone(), - }, - is_synthetic: false, - }, - wid, - ); - if let Some(txt) = repeat_event.utf8 { - for chr in txt.chars() { - repeat_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid); - } - } - } - }, - ); - - match ret { - Ok(keyboard) => keyboard, - Err(_) => { - // This is a fallback impl if libxkbcommon was not available - // This case should probably never happen, as most wayland - // compositors _need_ libxkbcommon anyway... - // - // In this case, we don't have the keymap information (it is - // supposed to be serialized by the compositor using libxkbcommon) - - seat.get_keyboard(|keyboard| { - // { variables to be captured by the closure - let mut target = None; - let my_sink = sink; - // } - - keyboard.implement_closure( - move |evt, _| match evt { - wl_keyboard::Event::Enter { surface, .. } => { - let wid = make_wid(&surface); - my_sink.send_window_event(WindowEvent::Focused(true), wid); - target = Some(wid); - } - wl_keyboard::Event::Leave { surface, .. } => { - let wid = make_wid(&surface); - my_sink.send_window_event(WindowEvent::Focused(false), wid); - target = None; - } - wl_keyboard::Event::Key { key, state, .. } => { - if let Some(wid) = target { - let state = match state { - wl_keyboard::KeyState::Pressed => ElementState::Pressed, - wl_keyboard::KeyState::Released => ElementState::Released, - _ => unreachable!(), - }; - my_sink.send_window_event( - #[allow(deprecated)] - WindowEvent::KeyboardInput { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - input: KeyboardInput { - state, - scancode: key, - virtual_keycode: None, - modifiers: ModifiersState::default(), - }, - is_synthetic: false, - }, - wid, - ); - } - } - _ => (), - }, - (), - ) - }) - .unwrap() - } - } -} - -fn key_to_vkey(rawkey: u32, keysym: u32) -> Option { - match rawkey { - 1 => Some(VirtualKeyCode::Escape), - 2 => Some(VirtualKeyCode::Key1), - 3 => Some(VirtualKeyCode::Key2), - 4 => Some(VirtualKeyCode::Key3), - 5 => Some(VirtualKeyCode::Key4), - 6 => Some(VirtualKeyCode::Key5), - 7 => Some(VirtualKeyCode::Key6), - 8 => Some(VirtualKeyCode::Key7), - 9 => Some(VirtualKeyCode::Key8), - 10 => Some(VirtualKeyCode::Key9), - 11 => Some(VirtualKeyCode::Key0), - _ => keysym_to_vkey(keysym), - } -} - -fn keysym_to_vkey(keysym: u32) -> Option { - use smithay_client_toolkit::keyboard::keysyms; - match keysym { - // letters - keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A), - keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B), - keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C), - keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D), - keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E), - keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F), - keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G), - keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H), - keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I), - keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J), - keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K), - keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L), - keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M), - keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N), - keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O), - keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P), - keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q), - keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R), - keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S), - keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T), - keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U), - keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V), - keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W), - keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X), - keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y), - keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z), - // F-- - keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1), - keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2), - keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3), - keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4), - keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5), - keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6), - keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7), - keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8), - keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9), - keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10), - keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11), - keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12), - keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13), - keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14), - keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15), - keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16), - keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17), - keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18), - keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19), - keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20), - keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21), - keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22), - keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23), - keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24), - // flow control - keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot), - keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll), - keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause), - keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert), - keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home), - keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete), - keysyms::XKB_KEY_End => Some(VirtualKeyCode::End), - keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown), - keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp), - // arrows - keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left), - keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up), - keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right), - keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down), - // - keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back), - keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return), - keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space), - // keypad - keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock), - keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0), - keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1), - keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2), - keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3), - keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4), - keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5), - keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6), - keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7), - keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8), - keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9), - // misc - // => Some(VirtualKeyCode::AbntC1), - // => Some(VirtualKeyCode::AbntC2), - keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus), - keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe), - // => Some(VirtualKeyCode::Apps), - // => Some(VirtualKeyCode::At), - // => Some(VirtualKeyCode::Ax), - keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash), - // => Some(VirtualKeyCode::Calculator), - // => Some(VirtualKeyCode::Capital), - keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon), - keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma), - // => Some(VirtualKeyCode::Convert), - keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals), - // => Some(VirtualKeyCode::Grave), - // => Some(VirtualKeyCode::Kana), - // => Some(VirtualKeyCode::Kanji), - keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt), - // => Some(VirtualKeyCode::LBracket), - keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl), - keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift), - // => Some(VirtualKeyCode::LWin), - // => Some(VirtualKeyCode::Mail), - // => Some(VirtualKeyCode::MediaSelect), - // => Some(VirtualKeyCode::MediaStop), - keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus), - keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk), - // => Some(VirtualKeyCode::Mute), - // => Some(VirtualKeyCode::MyComputer), - // => Some(VirtualKeyCode::NextTrack), - // => Some(VirtualKeyCode::NoConvert), - keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma), - keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), - keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), - keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd), - keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), - keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply), - keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal), - keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide), - keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), - keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), - keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), - keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End), - // => Some(VirtualKeyCode::OEM102), - // => Some(VirtualKeyCode::Period), - // => Some(VirtualKeyCode::Playpause), - // => Some(VirtualKeyCode::Power), - // => Some(VirtualKeyCode::Prevtrack), - keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt), - // => Some(VirtualKeyCode::RBracket), - keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl), - keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift), - // => Some(VirtualKeyCode::RWin), - keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon), - keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash), - // => Some(VirtualKeyCode::Sleep), - // => Some(VirtualKeyCode::Stop), - // => Some(VirtualKeyCode::Sysrq), - keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab), - keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab), - // => Some(VirtualKeyCode::Underline), - // => Some(VirtualKeyCode::Unlabeled), - keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown), - keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp), - // => Some(VirtualKeyCode::Wake), - // => Some(VirtualKeyCode::Webback), - // => Some(VirtualKeyCode::WebFavorites), - // => Some(VirtualKeyCode::WebForward), - // => Some(VirtualKeyCode::WebHome), - // => Some(VirtualKeyCode::WebRefresh), - // => Some(VirtualKeyCode::WebSearch), - // => Some(VirtualKeyCode::WebStop), - // => Some(VirtualKeyCode::Yen), - keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy), - keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste), - keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut), - // fallback - _ => None, - } -} - -impl ModifiersState { - pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState { - let mut m = ModifiersState::empty(); - m.set(ModifiersState::SHIFT, mods.shift); - m.set(ModifiersState::CTRL, mods.ctrl); - m.set(ModifiersState::ALT, mods.alt); - m.set(ModifiersState::LOGO, mods.logo); - m - } -} diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs index 7fdb3fd1..a63a6f1e 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -1,17 +1,21 @@ -#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", - target_os = "netbsd", target_os = "openbsd"))] +#![cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] -pub use self::{ - event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode}, - window::Window, -}; +use sctk::reexports::client::protocol::wl_surface::WlSurface; -use smithay_client_toolkit::reexports::client::protocol::wl_surface; +pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; +pub use output::{MonitorHandle, VideoMode}; +pub use window::Window; +mod env; mod event_loop; -mod keyboard; -mod pointer; -mod touch; +mod output; +mod seat; mod window; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -33,6 +37,6 @@ impl WindowId { } #[inline] -fn make_wid(s: &wl_surface::WlSurface) -> WindowId { - WindowId(s.as_ref().c_ptr() as usize) +fn make_wid(surface: &WlSurface) -> WindowId { + WindowId(surface.as_ref().c_ptr() as usize) } diff --git a/src/platform_impl/linux/wayland/output.rs b/src/platform_impl/linux/wayland/output.rs new file mode 100644 index 00000000..fc4d0873 --- /dev/null +++ b/src/platform_impl/linux/wayland/output.rs @@ -0,0 +1,240 @@ +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; + +use sctk::reexports::client::protocol::wl_output::WlOutput; +use sctk::reexports::client::Display; + +use sctk::environment::Environment; +use sctk::output::OutputStatusListener; + +use crate::dpi::{PhysicalPosition, PhysicalSize}; +use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}; +use crate::platform_impl::platform::{ + MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode, +}; + +use super::env::WinitEnv; +use super::event_loop::EventLoopWindowTarget; + +/// Output manager. +pub struct OutputManager { + /// A handle that actually performs all operations on outputs. + handle: OutputManagerHandle, + + _output_listener: OutputStatusListener, +} + +impl OutputManager { + pub fn new(env: &Environment) -> Self { + let handle = OutputManagerHandle::new(); + + // Handle existing outputs. + for output in env.get_all_outputs() { + match sctk::output::with_output_info(&output, |info| info.obsolete) { + Some(false) => (), + // The output is obsolete or we've failed to access its data, skipping. + _ => continue, + } + + // The output is present and unusable, add it to the output manager manager. + handle.add_output(output); + } + + let handle_for_listener = handle.clone(); + + let output_listener = env.listen_for_outputs(move |output, info, _| { + if info.obsolete { + handle_for_listener.remove_output(output) + } else { + handle_for_listener.add_output(output) + } + }); + + Self { + handle, + _output_listener: output_listener, + } + } + + pub fn handle(&self) -> OutputManagerHandle { + self.handle.clone() + } +} + +/// A handle to output manager. +#[derive(Debug, Clone)] +pub struct OutputManagerHandle { + outputs: Arc>>, +} + +impl OutputManagerHandle { + fn new() -> Self { + let outputs = Arc::new(Mutex::new(VecDeque::new())); + Self { outputs } + } + + /// Handle addition of the output. + fn add_output(&self, output: WlOutput) { + let mut outputs = self.outputs.lock().unwrap(); + let position = outputs.iter().position(|handle| handle.proxy == output); + if position.is_none() { + outputs.push_back(MonitorHandle::new(output)); + } + } + + /// Handle removal of the output. + fn remove_output(&self, output: WlOutput) { + let mut outputs = self.outputs.lock().unwrap(); + let position = outputs.iter().position(|handle| handle.proxy == output); + if let Some(position) = position { + outputs.remove(position); + } + } + + /// Get all observed outputs. + pub fn available_outputs(&self) -> VecDeque { + self.outputs.lock().unwrap().clone() + } +} + +#[derive(Clone, Debug)] +pub struct MonitorHandle { + pub(crate) proxy: WlOutput, +} + +impl PartialEq for MonitorHandle { + fn eq(&self, other: &Self) -> bool { + self.native_identifier() == other.native_identifier() + } +} + +impl Eq for MonitorHandle {} + +impl PartialOrd for MonitorHandle { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(&other)) + } +} + +impl Ord for MonitorHandle { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.native_identifier().cmp(&other.native_identifier()) + } +} + +impl std::hash::Hash for MonitorHandle { + fn hash(&self, state: &mut H) { + self.native_identifier().hash(state); + } +} + +impl MonitorHandle { + #[inline] + pub(crate) fn new(proxy: WlOutput) -> Self { + Self { proxy } + } + + #[inline] + pub fn name(&self) -> Option { + sctk::output::with_output_info(&self.proxy, |info| { + format!("{} ({})", info.model, info.make) + }) + } + + #[inline] + pub fn native_identifier(&self) -> u32 { + sctk::output::with_output_info(&self.proxy, |info| info.id).unwrap_or(0) + } + + #[inline] + pub fn size(&self) -> PhysicalSize { + match sctk::output::with_output_info(&self.proxy, |info| { + info.modes + .iter() + .find(|mode| mode.is_current) + .map(|mode| mode.dimensions) + }) { + Some(Some((w, h))) => (w as u32, h as u32), + _ => (0, 0), + } + .into() + } + + #[inline] + pub fn position(&self) -> PhysicalPosition { + sctk::output::with_output_info(&self.proxy, |info| info.location) + .unwrap_or((0, 0)) + .into() + } + + #[inline] + pub fn scale_factor(&self) -> i32 { + sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1) + } + + #[inline] + pub fn video_modes(&self) -> impl Iterator { + let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone()) + .unwrap_or_else(Vec::new); + + let monitor = self.clone(); + + modes.into_iter().map(move |mode| RootVideoMode { + video_mode: PlatformVideoMode::Wayland(VideoMode { + size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(), + refresh_rate: (mode.refresh_rate as f32 / 1000.0).round() as u16, + bit_depth: 32, + monitor: monitor.clone(), + }), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct VideoMode { + pub(crate) size: PhysicalSize, + pub(crate) bit_depth: u16, + pub(crate) refresh_rate: u16, + pub(crate) monitor: MonitorHandle, +} + +impl VideoMode { + #[inline] + pub fn size(&self) -> PhysicalSize { + self.size + } + + #[inline] + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + #[inline] + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } + + pub fn monitor(&self) -> RootMonitorHandle { + RootMonitorHandle { + inner: PlatformMonitorHandle::Wayland(self.monitor.clone()), + } + } +} + +impl EventLoopWindowTarget { + #[inline] + pub fn display(&self) -> &Display { + &self.display + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + self.output_manager.handle.available_outputs() + } + + #[inline] + pub fn primary_monitor(&self) -> Option { + // There's no primary monitor on Wayland. + None + } +} diff --git a/src/platform_impl/linux/wayland/pointer.rs b/src/platform_impl/linux/wayland/pointer.rs deleted file mode 100644 index 4f2f4c2e..00000000 --- a/src/platform_impl/linux/wayland/pointer.rs +++ /dev/null @@ -1,290 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use crate::dpi::LogicalPosition; -use crate::event::{ - DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, - WindowEvent, -}; - -use super::{ - event_loop::{CursorManager, EventsSink}, - make_wid, - window::WindowStore, - DeviceId, -}; - -use smithay_client_toolkit::surface; - -use smithay_client_toolkit::reexports::client::protocol::{ - wl_pointer::{self, Event as PtrEvent, WlPointer}, - wl_seat, -}; - -use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{ - zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, zwp_relative_pointer_v1::Event, - zwp_relative_pointer_v1::ZwpRelativePointerV1, -}; - -use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{ - zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::Lifetime, - zwp_pointer_constraints_v1::ZwpPointerConstraintsV1, -}; - -use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface; - -pub fn implement_pointer( - seat: &wl_seat::WlSeat, - sink: EventsSink, - store: Arc>, - modifiers_tracker: Arc>, - cursor_manager: Arc>, -) -> WlPointer { - seat.get_pointer(|pointer| { - // Currently focused winit surface - let mut mouse_focus = None; - let mut axis_buffer = None; - let mut axis_discrete_buffer = None; - let mut axis_state = TouchPhase::Ended; - - pointer.implement_closure( - move |evt, pointer| { - let store = store.lock().unwrap(); - let mut cursor_manager = cursor_manager.lock().unwrap(); - match evt { - PtrEvent::Enter { - surface, - surface_x, - surface_y, - .. - } => { - let wid = store.find_wid(&surface); - - if let Some(wid) = wid { - let scale_factor = surface::get_dpi_factor(&surface) as f64; - mouse_focus = Some(surface); - - // Reload cursor style only when we enter winit's surface. Calling - // this function every time on `PtrEvent::Enter` could interfere with - // SCTK CSD handling, since it changes cursor icons when you hover - // cursor over the window borders. - cursor_manager.reload_cursor_style(); - - sink.send_window_event( - WindowEvent::CursorEntered { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - }, - wid, - ); - - let position = LogicalPosition::new(surface_x, surface_y) - .to_physical(scale_factor); - - sink.send_window_event( - WindowEvent::CursorMoved { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - position, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - wid, - ); - } - } - PtrEvent::Leave { surface, .. } => { - mouse_focus = None; - let wid = store.find_wid(&surface); - if let Some(wid) = wid { - sink.send_window_event( - WindowEvent::CursorLeft { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - }, - wid, - ); - } - } - PtrEvent::Motion { - surface_x, - surface_y, - .. - } => { - if let Some(surface) = mouse_focus.as_ref() { - let wid = make_wid(surface); - - let scale_factor = surface::get_dpi_factor(&surface) as f64; - let position = LogicalPosition::new(surface_x, surface_y) - .to_physical(scale_factor); - - sink.send_window_event( - WindowEvent::CursorMoved { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - position, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - wid, - ); - } - } - PtrEvent::Button { button, state, .. } => { - if let Some(surface) = mouse_focus.as_ref() { - let state = match state { - wl_pointer::ButtonState::Pressed => ElementState::Pressed, - wl_pointer::ButtonState::Released => ElementState::Released, - _ => unreachable!(), - }; - let button = match button { - 0x110 => MouseButton::Left, - 0x111 => MouseButton::Right, - 0x112 => MouseButton::Middle, - // TODO figure out the translation ? - _ => return, - }; - sink.send_window_event( - WindowEvent::MouseInput { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - state, - button, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - make_wid(surface), - ); - } - } - PtrEvent::Axis { axis, value, .. } => { - if let Some(surface) = mouse_focus.as_ref() { - let wid = make_wid(surface); - if pointer.as_ref().version() < 5 { - let (mut x, mut y) = (0.0, 0.0); - // old seat compatibility - match axis { - // wayland vertical sign convention is the inverse of winit - wl_pointer::Axis::VerticalScroll => y -= value as f32, - wl_pointer::Axis::HorizontalScroll => x += value as f32, - _ => unreachable!(), - } - let scale_factor = surface::get_dpi_factor(&surface) as f64; - let delta = LogicalPosition::new(x as f64, y as f64) - .to_physical(scale_factor); - sink.send_window_event( - WindowEvent::MouseWheel { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - delta: MouseScrollDelta::PixelDelta(delta), - phase: TouchPhase::Moved, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - wid, - ); - } else { - let (mut x, mut y) = axis_buffer.unwrap_or((0.0, 0.0)); - match axis { - // wayland vertical sign convention is the inverse of winit - wl_pointer::Axis::VerticalScroll => y -= value as f32, - wl_pointer::Axis::HorizontalScroll => x += value as f32, - _ => unreachable!(), - } - axis_buffer = Some((x, y)); - axis_state = match axis_state { - TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, - _ => TouchPhase::Started, - } - } - } - } - PtrEvent::Frame => { - let axis_buffer = axis_buffer.take(); - let axis_discrete_buffer = axis_discrete_buffer.take(); - if let Some(surface) = mouse_focus.as_ref() { - let wid = make_wid(surface); - if let Some((x, y)) = axis_discrete_buffer { - sink.send_window_event( - WindowEvent::MouseWheel { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - delta: MouseScrollDelta::LineDelta(x, y), - phase: axis_state, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - wid, - ); - } else if let Some((x, y)) = axis_buffer { - let scale_factor = surface::get_dpi_factor(&surface) as f64; - let delta = LogicalPosition::new(x, y).to_physical(scale_factor); - sink.send_window_event( - WindowEvent::MouseWheel { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - delta: MouseScrollDelta::PixelDelta(delta), - phase: axis_state, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - wid, - ); - } - } - } - PtrEvent::AxisSource { .. } => (), - PtrEvent::AxisStop { .. } => { - axis_state = TouchPhase::Ended; - } - PtrEvent::AxisDiscrete { axis, discrete } => { - let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0.0, 0.0)); - match axis { - // wayland vertical sign convention is the inverse of winit - wl_pointer::Axis::VerticalScroll => y -= discrete as f32, - wl_pointer::Axis::HorizontalScroll => x += discrete as f32, - _ => unreachable!(), - } - axis_discrete_buffer = Some((x, y)); - axis_state = match axis_state { - TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, - _ => TouchPhase::Started, - } - } - _ => unreachable!(), - } - }, - (), - ) - }) - .unwrap() -} - -pub fn implement_relative_pointer( - sink: EventsSink, - pointer: &WlPointer, - manager: &ZwpRelativePointerManagerV1, -) -> Result { - manager.get_relative_pointer(pointer, |rel_pointer| { - rel_pointer.implement_closure( - move |evt, _rel_pointer| match evt { - Event::RelativeMotion { dx, dy, .. } => { - sink.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId) - } - _ => unreachable!(), - }, - (), - ) - }) -} - -pub fn implement_locked_pointer( - surface: &WlSurface, - pointer: &WlPointer, - constraints: &ZwpPointerConstraintsV1, -) -> Result { - constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent.to_raw(), |c| { - c.implement_closure(|_, _| (), ()) - }) -} diff --git a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs new file mode 100644 index 00000000..7c320973 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs @@ -0,0 +1,151 @@ +//! Handling of various keyboard events. + +use sctk::reexports::client::protocol::wl_keyboard::KeyState; + +use sctk::seat::keyboard::Event as KeyboardEvent; + +use crate::event::{ElementState, KeyboardInput, ModifiersState, WindowEvent}; +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::{self, DeviceId}; + +use super::keymap; +use super::KeyboardInner; + +#[inline] +pub(super) fn handle_keyboard( + event: KeyboardEvent<'_>, + inner: &mut KeyboardInner, + winit_state: &mut WinitState, +) { + let event_sink = &mut winit_state.event_sink; + match event { + KeyboardEvent::Enter { surface, .. } => { + let window_id = wayland::make_wid(&surface); + + // Window gained focus. + event_sink.push_window_event(WindowEvent::Focused(true), window_id); + + // Dispatch modifers changes that we've received before getting `Enter` event. + if let Some(modifiers) = inner.pending_modifers_state.take() { + *inner.modifiers_state.borrow_mut() = modifiers; + event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id); + } + + inner.target_window_id = Some(window_id); + } + KeyboardEvent::Leave { surface, .. } => { + let window_id = wayland::make_wid(&surface); + + // Notify that no modifiers are being pressed. + if !inner.modifiers_state.borrow().is_empty() { + event_sink.push_window_event( + WindowEvent::ModifiersChanged(ModifiersState::empty()), + window_id, + ); + } + + // Window lost focus. + event_sink.push_window_event(WindowEvent::Focused(false), window_id); + + // Reset the id. + inner.target_window_id = None; + } + KeyboardEvent::Key { + rawkey, + keysym, + state, + utf8, + .. + } => { + let window_id = match inner.target_window_id { + Some(window_id) => window_id, + None => return, + }; + + let state = match state { + KeyState::Pressed => ElementState::Pressed, + KeyState::Released => ElementState::Released, + _ => unreachable!(), + }; + + let virtual_keycode = keymap::keysym_to_vkey(keysym); + + event_sink.push_window_event( + #[allow(deprecated)] + WindowEvent::KeyboardInput { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + input: KeyboardInput { + state, + scancode: rawkey, + virtual_keycode, + modifiers: *inner.modifiers_state.borrow(), + }, + is_synthetic: false, + }, + window_id, + ); + + // Send ReceivedCharacter event only on ElementState::Pressed. + if ElementState::Released == state { + return; + } + + if let Some(txt) = utf8 { + for ch in txt.chars() { + event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); + } + } + } + KeyboardEvent::Repeat { + rawkey, + keysym, + utf8, + .. + } => { + let window_id = match inner.target_window_id { + Some(window_id) => window_id, + None => return, + }; + + let virtual_keycode = keymap::keysym_to_vkey(keysym); + + event_sink.push_window_event( + #[allow(deprecated)] + WindowEvent::KeyboardInput { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + input: KeyboardInput { + state: ElementState::Pressed, + scancode: rawkey, + virtual_keycode, + modifiers: *inner.modifiers_state.borrow(), + }, + is_synthetic: false, + }, + window_id, + ); + + if let Some(txt) = utf8 { + for ch in txt.chars() { + event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); + } + } + } + KeyboardEvent::Modifiers { modifiers } => { + let modifiers = ModifiersState::from(modifiers); + if let Some(window_id) = inner.target_window_id { + *inner.modifiers_state.borrow_mut() = modifiers; + + event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id); + } else { + // Compositor must send modifiers after wl_keyboard::enter, however certain + // compositors are still sending it before, so stash such events and send + // them on wl_keyboard::enter. + inner.pending_modifers_state = Some(modifiers); + } + } + } +} diff --git a/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs b/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs new file mode 100644 index 00000000..717a68df --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs @@ -0,0 +1,188 @@ +//! Convert Wayland keys to winit keys. + +use crate::event::VirtualKeyCode; + +pub fn keysym_to_vkey(keysym: u32) -> Option { + use sctk::seat::keyboard::keysyms; + match keysym { + // Numbers. + keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1), + keysyms::XKB_KEY_2 => Some(VirtualKeyCode::Key2), + keysyms::XKB_KEY_3 => Some(VirtualKeyCode::Key3), + keysyms::XKB_KEY_4 => Some(VirtualKeyCode::Key4), + keysyms::XKB_KEY_5 => Some(VirtualKeyCode::Key5), + keysyms::XKB_KEY_6 => Some(VirtualKeyCode::Key6), + keysyms::XKB_KEY_7 => Some(VirtualKeyCode::Key7), + keysyms::XKB_KEY_8 => Some(VirtualKeyCode::Key8), + keysyms::XKB_KEY_9 => Some(VirtualKeyCode::Key9), + keysyms::XKB_KEY_0 => Some(VirtualKeyCode::Key0), + // Letters. + keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A), + keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B), + keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C), + keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D), + keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E), + keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F), + keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G), + keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H), + keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I), + keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J), + keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K), + keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L), + keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M), + keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N), + keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O), + keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P), + keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q), + keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R), + keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S), + keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T), + keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U), + keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V), + keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W), + keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X), + keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y), + keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z), + // Escape. + keysyms::XKB_KEY_Escape => Some(VirtualKeyCode::Escape), + // Function keys. + keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1), + keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2), + keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3), + keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4), + keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5), + keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6), + keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7), + keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8), + keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9), + keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10), + keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11), + keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12), + keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13), + keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14), + keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15), + keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16), + keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17), + keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18), + keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19), + keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20), + keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21), + keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22), + keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23), + keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24), + // Flow control. + keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot), + keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll), + keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause), + keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert), + keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home), + keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete), + keysyms::XKB_KEY_End => Some(VirtualKeyCode::End), + keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown), + keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp), + // Arrows. + keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left), + keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up), + keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right), + keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down), + + keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back), + keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return), + keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space), + + keysyms::XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose), + keysyms::XKB_KEY_caret => Some(VirtualKeyCode::Caret), + + // Keypad. + keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock), + keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0), + keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1), + keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2), + keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3), + keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4), + keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5), + keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6), + keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7), + keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8), + keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9), + // Misc. + // => Some(VirtualKeyCode::AbntC1), + // => Some(VirtualKeyCode::AbntC2), + keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus), + keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe), + // => Some(VirtualKeyCode::Apps), + keysyms::XKB_KEY_at => Some(VirtualKeyCode::At), + // => Some(VirtualKeyCode::Ax), + keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash), + keysyms::XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator), + // => Some(VirtualKeyCode::Capital), + keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon), + keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma), + // => Some(VirtualKeyCode::Convert), + keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals), + keysyms::XKB_KEY_grave => Some(VirtualKeyCode::Grave), + // => Some(VirtualKeyCode::Kana), + keysyms::XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji), + keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt), + keysyms::XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket), + keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl), + keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift), + keysyms::XKB_KEY_Super_L => Some(VirtualKeyCode::LWin), + keysyms::XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail), + // => Some(VirtualKeyCode::MediaSelect), + // => Some(VirtualKeyCode::MediaStop), + keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus), + keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk), + keysyms::XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute), + // => Some(VirtualKeyCode::MyComputer), + keysyms::XKB_KEY_XF86AudioNext => Some(VirtualKeyCode::NextTrack), + // => Some(VirtualKeyCode::NoConvert), + keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma), + keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), + keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), + keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd), + keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), + keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply), + keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide), + keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal), + keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), + keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), + keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), + keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End), + // => Some(VirtualKeyCode::OEM102), + keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period), + // => Some(VirtualKeyCode::Playpause), + keysyms::XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power), + keysyms::XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack), + keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt), + keysyms::XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket), + keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl), + keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift), + keysyms::XKB_KEY_Super_R => Some(VirtualKeyCode::RWin), + keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon), + keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash), + keysyms::XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep), + // => Some(VirtualKeyCode::Stop), + // => Some(VirtualKeyCode::Sysrq), + keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab), + keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab), + keysyms::XKB_KEY_underscore => Some(VirtualKeyCode::Underline), + // => Some(VirtualKeyCode::Unlabeled), + keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown), + keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp), + // => Some(VirtualKeyCode::Wake), + // => Some(VirtualKeyCode::Webback), + // => Some(VirtualKeyCode::WebFavorites), + // => Some(VirtualKeyCode::WebForward), + // => Some(VirtualKeyCode::WebHome), + // => Some(VirtualKeyCode::WebRefresh), + // => Some(VirtualKeyCode::WebSearch), + // => Some(VirtualKeyCode::WebStop), + keysyms::XKB_KEY_yen => Some(VirtualKeyCode::Yen), + keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy), + keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste), + keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut), + // Fallback. + _ => None, + } +} diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs new file mode 100644 index 00000000..1362dcf7 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -0,0 +1,105 @@ +//! Wayland keyboard handling. + +use std::cell::RefCell; +use std::rc::Rc; + +use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::Attached; + +use sctk::reexports::calloop::{LoopHandle, Source}; + +use sctk::seat::keyboard::{self, RepeatSource}; + +use crate::event::ModifiersState; +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::WindowId; + +mod handlers; +mod keymap; + +pub(crate) struct Keyboard { + pub keyboard: WlKeyboard, + + /// The source for repeat keys. + pub repeat_source: Option>, + + /// LoopHandle to drop `RepeatSource`, when dropping the keyboard. + pub loop_handle: LoopHandle, +} + +impl Keyboard { + pub fn new( + seat: &Attached, + loop_handle: LoopHandle, + modifiers_state: Rc>, + ) -> Option { + let mut inner = KeyboardInner::new(modifiers_state); + let keyboard_data = keyboard::map_keyboard_repeat( + loop_handle.clone(), + &seat, + None, + keyboard::RepeatKind::System, + move |event, _, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + handlers::handle_keyboard(event, &mut inner, winit_state); + }, + ); + + let (keyboard, repeat_source) = keyboard_data.ok()?; + + Some(Self { + keyboard, + loop_handle, + repeat_source: Some(repeat_source), + }) + } +} + +impl Drop for Keyboard { + fn drop(&mut self) { + if self.keyboard.as_ref().version() >= 3 { + self.keyboard.release(); + } + + if let Some(repeat_source) = self.repeat_source.take() { + self.loop_handle.remove(repeat_source); + } + } +} + +struct KeyboardInner { + /// Currently focused surface. + target_window_id: Option, + + /// A pending state of modifiers. + /// + /// This state is getting set if we've got a modifiers update + /// before `Enter` event, which shouldn't happen in general, however + /// some compositors are still doing so. + pending_modifers_state: Option, + + /// Current state of modifiers keys. + modifiers_state: Rc>, +} + +impl KeyboardInner { + fn new(modifiers_state: Rc>) -> Self { + Self { + target_window_id: None, + pending_modifers_state: None, + modifiers_state, + } + } +} + +impl From for ModifiersState { + fn from(mods: keyboard::ModifiersState) -> ModifiersState { + let mut wl_mods = ModifiersState::empty(); + wl_mods.set(ModifiersState::SHIFT, mods.shift); + wl_mods.set(ModifiersState::CTRL, mods.ctrl); + wl_mods.set(ModifiersState::ALT, mods.alt); + wl_mods.set(ModifiersState::LOGO, mods.logo); + wl_mods + } +} diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs new file mode 100644 index 00000000..23098d08 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -0,0 +1,208 @@ +//! Seat handling and managing. + +use std::cell::RefCell; +use std::rc::Rc; + +use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1; +use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; + +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::Attached; + +use sctk::environment::Environment; +use sctk::reexports::calloop::LoopHandle; +use sctk::seat::pointer::ThemeManager; +use sctk::seat::{SeatData, SeatListener}; + +use super::env::WinitEnv; +use super::event_loop::WinitState; +use crate::event::ModifiersState; + +mod keyboard; +pub mod pointer; +pub mod text_input; +mod touch; + +use keyboard::Keyboard; +use pointer::Pointers; +use text_input::TextInput; +use touch::Touch; + +pub struct SeatManager { + /// Listener for seats. + _seat_listener: SeatListener, +} + +impl SeatManager { + pub fn new( + env: &Environment, + loop_handle: LoopHandle, + theme_manager: ThemeManager, + ) -> Self { + let relative_pointer_manager = env.get_global::(); + let pointer_constraints = env.get_global::(); + let text_input_manager = env.get_global::(); + + let mut inner = SeatManagerInner::new( + theme_manager, + relative_pointer_manager, + pointer_constraints, + text_input_manager, + loop_handle, + ); + + // Handle existing seats. + for seat in env.get_all_seats() { + let seat_data = match sctk::seat::clone_seat_data(&seat) { + Some(seat_data) => seat_data, + None => continue, + }; + + inner.process_seat_update(&seat, &seat_data); + } + + let seat_listener = env.listen_for_seats(move |seat, seat_data, _| { + inner.process_seat_update(&seat, &seat_data); + }); + + Self { + _seat_listener: seat_listener, + } + } +} + +/// Inner state of the seat manager. +struct SeatManagerInner { + /// Currently observed seats. + seats: Vec, + + /// Loop handle. + loop_handle: LoopHandle, + + /// Relative pointer manager. + relative_pointer_manager: Option>, + + /// Pointer constraints. + pointer_constraints: Option>, + + /// Text input manager. + text_input_manager: Option>, + + /// A theme manager. + theme_manager: ThemeManager, +} + +impl SeatManagerInner { + fn new( + theme_manager: ThemeManager, + relative_pointer_manager: Option>, + pointer_constraints: Option>, + text_input_manager: Option>, + loop_handle: LoopHandle, + ) -> Self { + Self { + seats: Vec::new(), + loop_handle, + relative_pointer_manager, + pointer_constraints, + text_input_manager, + theme_manager, + } + } + + /// Handle seats update from the `SeatListener`. + pub fn process_seat_update(&mut self, seat: &Attached, seat_data: &SeatData) { + let detached_seat = seat.detach(); + + let position = self.seats.iter().position(|si| si.seat == detached_seat); + let index = position.unwrap_or_else(|| { + self.seats.push(SeatInfo::new(detached_seat)); + self.seats.len() - 1 + }); + + let seat_info = &mut self.seats[index]; + + // Pointer handling. + if seat_data.has_pointer && !seat_data.defunct { + if seat_info.pointer.is_none() { + seat_info.pointer = Some(Pointers::new( + &seat, + &self.theme_manager, + &self.relative_pointer_manager, + &self.pointer_constraints, + seat_info.modifiers_state.clone(), + )); + } + } else { + seat_info.pointer = None; + } + + // Handle keyboard. + if seat_data.has_keyboard && !seat_data.defunct { + if seat_info.keyboard.is_none() { + seat_info.keyboard = Keyboard::new( + &seat, + self.loop_handle.clone(), + seat_info.modifiers_state.clone(), + ); + } + } else { + seat_info.keyboard = None; + } + + // Handle touch. + if seat_data.has_touch && !seat_data.defunct { + if seat_info.touch.is_none() { + seat_info.touch = Some(Touch::new(&seat)); + } + } else { + seat_info.touch = None; + } + + // Handle text input. + if let Some(text_input_manager) = self.text_input_manager.as_ref() { + if seat_data.defunct { + seat_info.text_input = None; + } else if seat_info.text_input.is_none() { + seat_info.text_input = Some(TextInput::new(&seat, &text_input_manager)); + } + } + } +} + +/// Resources associtated with a given seat. +struct SeatInfo { + /// Seat to which this `SeatInfo` belongs. + seat: WlSeat, + + /// A keyboard handle with its repeat rate handling. + keyboard: Option, + + /// All pointers we're using on a seat. + pointer: Option, + + /// Touch handling. + touch: Option, + + /// Text input handling aka IME. + text_input: Option, + + /// The current state of modifiers observed in keyboard handler. + /// + /// We keep modifiers state on a seat, since it's being used by pointer events as well. + modifiers_state: Rc>, +} + +impl SeatInfo { + pub fn new(seat: WlSeat) -> Self { + Self { + seat, + keyboard: None, + pointer: None, + touch: None, + text_input: None, + modifiers_state: Rc::new(RefCell::new(ModifiersState::default())), + } + } +} diff --git a/src/platform_impl/linux/wayland/seat/pointer/data.rs b/src/platform_impl/linux/wayland/seat/pointer/data.rs new file mode 100644 index 00000000..1da60d35 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/pointer/data.rs @@ -0,0 +1,74 @@ +//! Data which is used in pointer callbacks. + +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Attached; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1}; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; + +use crate::event::{ModifiersState, TouchPhase}; + +/// A data being used by pointer handlers. +pub(super) struct PointerData { + /// Winit's surface the pointer is currently over. + pub surface: Option, + + /// Current modifiers state. + /// + /// This refers a state of modifiers from `WlKeyboard` on + /// the given seat. + pub modifiers_state: Rc>, + + /// Pointer constraints. + pub pointer_constraints: Option>, + + pub confined_pointer: Rc>>, + + /// A latest event serial. + pub latest_serial: Rc>, + + /// The currently accumulated axis data on a pointer. + pub axis_data: AxisData, +} + +impl PointerData { + pub fn new( + confined_pointer: Rc>>, + pointer_constraints: Option>, + modifiers_state: Rc>, + ) -> Self { + Self { + surface: None, + latest_serial: Rc::new(Cell::new(0)), + confined_pointer, + modifiers_state, + pointer_constraints, + axis_data: AxisData::new(), + } + } +} + +/// Axis data. +#[derive(Clone, Copy)] +pub(super) struct AxisData { + /// Current state of the axis. + pub axis_state: TouchPhase, + + /// A buffer for `PixelDelta` event. + pub axis_buffer: Option<(f32, f32)>, + + /// A buffer for `LineDelta` event. + pub axis_discrete_buffer: Option<(f32, f32)>, +} + +impl AxisData { + pub fn new() -> Self { + Self { + axis_state: TouchPhase::Ended, + axis_buffer: None, + axis_discrete_buffer: None, + } + } +} diff --git a/src/platform_impl/linux/wayland/seat/pointer/handlers.rs b/src/platform_impl/linux/wayland/seat/pointer/handlers.rs new file mode 100644 index 00000000..06165a49 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/pointer/handlers.rs @@ -0,0 +1,297 @@ +//! Handlers for the pointers we're using. + +use std::cell::RefCell; +use std::rc::Rc; + +use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent}; +use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent; + +use sctk::seat::pointer::ThemedPointer; + +use crate::dpi::LogicalPosition; +use crate::event::{ + DeviceEvent, ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent, +}; +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::{self, DeviceId}; + +use super::{PointerData, WinitPointer}; + +#[inline] +pub(super) fn handle_pointer( + pointer: ThemedPointer, + event: PointerEvent, + pointer_data: &Rc>, + winit_state: &mut WinitState, +) { + let event_sink = &mut winit_state.event_sink; + let mut pointer_data = pointer_data.borrow_mut(); + match event { + PointerEvent::Enter { + surface, + surface_x, + surface_y, + serial, + .. + } => { + pointer_data.latest_serial.replace(serial); + + let window_id = wayland::make_wid(&surface); + if !winit_state.window_map.contains_key(&window_id) { + return; + } + let window_handle = match winit_state.window_map.get_mut(&window_id) { + Some(window_handle) => window_handle, + None => return, + }; + + let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + pointer_data.surface = Some(surface); + + // Notify window that pointer entered the surface. + let winit_pointer = WinitPointer { + pointer, + confined_pointer: Rc::downgrade(&pointer_data.confined_pointer), + pointer_constraints: pointer_data.pointer_constraints.clone(), + latest_serial: pointer_data.latest_serial.clone(), + }; + window_handle.pointer_entered(winit_pointer); + + event_sink.push_window_event( + WindowEvent::CursorEntered { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + }, + window_id, + ); + + let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor); + + event_sink.push_window_event( + WindowEvent::CursorMoved { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + position, + modifiers: *pointer_data.modifiers_state.borrow(), + }, + window_id, + ); + } + PointerEvent::Leave { surface, serial } => { + pointer_data.surface = None; + pointer_data.latest_serial.replace(serial); + + let window_id = wayland::make_wid(&surface); + + let window_handle = match winit_state.window_map.get_mut(&window_id) { + Some(window_handle) => window_handle, + None => return, + }; + + // Notify a window that pointer is no longer observing it. + let winit_pointer = WinitPointer { + pointer, + confined_pointer: Rc::downgrade(&pointer_data.confined_pointer), + pointer_constraints: pointer_data.pointer_constraints.clone(), + latest_serial: pointer_data.latest_serial.clone(), + }; + window_handle.pointer_left(winit_pointer); + + event_sink.push_window_event( + WindowEvent::CursorLeft { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + }, + window_id, + ); + } + PointerEvent::Motion { + surface_x, + surface_y, + .. + } => { + let surface = match pointer_data.surface.as_ref() { + Some(surface) => surface, + None => return, + }; + + let window_id = wayland::make_wid(surface); + + let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor); + + event_sink.push_window_event( + WindowEvent::CursorMoved { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + position, + modifiers: *pointer_data.modifiers_state.borrow(), + }, + window_id, + ); + } + PointerEvent::Button { + button, + state, + serial, + .. + } => { + pointer_data.latest_serial.replace(serial); + let window_id = match pointer_data.surface.as_ref().map(wayland::make_wid) { + Some(window_id) => window_id, + None => return, + }; + + let state = match state { + wl_pointer::ButtonState::Pressed => ElementState::Pressed, + wl_pointer::ButtonState::Released => ElementState::Released, + _ => unreachable!(), + }; + + let button = match button { + 0x110 => MouseButton::Left, + 0x111 => MouseButton::Right, + 0x112 => MouseButton::Middle, + // TODO - figure out the translation. + _ => return, + }; + + event_sink.push_window_event( + WindowEvent::MouseInput { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + state, + button, + modifiers: *pointer_data.modifiers_state.borrow(), + }, + window_id, + ); + } + PointerEvent::Axis { axis, value, .. } => { + let surface = match pointer_data.surface.as_ref() { + Some(surface) => surface, + None => return, + }; + + let window_id = wayland::make_wid(&surface); + + if pointer.as_ref().version() < 5 { + let (mut x, mut y) = (0.0, 0.0); + + // Old seat compatibility. + match axis { + // Wayland vertical sign convention is the inverse of winit. + wl_pointer::Axis::VerticalScroll => y -= value as f32, + wl_pointer::Axis::HorizontalScroll => x += value as f32, + _ => unreachable!(), + } + + let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + let delta = LogicalPosition::new(x as f64, y as f64).to_physical(scale_factor); + + event_sink.push_window_event( + WindowEvent::MouseWheel { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + delta: MouseScrollDelta::PixelDelta(delta), + phase: TouchPhase::Moved, + modifiers: *pointer_data.modifiers_state.borrow(), + }, + window_id, + ); + } else { + let (mut x, mut y) = pointer_data.axis_data.axis_buffer.unwrap_or((0.0, 0.0)); + match axis { + // Wayland vertical sign convention is the inverse of winit. + wl_pointer::Axis::VerticalScroll => y -= value as f32, + wl_pointer::Axis::HorizontalScroll => x += value as f32, + _ => unreachable!(), + } + + pointer_data.axis_data.axis_buffer = Some((x, y)); + + pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state { + TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, + _ => TouchPhase::Started, + } + } + } + PointerEvent::AxisDiscrete { axis, discrete } => { + let (mut x, mut y) = pointer_data + .axis_data + .axis_discrete_buffer + .unwrap_or((0., 0.)); + + match axis { + // Wayland vertical sign convention is the inverse of winit. + wl_pointer::Axis::VerticalScroll => y -= discrete as f32, + wl_pointer::Axis::HorizontalScroll => x += discrete as f32, + _ => unreachable!(), + } + + pointer_data.axis_data.axis_discrete_buffer = Some((x, y)); + + pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state { + TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, + _ => TouchPhase::Started, + } + } + PointerEvent::AxisSource { .. } => (), + PointerEvent::AxisStop { .. } => { + pointer_data.axis_data.axis_state = TouchPhase::Ended; + } + PointerEvent::Frame => { + let axis_buffer = pointer_data.axis_data.axis_buffer.take(); + let axis_discrete_buffer = pointer_data.axis_data.axis_discrete_buffer.take(); + + let surface = match pointer_data.surface.as_ref() { + Some(surface) => surface, + None => return, + }; + let window_id = wayland::make_wid(&surface); + + let window_event = if let Some((x, y)) = axis_discrete_buffer { + WindowEvent::MouseWheel { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + delta: MouseScrollDelta::LineDelta(x, y), + phase: pointer_data.axis_data.axis_state, + modifiers: *pointer_data.modifiers_state.borrow(), + } + } else if let Some((x, y)) = axis_buffer { + let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + let delta = LogicalPosition::new(x, y).to_physical(scale_factor); + + WindowEvent::MouseWheel { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + delta: MouseScrollDelta::PixelDelta(delta), + phase: pointer_data.axis_data.axis_state, + modifiers: *pointer_data.modifiers_state.borrow(), + } + } else { + return; + }; + + event_sink.push_window_event(window_event, window_id); + } + _ => (), + } +} + +#[inline] +pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) { + if let RelativePointerEvent::RelativeMotion { dx, dy, .. } = event { + winit_state + .event_sink + .push_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId) + } +} diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs new file mode 100644 index 00000000..5debc8cb --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -0,0 +1,242 @@ +//! All pointer related handling. + +use std::cell::{Cell, RefCell}; +use std::rc::{Rc, Weak}; + +use sctk::reexports::client::protocol::wl_pointer::WlPointer; +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Attached; +use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1; +use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime}; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; + +use sctk::seat::pointer::{ThemeManager, ThemedPointer}; + +use crate::event::ModifiersState; +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::window::CursorIcon; + +mod data; +mod handlers; + +use data::PointerData; + +/// A proxy to Wayland pointer, which serves requests from a `WindowHandle`. +pub struct WinitPointer { + pointer: ThemedPointer, + + /// Create confined pointers. + pointer_constraints: Option>, + + /// Cursor to handle confine requests. + confined_pointer: Weak>>, + + /// Latest observed serial in pointer events. + latest_serial: Rc>, +} + +impl PartialEq for WinitPointer { + fn eq(&self, other: &Self) -> bool { + *self.pointer == *other.pointer + } +} + +impl Eq for WinitPointer {} + +impl WinitPointer { + /// Set the cursor icon. + /// + /// Providing `None` will hide the cursor. + pub fn set_cursor(&self, cursor_icon: Option) { + let cursor_icon = match cursor_icon { + Some(cursor_icon) => cursor_icon, + None => { + // Hide the cursor. + (*self.pointer).set_cursor(self.latest_serial.get(), None, 0, 0); + return; + } + }; + + let cursors: &[&str] = match cursor_icon { + CursorIcon::Alias => &["link"], + CursorIcon::Arrow => &["arrow"], + CursorIcon::Cell => &["plus"], + CursorIcon::Copy => &["copy"], + CursorIcon::Crosshair => &["crosshair"], + CursorIcon::Default => &["left_ptr"], + CursorIcon::Hand => &["hand"], + CursorIcon::Help => &["question_arrow"], + CursorIcon::Move => &["move"], + CursorIcon::Grab => &["openhand", "grab"], + CursorIcon::Grabbing => &["closedhand", "grabbing"], + CursorIcon::Progress => &["progress"], + CursorIcon::AllScroll => &["all-scroll"], + CursorIcon::ContextMenu => &["context-menu"], + + CursorIcon::NoDrop => &["no-drop", "circle"], + CursorIcon::NotAllowed => &["crossed_circle"], + + // Resize cursors + CursorIcon::EResize => &["right_side"], + CursorIcon::NResize => &["top_side"], + CursorIcon::NeResize => &["top_right_corner"], + CursorIcon::NwResize => &["top_left_corner"], + CursorIcon::SResize => &["bottom_side"], + CursorIcon::SeResize => &["bottom_right_corner"], + CursorIcon::SwResize => &["bottom_left_corner"], + CursorIcon::WResize => &["left_side"], + CursorIcon::EwResize => &["h_double_arrow"], + CursorIcon::NsResize => &["v_double_arrow"], + CursorIcon::NwseResize => &["bd_double_arrow", "size_bdiag"], + CursorIcon::NeswResize => &["fd_double_arrow", "size_fdiag"], + CursorIcon::ColResize => &["split_h", "h_double_arrow"], + CursorIcon::RowResize => &["split_v", "v_double_arrow"], + CursorIcon::Text => &["text", "xterm"], + CursorIcon::VerticalText => &["vertical-text"], + + CursorIcon::Wait => &["watch"], + + CursorIcon::ZoomIn => &["zoom-in"], + CursorIcon::ZoomOut => &["zoom-out"], + }; + + let serial = Some(self.latest_serial.get()); + for cursor in cursors { + if self.pointer.set_cursor(cursor, serial).is_ok() { + break; + } + } + } + + /// Confine the pointer to a surface. + pub fn confine(&self, surface: &WlSurface) { + let pointer_constraints = match &self.pointer_constraints { + Some(pointer_constraints) => pointer_constraints, + None => return, + }; + + let confined_pointer = match self.confined_pointer.upgrade() { + Some(confined_pointer) => confined_pointer, + // A pointer is gone. + None => return, + }; + + *confined_pointer.borrow_mut() = Some(init_confined_pointer( + &pointer_constraints, + &surface, + &*self.pointer, + )); + } + + /// Tries to unconfine the pointer if the current pointer is confined. + pub fn unconfine(&self) { + let confined_pointer = match self.confined_pointer.upgrade() { + Some(confined_pointer) => confined_pointer, + // A pointer is gone. + None => return, + }; + + let mut confined_pointer = confined_pointer.borrow_mut(); + + if let Some(confined_pointer) = confined_pointer.take() { + confined_pointer.destroy(); + } + } +} + +/// A pointer wrapper for easy releasing and managing pointers. +pub(super) struct Pointers { + /// A pointer itself. + pointer: ThemedPointer, + + /// A relative pointer handler. + relative_pointer: Option, + + /// Confined pointer. + confined_pointer: Rc>>, +} + +impl Pointers { + pub(super) fn new( + seat: &Attached, + theme_manager: &ThemeManager, + relative_pointer_manager: &Option>, + pointer_constraints: &Option>, + modifiers_state: Rc>, + ) -> Self { + let confined_pointer = Rc::new(RefCell::new(None)); + let pointer_data = Rc::new(RefCell::new(PointerData::new( + confined_pointer.clone(), + pointer_constraints.clone(), + modifiers_state, + ))); + let pointer = theme_manager.theme_pointer_with_impl( + seat, + move |event, pointer, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + handlers::handle_pointer(pointer, event, &pointer_data, winit_state); + }, + ); + + // Setup relative_pointer if it's available. + let relative_pointer = match relative_pointer_manager.as_ref() { + Some(relative_pointer_manager) => { + Some(init_relative_pointer(&relative_pointer_manager, &*pointer)) + } + None => None, + }; + + Self { + pointer, + relative_pointer, + confined_pointer, + } + } +} + +impl Drop for Pointers { + fn drop(&mut self) { + // Drop relative pointer. + if let Some(relative_pointer) = self.relative_pointer.take() { + relative_pointer.destroy(); + } + + // Drop confined pointer. + if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() { + confined_pointer.destroy(); + } + + // Drop the pointer itself in case it's possible. + if self.pointer.as_ref().version() >= 3 { + self.pointer.release(); + } + } +} + +pub(super) fn init_relative_pointer( + relative_pointer_manager: &ZwpRelativePointerManagerV1, + pointer: &WlPointer, +) -> ZwpRelativePointerV1 { + let relative_pointer = relative_pointer_manager.get_relative_pointer(&*pointer); + relative_pointer.quick_assign(move |_, event, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + handlers::handle_relative_pointer(event, winit_state); + }); + + relative_pointer.detach() +} + +pub(super) fn init_confined_pointer( + pointer_constraints: &Attached, + surface: &WlSurface, + pointer: &WlPointer, +) -> ZwpConfinedPointerV1 { + let confined_pointer = + pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent.to_raw()); + + confined_pointer.quick_assign(move |_, _, _| {}); + + confined_pointer.detach() +} diff --git a/src/platform_impl/linux/wayland/seat/text_input/handlers.rs b/src/platform_impl/linux/wayland/seat/text_input/handlers.rs new file mode 100644 index 00000000..4ba13d67 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/text_input/handlers.rs @@ -0,0 +1,78 @@ +//! Handling of IME events. + +use sctk::reexports::client::Main; +use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::{ + Event as TextInputEvent, ZwpTextInputV3, +}; + +use crate::event::WindowEvent; +use crate::platform_impl::wayland; +use crate::platform_impl::wayland::event_loop::WinitState; + +use super::{TextInputHandler, TextInputInner}; + +#[inline] +pub(super) fn handle_text_input( + text_input: Main, + inner: &mut TextInputInner, + event: TextInputEvent, + winit_state: &mut WinitState, +) { + let event_sink = &mut winit_state.event_sink; + match event { + TextInputEvent::Enter { surface } => { + let window_id = wayland::make_wid(&surface); + + let window_handle = match winit_state.window_map.get_mut(&window_id) { + Some(window_handle) => window_handle, + None => return, + }; + inner.target_window_id = Some(window_id); + + // Enable text input on that surface. + text_input.enable(); + text_input.commit(); + + // Notify a window we're currently over about text input handler. + let text_input_handler = TextInputHandler { + text_input: text_input.detach(), + }; + window_handle.text_input_entered(text_input_handler); + } + TextInputEvent::Leave { surface } => { + // Always issue a disable. + text_input.disable(); + text_input.commit(); + + let window_id = wayland::make_wid(&surface); + + let window_handle = match winit_state.window_map.get_mut(&window_id) { + Some(window_handle) => window_handle, + None => return, + }; + + inner.target_window_id = None; + + // Remove text input handler from the window we're leaving. + let text_input_handler = TextInputHandler { + text_input: text_input.detach(), + }; + window_handle.text_input_left(text_input_handler); + } + TextInputEvent::CommitString { text } => { + // Update currenly commited string. + inner.commit_string = text; + } + TextInputEvent::Done { .. } => { + let (window_id, text) = match (inner.target_window_id, inner.commit_string.take()) { + (Some(window_id), Some(text)) => (window_id, text), + _ => return, + }; + + for ch in text.chars() { + event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); + } + } + _ => (), + } +} diff --git a/src/platform_impl/linux/wayland/seat/text_input/mod.rs b/src/platform_impl/linux/wayland/seat/text_input/mod.rs new file mode 100644 index 00000000..77f4ff08 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/text_input/mod.rs @@ -0,0 +1,66 @@ +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::Attached; +use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; +use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::ZwpTextInputV3; + +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::WindowId; + +mod handlers; + +/// A handler for text input that we're advertising for `WindowHandle`. +#[derive(Eq, PartialEq)] +pub struct TextInputHandler { + text_input: ZwpTextInputV3, +} + +impl TextInputHandler { + #[inline] + pub fn set_ime_position(&self, x: i32, y: i32) { + self.text_input.set_cursor_rectangle(x, y, 0, 0); + self.text_input.commit(); + } +} + +/// A wrapper around text input to automatically destroy the object on `Drop`. +pub struct TextInput { + text_input: Attached, +} + +impl TextInput { + pub fn new(seat: &Attached, text_input_manager: &ZwpTextInputManagerV3) -> Self { + let text_input = text_input_manager.get_text_input(seat); + let mut text_input_inner = TextInputInner::new(); + text_input.quick_assign(move |text_input, event, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + handlers::handle_text_input(text_input, &mut text_input_inner, event, winit_state); + }); + + let text_input: Attached = text_input.into(); + + Self { text_input } + } +} + +impl Drop for TextInput { + fn drop(&mut self) { + self.text_input.destroy(); + } +} + +struct TextInputInner { + /// Currently focused surface. + target_window_id: Option, + + /// Pending string to commit. + commit_string: Option, +} + +impl TextInputInner { + fn new() -> Self { + Self { + target_window_id: None, + commit_string: None, + } + } +} diff --git a/src/platform_impl/linux/wayland/seat/touch/handlers.rs b/src/platform_impl/linux/wayland/seat/touch/handlers.rs new file mode 100644 index 00000000..8a17b393 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/touch/handlers.rs @@ -0,0 +1,122 @@ +//! Various handlers for touch events. + +use sctk::reexports::client::protocol::wl_touch::Event as TouchEvent; + +use crate::dpi::LogicalPosition; +use crate::event::{TouchPhase, WindowEvent}; + +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::{self, DeviceId}; + +use super::{TouchInner, TouchPoint}; + +/// Handle WlTouch events. +#[inline] +pub(super) fn handle_touch( + event: TouchEvent, + inner: &mut TouchInner, + winit_state: &mut WinitState, +) { + let event_sink = &mut winit_state.event_sink; + + match event { + TouchEvent::Down { + surface, id, x, y, .. + } => { + let window_id = wayland::make_wid(&surface); + if !winit_state.window_map.contains_key(&window_id) { + return; + } + + let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + let position = LogicalPosition::new(x, y); + + event_sink.push_window_event( + WindowEvent::Touch(crate::event::Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Started, + location: position.to_physical(scale_factor), + force: None, // TODO + id: id as u64, + }), + window_id, + ); + + inner + .touch_points + .push(TouchPoint::new(surface, position, id)); + } + TouchEvent::Up { id, .. } => { + let touch_point = match inner.touch_points.iter().find(|p| p.id == id) { + Some(touch_point) => touch_point, + None => return, + }; + + let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64; + let location = touch_point.position.to_physical(scale_factor); + let window_id = wayland::make_wid(&touch_point.surface); + + event_sink.push_window_event( + WindowEvent::Touch(crate::event::Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Ended, + location, + force: None, // TODO + id: id as u64, + }), + window_id, + ); + } + TouchEvent::Motion { id, x, y, .. } => { + let touch_point = match inner.touch_points.iter_mut().find(|p| p.id == id) { + Some(touch_point) => touch_point, + None => return, + }; + + touch_point.position = LogicalPosition::new(x, y); + + let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64; + let location = touch_point.position.to_physical(scale_factor); + let window_id = wayland::make_wid(&touch_point.surface); + + event_sink.push_window_event( + WindowEvent::Touch(crate::event::Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Moved, + location, + force: None, // TODO + id: id as u64, + }), + window_id, + ); + } + TouchEvent::Frame => (), + TouchEvent::Cancel => { + for touch_point in inner.touch_points.drain(..) { + let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64; + let location = touch_point.position.to_physical(scale_factor); + let window_id = wayland::make_wid(&touch_point.surface); + + event_sink.push_window_event( + WindowEvent::Touch(crate::event::Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Cancelled, + location, + force: None, // TODO + id: touch_point.id as u64, + }), + window_id, + ); + } + } + _ => (), + } +} diff --git a/src/platform_impl/linux/wayland/seat/touch/mod.rs b/src/platform_impl/linux/wayland/seat/touch/mod.rs new file mode 100644 index 00000000..197e9ac9 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/touch/mod.rs @@ -0,0 +1,78 @@ +//! Touch handling. + +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::protocol::wl_touch::WlTouch; +use sctk::reexports::client::Attached; + +use crate::dpi::LogicalPosition; + +use crate::platform_impl::wayland::event_loop::WinitState; + +mod handlers; + +/// Wrapper around touch to handle release. +pub struct Touch { + /// Proxy to touch. + touch: WlTouch, +} + +impl Touch { + pub fn new(seat: &Attached) -> Self { + let touch = seat.get_touch(); + let mut inner = TouchInner::new(); + + touch.quick_assign(move |_, event, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + handlers::handle_touch(event, &mut inner, winit_state); + }); + + Self { + touch: touch.detach(), + } + } +} + +impl Drop for Touch { + fn drop(&mut self) { + if self.touch.as_ref().version() >= 3 { + self.touch.release(); + } + } +} + +/// The data used by touch handlers. +pub(super) struct TouchInner { + /// Current touch points. + touch_points: Vec, +} + +impl TouchInner { + fn new() -> Self { + Self { + touch_points: Vec::new(), + } + } +} + +/// Location of touch press. +pub(super) struct TouchPoint { + /// A surface where the touch point is located. + surface: WlSurface, + + /// Location of the touch point. + position: LogicalPosition, + + /// Id. + id: i32, +} + +impl TouchPoint { + pub fn new(surface: WlSurface, position: LogicalPosition, id: i32) -> Self { + Self { + surface, + position, + id, + } + } +} diff --git a/src/platform_impl/linux/wayland/touch.rs b/src/platform_impl/linux/wayland/touch.rs deleted file mode 100644 index ce4e32c2..00000000 --- a/src/platform_impl/linux/wayland/touch.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use crate::dpi::LogicalPosition; -use crate::event::{TouchPhase, WindowEvent}; - -use super::{event_loop::EventsSink, make_wid, window::WindowStore, DeviceId}; - -use smithay_client_toolkit::surface; - -use smithay_client_toolkit::reexports::client::protocol::{ - wl_seat, - wl_surface::WlSurface, - wl_touch::{Event as TouchEvent, WlTouch}, -}; - -// location is in logical coordinates. -struct TouchPoint { - surface: WlSurface, - position: LogicalPosition, - id: i32, -} - -pub(crate) fn implement_touch( - seat: &wl_seat::WlSeat, - sink: EventsSink, - store: Arc>, -) -> WlTouch { - let mut pending_ids = Vec::new(); - seat.get_touch(|touch| { - touch.implement_closure( - move |evt, _| { - let store = store.lock().unwrap(); - match evt { - TouchEvent::Down { - surface, id, x, y, .. - } => { - let wid = store.find_wid(&surface); - if let Some(wid) = wid { - let scale_factor = surface::get_dpi_factor(&surface) as f64; - let position = LogicalPosition::new(x, y); - - sink.send_window_event( - WindowEvent::Touch(crate::event::Touch { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - phase: TouchPhase::Started, - location: position.to_physical(scale_factor), - force: None, // TODO - id: id as u64, - }), - wid, - ); - pending_ids.push(TouchPoint { - surface, - position, - id, - }); - } - } - TouchEvent::Up { id, .. } => { - let idx = pending_ids.iter().position(|p| p.id == id); - if let Some(idx) = idx { - let pt = pending_ids.remove(idx); - - let scale_factor = surface::get_dpi_factor(&pt.surface) as f64; - let location = pt.position.to_physical(scale_factor); - - sink.send_window_event( - WindowEvent::Touch(crate::event::Touch { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - phase: TouchPhase::Ended, - location, - force: None, // TODO - id: id as u64, - }), - make_wid(&pt.surface), - ); - } - } - TouchEvent::Motion { id, x, y, .. } => { - let pt = pending_ids.iter_mut().find(|p| p.id == id); - if let Some(pt) = pt { - pt.position = LogicalPosition::new(x, y); - - let scale_factor = surface::get_dpi_factor(&pt.surface) as f64; - let location = pt.position.to_physical(scale_factor); - - sink.send_window_event( - WindowEvent::Touch(crate::event::Touch { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - phase: TouchPhase::Moved, - location, - force: None, // TODO - id: id as u64, - }), - make_wid(&pt.surface), - ); - } - } - TouchEvent::Frame => (), - TouchEvent::Cancel => { - for pt in pending_ids.drain(..) { - let scale_factor = surface::get_dpi_factor(&pt.surface) as f64; - let location = pt.position.to_physical(scale_factor); - - sink.send_window_event( - WindowEvent::Touch(crate::event::Touch { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - phase: TouchPhase::Cancelled, - location, - force: None, // TODO - id: pt.id as u64, - }), - make_wid(&pt.surface), - ); - } - } - _ => unreachable!(), - } - }, - (), - ) - }) - .unwrap() -} diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs deleted file mode 100644 index 06f668cc..00000000 --- a/src/platform_impl/linux/wayland/window.rs +++ /dev/null @@ -1,569 +0,0 @@ -use raw_window_handle::unix::WaylandHandle; -use std::{ - collections::VecDeque, - mem::replace, - sync::{Arc, Mutex, Weak}, -}; - -use crate::{ - dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, - error::{ExternalError, NotSupportedError, OsError as RootOsError}, - monitor::MonitorHandle as RootMonitorHandle, - platform_impl::{ - platform::wayland::event_loop::available_monitors, MonitorHandle as PlatformMonitorHandle, - PlatformSpecificWindowBuilderAttributes as PlAttributes, - }, - window::{CursorIcon, Fullscreen, WindowAttributes}, -}; - -use smithay_client_toolkit::{ - output::OutputMgr, - reexports::client::{ - protocol::{wl_seat, wl_surface}, - Display, - }, - surface::{get_dpi_factor, get_outputs}, - window::{ConceptFrame, Event as WEvent, State as WState, Theme, Window as SWindow}, -}; - -use super::{event_loop::CursorManager, make_wid, EventLoopWindowTarget, MonitorHandle, WindowId}; - -pub struct Window { - surface: wl_surface::WlSurface, - frame: Arc>>, - cursor_manager: Arc>, - outputs: OutputMgr, // Access to info for all monitors - size: Arc>, - kill_switch: (Arc>, Arc>), - display: Arc, - need_frame_refresh: Arc>, - need_refresh: Arc>, - fullscreen: Arc>, - cursor_grab_changed: Arc>>, // Update grab state - decorated: Arc>, -} - -#[derive(Clone, Copy, Debug)] -pub enum DecorationsAction { - Hide, - Show, -} - -impl Window { - pub fn new( - evlp: &EventLoopWindowTarget, - attributes: WindowAttributes, - pl_attribs: PlAttributes, - ) -> Result { - // Create the surface first to get initial DPI - let window_store = evlp.store.clone(); - let cursor_manager = evlp.cursor_manager.clone(); - let surface = evlp.env.create_surface(move |scale_factor, surface| { - window_store - .lock() - .unwrap() - .scale_factor_change(&surface, scale_factor); - surface.set_buffer_scale(scale_factor); - }); - - // Always 1. - let scale_factor = get_dpi_factor(&surface); - - let (width, height) = attributes - .inner_size - .map(|size| size.to_logical::(scale_factor as f64).into()) - .unwrap_or((800, 600)); - - // Create the window - let size = Arc::new(Mutex::new((width, height))); - let fullscreen = Arc::new(Mutex::new(false)); - - let window_store = evlp.store.clone(); - - let decorated = Arc::new(Mutex::new(attributes.decorations)); - let pending_decorations_action = Arc::new(Mutex::new(None)); - - let my_surface = surface.clone(); - let mut frame = SWindow::::init_from_env( - &evlp.env, - surface.clone(), - (width, height), - move |event| match event { - WEvent::Configure { new_size, states } => { - let mut store = window_store.lock().unwrap(); - let is_fullscreen = states.contains(&WState::Fullscreen); - - for window in &mut store.windows { - if window.surface.as_ref().equals(&my_surface.as_ref()) { - window.new_size = new_size; - *(window.need_refresh.lock().unwrap()) = true; - { - // Get whether we're in fullscreen - let mut fullscreen = window.fullscreen.lock().unwrap(); - // Fullscreen state was changed, so update decorations - if *fullscreen != is_fullscreen { - let decorated = { *window.decorated.lock().unwrap() }; - if decorated { - *window.pending_decorations_action.lock().unwrap() = - if is_fullscreen { - Some(DecorationsAction::Hide) - } else { - Some(DecorationsAction::Show) - }; - } - } - *fullscreen = is_fullscreen; - } - *(window.need_frame_refresh.lock().unwrap()) = true; - return; - } - } - } - WEvent::Refresh => { - let store = window_store.lock().unwrap(); - for window in &store.windows { - if window.surface.as_ref().equals(&my_surface.as_ref()) { - *(window.need_frame_refresh.lock().unwrap()) = true; - return; - } - } - } - WEvent::Close => { - let mut store = window_store.lock().unwrap(); - for window in &mut store.windows { - if window.surface.as_ref().equals(&my_surface.as_ref()) { - window.closed = true; - return; - } - } - } - }, - ) - .unwrap(); - - if let Some(app_id) = pl_attribs.app_id { - frame.set_app_id(app_id); - } - - for &(_, ref seat) in evlp.seats.lock().unwrap().iter() { - frame.new_seat(seat); - } - - // Check for fullscreen requirements - match attributes.fullscreen { - Some(Fullscreen::Exclusive(_)) => { - panic!("Wayland doesn't support exclusive fullscreen") - } - Some(Fullscreen::Borderless(monitor)) => { - let monitor = - monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor { - PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), - #[cfg(feature = "x11")] - PlatformMonitorHandle::X(_) => None, - }); - - frame.set_fullscreen(monitor.as_ref()) - } - None => { - if attributes.maximized { - frame.set_maximized(); - } - } - } - - frame.set_resizable(attributes.resizable); - - // set decorations - frame.set_decorate(attributes.decorations); - - // set title - frame.set_title(attributes.title); - - // min-max dimensions - frame.set_min_size( - attributes - .min_inner_size - .map(|size| size.to_logical::(scale_factor as f64).into()), - ); - frame.set_max_size( - attributes - .max_inner_size - .map(|size| size.to_logical::(scale_factor as f64).into()), - ); - - let kill_switch = Arc::new(Mutex::new(false)); - let need_frame_refresh = Arc::new(Mutex::new(true)); - let frame = Arc::new(Mutex::new(frame)); - let need_refresh = Arc::new(Mutex::new(true)); - let cursor_grab_changed = Arc::new(Mutex::new(None)); - - evlp.store.lock().unwrap().windows.push(InternalWindow { - closed: false, - new_size: None, - size: size.clone(), - need_refresh: need_refresh.clone(), - fullscreen: fullscreen.clone(), - cursor_grab_changed: cursor_grab_changed.clone(), - need_frame_refresh: need_frame_refresh.clone(), - surface: surface.clone(), - kill_switch: kill_switch.clone(), - frame: Arc::downgrade(&frame), - current_scale_factor: scale_factor, - new_scale_factor: None, - decorated: decorated.clone(), - pending_decorations_action: pending_decorations_action.clone(), - }); - evlp.evq.borrow_mut().sync_roundtrip().unwrap(); - - Ok(Window { - display: evlp.display.clone(), - surface, - frame, - outputs: evlp.env.outputs.clone(), - size, - kill_switch: (kill_switch, evlp.cleanup_needed.clone()), - need_frame_refresh, - need_refresh, - cursor_manager, - fullscreen, - cursor_grab_changed, - decorated, - }) - } - - #[inline] - pub fn id(&self) -> WindowId { - make_wid(&self.surface) - } - - pub fn set_title(&self, title: &str) { - self.frame.lock().unwrap().set_title(title.into()); - } - - pub fn set_visible(&self, _visible: bool) { - // TODO - } - - #[inline] - pub fn outer_position(&self) -> Result, NotSupportedError> { - Err(NotSupportedError::new()) - } - - #[inline] - pub fn inner_position(&self) -> Result, NotSupportedError> { - Err(NotSupportedError::new()) - } - - #[inline] - pub fn set_outer_position(&self, _pos: Position) { - // Not possible with wayland - } - - pub fn inner_size(&self) -> PhysicalSize { - let scale_factor = self.scale_factor() as f64; - let size = LogicalSize::::from(*self.size.lock().unwrap()); - size.to_physical(scale_factor) - } - - pub fn request_redraw(&self) { - *self.need_refresh.lock().unwrap() = true; - } - - #[inline] - pub fn outer_size(&self) -> PhysicalSize { - let scale_factor = self.scale_factor() as f64; - let (w, h) = self.size.lock().unwrap().clone(); - // let (w, h) = super::wayland_window::add_borders(w as i32, h as i32); - let size = LogicalSize::::from((w, h)); - size.to_physical(scale_factor) - } - - #[inline] - // NOTE: This will only resize the borders, the contents must be updated by the user - pub fn set_inner_size(&self, size: Size) { - let scale_factor = self.scale_factor() as f64; - let (w, h) = size.to_logical::(scale_factor).into(); - self.frame.lock().unwrap().resize(w, h); - *(self.size.lock().unwrap()) = (w, h); - } - - #[inline] - pub fn set_min_inner_size(&self, dimensions: Option) { - let scale_factor = self.scale_factor() as f64; - self.frame - .lock() - .unwrap() - .set_min_size(dimensions.map(|dim| dim.to_logical::(scale_factor).into())); - } - - #[inline] - pub fn set_max_inner_size(&self, dimensions: Option) { - let scale_factor = self.scale_factor() as f64; - self.frame - .lock() - .unwrap() - .set_max_size(dimensions.map(|dim| dim.to_logical::(scale_factor).into())); - } - - #[inline] - pub fn set_resizable(&self, resizable: bool) { - self.frame.lock().unwrap().set_resizable(resizable); - } - - #[inline] - pub fn scale_factor(&self) -> i32 { - get_dpi_factor(&self.surface) - } - - pub fn set_decorations(&self, decorate: bool) { - *(self.decorated.lock().unwrap()) = decorate; - self.frame.lock().unwrap().set_decorate(decorate); - *(self.need_frame_refresh.lock().unwrap()) = true; - } - - pub fn set_minimized(&self, minimized: bool) { - // An app cannot un-minimize itself on Wayland - if minimized { - self.frame.lock().unwrap().set_minimized(); - } - } - - pub fn set_maximized(&self, maximized: bool) { - if maximized { - self.frame.lock().unwrap().set_maximized(); - } else { - self.frame.lock().unwrap().unset_maximized(); - } - } - - pub fn fullscreen(&self) -> Option { - if *(self.fullscreen.lock().unwrap()) { - let current_monitor = self - .current_monitor() - .map(|current_monitor| RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(current_monitor), - }); - - Some(Fullscreen::Borderless(current_monitor)) - } else { - None - } - } - - pub fn set_fullscreen(&self, fullscreen: Option) { - match fullscreen { - Some(Fullscreen::Exclusive(_)) => { - panic!("Wayland doesn't support exclusive fullscreen") - } - Some(Fullscreen::Borderless(monitor)) => { - let monitor = - monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor { - PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), - #[cfg(feature = "x11")] - PlatformMonitorHandle::X(_) => None, - }); - - self.frame.lock().unwrap().set_fullscreen(monitor.as_ref()); - } - None => self.frame.lock().unwrap().unset_fullscreen(), - } - } - - pub fn set_theme(&self, theme: T) { - self.frame.lock().unwrap().set_theme(theme) - } - - #[inline] - pub fn set_cursor_icon(&self, cursor: CursorIcon) { - let mut cursor_manager = self.cursor_manager.lock().unwrap(); - cursor_manager.set_cursor_icon(cursor); - } - - #[inline] - pub fn set_cursor_visible(&self, visible: bool) { - let mut cursor_manager = self.cursor_manager.lock().unwrap(); - cursor_manager.set_cursor_visible(visible); - } - - #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - *self.cursor_grab_changed.lock().unwrap() = Some(grab); - Ok(()) - } - - #[inline] - pub fn set_cursor_position(&self, _pos: Position) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } - - pub fn display(&self) -> &Display { - &*self.display - } - - pub fn surface(&self) -> &wl_surface::WlSurface { - &self.surface - } - - pub fn current_monitor(&self) -> Option { - let output = get_outputs(&self.surface).last()?.clone(); - Some(MonitorHandle { - proxy: output, - mgr: self.outputs.clone(), - }) - } - - pub fn available_monitors(&self) -> VecDeque { - available_monitors(&self.outputs) - } - - pub fn primary_monitor(&self) -> Option { - // Wayland doesn't have a notion of primary monitor. - None - } - - pub fn raw_window_handle(&self) -> WaylandHandle { - WaylandHandle { - surface: self.surface().as_ref().c_ptr() as *mut _, - display: self.display().as_ref().c_ptr() as *mut _, - ..WaylandHandle::empty() - } - } -} - -impl Drop for Window { - fn drop(&mut self) { - *(self.kill_switch.0.lock().unwrap()) = true; - *(self.kill_switch.1.lock().unwrap()) = true; - } -} - -/* - * Internal store for windows - */ - -struct InternalWindow { - surface: wl_surface::WlSurface, - // TODO: CONVERT TO LogicalSizes - new_size: Option<(u32, u32)>, - size: Arc>, - need_refresh: Arc>, - fullscreen: Arc>, - need_frame_refresh: Arc>, - cursor_grab_changed: Arc>>, - closed: bool, - kill_switch: Arc>, - frame: Weak>>, - current_scale_factor: i32, - new_scale_factor: Option, - decorated: Arc>, - pending_decorations_action: Arc>>, -} - -pub struct WindowStore { - windows: Vec, -} - -pub struct WindowStoreForEach<'a> { - pub new_size: Option<(u32, u32)>, - pub size: &'a Mutex<(u32, u32)>, - pub prev_scale_factor: i32, - pub new_scale_factor: Option, - pub closed: bool, - pub grab_cursor: Option, - pub surface: &'a wl_surface::WlSurface, - pub wid: WindowId, - pub frame: Option>>>, - pub decorations_action: Option, -} - -impl WindowStore { - pub fn new() -> WindowStore { - WindowStore { - windows: Vec::new(), - } - } - - pub fn find_wid(&self, surface: &wl_surface::WlSurface) -> Option { - for window in &self.windows { - if surface.as_ref().equals(&window.surface.as_ref()) { - return Some(make_wid(surface)); - } - } - None - } - - pub fn cleanup(&mut self) -> Vec { - let mut pruned = Vec::new(); - self.windows.retain(|w| { - if *w.kill_switch.lock().unwrap() { - // window is dead, cleanup - pruned.push(make_wid(&w.surface)); - w.surface.destroy(); - false - } else { - true - } - }); - pruned - } - - pub fn new_seat(&self, seat: &wl_seat::WlSeat) { - for window in &self.windows { - if let Some(w) = window.frame.upgrade() { - w.lock().unwrap().new_seat(seat); - } - } - } - - fn scale_factor_change(&mut self, surface: &wl_surface::WlSurface, new: i32) { - for window in &mut self.windows { - if surface.as_ref().equals(&window.surface.as_ref()) { - window.new_scale_factor = Some(new); - *(window.need_refresh.lock().unwrap()) = true; - } - } - } - - pub fn for_each(&mut self, mut f: F) - where - F: FnMut(WindowStoreForEach<'_>), - { - for window in &mut self.windows { - let prev_scale_factor = window.current_scale_factor; - if let Some(scale_factor) = window.new_scale_factor { - window.current_scale_factor = scale_factor; - } - let frame = window.frame.upgrade(); - let decorations_action = { window.pending_decorations_action.lock().unwrap().take() }; - f(WindowStoreForEach { - new_size: window.new_size.take(), - size: &window.size, - prev_scale_factor, - new_scale_factor: window.new_scale_factor.take(), - closed: window.closed, - grab_cursor: window.cursor_grab_changed.lock().unwrap().take(), - surface: &window.surface, - wid: make_wid(&window.surface), - frame, - decorations_action, - }); - // avoid re-spamming the event - window.closed = false; - } - } - - pub fn for_each_redraw_trigger(&mut self, mut f: F) - where - F: FnMut(bool, bool, WindowId, Option>>>), - { - for window in &mut self.windows { - let frame = window.frame.upgrade(); - f( - replace(&mut *window.need_refresh.lock().unwrap(), false), - replace(&mut *window.need_frame_refresh.lock().unwrap(), false), - make_wid(&window.surface), - frame, - ); - } - } -} diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs new file mode 100644 index 00000000..271e8058 --- /dev/null +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -0,0 +1,656 @@ +use std::collections::VecDeque; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; + +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Display; + +use sctk::reexports::calloop; + +use sctk::window::{ + ARGBColor, ButtonColorSpec, ColorSpec, ConceptConfig, ConceptFrame, Decorations, +}; + +use raw_window_handle::unix::WaylandHandle; + +use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; +use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; +use crate::monitor::MonitorHandle as RootMonitorHandle; +use crate::platform::unix::{ARGBColor as LocalARGBColor, Button, ButtonState, Element, Theme}; +use crate::platform_impl::{ + MonitorHandle as PlatformMonitorHandle, OsError, + PlatformSpecificWindowBuilderAttributes as PlatformAttributes, +}; +use crate::window::{CursorIcon, Fullscreen, WindowAttributes}; + +use super::env::WindowingFeatures; +use super::event_loop::WinitState; +use super::output::{MonitorHandle, OutputManagerHandle}; +use super::{EventLoopWindowTarget, WindowId}; + +pub mod shim; + +use shim::{WindowHandle, WindowRequest, WindowUpdate}; + +pub struct Window { + /// Window id. + window_id: WindowId, + + /// The Wayland display. + display: Display, + + /// The underlying wl_surface. + surface: WlSurface, + + /// The current window size. + size: Arc>>, + + /// A handle to output manager. + output_manager_handle: OutputManagerHandle, + + /// Event loop proxy to wake it up. + event_loop_awakener: calloop::ping::Ping, + + /// Fullscreen state. + fullscreen: Arc, + + /// Available windowing features. + windowing_features: WindowingFeatures, + + /// Requests that SCTK window should perform. + window_requests: Arc>>, +} + +impl Window { + pub fn new( + event_loop_window_target: &EventLoopWindowTarget, + attributes: WindowAttributes, + platform_attributes: PlatformAttributes, + ) -> Result { + let surface = event_loop_window_target + .env + .create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + + // Get the window that receiced the event. + let window_id = super::make_wid(&surface); + let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap(); + + // Set pending scale factor. + window_update.scale_factor = Some(scale); + window_update.redraw_requested = true; + + surface.set_buffer_scale(scale); + }) + .detach(); + + let scale_factor = sctk::get_surface_scale_factor(&surface); + + let window_id = super::make_wid(&surface); + let fullscreen = Arc::new(AtomicBool::new(false)); + let fullscreen_clone = fullscreen.clone(); + + let (width, height) = attributes + .inner_size + .map(|size| size.to_logical::(scale_factor as f64).into()) + .unwrap_or((800, 600)); + + let theme_manager = event_loop_window_target.theme_manager.clone(); + let mut window = event_loop_window_target + .env + .create_window::( + surface.clone(), + Some(theme_manager), + (width, height), + move |event, mut dispatch_data| { + use sctk::window::{Event, State}; + + let winit_state = dispatch_data.get::().unwrap(); + let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap(); + + match event { + Event::Refresh => { + window_update.refresh_frame = true; + } + Event::Configure { new_size, states } => { + let is_fullscreen = states.contains(&State::Fullscreen); + fullscreen_clone.store(is_fullscreen, Ordering::Relaxed); + + window_update.refresh_frame = true; + window_update.redraw_requested = true; + if let Some((w, h)) = new_size { + window_update.size = Some(LogicalSize::new(w, h)); + } + } + Event::Close => { + window_update.close_window = true; + } + } + }, + ) + .map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?; + + // Set decorations. + if attributes.decorations { + window.set_decorate(Decorations::FollowServer); + } else { + window.set_decorate(Decorations::None); + } + + // Min dimensions. + let min_size = attributes + .min_inner_size + .map(|size| size.to_logical::(scale_factor as f64).into()); + window.set_min_size(min_size); + + // Max dimensions. + let max_size = attributes + .min_inner_size + .map(|size| size.to_logical::(scale_factor as f64).into()); + window.set_max_size(max_size); + + // Set Wayland specific window attributes. + if let Some(app_id) = platform_attributes.app_id { + window.set_app_id(app_id); + } + + // Set common window attributes. + // + // We set resizable after other attributes, since it touches min and max size under + // the hood. + window.set_resizable(attributes.resizable); + window.set_title(attributes.title); + + // Set fullscreen/maximized if so was requested. + match attributes.fullscreen { + Some(Fullscreen::Exclusive(_)) => { + warn!("`Fullscreen::Exclusive` is ignored on Wayland") + } + Some(Fullscreen::Borderless(monitor)) => { + let monitor = + monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor { + PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), + #[cfg(feature = "x11")] + PlatformMonitorHandle::X(_) => None, + }); + + window.set_fullscreen(monitor.as_ref()); + } + None => { + if attributes.maximized { + window.set_maximized(); + } + } + } + + let size = Arc::new(Mutex::new(LogicalSize::new(width, height))); + + // We should trigger redraw and commit the surface for the newly created window. + let mut window_update = WindowUpdate::new(); + window_update.refresh_frame = true; + window_update.redraw_requested = true; + + let window_id = super::make_wid(&surface); + let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64))); + + // Create a handle that performs all the requests on underlying sctk a window. + let window_handle = WindowHandle::new(window, size.clone(), window_requests.clone()); + + let mut winit_state = event_loop_window_target.state.borrow_mut(); + + winit_state.window_map.insert(window_id, window_handle); + + winit_state + .window_updates + .insert(window_id, WindowUpdate::new()); + + let windowing_features = event_loop_window_target.windowing_features; + + // Send all updates to the server. + let wayland_source = &event_loop_window_target.wayland_source; + let event_loop_handle = &event_loop_window_target.event_loop_handle; + + // To make our window usable for drawing right away we must `ack` a `configure` + // from the server, the acking part here is done by SCTK window frame, so we just + // need to sync with server so it'll be done automatically for us. + event_loop_handle.with_source(&wayland_source, |event_queue| { + let event_queue = event_queue.queue(); + let _ = event_queue.sync_roundtrip(&mut *winit_state, |_, _, _| unreachable!()); + }); + + // We all praise GNOME for these 3 lines of pure magic. If we don't do that, + // GNOME will shrink our window a bit for the size of the decorations. I guess it + // happens because we haven't committed them with buffers to the server. + let window_handle = winit_state.window_map.get_mut(&window_id).unwrap(); + window_handle.window.refresh(); + + let output_manager_handle = event_loop_window_target.output_manager.handle(); + + let window = Self { + window_id, + surface, + display: event_loop_window_target.display.clone(), + output_manager_handle, + size, + window_requests, + event_loop_awakener: event_loop_window_target.event_loop_awakener.clone(), + fullscreen, + windowing_features, + }; + + Ok(window) + } +} + +impl Window { + #[inline] + pub fn id(&self) -> WindowId { + self.window_id + } + + #[inline] + pub fn set_title(&self, title: &str) { + let title_request = WindowRequest::Title(title.to_owned()); + self.window_requests.lock().unwrap().push(title_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_visible(&self, _visible: bool) { + // Not possible on Wayland. + } + + #[inline] + pub fn outer_position(&self) -> Result, NotSupportedError> { + Err(NotSupportedError::new()) + } + + #[inline] + pub fn inner_position(&self) -> Result, NotSupportedError> { + Err(NotSupportedError::new()) + } + + #[inline] + pub fn set_outer_position(&self, _: Position) { + // Not possible on Wayland. + } + + pub fn inner_size(&self) -> PhysicalSize { + self.size + .lock() + .unwrap() + .to_physical(self.scale_factor() as f64) + } + + #[inline] + pub fn request_redraw(&self) { + let redraw_request = WindowRequest::Redraw; + self.window_requests.lock().unwrap().push(redraw_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn outer_size(&self) -> PhysicalSize { + self.size + .lock() + .unwrap() + .to_physical(self.scale_factor() as f64) + } + + #[inline] + pub fn set_inner_size(&self, size: Size) { + let scale_factor = self.scale_factor() as f64; + + let size = size.to_logical::(scale_factor); + *self.size.lock().unwrap() = size; + + let frame_size_request = WindowRequest::FrameSize(size); + self.window_requests + .lock() + .unwrap() + .push(frame_size_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_min_inner_size(&self, dimensions: Option) { + let scale_factor = self.scale_factor() as f64; + let size = dimensions.map(|size| size.to_logical::(scale_factor)); + + let min_size_request = WindowRequest::MinSize(size); + self.window_requests.lock().unwrap().push(min_size_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_max_inner_size(&self, dimensions: Option) { + let scale_factor = self.scale_factor() as f64; + let size = dimensions.map(|size| size.to_logical::(scale_factor)); + + let max_size_request = WindowRequest::MaxSize(size); + self.window_requests.lock().unwrap().push(max_size_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_resizable(&self, resizable: bool) { + let resizeable_request = WindowRequest::Resizeable(resizable); + self.window_requests + .lock() + .unwrap() + .push(resizeable_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn scale_factor(&self) -> u32 { + // The scale factor from `get_surface_scale_factor` is always greater than zero, so + // u32 conversion is safe. + sctk::get_surface_scale_factor(&self.surface) as u32 + } + + #[inline] + pub fn set_decorations(&self, decorate: bool) { + let decorate_request = WindowRequest::Decorate(decorate); + self.window_requests.lock().unwrap().push(decorate_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_minimized(&self, minimized: bool) { + // You can't unminimize the window on Wayland. + if !minimized { + return; + } + + let minimize_request = WindowRequest::Minimize; + self.window_requests.lock().unwrap().push(minimize_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_maximized(&self, maximized: bool) { + let maximize_request = WindowRequest::Maximize(maximized); + self.window_requests.lock().unwrap().push(maximize_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn fullscreen(&self) -> Option { + if self.fullscreen.load(Ordering::Relaxed) { + let current_monitor = self.current_monitor().map(|monitor| RootMonitorHandle { + inner: PlatformMonitorHandle::Wayland(monitor), + }); + + Some(Fullscreen::Borderless(current_monitor)) + } else { + None + } + } + + #[inline] + pub fn set_fullscreen(&self, fullscreen: Option) { + let fullscreen_request = match fullscreen { + Some(Fullscreen::Exclusive(_)) => { + warn!("`Fullscreen::Exclusive` is ignored on Wayland"); + return; + } + Some(Fullscreen::Borderless(monitor)) => { + let monitor = + monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor { + PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), + #[cfg(feature = "x11")] + PlatformMonitorHandle::X(_) => None, + }); + + WindowRequest::Fullscreen(monitor) + } + None => WindowRequest::UnsetFullscreen, + }; + + self.window_requests + .lock() + .unwrap() + .push(fullscreen_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_theme(&self, theme: T) { + // First buttons is minimize, then maximize, and then close. + let buttons: Vec<(ButtonColorSpec, ButtonColorSpec)> = + [Button::Minimize, Button::Maximize, Button::Close] + .iter() + .map(|button| { + let button = *button; + let idle_active_bg = theme + .button_color(button, ButtonState::Idle, false, true) + .into(); + let idle_inactive_bg = theme + .button_color(button, ButtonState::Idle, false, false) + .into(); + let idle_active_icon = theme + .button_color(button, ButtonState::Idle, true, true) + .into(); + let idle_inactive_icon = theme + .button_color(button, ButtonState::Idle, true, false) + .into(); + let idle_bg = ColorSpec { + active: idle_active_bg, + inactive: idle_inactive_bg, + }; + let idle_icon = ColorSpec { + active: idle_active_icon, + inactive: idle_inactive_icon, + }; + + let hovered_active_bg = theme + .button_color(button, ButtonState::Hovered, false, true) + .into(); + let hovered_inactive_bg = theme + .button_color(button, ButtonState::Hovered, false, false) + .into(); + let hovered_active_icon = theme + .button_color(button, ButtonState::Hovered, true, true) + .into(); + let hovered_inactive_icon = theme + .button_color(button, ButtonState::Hovered, true, false) + .into(); + let hovered_bg = ColorSpec { + active: hovered_active_bg, + inactive: hovered_inactive_bg, + }; + let hovered_icon = ColorSpec { + active: hovered_active_icon, + inactive: hovered_inactive_icon, + }; + + let disabled_active_bg = theme + .button_color(button, ButtonState::Disabled, false, true) + .into(); + let disabled_inactive_bg = theme + .button_color(button, ButtonState::Disabled, false, false) + .into(); + let disabled_active_icon = theme + .button_color(button, ButtonState::Disabled, true, true) + .into(); + let disabled_inactive_icon = theme + .button_color(button, ButtonState::Disabled, true, false) + .into(); + let disabled_bg = ColorSpec { + active: disabled_active_bg, + inactive: disabled_inactive_bg, + }; + let disabled_icon = ColorSpec { + active: disabled_active_icon, + inactive: disabled_inactive_icon, + }; + + let button_bg = ButtonColorSpec { + idle: idle_bg, + hovered: hovered_bg, + disabled: disabled_bg, + }; + let button_icon = ButtonColorSpec { + idle: idle_icon, + hovered: hovered_icon, + disabled: disabled_icon, + }; + + (button_icon, button_bg) + }) + .collect(); + + let minimize_button = Some(buttons[0]); + let maximize_button = Some(buttons[1]); + let close_button = Some(buttons[2]); + + // The first color is bar, then separator, and then text color. + let titlebar_colors: Vec = [Element::Bar, Element::Separator, Element::Text] + .iter() + .map(|element| { + let element = *element; + let active = theme.element_color(element, true).into(); + let inactive = theme.element_color(element, false).into(); + + ColorSpec { active, inactive } + }) + .collect(); + + let primary_color = titlebar_colors[0]; + let secondary_color = titlebar_colors[1]; + let title_color = titlebar_colors[2]; + + let title_font = theme.font(); + + let concept_config = ConceptConfig { + primary_color, + secondary_color, + title_color, + title_font, + minimize_button, + maximize_button, + close_button, + }; + + let theme_request = WindowRequest::Theme(concept_config); + self.window_requests.lock().unwrap().push(theme_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_cursor_icon(&self, cursor: CursorIcon) { + let cursor_icon_request = WindowRequest::NewCursorIcon(cursor); + self.window_requests + .lock() + .unwrap() + .push(cursor_icon_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_cursor_visible(&self, visible: bool) { + let cursor_visible_request = WindowRequest::ShowCursor(visible); + self.window_requests + .lock() + .unwrap() + .push(cursor_visible_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + if !self.windowing_features.cursor_grab() { + return Err(ExternalError::NotSupported(NotSupportedError::new())); + } + + let cursor_grab_request = WindowRequest::GrabCursor(grab); + self.window_requests + .lock() + .unwrap() + .push(cursor_grab_request); + self.event_loop_awakener.ping(); + + Ok(()) + } + + #[inline] + pub fn set_cursor_position(&self, _: Position) -> Result<(), ExternalError> { + // XXX This is possible if the locked pointer is being used. We don't have any + // API for that right now, but it could be added in + // https://github.com/rust-windowing/winit/issues/1677. + // + // This function is essential for the locked pointer API. + // + // See pointer-constraints-unstable-v1.xml. + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + #[inline] + pub fn set_ime_position(&self, position: Position) { + let scale_factor = self.scale_factor() as f64; + let position = position.to_logical(scale_factor); + let ime_position_request = WindowRequest::IMEPosition(position); + self.window_requests + .lock() + .unwrap() + .push(ime_position_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn display(&self) -> &Display { + &self.display + } + + #[inline] + pub fn surface(&self) -> &WlSurface { + &self.surface + } + + #[inline] + pub fn current_monitor(&self) -> Option { + let output = sctk::get_surface_outputs(&self.surface).last()?.clone(); + Some(MonitorHandle::new(output)) + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + self.output_manager_handle.available_outputs() + } + + #[inline] + pub fn primary_monitor(&self) -> Option { + None + } + + #[inline] + pub fn raw_window_handle(&self) -> WaylandHandle { + let display = self.display.get_display_ptr() as *mut _; + let surface = self.surface.as_ref().c_ptr() as *mut _; + + WaylandHandle { + display, + surface, + ..WaylandHandle::empty() + } + } +} + +impl From for ARGBColor { + fn from(color: LocalARGBColor) -> Self { + let a = color.a; + let r = color.r; + let g = color.g; + let b = color.b; + Self { a, r, g, b } + } +} + +impl Drop for Window { + fn drop(&mut self) { + let close_request = WindowRequest::Close; + self.window_requests.lock().unwrap().push(close_request); + self.event_loop_awakener.ping(); + } +} diff --git a/src/platform_impl/linux/wayland/window/shim.rs b/src/platform_impl/linux/wayland/window/shim.rs new file mode 100644 index 00000000..59cf921a --- /dev/null +++ b/src/platform_impl/linux/wayland/window/shim.rs @@ -0,0 +1,388 @@ +use std::cell::Cell; +use std::sync::{Arc, Mutex}; + +use sctk::reexports::client::protocol::wl_output::WlOutput; + +use sctk::window::{ConceptConfig, ConceptFrame, Decorations, Window}; + +use crate::dpi::{LogicalPosition, LogicalSize}; + +use crate::event::WindowEvent; +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::seat::pointer::WinitPointer; +use crate::platform_impl::wayland::seat::text_input::TextInputHandler; +use crate::platform_impl::wayland::WindowId; +use crate::window::CursorIcon; + +/// A request to SCTK window from Winit window. +#[derive(Debug, Clone)] +pub enum WindowRequest { + /// Set fullscreen. + /// + /// Passing `None` will set it on the current monitor. + Fullscreen(Option), + + /// Unset fullscreen. + UnsetFullscreen, + + /// Show cursor for the certain window or not. + ShowCursor(bool), + + /// Change the cursor icon. + NewCursorIcon(CursorIcon), + + /// Grab cursor. + GrabCursor(bool), + + /// Maximize the window. + Maximize(bool), + + /// Minimize the window. + Minimize, + + /// Request decorations change. + Decorate(bool), + + /// Make the window resizeable. + Resizeable(bool), + + /// Set the title for window. + Title(String), + + /// Min size. + MinSize(Option>), + + /// Max size. + MaxSize(Option>), + + /// New frame size. + FrameSize(LogicalSize), + + /// Set IME window position. + IMEPosition(LogicalPosition), + + /// Redraw was requested. + Redraw, + + /// A new theme for a concept frame was requested. + Theme(ConceptConfig), + + /// Window should be closed. + Close, +} + +/// Pending update to a window from SCTK window. +#[derive(Debug, Clone, Copy)] +pub struct WindowUpdate { + /// New window size. + pub size: Option>, + + /// New scale factor. + pub scale_factor: Option, + + /// Whether `redraw` was requested. + pub redraw_requested: bool, + + /// Wether the frame should be refreshed. + pub refresh_frame: bool, + + /// Close the window. + pub close_window: bool, +} + +impl WindowUpdate { + pub fn new() -> Self { + Self { + size: None, + scale_factor: None, + redraw_requested: false, + refresh_frame: false, + close_window: false, + } + } + + pub fn take(&mut self) -> Self { + let size = self.size.take(); + let scale_factor = self.scale_factor.take(); + + let redraw_requested = self.redraw_requested; + self.redraw_requested = false; + + let refresh_frame = self.refresh_frame; + self.refresh_frame = false; + + let close_window = self.close_window; + self.close_window = false; + + Self { + size, + scale_factor, + redraw_requested, + refresh_frame, + close_window, + } + } +} + +/// A handle to perform operations on SCTK window +/// and react to events. +pub struct WindowHandle { + /// An actual window. + pub window: Window, + + /// The current size of the window. + pub size: Arc>>, + + /// A pending requests to SCTK window. + pub pending_window_requests: Arc>>, + + /// Current cursor icon. + pub cursor_icon: Cell, + + /// Visible cursor or not. + cursor_visible: Cell, + + /// Cursor confined to the surface. + confined: Cell, + + /// Pointers over the current surface. + pointers: Vec, + + /// Text inputs on the current surface. + text_inputs: Vec, +} + +impl WindowHandle { + pub fn new( + window: Window, + size: Arc>>, + pending_window_requests: Arc>>, + ) -> Self { + Self { + window, + size, + pending_window_requests, + cursor_icon: Cell::new(CursorIcon::Default), + confined: Cell::new(false), + cursor_visible: Cell::new(true), + pointers: Vec::new(), + text_inputs: Vec::new(), + } + } + + pub fn set_cursor_grab(&self, grab: bool) { + // The new requested state matches the current confine status, return. + if self.confined.get() == grab { + return; + } + + self.confined.replace(grab); + + for pointer in self.pointers.iter() { + if self.confined.get() { + let surface = self.window.surface(); + pointer.confine(&surface); + } else { + pointer.unconfine(); + } + } + } + + /// Pointer appeared over the window. + pub fn pointer_entered(&mut self, pointer: WinitPointer) { + let position = self.pointers.iter().position(|p| *p == pointer); + + if position.is_none() { + if self.confined.get() { + let surface = self.window.surface(); + pointer.confine(&surface); + } + self.pointers.push(pointer); + } + + // Apply the current cursor style. + self.set_cursor_visible(self.cursor_visible.get()); + } + + /// Pointer left the window. + pub fn pointer_left(&mut self, pointer: WinitPointer) { + let position = self.pointers.iter().position(|p| *p == pointer); + + if let Some(position) = position { + let pointer = self.pointers.remove(position); + + // Drop the confined pointer. + if self.confined.get() { + pointer.unconfine(); + } + } + } + + pub fn text_input_entered(&mut self, text_input: TextInputHandler) { + if self + .text_inputs + .iter() + .find(|t| *t == &text_input) + .is_none() + { + self.text_inputs.push(text_input); + } + } + + pub fn text_input_left(&mut self, text_input: TextInputHandler) { + if let Some(position) = self.text_inputs.iter().position(|t| *t == text_input) { + self.text_inputs.remove(position); + } + } + + pub fn set_ime_position(&self, position: LogicalPosition) { + // XXX This won't fly unless user will have a way to request IME window per seat, since + // the ime windows will be overlapping, but winit doesn't expose API to specify for + // which seat we're setting IME position. + let (x, y) = (position.x as i32, position.y as i32); + for text_input in self.text_inputs.iter() { + text_input.set_ime_position(x, y); + } + } + + pub fn set_cursor_visible(&self, visible: bool) { + self.cursor_visible.replace(visible); + let cursor_icon = match visible { + true => Some(self.cursor_icon.get()), + false => None, + }; + + for pointer in self.pointers.iter() { + pointer.set_cursor(cursor_icon) + } + } + + pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) { + self.cursor_icon.replace(cursor_icon); + + if !self.cursor_visible.get() { + return; + } + + for pointer in self.pointers.iter() { + pointer.set_cursor(Some(cursor_icon)); + } + } +} + +#[inline] +pub fn handle_window_requests(winit_state: &mut WinitState) { + let window_map = &mut winit_state.window_map; + let window_updates = &mut winit_state.window_updates; + let mut windows_to_close: Vec = Vec::new(); + + // Process the rest of the events. + for (window_id, window_handle) in window_map.iter_mut() { + let mut requests = window_handle.pending_window_requests.lock().unwrap(); + for request in requests.drain(..) { + match request { + WindowRequest::Fullscreen(fullscreen) => { + window_handle.window.set_fullscreen(fullscreen.as_ref()); + } + WindowRequest::UnsetFullscreen => { + window_handle.window.unset_fullscreen(); + } + WindowRequest::ShowCursor(show_cursor) => { + window_handle.set_cursor_visible(show_cursor); + } + WindowRequest::NewCursorIcon(cursor_icon) => { + window_handle.set_cursor_icon(cursor_icon); + } + WindowRequest::IMEPosition(position) => { + window_handle.set_ime_position(position); + } + WindowRequest::GrabCursor(grab) => { + window_handle.set_cursor_grab(grab); + } + WindowRequest::Maximize(maximize) => { + if maximize { + window_handle.window.set_maximized(); + } else { + window_handle.window.unset_maximized(); + } + } + WindowRequest::Minimize => { + window_handle.window.set_minimized(); + } + WindowRequest::Decorate(decorate) => { + let decorations = match decorate { + true => Decorations::FollowServer, + false => Decorations::None, + }; + + window_handle.window.set_decorate(decorations); + + // We should refresh the frame to apply decorations change. + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.refresh_frame = true; + } + WindowRequest::Resizeable(resizeable) => { + window_handle.window.set_resizable(resizeable); + + // We should refresh the frame to update button state. + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.refresh_frame = true; + } + WindowRequest::Title(title) => { + window_handle.window.set_title(title); + + // We should refresh the frame to draw new title. + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.refresh_frame = true; + } + WindowRequest::MinSize(size) => { + let size = size.map(|size| (size.width, size.height)); + window_handle.window.set_min_size(size); + + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.redraw_requested = true; + } + WindowRequest::MaxSize(size) => { + let size = size.map(|size| (size.width, size.height)); + window_handle.window.set_max_size(size); + + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.redraw_requested = true; + } + WindowRequest::FrameSize(size) => { + // Set new size. + window_handle.window.resize(size.width, size.height); + + // We should refresh the frame after resize. + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.refresh_frame = true; + } + WindowRequest::Redraw => { + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.redraw_requested = true; + } + WindowRequest::Theme(concept_config) => { + window_handle.window.set_frame_config(concept_config); + + // We should refresh the frame to apply new theme. + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.refresh_frame = true; + } + WindowRequest::Close => { + // The window was requested to be closed. + windows_to_close.push(*window_id); + + // Send event that the window was destroyed. + let event_sink = &mut winit_state.event_sink; + event_sink.push_window_event(WindowEvent::Destroyed, *window_id); + } + }; + } + } + + // Close the windows. + for window in windows_to_close { + let _ = window_map.remove(&window); + let _ = window_updates.remove(&window); + } +} diff --git a/src/window.rs b/src/window.rs index d6cd8e6c..ed8e7af3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -614,7 +614,7 @@ impl Window { /// /// The dock and the menu bar are always disabled in fullscreen mode. /// - **iOS:** Can only be called on the main thread. - /// - **Wayland:** Does not support exclusive fullscreen mode. + /// - **Wayland:** Does not support exclusive fullscreen mode and will no-op a request. /// - **Windows:** Screen saver is disabled in fullscreen mode. /// - **Android:** Unsupported. #[inline] @@ -677,7 +677,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland / Windows:** Unsupported. + /// - **iOS / Android / Web / Windows:** Unsupported. #[inline] pub fn set_ime_position>(&self, position: P) { self.window.set_ime_position(position.into()) @@ -708,9 +708,12 @@ impl Window { /// Grabs the cursor, preventing it from leaving the window. /// + /// There's no guarantee that the cursor will be hidden. You should + /// hide it by yourself if you want so. + /// /// ## Platform-specific /// - /// - **macOS / Wayland:** This locks the cursor in a fixed location, which looks visually awkward. + /// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward. /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {