From 867136bb46fb98ad0af165a4261eaa3fb573c097 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 4 Feb 2023 15:39:49 +0300 Subject: [PATCH] Update wayland-rs to 0.30.0 This update rewrites the winit's Wayland backend using new wayland-rs 0.30 API. This fixes long standing issue with the forward compatibility of the wayland backend, meaning that future updates to the wayland protocol won't break rust code anymore. like it was before when adding new shm/enum variants into the protocol. Fixes #2560. Fixes #2128. --- Cargo.toml | 22 +- build.rs | 74 +- src/platform/wayland.rs | 8 +- src/platform_impl/linux/wayland/env.rs | 183 ----- .../linux/wayland/event_loop/mod.rs | 427 ++++------ .../linux/wayland/event_loop/state.rs | 31 - src/platform_impl/linux/wayland/mod.rs | 11 +- src/platform_impl/linux/wayland/output.rs | 206 ++--- src/platform_impl/linux/wayland/protocols.rs | 13 - .../linux/wayland/seat/keyboard/handlers.rs | 165 ---- .../linux/wayland/seat/keyboard/keymap.rs | 4 +- .../linux/wayland/seat/keyboard/mod.rs | 254 ++++-- src/platform_impl/linux/wayland/seat/mod.rs | 385 +++++---- .../linux/wayland/seat/pointer/data.rs | 82 -- .../linux/wayland/seat/pointer/handlers.rs | 327 -------- .../linux/wayland/seat/pointer/mod.rs | 596 +++++++------- .../wayland/seat/pointer/relative_pointer.rs | 80 ++ .../linux/wayland/seat/text_input/handlers.rs | 122 --- .../linux/wayland/seat/text_input/mod.rs | 248 ++++-- .../linux/wayland/seat/touch/handlers.rs | 144 ---- .../linux/wayland/seat/touch/mod.rs | 225 +++-- src/platform_impl/linux/wayland/state.rs | 392 +++++++++ src/platform_impl/linux/wayland/window/mod.rs | 777 ++++++++---------- .../linux/wayland/window/shim.rs | 666 --------------- .../linux/wayland/window/state.rs | 469 +++++++++++ .../linux/wayland/xdg_activation.rs | 103 +++ 26 files changed, 2767 insertions(+), 3247 deletions(-) delete mode 100644 src/platform_impl/linux/wayland/env.rs delete mode 100644 src/platform_impl/linux/wayland/event_loop/state.rs delete mode 100644 src/platform_impl/linux/wayland/protocols.rs delete mode 100644 src/platform_impl/linux/wayland/seat/keyboard/handlers.rs delete mode 100644 src/platform_impl/linux/wayland/seat/pointer/data.rs delete mode 100644 src/platform_impl/linux/wayland/seat/pointer/handlers.rs create mode 100644 src/platform_impl/linux/wayland/seat/pointer/relative_pointer.rs delete mode 100644 src/platform_impl/linux/wayland/seat/text_input/handlers.rs delete mode 100644 src/platform_impl/linux/wayland/seat/touch/handlers.rs create mode 100644 src/platform_impl/linux/wayland/state.rs delete mode 100644 src/platform_impl/linux/wayland/window/shim.rs create mode 100644 src/platform_impl/linux/wayland/window/state.rs create mode 100644 src/platform_impl/linux/wayland/xdg_activation.rs diff --git a/Cargo.toml b/Cargo.toml index 71c34d7445d..a2cb6f25b8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,13 +35,13 @@ targets = [ rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] +default = ["x11", "wayland", "wayland-dlopen"] x11 = ["x11-dl", "mio", "percent-encoding"] -wayland = ["wayland-client", "wayland-protocols", "sctk", "wayland-commons"] -wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"] -wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] -wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"] -wayland-csd-adwaita-notitle = ["sctk-adwaita"] +wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk"] +wayland-dlopen = ["wayland-backend/dlopen"] +# wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] +# wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"] +# wayland-csd-adwaita-notitle = ["sctk-adwaita"] android-native-activity = [ "android-activity/native-activity" ] android-game-activity = [ "android-activity/game-activity" ] @@ -107,11 +107,11 @@ features = [ libc = "0.2.64" mio = { version = "0.8", features = ["os-ext"], optional = true } percent-encoding = { version = "2.0", optional = true } -sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true } -sctk-adwaita = { version = "0.5.1", default_features = false, optional = true } -wayland-client = { version = "0.29.5", default_features = false, features = ["use_system_lib"], optional = true } -wayland-protocols = { version = "0.29.5", features = [ "staging_protocols"], optional = true } -wayland-commons = { version = "0.29.5", optional = true } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/kchibisov/client-toolkit", branch = "winit-update", optional = true } +wayland-client = { version = "0.30.0", optional = true } +wayland-backend = { version = "0.1.0", default_features = false, features = ["client_system"], optional = true } +wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true } +calloop = "0.10.5" x11-dl = { version = "2.18.5", optional = true } [target.'cfg(target_os = "redox")'.dependencies] diff --git a/build.rs b/build.rs index 98e9229c953..9c172daef7e 100644 --- a/build.rs +++ b/build.rs @@ -1,35 +1,37 @@ use cfg_aliases::cfg_aliases; -#[cfg(all( - any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - ), - feature = "wayland", -))] -mod wayland { - use std::env; - use std::path::PathBuf; - use wayland_scanner::Side; +// #[cfg(all( +// any( +// target_os = "linux", +// target_os = "dragonfly", +// target_os = "freebsd", +// target_os = "openbsd", +// target_os = "netbsd", +// ), +// feature = "wayland", +// ))] +// mod wayland { +// use std::env; +// use std::path::PathBuf; +// use wayland_scanner::Side; - pub fn main() { - let mut path = PathBuf::from(env::var("OUT_DIR").unwrap()); - path.push("fractional_scale_v1.rs"); - wayland_scanner::generate_code( - "wayland_protocols/fractional-scale-v1.xml", - &path, - Side::Client, - ); - } -} +// pub fn main() { +// let mut path = PathBuf::from(env::var("OUT_DIR").unwrap()); +// path.push("fractional_scale_v1.rs"); +// wayland_scanner::generate_code( +// "wayland_protocols/fractional-scale-v1.xml", +// &path, +// Side::Client, +// ); +// } +// } fn main() { // The script doesn't depend on our code println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=wayland_protocols"); + // println!("cargo:rerun-if-changed=wayland_protocols"); + + // TODO // Setup cfg aliases cfg_aliases! { @@ -50,15 +52,15 @@ fn main() { } // XXX aliases are not available for the build script itself. - #[cfg(all( - any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - ), - feature = "wayland", - ))] - wayland::main(); + // #[cfg(all( + // any( + // target_os = "linux", + // target_os = "dragonfly", + // target_os = "freebsd", + // target_os = "openbsd", + // target_os = "netbsd", + // ), + // feature = "wayland", + // ))] + // wayland::main(); } diff --git a/src/platform/wayland.rs b/src/platform/wayland.rs index 7bbbd991621..3e59184d702 100644 --- a/src/platform/wayland.rs +++ b/src/platform/wayland.rs @@ -1,5 +1,7 @@ use std::os::raw; +use sctk::reexports::client::Proxy; + use crate::{ event_loop::{EventLoopBuilder, EventLoopWindowTarget}, monitor::MonitorHandle, @@ -39,7 +41,7 @@ impl EventLoopWindowTargetExtWayland for EventLoopWindowTarget { fn wayland_display(&self) -> Option<*mut raw::c_void> { match self.p { LinuxEventLoopWindowTarget::Wayland(ref p) => { - Some(p.display().get_display_ptr() as *mut _) + Some(p.connection.display().id().as_ptr() as *mut _) } #[cfg(x11_platform)] _ => None, @@ -94,7 +96,7 @@ impl WindowExtWayland for Window { #[inline] fn wayland_surface(&self) -> Option<*mut raw::c_void> { match self.window { - LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _), + LinuxWindow::Wayland(ref w) => Some(w.surface().id().as_ptr() as *mut _), #[cfg(x11_platform)] _ => None, } @@ -103,7 +105,7 @@ impl WindowExtWayland for Window { #[inline] fn wayland_display(&self) -> Option<*mut raw::c_void> { match self.window { - LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _), + LinuxWindow::Wayland(ref w) => Some(w.display().id().as_ptr() as *mut _), #[cfg(x11_platform)] _ => None, } diff --git a/src/platform_impl/linux/wayland/env.rs b/src/platform_impl/linux/wayland/env.rs deleted file mode 100644 index e88b1bad082..00000000000 --- a/src/platform_impl/linux/wayland/env.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! 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::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1; -use sctk::reexports::protocols::viewporter::client::wp_viewporter::WpViewporter; - -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; - -use crate::platform_impl::wayland::protocols::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; - -/// Set of extra features that are supported by the compositor. -#[derive(Debug, Clone, Copy)] -pub struct WindowingFeatures { - pointer_constraints: bool, - xdg_activation: bool, -} - -impl WindowingFeatures { - /// Create `WindowingFeatures` based on the presented interfaces. - pub fn new(env: &Environment) -> Self { - let pointer_constraints = env.get_global::().is_some(); - let xdg_activation = env.get_global::().is_some(); - Self { - pointer_constraints, - xdg_activation, - } - } - - pub fn pointer_constraints(&self) -> bool { - self.pointer_constraints - } - - pub fn xdg_activation(&self) -> bool { - self.xdg_activation - } -} - -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, - XdgActivationV1 => xdg_activation, - WpFractionalScaleManagerV1 => fractional_scale_manager, - WpViewporter => viewporter, - ], - 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, - - xdg_activation: SimpleGlobal, - - fractional_scale_manager: SimpleGlobal, - - viewporter: 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(); - - // Surface activation. - let xdg_activation = SimpleGlobal::new(); - - // Fractional surface scaling. - let fractional_scale_manager = SimpleGlobal::new(); - - // Surface resizing (used for fractional scaling). - let viewporter = SimpleGlobal::new(); - - Self { - seats, - outputs, - shm, - compositor, - subcompositor, - shell, - decoration_manager, - relative_pointer_manager, - pointer_constraints, - text_input_manager, - xdg_activation, - fractional_scale_manager, - viewporter, - } - } -} - -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/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 5a4c7e2cdc7..042f871b90b 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -1,158 +1,84 @@ +//! The event-loop routines. + use std::cell::RefCell; -use std::collections::HashMap; use std::error::Error; use std::io::Result as IOResult; -use std::mem; +use std::marker::PhantomData; use std::process; use std::rc::Rc; use std::time::{Duration, Instant}; use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle}; -use sctk::reexports::client::protocol::wl_compositor::WlCompositor; -use sctk::reexports::client::protocol::wl_shm::WlShm; -use sctk::reexports::client::Display; - +use sctk::compositor::SurfaceData; use sctk::reexports::calloop; - -use sctk::environment::Environment; -use sctk::seat::pointer::{ThemeManager, ThemeSpec}; -use sctk::WaylandSource; +use sctk::reexports::client::globals; +use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource}; use crate::event::{Event, StartCause, WindowEvent}; use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget}; use crate::platform_impl::platform::sticky_exit_callback; use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget; -use super::env::{WindowingFeatures, WinitEnv}; -use super::output::OutputManager; -use super::seat::SeatManager; -use super::window::shim::{self, WindowCompositorUpdate, WindowUserRequest}; -use super::{DeviceId, WindowId}; - mod proxy; -mod sink; -mod state; +pub mod sink; pub use proxy::EventLoopProxy; -pub use sink::EventSink; -pub use state::WinitState; - -type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>; - -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<'static, WinitState>, - - /// Output manager. - pub output_manager: OutputManager, - - /// State that we share across callbacks. - pub state: RefCell, - - /// Dispatcher of Wayland events. - pub wayland_dispatcher: WinitDispatcher, - - /// 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 between all windows to avoid loading - /// multiple similar themes. - pub theme_manager: ThemeManager, - - _marker: std::marker::PhantomData, -} +use super::state::WinitState; +use super::{DeviceId, WindowId}; -impl EventLoopWindowTarget { - pub fn raw_display_handle(&self) -> RawDisplayHandle { - let mut display_handle = WaylandDisplayHandle::empty(); - display_handle.display = self.display.get_display_ptr() as *mut _; - RawDisplayHandle::Wayland(display_handle) - } -} +type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>; +/// The Wayland event loop. pub struct EventLoop { - /// Dispatcher of Wayland events. - pub wayland_dispatcher: WinitDispatcher, - - /// Event loop. - event_loop: calloop::EventLoop<'static, WinitState>, - - /// Wayland display. - display: Display, + /// Sender of user events. + user_events_sender: calloop::channel::Sender, - /// Pending user events. + // Pending events from the user. pending_user_events: Rc>>, - /// Sender of user events. - user_events_sender: calloop::channel::Sender, + /// The Wayland dispatcher to has raw access to the queue when needed, such as + /// when creating a new window. + wayland_dispatcher: WaylandDispatcher, - /// Window target. + /// Connection to the wayland server. + connection: Connection, + + /// Event loop window target. window_target: RootEventLoopWindowTarget, - /// Output manager. - _seat_manager: SeatManager, + // XXX drop after everything else, just to be safe. + /// Calloop's event loop. + event_loop: calloop::EventLoop<'static, WinitState>, } 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::new(&display_proxy, &mut event_queue, WinitEnv::new())?; - - // Create event loop. - let event_loop = calloop::EventLoop::<'static, WinitState>::try_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); + let connection = Connection::connect_to_env()?; + + let (globals, event_queue) = globals::registry_queue_init(&connection)?; + let queue_handle = event_queue.handle(); + + let event_loop = calloop::EventLoop::::try_new()?; + + let winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?; + + // Register Wayland source. + let wayland_source = WaylandSource::new(event_queue)?; let wayland_dispatcher = calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| { - queue.dispatch_pending(winit_state, |event, object, _| { - panic!( - "[calloop] Encountered an orphan event: {}@{} : {}", - event.interface, - object.as_ref().id(), - event.name - ); - }) + queue.dispatch_pending(winit_state) }); - let _wayland_source_dispatcher = event_loop + event_loop .handle() .register_dispatcher(wayland_dispatcher.clone())?; - // A source of user events. + // Setup the user proxy. 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, _, _| { @@ -163,52 +89,30 @@ impl EventLoop { // 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 |_, _, state| { - // Drain events here as well to account for application doing batch event processing - // on RedrawEventsCleared. - shim::handle_window_requests(state); + .insert_source(event_loop_awakener_source, move |_, _, _| { + // No extra handling is required, we just need to wake-up. })?; - let event_loop_handle = event_loop.handle(); - let window_map = HashMap::new(); - let event_sink = EventSink::new(); - let window_user_requests = HashMap::new(); - let window_compositor_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_user_requests, - window_compositor_updates, - }), - event_loop_handle, - output_manager, - event_loop_awakener, + let window_target = EventLoopWindowTarget { + connection: connection.clone(), wayland_dispatcher: wayland_dispatcher.clone(), - windowing_features, - theme_manager, - _marker: std::marker::PhantomData, + event_loop_awakener, + queue_handle, + state: RefCell::new(winit_state), + _marker: PhantomData, }; - // Create event loop itself. let event_loop = Self { - event_loop, - display, - pending_user_events, + connection, wayland_dispatcher, - _seat_manager: seat_manager, user_events_sender, + pending_user_events, + event_loop, window_target: RootEventLoopWindowTarget { - p: PlatformEventLoopWindowTarget::Wayland(event_loop_window_target), - _marker: std::marker::PhantomData, + p: PlatformEventLoopWindowTarget::Wayland(window_target), + _marker: PhantomData, }, }; @@ -236,14 +140,10 @@ impl EventLoop { &mut control_flow, ); - // NB: For consistency all platforms must emit a 'resumed' event even though Wayland + // NB: For consistency all platforms must emit a 'Resumed' event even though Wayland // applications don't themselves have a formal suspend/resume lifecycle. callback(Event::Resumed, &self.window_target, &mut control_flow); - let mut window_compositor_updates: Vec<(WindowId, WindowCompositorUpdate)> = Vec::new(); - let mut window_user_requests: Vec<(WindowId, WindowUserRequest)> = 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 @@ -251,8 +151,8 @@ impl EventLoop { // with an API to do that via some event. // Still, we set the exit code to the error's OS error code, or to 1 if not possible. let exit_code = loop { - // Send pending events to the server. - let _ = self.display.flush(); + // Flush the connection. + let _ = self.connection.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 @@ -272,9 +172,10 @@ impl EventLoop { _ => unreachable!(), }; - match queue.dispatch_pending(state, |_, _, _| unimplemented!()) { + match queue.dispatch_pending(state) { Ok(dispatched) => dispatched > 0, - Err(error) => break error.raw_os_error().unwrap_or(1), + Err(_) => todo!(), + // Err(error) =>c break error.raw_os_error().unwrap_or(1), } }; @@ -282,7 +183,7 @@ impl EventLoop { ControlFlow::ExitWithCode(code) => break code, ControlFlow::Poll => { // Non-blocking dispatch. - let timeout = Duration::from_millis(0); + let timeout = Duration::ZERO; if let Err(error) = self.loop_dispatch(Some(timeout)) { break error.raw_os_error().unwrap_or(1); } @@ -295,7 +196,7 @@ impl EventLoop { } ControlFlow::Wait => { let timeout = if instant_wakeup { - Some(Duration::from_millis(0)) + Some(Duration::ZERO) } else { None }; @@ -320,7 +221,7 @@ impl EventLoop { let duration = if deadline > start && !instant_wakeup { deadline - start } else { - Duration::from_millis(0) + Duration::ZERO }; if let Err(error) = self.loop_dispatch(Some(duration)) { @@ -362,35 +263,26 @@ impl EventLoop { ); } - // Process 'new' pending updates from compositor. - self.with_state(|state| { - window_compositor_updates.clear(); - window_compositor_updates.extend( - state - .window_compositor_updates - .iter_mut() - .map(|(wid, window_update)| (*wid, mem::take(window_update))), - ); - }); + for mut compositor_update in + self.with_state(|state| std::mem::take(&mut state.window_compositor_updates)) + { + let window_id = compositor_update.window_id; - for (window_id, window_compositor_update) in window_compositor_updates.iter_mut() { - if let Some(scale_factor) = window_compositor_update.scale_factor { + if let Some(scale_factor) = compositor_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(); - *window_handle.scale_factor.lock().unwrap() = scale_factor; - - let mut size = window_handle.size.lock().unwrap(); + let windows = state.windows.borrow_mut(); + let mut window = windows.get(&window_id).unwrap().lock().unwrap(); // Update the new logical size if it was changed. - let window_size = window_compositor_update.size.unwrap_or(*size); - *size = window_size; + let window_size = compositor_update.size.unwrap_or(window.size); + window.size = window_size; window_size.to_physical(scale_factor) }); sticky_exit_callback( Event::WindowEvent { - window_id: crate::window::WindowId(*window_id), + window_id: crate::window::WindowId(window_id), event: WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size: &mut physical_size, @@ -404,56 +296,45 @@ impl EventLoop { // 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_compositor_update.size = Some(new_logical_size); + compositor_update.size = Some(new_logical_size); } - if let Some(size) = window_compositor_update.size.take() { + if let Some(size) = compositor_update.size.take() { let physical_size = self.with_state(|state| { - let window_handle = state.window_map.get_mut(window_id).unwrap(); - - if let Some(fs_state) = window_handle.fractional_scaling_state.as_ref() { - // If we have a viewport then we support fractional scaling. As per the - // protocol, we have to set the viewport size of the size prior scaling. - fs_state - .viewport - .set_destination(size.width as _, size.height as _); - } - - let mut window_size = window_handle.size.lock().unwrap(); + let windows = state.windows.borrow_mut(); + let mut window = windows.get(&window_id).unwrap().lock().unwrap(); // Always issue resize event on scale factor change. - let physical_size = if window_compositor_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 = window_handle.scale_factor(); - let physical_size = size.to_physical(scale_factor); - 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(); - - // Update the opaque region. - window_handle.set_transparent(window_handle.transparent.get()); + let physical_size = + if compositor_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 = window + .window + .wl_surface() + .data::() + .expect("the surface user data can't be accessed.") + .scale_factor(); + + let physical_size = size.to_physical(scale_factor as f64); + Some(physical_size) + }; + + // TODO(kchibisov) CSD resize/refresh + + // Reapply the tranparency state. + window.set_transparent(window.transparent()); // Mark that refresh isn't required, since we've done it right now. - state - .window_user_requests - .get_mut(window_id) - .unwrap() - .refresh_frame = false; - // Queue redraw requested. state - .window_user_requests - .get_mut(window_id) + .window_requests + .borrow_mut() + .get_mut(&window_id) + .unwrap() + .lock() .unwrap() .redraw_requested = true; @@ -463,7 +344,7 @@ impl EventLoop { if let Some(physical_size) = physical_size { sticky_exit_callback( Event::WindowEvent { - window_id: crate::window::WindowId(*window_id), + window_id: crate::window::WindowId(window_id), event: WindowEvent::Resized(physical_size), }, &self.window_target, @@ -473,11 +354,10 @@ impl EventLoop { } } - // If the close is requested, send it here. - if window_compositor_update.close_window { + if compositor_update.close_window { sticky_exit_callback( Event::WindowEvent { - window_id: crate::window::WindowId(*window_id), + window_id: crate::window::WindowId(window_id), event: WindowEvent::CloseRequested, }, &self.window_target, @@ -487,18 +367,21 @@ impl EventLoop { } } - // 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, - ) - }); + // TODO clean-up this mess. + + // Push the events directly from the window. + for event in self.with_state(|state| { + let mut event_sink = state.window_events_sink.lock().unwrap(); + std::mem::take(&mut event_sink.window_events) + }) { + let event = event.map_nonuser_event().unwrap(); + sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + } - // Handle pending window events. - for event in event_sink_back_buffer.drain(..) { + // Handle non-synthetic events. + for event in + self.with_state(|state| std::mem::take(&mut state.events_sink.window_events)) + { let event = event.map_nonuser_event().unwrap(); sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); } @@ -511,42 +394,18 @@ impl EventLoop { &mut callback, ); - // Apply user requests, so every event required resize and latter surface commit will - // be applied right before drawing. This will also ensure that every `RedrawRequested` - // event will be delivered in time. - self.with_state(|state| { - shim::handle_window_requests(state); - }); - - // Process 'new' pending updates from compositor. - self.with_state(|state| { - window_user_requests.clear(); - window_user_requests.extend( - state - .window_user_requests - .iter_mut() - .map(|(wid, window_request)| (*wid, mem::take(window_request))), - ); - }); - - // Handle RedrawRequested events. - for (window_id, mut window_request) in window_user_requests.iter() { - // Handle refresh of the frame. - if window_request.refresh_frame { + let window_requests = self.with_state(|state| state.window_requests.borrow().clone()); + for (window_id, window_requests) in window_requests { + let window_requests = window_requests.lock().unwrap().take(); + if window_requests.closed { + // Drop the inner window structs. self.with_state(|state| { - let window_handle = state.window_map.get_mut(window_id).unwrap(); - window_handle.window.refresh(); + std::mem::drop(state.window_requests.borrow_mut().remove(&window_id)); + std::mem::drop(state.windows.borrow_mut().remove(&window_id)); }); - - // In general refreshing the frame requires surface commit, those force user - // to redraw. - window_request.redraw_requested = true; - } - - // Handle redraw request. - if window_request.redraw_requested { + } else if window_requests.redraw_requested { sticky_exit_callback( - Event::RedrawRequested(crate::window::WindowId(*window_id)), + Event::RedrawRequested(crate::window::WindowId(window_id)), &self.window_target, &mut control_flow, &mut callback, @@ -577,20 +436,20 @@ impl EventLoop { &self.window_target } - fn with_state U>(&mut self, f: F) -> U { + fn with_state U>(&mut self, callback: F) -> U { let state = match &mut self.window_target.p { PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(), #[cfg(x11_platform)] _ => unreachable!(), }; - f(state) + callback(state) } fn loop_dispatch>>(&mut self, timeout: D) -> IOResult<()> { let state = match &mut self.window_target.p { PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(), - #[cfg(x11_platform)] + #[cfg(feature = "x11")] _ => unreachable!(), }; @@ -599,3 +458,31 @@ impl EventLoop { .map_err(|error| error.into()) } } + +pub struct EventLoopWindowTarget { + /// The event loop wakeup source. + pub event_loop_awakener: calloop::ping::Ping, + + /// The main queue used by the event loop. + pub queue_handle: QueueHandle, + + // TODO remove that RefCell once we can pass `&mut` in `Window::new`. + /// Winit state. + pub state: RefCell, + + /// Dispatcher of Wayland events. + pub wayland_dispatcher: WaylandDispatcher, + + /// Connection to the wayland server. + pub connection: Connection, + + _marker: std::marker::PhantomData, +} + +impl EventLoopWindowTarget { + pub fn raw_display_handle(&self) -> RawDisplayHandle { + let mut display_handle = WaylandDisplayHandle::empty(); + display_handle.display = self.connection.display().id().as_ptr() as *mut _; + RawDisplayHandle::Wayland(display_handle) + } +} diff --git a/src/platform_impl/linux/wayland/event_loop/state.rs b/src/platform_impl/linux/wayland/event_loop/state.rs deleted file mode 100644 index 0cf1c6680ee..00000000000 --- a/src/platform_impl/linux/wayland/event_loop/state.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! A state that we pass around in a dispatch. - -use std::collections::HashMap; - -use super::EventSink; -use crate::platform_impl::wayland::window::shim::{ - WindowCompositorUpdate, WindowHandle, WindowUserRequest, -}; -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 comming from the user requests. Those are separatelly dispatched right after - /// `MainEventsCleared`. - pub window_user_requests: HashMap, - - /// 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_compositor_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/mod.rs b/src/platform_impl/linux/wayland/mod.rs index fce6d8ecdbd..af78d8f943c 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -1,19 +1,23 @@ #![cfg(wayland_platform)] +//! Winit's Wayland backend. + use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Proxy; pub use crate::platform_impl::platform::WindowId; pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; pub use output::{MonitorHandle, VideoMode}; pub use window::Window; -mod env; mod event_loop; mod output; -mod protocols; mod seat; +mod state; mod window; +mod xdg_activation; +/// Dummy device id, since Wayland doesn't have device events. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; @@ -23,7 +27,8 @@ impl DeviceId { } } +/// Get the WindowId out of the surface. #[inline] fn make_wid(surface: &WlSurface) -> WindowId { - WindowId(surface.as_ref().c_ptr() as u64) + WindowId(surface.id().as_ptr() as u64) } diff --git a/src/platform_impl/linux/wayland/output.rs b/src/platform_impl/linux/wayland/output.rs index 5744f62adb7..d444e33fed5 100644 --- a/src/platform_impl/linux/wayland/output.rs +++ b/src/platform_impl/linux/wayland/output.rs @@ -1,98 +1,30 @@ -use std::collections::VecDeque; -use std::sync::{Arc, Mutex}; - use sctk::reexports::client::protocol::wl_output::WlOutput; -use sctk::reexports::client::Display; +use sctk::reexports::client::Proxy; -use sctk::environment::Environment; -use sctk::output::OutputStatusListener; +use sctk::output::OutputData; use crate::dpi::{PhysicalPosition, PhysicalSize}; 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); - } +impl EventLoopWindowTarget { + #[inline] + pub fn available_monitors(&self) -> Vec { + self.state + .borrow() + .output_state + .outputs() + .map(MonitorHandle::new) + .collect() } - /// Get all observed outputs. - pub fn available_outputs(&self) -> VecDeque { - self.outputs.lock().unwrap().clone() + #[inline] + pub fn primary_monitor(&self) -> Option { + // There's no primary monitor on Wayland. + None } } @@ -101,32 +33,6 @@ 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 { @@ -135,25 +41,27 @@ impl MonitorHandle { #[inline] pub fn name(&self) -> Option { - sctk::output::with_output_info(&self.proxy, |info| { - format!("{} ({})", info.model, info.make) - }) + let output_data = self.proxy.data::().unwrap(); + output_data.with_output_info(|info| info.name.clone()) } #[inline] pub fn native_identifier(&self) -> u32 { - sctk::output::with_output_info(&self.proxy, |info| info.id).unwrap_or(0) + let output_data = self.proxy.data::().unwrap(); + output_data.with_output_info(|info| info.id) } #[inline] pub fn size(&self) -> PhysicalSize { - match sctk::output::with_output_info(&self.proxy, |info| { + let output_data = self.proxy.data::().unwrap(); + let dimensions = output_data.with_output_info(|info| { info.modes .iter() - .find(|mode| mode.is_current) - .map(|mode| mode.dimensions) - }) { - Some(Some((w, h))) => (w as u32, h as u32), + .find_map(|mode| mode.current.then(|| mode.dimensions)) + }); + + match dimensions { + Some((width, height)) => (width as u32, height as u32), _ => (0, 0), } .into() @@ -161,30 +69,30 @@ impl MonitorHandle { #[inline] pub fn position(&self) -> PhysicalPosition { - sctk::output::with_output_info(&self.proxy, |info| info.location) - .unwrap_or((0, 0)) - .into() + let output_data = self.proxy.data::().unwrap(); + output_data.with_output_info(|info| info.location).into() } #[inline] pub fn refresh_rate_millihertz(&self) -> Option { - sctk::output::with_output_info(&self.proxy, |info| { + let output_data = self.proxy.data::().unwrap(); + output_data.with_output_info(|info| { info.modes .iter() - .find_map(|mode| mode.is_current.then(|| mode.refresh_rate as u32)) + .find_map(|mode| mode.current.then(|| mode.refresh_rate as u32)) }) - .flatten() } #[inline] pub fn scale_factor(&self) -> i32 { - sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1) + let output_data = self.proxy.data::().unwrap(); + output_data.scale_factor() } #[inline] pub fn video_modes(&self) -> impl Iterator { - let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone()) - .unwrap_or_default(); + let output_data = self.proxy.data::().unwrap(); + let modes = output_data.with_output_info(|info| info.modes.clone()); let monitor = self.clone(); @@ -199,6 +107,32 @@ impl MonitorHandle { } } +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); + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VideoMode { pub(crate) size: PhysicalSize, @@ -227,21 +161,3 @@ impl VideoMode { 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/protocols.rs b/src/platform_impl/linux/wayland/protocols.rs deleted file mode 100644 index 7612ff67d83..00000000000 --- a/src/platform_impl/linux/wayland/protocols.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)] -#![allow(non_upper_case_globals, non_snake_case, unused_imports)] -#![allow(missing_docs, clippy::all)] - -use wayland_client::protocol::wl_surface; -use wayland_client::sys; -use wayland_client::{AnonymousObject, Attached, Main, Proxy, ProxyMap}; -use wayland_commons::map::{Object, ObjectMetadata}; -use wayland_commons::smallvec; -use wayland_commons::wire::{Argument, ArgumentType, Message, MessageDesc}; -use wayland_commons::{Interface, MessageGroup}; - -include!(concat!(env!("OUT_DIR"), "/fractional_scale_v1.rs")); diff --git a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs deleted file mode 100644 index 36fda96b133..00000000000 --- a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs +++ /dev/null @@ -1,165 +0,0 @@ -//! Handling of various keyboard events. - -use std::sync::atomic::Ordering; - -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); - - let window_handle = match winit_state.window_map.get_mut(&window_id) { - Some(window_handle) => window_handle, - None => return, - }; - window_handle.has_focus.store(true, Ordering::Relaxed); - - // 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, .. } => { - // Reset the id. - inner.target_window_id = None; - - 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 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_handle.has_focus.store(false, Ordering::Relaxed); - - // Window lost focus. - event_sink.push_window_event(WindowEvent::Focused(false), window_id); - } - 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 index 991afff2c9b..e13488e2295 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs @@ -1,9 +1,9 @@ -//! Convert Wayland keys to winit keys. +use sctk::seat::keyboard::keysyms; use crate::event::VirtualKeyCode; +/// Convert xkb keysym into winit's VirtualKeyCode. pub fn keysym_to_vkey(keysym: u32) -> Option { - use sctk::seat::keyboard::keysyms; match keysym { // Numbers. keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1), diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index 262a014bac9..0e4cfbd19da 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -1,85 +1,229 @@ -//! Wayland keyboard handling. +//! The keyboard input handling. -use std::cell::RefCell; -use std::rc::Rc; +use std::sync::Mutex; +use sctk::reexports::client::delegate_dispatch; use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; use sctk::reexports::client::protocol::wl_seat::WlSeat; -use sctk::reexports::client::Attached; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::{Connection, Proxy, QueueHandle}; -use sctk::reexports::calloop::LoopHandle; +use sctk::seat::keyboard::{KeyEvent, KeyboardData, KeyboardDataExt, KeyboardHandler, Modifiers}; +use sctk::seat::SeatState; -use sctk::seat::keyboard; +use crate::event::{ElementState, ModifiersState, WindowEvent}; -use crate::event::ModifiersState; -use crate::platform_impl::wayland::event_loop::WinitState; -use crate::platform_impl::wayland::WindowId; +use crate::platform_impl::wayland::state::WinitState; +use crate::platform_impl::wayland::{self, DeviceId, WindowId}; -mod handlers; mod keymap; -pub(crate) struct Keyboard { - pub keyboard: WlKeyboard, -} +impl WinitState { + pub fn handle_key_input( + &mut self, + keyboard: &WlKeyboard, + event: KeyEvent, + state: ElementState, + ) { + let window_id = match *keyboard.winit_data().window_id.lock().unwrap() { + Some(window_id) => window_id, + None => return, + }; + + let virtual_keycode = keymap::keysym_to_vkey(event.keysym); + + let seat_state = self.seats.get(&keyboard.seat().id()).unwrap(); + let modifiers = seat_state.modifiers; -impl Keyboard { - pub fn new( - seat: &Attached, - loop_handle: LoopHandle<'static, WinitState>, - modifiers_state: Rc>, - ) -> Option { - let mut inner = KeyboardInner::new(modifiers_state); - let keyboard = 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); + self.events_sink.push_window_event( + #[allow(deprecated)] + WindowEvent::KeyboardInput { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + input: crate::event::KeyboardInput { + state, + scancode: event.raw_code, + virtual_keycode, + modifiers, + }, + is_synthetic: false, }, - ) - .ok()?; + window_id, + ); + + // Don't send utf8 chars on release. + if state == ElementState::Released { + return; + } - Some(Self { keyboard }) + if let Some(txt) = event.utf8 { + for ch in txt.chars() { + self.events_sink + .push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); + } + } } } -impl Drop for Keyboard { - fn drop(&mut self) { - if self.keyboard.as_ref().version() >= 3 { - self.keyboard.release(); +impl KeyboardHandler for WinitState { + fn enter( + &mut self, + _: &Connection, + _: &QueueHandle, + keyboard: &WlKeyboard, + surface: &WlSurface, + _serial: u32, + _raw: &[u32], + _keysyms: &[u32], + ) { + let window_id = wayland::make_wid(surface); + + // Mark the window as focused. + match self.windows.borrow().get(&window_id) { + Some(window) => window.lock().unwrap().set_has_focus(true), + None => return, + }; + + // Window gained focus. + self.events_sink + .push_window_event(WindowEvent::Focused(true), window_id); + + *keyboard.winit_data().window_id.lock().unwrap() = Some(window_id); + + // Work-around buggy compositors. + let seat_state = self.seats.get_mut(&keyboard.seat().id()).unwrap(); + if std::mem::take(&mut seat_state.modifiers_pending) { + self.events_sink.push_window_event( + WindowEvent::ModifiersChanged(seat_state.modifiers), + window_id, + ); } } -} -struct KeyboardInner { - /// Currently focused surface. - target_window_id: Option, + fn leave( + &mut self, + _: &Connection, + _: &QueueHandle, + keyboard: &WlKeyboard, + surface: &WlSurface, + _serial: u32, + ) { + let window_id = wayland::make_wid(surface); + + // Mark the window as not focused. + match self.windows.borrow().get(&window_id) { + Some(window) => window.lock().unwrap().set_has_focus(false), + None => return, + }; + + // Notify that no modifiers are being pressed. + self.events_sink.push_window_event( + WindowEvent::ModifiersChanged(ModifiersState::empty()), + window_id, + ); + + *keyboard.winit_data().window_id.lock().unwrap() = None; + + // Window lost focus. + self.events_sink + .push_window_event(WindowEvent::Focused(false), window_id); + } + + fn press_key( + &mut self, + _: &Connection, + _: &QueueHandle, + keyboard: &WlKeyboard, + _serial: u32, + event: KeyEvent, + ) { + self.handle_key_input(keyboard, event, ElementState::Pressed); + } + + fn release_key( + &mut self, + _: &Connection, + _: &QueueHandle, + keyboard: &WlKeyboard, + _serial: u32, + event: KeyEvent, + ) { + self.handle_key_input(keyboard, event, ElementState::Released); + } + + fn update_modifiers( + &mut self, + _: &Connection, + _: &QueueHandle, + keyboard: &WlKeyboard, + _serial: u32, + modifiers: Modifiers, + ) { + let modifiers = ModifiersState::from(modifiers); + let mut seat_state = self.seats.get_mut(&keyboard.seat().id()).unwrap(); + seat_state.modifiers = modifiers; + + // Work around for mutter, since event must be after the `Enter`, but mutter sends it + // before. + let window_id = match *keyboard.winit_data().window_id.lock().unwrap() { + Some(window_id) => window_id, + None => { + seat_state.modifiers_pending = true; + return; + } + }; + + self.events_sink + .push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id); + } +} - /// 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, +/// The extension to KeyboardData used to store the `window_id`. +pub struct WinitKeyboardData { + /// The currently focused window surface. Could be `None` on bugged compositors, like mutter. + window_id: Mutex>, - /// Current state of modifiers keys. - modifiers_state: Rc>, + /// The original keyboard date. + keyboard_data: KeyboardData, } -impl KeyboardInner { - fn new(modifiers_state: Rc>) -> Self { +impl WinitKeyboardData { + pub fn new(seat: WlSeat) -> Self { Self { - target_window_id: None, - pending_modifers_state: None, - modifiers_state, + window_id: Default::default(), + keyboard_data: KeyboardData::new(seat), } } } -impl From for ModifiersState { - fn from(mods: keyboard::ModifiersState) -> ModifiersState { +impl KeyboardDataExt for WinitKeyboardData { + fn keyboard_data(&self) -> &KeyboardData { + &self.keyboard_data + } + + fn keyboard_data_mut(&mut self) -> &mut KeyboardData { + &mut self.keyboard_data + } +} + +pub trait WinitKeyboardDataExt { + fn winit_data(&self) -> &WinitKeyboardData; + + fn seat(&self) -> &WlSeat { + self.winit_data().keyboard_data().seat() + } +} + +impl WinitKeyboardDataExt for WlKeyboard { + fn winit_data(&self) -> &WinitKeyboardData { + self.data::() + .expect("failed to get keyboard data.") + } +} + +impl From for ModifiersState { + fn from(mods: Modifiers) -> ModifiersState { let mut wl_mods = ModifiersState::empty(); wl_mods.set(ModifiersState::SHIFT, mods.shift); wl_mods.set(ModifiersState::CTRL, mods.ctrl); @@ -88,3 +232,5 @@ impl From for ModifiersState { wl_mods } } + +delegate_dispatch!(WinitState: [ WlKeyboard: WinitKeyboardData] => SeatState); diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 2d7b7533d05..a43c2400485 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -1,208 +1,259 @@ -//! Seat handling and managing. +//! Seat handling. -use std::cell::RefCell; -use std::rc::Rc; +use std::collections::HashMap; +use std::sync::Arc; -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 calloop::RegistrationToken; +use log::warn; +use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; use sctk::reexports::client::protocol::wl_seat::WlSeat; -use sctk::reexports::client::Attached; +use sctk::reexports::client::protocol::wl_touch::WlTouch; +use sctk::reexports::client::{Connection, Proxy, QueueHandle}; +use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1; +use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; -use sctk::environment::Environment; -use sctk::reexports::calloop::LoopHandle; -use sctk::seat::pointer::ThemeManager; -use sctk::seat::{SeatData, SeatListener}; +use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; +use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; -use super::env::WinitEnv; -use super::event_loop::WinitState; -use crate::event::ModifiersState; +use crate::event::{ElementState, ModifiersState}; +use crate::platform_impl::wayland::state::WinitState; mod keyboard; -pub mod pointer; -pub mod text_input; +mod pointer; +mod text_input; mod touch; -use keyboard::Keyboard; -use pointer::Pointers; -use text_input::TextInput; -use touch::Touch; +pub use pointer::relative_pointer::RelativePointerState; +pub use pointer::{WinitPointerData, WinitPointerDataExt}; +pub use text_input::{TextInputState, ZwpTextInputV3Ext}; -pub struct SeatManager { - /// Listener for seats. - _seat_listener: SeatListener, -} +use keyboard::WinitKeyboardData; +use text_input::TextInputData; +use touch::TouchPoint; -impl SeatManager { - pub fn new( - env: &Environment, - loop_handle: LoopHandle<'static, WinitState>, - 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); - } +#[derive(Debug)] +pub struct WinitSeatState { + /// The pointer bound on the seat. + pointer: Option>>, - let seat_listener = env.listen_for_seats(move |seat, seat_data, _| { - inner.process_seat_update(&seat, seat_data); - }); + /// The touch bound on the seat. + touch: Option, - Self { - _seat_listener: seat_listener, - } - } -} - -/// Inner state of the seat manager. -struct SeatManagerInner { - /// Currently observed seats. - seats: Vec, + /// The mapping from touched points to the surfaces they're present. + touch_map: HashMap, - /// Loop handle. - loop_handle: LoopHandle<'static, WinitState>, + /// The text input bound on the seat. + text_input: Option>, - /// Relative pointer manager. - relative_pointer_manager: Option>, + /// The relative pointer bound on the seat. + relative_pointer: Option, - /// Pointer constraints. - pointer_constraints: Option>, + /// The keyboard bound on the seat. + keyboard_state: Option, - /// Text input manager. - text_input_manager: Option>, + /// The current modifiers state on the seat. + modifiers: ModifiersState, - /// A theme manager. - theme_manager: ThemeManager, + /// Wether we have pending modifiers. + modifiers_pending: bool, } -impl SeatManagerInner { - fn new( - theme_manager: ThemeManager, - relative_pointer_manager: Option>, - pointer_constraints: Option>, - text_input_manager: Option>, - loop_handle: LoopHandle<'static, WinitState>, - ) -> Self { +impl WinitSeatState { + pub fn new() -> Self { Self { - seats: Vec::new(), - loop_handle, - relative_pointer_manager, - pointer_constraints, - text_input_manager, - theme_manager, + pointer: None, + touch: None, + relative_pointer: None, + text_input: None, + touch_map: Default::default(), + keyboard_state: None, + modifiers: ModifiersState::empty(), + modifiers_pending: false, } } +} + +impl SeatHandler for WinitState { + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat_state + } - /// 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(), - )); + fn new_capability( + &mut self, + _: &Connection, + queue_handle: &QueueHandle, + seat: WlSeat, + capability: SeatCapability, + ) { + let seat_state = self.seats.get_mut(&seat.id()).unwrap(); + + match capability { + SeatCapability::Touch if seat_state.touch.is_none() => { + seat_state.touch = self.seat_state.get_touch(queue_handle, &seat).ok(); } - } else { - seat_info.pointer = None; + SeatCapability::Keyboard if seat_state.keyboard_state.is_none() => { + let (keyboard, source) = self + .seat_state + .get_keyboard_with_repeat_with_data( + queue_handle, + &seat, + WinitKeyboardData::new(seat.clone()), + ) + .expect("failed to create keyboard with present capability."); + + let keyboard_clone = keyboard.clone(); + + // Try to setup repeat source. + let repeat_token = + match self + .loop_handle + .insert_source(source, move |event, _, state| { + state.handle_key_input(&keyboard_clone, event, ElementState::Pressed); + }) { + Ok(repeat_token) => Some(repeat_token), + Err(err) => { + warn!("Failed to register repeat source: {err}"); + None + } + }; + + seat_state.keyboard_state = Some(KeyboardState { + keyboard, + repeat_token, + }); + } + SeatCapability::Pointer if seat_state.pointer.is_none() => { + let surface = self.compositor_state.create_surface(queue_handle); + let surface_id = surface.id(); + let pointer_data = WinitPointerData::new(seat.clone(), surface); + let themed_pointer = self + .seat_state + .get_pointer_with_theme_and_data( + queue_handle, + &seat, + ThemeSpec::System, + pointer_data, + ) + .expect("failed to create pointer with present capability."); + + seat_state.relative_pointer = self.relative_pointer.as_ref().map(|manager| { + manager.get_relative_pointer( + themed_pointer.pointer(), + queue_handle, + sctk::globals::GlobalData, + ) + }); + + let themed_pointer = Arc::new(themed_pointer); + + // Register cursor surface. + self.pointer_surfaces + .insert(surface_id, themed_pointer.clone()); + + seat_state.pointer = Some(themed_pointer); + } + _ => (), } - // 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; + if let Some(text_input_state) = seat_state + .text_input + .is_none() + .then(|| self.text_input_state.as_ref()) + .flatten() + { + seat_state.text_input = Some(Arc::new(text_input_state.get_text_input( + &seat, + queue_handle, + TextInputData::default(), + ))); } + } - // Handle touch. - if seat_data.has_touch && !seat_data.defunct { - if seat_info.touch.is_none() { - seat_info.touch = Some(Touch::new(seat)); + fn remove_capability( + &mut self, + _: &Connection, + _queue_handle: &QueueHandle, + seat: WlSeat, + capability: SeatCapability, + ) { + let seat_state = self.seats.get_mut(&seat.id()).unwrap(); + + match capability { + SeatCapability::Touch => { + if let Some(touch) = seat_state.touch.take() { + if touch.version() >= 3 { + touch.release(); + } + } } - } else { - seat_info.touch = None; + SeatCapability::Pointer => { + if let Some(relative_pointer) = seat_state.relative_pointer.take() { + relative_pointer.destroy(); + } + + if let Some(pointer) = seat_state.pointer.take() { + let pointer_data = pointer.pointer().winit_data(); + + // Remove the cursor from the mapping. + let surface_id = pointer_data.cursor_surface().id(); + let _ = self.pointer_surfaces.remove(&surface_id); + + // Remove the inner locks/confines before dropping the pointer. + pointer_data.unlock_pointer(); + pointer_data.unconfine_pointer(); + + if pointer.pointer().version() >= 3 { + pointer.pointer().release(); + } + } + } + SeatCapability::Keyboard => { + if let Some(keyboard_state) = seat_state.keyboard_state.take() { + // Remove the source. + if let Some(repeat_token) = keyboard_state.repeat_token { + self.loop_handle.remove(repeat_token); + } + + if keyboard_state.keyboard.version() >= 3 { + keyboard_state.keyboard.release(); + } + } + } + _ => (), } - // 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)); - } + if let Some(text_input) = seat_state.text_input.take() { + text_input.destroy(); } } -} -/// 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, + fn new_seat( + &mut self, + _connection: &Connection, + _queue_handle: &QueueHandle, + seat: WlSeat, + ) { + self.seats.insert(seat.id(), WinitSeatState::new()); + } - /// Touch handling. - touch: Option, + fn remove_seat( + &mut self, + _connection: &Connection, + _queue_handle: &QueueHandle, + seat: WlSeat, + ) { + let _ = self.seats.remove(&seat.id()); + } +} - /// Text input handling aka IME. - text_input: Option, +#[derive(Debug)] +pub struct KeyboardState { + /// The underlying WlKeyboard. + pub keyboard: WlKeyboard, - /// 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>, + /// The repeat rate registration token. + pub repeat_token: Option, } -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())), - } - } -} +sctk::delegate_seat!(WinitState); diff --git a/src/platform_impl/linux/wayland/seat/pointer/data.rs b/src/platform_impl/linux/wayland/seat/pointer/data.rs deleted file mode 100644 index 17e7a57a07c..00000000000 --- a/src/platform_impl/linux/wayland/seat/pointer/data.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! 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 sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1; - -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>>, - pub locked_pointer: Rc>>, - - /// Latest observed serial in pointer events. - pub latest_serial: Rc>, - - /// Latest observed serial in pointer enter events. - pub latest_enter_serial: Rc>, - - /// The currently accumulated axis data on a pointer. - pub axis_data: AxisData, -} - -impl PointerData { - pub fn new( - confined_pointer: Rc>>, - locked_pointer: Rc>>, - pointer_constraints: Option>, - modifiers_state: Rc>, - ) -> Self { - Self { - surface: None, - latest_serial: Rc::new(Cell::new(0)), - latest_enter_serial: Rc::new(Cell::new(0)), - confined_pointer, - locked_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 deleted file mode 100644 index 945443f87ae..00000000000 --- a/src/platform_impl/linux/wayland/seat/pointer/handlers.rs +++ /dev/null @@ -1,327 +0,0 @@ -//! 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::client::protocol::wl_seat::WlSeat; -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}; - -// These values are comming from . -const BTN_LEFT: u32 = 0x110; -const BTN_RIGHT: u32 = 0x111; -const BTN_MIDDLE: u32 = 0x112; - -#[inline] -pub(super) fn handle_pointer( - pointer: ThemedPointer, - event: PointerEvent, - pointer_data: &Rc>, - winit_state: &mut WinitState, - seat: WlSeat, -) { - 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); - pointer_data.latest_enter_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, - }; - - let scale_factor = window_handle.scale_factor(); - 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), - locked_pointer: Rc::downgrade(&pointer_data.locked_pointer), - pointer_constraints: pointer_data.pointer_constraints.clone(), - latest_serial: pointer_data.latest_serial.clone(), - latest_enter_serial: pointer_data.latest_enter_serial.clone(), - seat, - }; - 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), - locked_pointer: Rc::downgrade(&pointer_data.locked_pointer), - pointer_constraints: pointer_data.pointer_constraints.clone(), - latest_serial: pointer_data.latest_serial.clone(), - latest_enter_serial: pointer_data.latest_enter_serial.clone(), - seat, - }; - 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 window_handle = match winit_state.window_map.get(&window_id) { - Some(w) => w, - _ => return, - }; - - let scale_factor = window_handle.scale_factor(); - 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 { - BTN_LEFT => MouseButton::Left, - BTN_RIGHT => MouseButton::Right, - BTN_MIDDLE => MouseButton::Middle, - button => MouseButton::Other(button as u16), - }; - - 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); - let window_handle = match winit_state.window_map.get(&window_id) { - Some(w) => w, - _ => return, - }; - - if pointer.as_ref().version() < 5 { - let (mut x, mut y) = (0.0, 0.0); - - // Old seat compatibility. - match axis { - // Wayland 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 = window_handle.scale_factor(); - 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 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 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_handle = match winit_state.window_map.get(&window_id) { - Some(w) => w, - _ => return, - }; - - 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 = window_handle.scale_factor(); - 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_unaccel, - dy_unaccel, - .. - } = event - { - winit_state.event_sink.push_device_event( - DeviceEvent::MouseMotion { - delta: (dx_unaccel, dy_unaccel), - }, - DeviceId, - ) - } -} diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index 04ab0119c5e..fc60fc5e6da 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -1,343 +1,369 @@ -//! All pointer related handling. +//! The pointer events. -use std::cell::{Cell, RefCell}; -use std::rc::{Rc, Weak}; +use std::sync::{Arc, Mutex}; +use sctk::reexports::client::delegate_dispatch; 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::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1; - -use sctk::seat::pointer::{ThemeManager, ThemedPointer}; -use sctk::window::Window; - -use crate::event::ModifiersState; -use crate::platform_impl::wayland::event_loop::WinitState; -use crate::platform_impl::wayland::window::WinitFrame; -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>>, - - /// Cursor to handle locked requests. - locked_pointer: Weak>>, +use sctk::reexports::client::{Connection, Proxy, QueueHandle}; +use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; +use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1; +use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::Lifetime; + +use sctk::compositor::SurfaceData; +use sctk::globals::GlobalData; +use sctk::seat::pointer::{PointerData, PointerDataExt}; +use sctk::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler}; +use sctk::seat::SeatState; + +use crate::dpi::{LogicalPosition, PhysicalPosition}; +use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}; + +use crate::platform_impl::wayland::state::{PointerConstraintsState, WinitState}; +use crate::platform_impl::wayland::{self, DeviceId, WindowId}; + +pub mod relative_pointer; + +impl PointerHandler for WinitState { + fn pointer_frame( + &mut self, + _: &Connection, + _: &QueueHandle, + pointer: &WlPointer, + events: &[PointerEvent], + ) { + let seat = pointer.winit_data().seat(); + let seat_state = self.seats.get(&seat.id()).unwrap(); + let modifiers = seat_state.modifiers; + + let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); + + for event in events { + let surface = &event.surface; + let window_id = wayland::make_wid(surface); + let scale_factor = surface + .data::() + .expect("failed to get surface data.") + .scale_factor() as f64; + + let position: PhysicalPosition = + LogicalPosition::new(event.position.0, event.position.1).to_physical(scale_factor); + + match event.kind { + PointerEventKind::Enter { .. } => { + self.events_sink + .push_window_event(WindowEvent::CursorEntered { device_id }, window_id); + + if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) { + let mut windows = self.windows.borrow_mut(); + let mut window = windows.get_mut(&window_id).unwrap().lock().unwrap(); + window.pointer_entered(pointer); + } + + // Set the currently focused surface. + pointer.winit_data().inner.lock().unwrap().surface = Some(window_id); + + self.events_sink.push_window_event( + WindowEvent::CursorMoved { + device_id, + position, + modifiers, + }, + window_id, + ); + } + PointerEventKind::Leave { .. } => { + if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) { + let mut windows = self.windows.borrow_mut(); + let mut window = windows.get_mut(&window_id).unwrap().lock().unwrap(); + window.pointer_left(pointer); + } + + // Remove the active surface. + pointer.winit_data().inner.lock().unwrap().surface = None; + + self.events_sink + .push_window_event(WindowEvent::CursorLeft { device_id }, window_id); + } + PointerEventKind::Motion { .. } => { + self.events_sink.push_window_event( + WindowEvent::CursorMoved { + device_id, + position, + modifiers, + }, + window_id, + ); + } + ref kind @ PointerEventKind::Press { button, serial, .. } + | ref kind @ PointerEventKind::Release { button, serial, .. } => { + // Update the last button serial. + pointer + .winit_data() + .inner + .lock() + .unwrap() + .latest_button_serial = serial; + + let button = wayland_button_to_winit(button); + let state = if matches!(kind, PointerEventKind::Press { .. }) { + ElementState::Pressed + } else { + ElementState::Released + }; + self.events_sink.push_window_event( + WindowEvent::MouseInput { + device_id, + state, + button, + modifiers, + }, + window_id, + ); + } + PointerEventKind::Axis { + horizontal, + vertical, + .. + } => { + // Get the current phase. + let mut pointer_data = pointer.winit_data().inner.lock().unwrap(); + + let has_discrete_scroll = horizontal.discrete != 0 || vertical.discrete != 0; + + // Figure out what to do about start/ended phases here. + // + // Figure out how to deal with `Started`. Also the `Ended` is not guaranteed + // to be sent for mouse wheels. + let phase = if horizontal.stop || vertical.stop { + TouchPhase::Ended + } else { + match pointer_data.phase { + // Descrete scroll only results in moved events. + _ if has_discrete_scroll => TouchPhase::Moved, + TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, + _ => TouchPhase::Started, + } + }; + + // Update the phase. + pointer_data.phase = phase; + + // Mice events have both pixel and discrete delta's at the same time. So prefer + // the descrite values if they are present. + let delta = if has_discrete_scroll { + // XXX Wayland sign convention is the inverse of winit. + MouseScrollDelta::LineDelta( + (-horizontal.discrete) as f32, + (-vertical.discrete) as f32, + ) + } else { + // XXX Wayland sign convention is the inverse of winit. + MouseScrollDelta::PixelDelta( + LogicalPosition::new(-horizontal.absolute, -vertical.absolute) + .to_physical(scale_factor), + ) + }; + + self.events_sink.push_window_event( + WindowEvent::MouseWheel { + device_id, + delta, + phase, + modifiers, + }, + window_id, + ) + } + } + } + } +} - /// Latest observed serial in pointer events. - /// used by Window::start_interactive_move() - latest_serial: Rc>, +#[derive(Debug)] +pub struct WinitPointerData { + /// The surface associated with this pointer, which is used for icons. + cursor_surface: WlSurface, - /// Latest observed serial in pointer enter events. - /// used by Window::set_cursor() - latest_enter_serial: Rc>, + /// The inner winit data associated with the pointer. + inner: Mutex, - /// Seat. - seat: WlSeat, + /// The data required by the sctk. + sctk_data: PointerData, } -impl PartialEq for WinitPointer { - fn eq(&self, other: &Self) -> bool { - *self.pointer == *other.pointer +impl WinitPointerData { + pub fn new(seat: WlSeat, surface: WlSurface) -> Self { + Self { + cursor_surface: surface, + inner: Mutex::new(WinitPointerDataInner::default()), + sctk_data: PointerData::new(seat), + } + } + + pub fn lock_pointer( + &self, + pointer_constraints: &PointerConstraintsState, + surface: &WlSurface, + pointer: &WlPointer, + queue_handle: &QueueHandle, + ) { + let mut inner = self.inner.lock().unwrap(); + if inner.locked_pointer.is_none() { + inner.locked_pointer = Some(pointer_constraints.lock_pointer( + surface, + pointer, + None, + Lifetime::Persistent, + queue_handle, + GlobalData, + )); + } } -} -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. - // WlPointer::set_cursor() expects the serial of the last *enter* - // event (compare to to start_interactive_move()). - (*self.pointer).set_cursor(self.latest_enter_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 => &["hand2", "hand1"], - 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_fdiag"], - CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"], - 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_enter_serial.get()); - for cursor in cursors { - if self.pointer.set_cursor(cursor, serial).is_ok() { - return; - } + pub fn unlock_pointer(&self) { + let mut inner = self.inner.lock().unwrap(); + if let Some(locked_pointer) = inner.locked_pointer.take() { + locked_pointer.destroy(); } - warn!("Failed to set cursor to {:?}", cursor_icon); } - /// 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, + pub fn confine_pointer( + &self, + pointer_constraints: &PointerConstraintsState, + surface: &WlSurface, + pointer: &WlPointer, + queue_handle: &QueueHandle, + ) { + self.inner.lock().unwrap().confined_pointer = Some(pointer_constraints.confine_pointer( surface, - &self.pointer, + pointer, + None, + Lifetime::Persistent, + queue_handle, + GlobalData, )); } - /// 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() { + pub fn unconfine_pointer(&self) { + let inner = self.inner.lock().unwrap(); + if let Some(confined_pointer) = inner.confined_pointer.as_ref() { confined_pointer.destroy(); } } - pub fn lock(&self, surface: &WlSurface) { - let pointer_constraints = match &self.pointer_constraints { - Some(pointer_constraints) => pointer_constraints, - None => return, - }; - - let locked_pointer = match self.locked_pointer.upgrade() { - Some(locked_pointer) => locked_pointer, - // A pointer is gone. - None => return, - }; - - *locked_pointer.borrow_mut() = Some(init_locked_pointer( - pointer_constraints, - surface, - &self.pointer, - )); + /// Seat associated with this pointer. + pub fn seat(&self) -> &WlSeat { + self.sctk_data.seat() } - pub fn unlock(&self) { - let locked_pointer = match self.locked_pointer.upgrade() { - Some(locked_pointer) => locked_pointer, - // A pointer is gone. - None => return, - }; + /// The WlSurface used to set cursor theme. + pub fn cursor_surface(&self) -> &WlSurface { + &self.cursor_surface + } - let mut locked_pointer = locked_pointer.borrow_mut(); + /// Active window. + pub fn focused_window(&self) -> Option { + self.inner.lock().unwrap().surface + } - if let Some(locked_pointer) = locked_pointer.take() { - locked_pointer.destroy(); - } + /// Last button serial. + pub fn latest_button_serial(&self) -> u32 { + self.inner.lock().unwrap().latest_button_serial } - pub fn set_cursor_position(&self, surface_x: u32, surface_y: u32) { - let locked_pointer = match self.locked_pointer.upgrade() { - Some(locked_pointer) => locked_pointer, - // A pointer is gone. - None => return, - }; + /// Last enter serial. + pub fn latest_enter_serial(&self) -> u32 { + self.sctk_data.latest_enter_serial().unwrap_or_default() + } - let locked_pointer = locked_pointer.borrow_mut(); - if let Some(locked_pointer) = locked_pointer.as_ref() { - locked_pointer.set_cursor_position_hint(surface_x.into(), surface_y.into()); + pub fn set_locked_cursor_position(&self, surface_x: f64, surface_y: f64) { + let inner = self.inner.lock().unwrap(); + if let Some(locked_pointer) = inner.locked_pointer.as_ref() { + locked_pointer.set_cursor_position_hint(surface_x, surface_y); } } +} - pub fn drag_window(&self, window: &Window) { - // WlPointer::setart_interactive_move() expects the last serial of *any* - // pointer event (compare to set_cursor()). - window.start_interactive_move(&self.seat, self.latest_serial.get()); +impl Drop for WinitPointerData { + fn drop(&mut self) { + self.cursor_surface.destroy(); } } -/// A pointer wrapper for easy releasing and managing pointers. -pub(super) struct Pointers { - /// A pointer itself. - pointer: ThemedPointer, +impl PointerDataExt for WinitPointerData { + fn pointer_data(&self) -> &PointerData { + &self.sctk_data + } +} - /// A relative pointer handler. - relative_pointer: Option, +#[derive(Debug)] +pub struct WinitPointerDataInner { + /// The associated locked pointer. + locked_pointer: Option, - /// Confined pointer. - confined_pointer: Rc>>, + /// The associated confined pointer. + confined_pointer: Option, - /// Locked pointer. - locked_pointer: Rc>>, -} + /// Serial of the last button event. + latest_button_serial: u32, -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 locked_pointer = Rc::new(RefCell::new(None)); - - let pointer_data = Rc::new(RefCell::new(PointerData::new( - confined_pointer.clone(), - locked_pointer.clone(), - pointer_constraints.clone(), - modifiers_state, - ))); - - let pointer_seat = seat.detach(); - 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, - pointer_seat.clone(), - ); - }, - ); - - // Setup relative_pointer if it's available. - let relative_pointer = relative_pointer_manager - .as_ref() - .map(|relative_pointer_manager| { - init_relative_pointer(relative_pointer_manager, &pointer) - }); + /// Currently focused window. + surface: Option, - Self { - pointer, - relative_pointer, - confined_pointer, - locked_pointer, - } - } + /// Current axis phase. + phase: TouchPhase, } -impl Drop for Pointers { +impl Drop for WinitPointerDataInner { fn drop(&mut self) { - // Drop relative pointer. - if let Some(relative_pointer) = self.relative_pointer.take() { - relative_pointer.destroy(); + if let Some(locked_pointer) = self.locked_pointer.take() { + locked_pointer.destroy(); } - // Drop confined pointer. - if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() { + if let Some(confined_pointer) = self.confined_pointer.take() { confined_pointer.destroy(); } + } +} - // Drop lock ponter. - if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() { - locked_pointer.destroy(); - } - - // Drop the pointer itself in case it's possible. - if self.pointer.as_ref().version() >= 3 { - self.pointer.release(); +impl Default for WinitPointerDataInner { + fn default() -> Self { + Self { + surface: None, + locked_pointer: None, + confined_pointer: None, + latest_button_serial: 0, + phase: TouchPhase::Ended, } } } -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() +/// Convert the Wayland button into winit. +fn wayland_button_to_winit(button: u32) -> MouseButton { + // These values are comming from . + const BTN_LEFT: u32 = 0x110; + const BTN_RIGHT: u32 = 0x111; + const BTN_MIDDLE: u32 = 0x112; + + match button { + BTN_LEFT => MouseButton::Left, + BTN_RIGHT => MouseButton::Right, + BTN_MIDDLE => MouseButton::Middle, + button => MouseButton::Other(button as u16), + } } -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); - - confined_pointer.quick_assign(move |_, _, _| {}); - - confined_pointer.detach() +pub trait WinitPointerDataExt { + fn winit_data(&self) -> &WinitPointerData; } -pub(super) fn init_locked_pointer( - pointer_constraints: &Attached, - surface: &WlSurface, - pointer: &WlPointer, -) -> ZwpLockedPointerV1 { - let locked_pointer = - pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent); - - locked_pointer.quick_assign(move |_, _, _| {}); - - locked_pointer.detach() +impl WinitPointerDataExt for WlPointer { + fn winit_data(&self) -> &WinitPointerData { + self.data::() + .expect("failed to get pointer data.") + } } + +delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState); diff --git a/src/platform_impl/linux/wayland/seat/pointer/relative_pointer.rs b/src/platform_impl/linux/wayland/seat/pointer/relative_pointer.rs new file mode 100644 index 00000000000..23a6e21992a --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/pointer/relative_pointer.rs @@ -0,0 +1,80 @@ +//! Relative pointer. + +use std::ops::Deref; + +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::{delegate_dispatch, Dispatch}; +use sctk::reexports::client::{Connection, QueueHandle}; +use sctk::reexports::protocols::wp::relative_pointer::zv1::{ + client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, + client::zwp_relative_pointer_v1::{self, ZwpRelativePointerV1}, +}; + +use sctk::globals::GlobalData; + +use crate::event::DeviceEvent; +use crate::platform_impl::wayland::state::WinitState; + +/// Wrapper around the relative pointer. +pub struct RelativePointerState { + manager: ZwpRelativePointerManagerV1, +} + +impl RelativePointerState { + /// Create new relative pointer manager. + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { manager }) + } +} + +impl Deref for RelativePointerState { + type Target = ZwpRelativePointerManagerV1; + + fn deref(&self) -> &Self::Target { + &self.manager + } +} + +impl Dispatch for RelativePointerState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpRelativePointerManagerV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for RelativePointerState { + fn event( + state: &mut WinitState, + _proxy: &ZwpRelativePointerV1, + event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + if let zwp_relative_pointer_v1::Event::RelativeMotion { + dx_unaccel, + dy_unaccel, + .. + } = event + { + state.events_sink.push_device_event( + DeviceEvent::MouseMotion { + delta: (dx_unaccel, dy_unaccel), + }, + super::DeviceId, + ); + } + } +} + +delegate_dispatch!(WinitState: [ZwpRelativePointerV1: GlobalData] => RelativePointerState); +delegate_dispatch!(WinitState: [ZwpRelativePointerManagerV1: GlobalData] => RelativePointerState); diff --git a/src/platform_impl/linux/wayland/seat/text_input/handlers.rs b/src/platform_impl/linux/wayland/seat/text_input/handlers.rs deleted file mode 100644 index 7f3b9fb60f7..00000000000 --- a/src/platform_impl/linux/wayland/seat/text_input/handlers.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! 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::{Ime, WindowEvent}; -use crate::platform_impl::wayland; -use crate::platform_impl::wayland::event_loop::WinitState; - -use super::{Preedit, TextInputHandler, TextInputInner, ZwpTextInputV3Ext}; - -#[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. - if window_handle.ime_allowed.get() { - text_input.enable(); - text_input.set_content_type_by_purpose(window_handle.ime_purpose.get()); - text_input.commit(); - event_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id); - } - - // 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); - event_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id); - } - TextInputEvent::PreeditString { - text, - cursor_begin, - cursor_end, - } => { - let text = text.unwrap_or_default(); - let cursor_begin = usize::try_from(cursor_begin) - .ok() - .and_then(|idx| text.is_char_boundary(idx).then(|| idx)); - let cursor_end = usize::try_from(cursor_end) - .ok() - .and_then(|idx| text.is_char_boundary(idx).then(|| idx)); - - inner.pending_preedit = Some(Preedit { - text, - cursor_begin, - cursor_end, - }); - } - TextInputEvent::CommitString { text } => { - // Update currenly commited string and reset previous preedit. - inner.pending_preedit = None; - inner.pending_commit = Some(text.unwrap_or_default()); - } - TextInputEvent::Done { .. } => { - let window_id = match inner.target_window_id { - Some(window_id) => window_id, - _ => return, - }; - - // Clear preedit at the start of `Done`. - event_sink.push_window_event( - WindowEvent::Ime(Ime::Preedit(String::new(), None)), - window_id, - ); - - // Send `Commit`. - if let Some(text) = inner.pending_commit.take() { - event_sink.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id); - } - - // Send preedit. - if let Some(preedit) = inner.pending_preedit.take() { - let cursor_range = preedit - .cursor_begin - .map(|b| (b, preedit.cursor_end.unwrap_or(b))); - - event_sink.push_window_event( - WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)), - 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 index 60219c70110..c1a14c84834 100644 --- a/src/platform_impl/linux/wayland/seat/text_input/mod.rs +++ b/src/platform_impl/linux/wayland/seat/text_input/mod.rs @@ -1,112 +1,210 @@ -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::{ +use std::ops::Deref; + +use sctk::globals::GlobalData; +use sctk::reexports::client::{Connection, Proxy, QueueHandle}; + +use wayland_client::delegate_dispatch; +use wayland_client::globals::{BindError, GlobalList}; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::Event as TextInputEvent; + +use wayland_client::Dispatch; +use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; +use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::{ ContentHint, ContentPurpose, ZwpTextInputV3, }; -use crate::platform_impl::wayland::event_loop::WinitState; -use crate::platform_impl::wayland::WindowId; +use crate::event::{Ime, WindowEvent}; +use crate::platform_impl::wayland; +use crate::platform_impl::wayland::state::WinitState; use crate::window::ImePurpose; -mod handlers; - -/// A handler for text input that we're advertising for `WindowHandle`. -#[derive(Eq, PartialEq)] -pub struct TextInputHandler { - text_input: ZwpTextInputV3, +pub struct TextInputState { + text_input_manager: ZwpTextInputManagerV3, } -trait ZwpTextInputV3Ext { - fn set_content_type_by_purpose(&self, purpose: ImePurpose); -} - -impl ZwpTextInputV3Ext for ZwpTextInputV3 { - fn set_content_type_by_purpose(&self, purpose: ImePurpose) { - let (hint, purpose) = match purpose { - ImePurpose::Normal => (ContentHint::None, ContentPurpose::Normal), - ImePurpose::Password => (ContentHint::SensitiveData, ContentPurpose::Password), - ImePurpose::Terminal => (ContentHint::None, ContentPurpose::Terminal), - }; - self.set_content_type(hint, purpose); +impl TextInputState { + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let text_input_manager = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { text_input_manager }) } } -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(); +impl Deref for TextInputState { + type Target = ZwpTextInputManagerV3; + + fn deref(&self) -> &Self::Target { + &self.text_input_manager } +} - #[inline] - pub fn set_content_type_by_purpose(&self, purpose: ImePurpose) { - self.text_input.set_content_type_by_purpose(purpose); - self.text_input.commit(); +impl Dispatch for TextInputState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpTextInputManagerV3, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { } +} - #[inline] - pub fn set_input_allowed(&self, allowed: Option) { - if let Some(purpose) = allowed { - self.text_input.set_content_type_by_purpose(purpose); - self.text_input.enable(); - } else { - self.text_input.disable(); +impl Dispatch for TextInputState { + fn event( + state: &mut WinitState, + text_input: &ZwpTextInputV3, + event: ::Event, + data: &TextInputData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + let windows = state.windows.borrow(); + let mut text_input_data = data.inner.lock().unwrap(); + match event { + TextInputEvent::Enter { surface } => { + let window_id = wayland::make_wid(&surface); + text_input_data.surface = Some(surface); + + let mut window = match windows.get(&window_id) { + Some(window) => window.lock().unwrap(), + None => return, + }; + + if window.ime_allowed() { + text_input.enable(); + text_input.commit(); + state + .events_sink + .push_window_event(WindowEvent::Ime(Ime::Enabled), window_id); + } + + window.text_input_entered(text_input); + } + TextInputEvent::Leave { surface } => { + text_input_data.surface = None; + + // Always issue a disable. + text_input.disable(); + text_input.commit(); + + let window_id = wayland::make_wid(&surface); + + let mut window = match windows.get(&window_id) { + Some(window) => window.lock().unwrap(), + None => return, + }; + + window.text_input_left(text_input); + + state + .events_sink + .push_window_event(WindowEvent::Ime(Ime::Disabled), window_id); + } + TextInputEvent::PreeditString { + text, + cursor_begin, + cursor_end, + } => { + let text = text.unwrap_or_default(); + let cursor_begin = usize::try_from(cursor_begin) + .ok() + .and_then(|idx| text.is_char_boundary(idx).then(|| idx)); + let cursor_end = usize::try_from(cursor_end) + .ok() + .and_then(|idx| text.is_char_boundary(idx).then(|| idx)); + + text_input_data.pending_preedit = Some(Preedit { + text, + cursor_begin, + cursor_end, + }) + } + TextInputEvent::CommitString { text } => { + text_input_data.pending_preedit = None; + text_input_data.pending_commit = text; + } + TextInputEvent::Done { .. } => { + let window_id = match text_input_data.surface.as_ref() { + Some(surface) => wayland::make_wid(surface), + None => return, + }; + + // Clear preedit at the start of `Done`. + state.events_sink.push_window_event( + WindowEvent::Ime(Ime::Preedit(String::new(), None)), + window_id, + ); + + // Send `Commit`. + if let Some(text) = text_input_data.pending_commit.take() { + state + .events_sink + .push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id); + } + + // Send preedit. + if let Some(preedit) = text_input_data.pending_preedit.take() { + let cursor_range = preedit + .cursor_begin + .map(|b| (b, preedit.cursor_end.unwrap_or(b))); + + state.events_sink.push_window_event( + WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)), + window_id, + ); + } + } + TextInputEvent::DeleteSurroundingText { .. } => { + // Not handled. + } + _ => {} } - - self.text_input.commit(); } } -/// A wrapper around text input to automatically destroy the object on `Drop`. -pub struct TextInput { - text_input: Attached, +pub trait ZwpTextInputV3Ext { + fn set_content_type_by_purpose(&self, purpose: ImePurpose); } -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 ZwpTextInputV3Ext for ZwpTextInputV3 { + fn set_content_type_by_purpose(&self, purpose: ImePurpose) { + let (hint, purpose) = match purpose { + ImePurpose::Normal => (ContentHint::None, ContentPurpose::Normal), + ImePurpose::Password => (ContentHint::SensitiveData, ContentPurpose::Password), + ImePurpose::Terminal => (ContentHint::None, ContentPurpose::Terminal), + }; + self.set_content_type(hint, purpose); } } -impl Drop for TextInput { - fn drop(&mut self) { - self.text_input.destroy(); - } +/// The Data associated with the text input. +#[derive(Default)] +pub struct TextInputData { + inner: std::sync::Mutex, } -struct TextInputInner { - /// Currently focused surface. - target_window_id: Option, +#[derive(Default)] +pub struct TextInputDataInner { + /// The `WlSurface` we're performing input to. + surface: Option, - /// Pending commit event which will be dispatched on `text_input_v3::Done`. + /// The commit to submit on `done`. pending_commit: Option, - /// Pending preedit event which will be dispatched on `text_input_v3::Done`. + /// The preedit to submit on `done`. pending_preedit: Option, } +/// The state of the preedit. struct Preedit { text: String, cursor_begin: Option, cursor_end: Option, } -impl TextInputInner { - fn new() -> Self { - Self { - target_window_id: None, - pending_commit: None, - pending_preedit: None, - } - } -} +delegate_dispatch!(WinitState: [ZwpTextInputManagerV3: GlobalData] => TextInputState); +delegate_dispatch!(WinitState: [ZwpTextInputV3: TextInputData] => TextInputState); diff --git a/src/platform_impl/linux/wayland/seat/touch/handlers.rs b/src/platform_impl/linux/wayland/seat/touch/handlers.rs deleted file mode 100644 index 190b5770f41..00000000000 --- a/src/platform_impl/linux/wayland/seat/touch/handlers.rs +++ /dev/null @@ -1,144 +0,0 @@ -//! 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); - let window_handle = match winit_state.window_map.get(&window_id) { - Some(w) => w, - _ => return, - }; - - let scale_factor = window_handle.scale_factor(); - 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, - ); - - // For `TouchEvent::Up` we don't receive a position, so we're tracking active - // touch points. Update either a known touch id or register a new one. - if let Some(i) = inner.touch_points.iter().position(|p| p.id == id) { - inner.touch_points[i].position = position; - } else { - 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 window_id = wayland::make_wid(&touch_point.surface); - let window_handle = match winit_state.window_map.get(&window_id) { - Some(w) => w, - _ => return, - }; - let scale_factor = window_handle.scale_factor(); - 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, - }; - let window_id = wayland::make_wid(&touch_point.surface); - let window_handle = match winit_state.window_map.get(&window_id) { - Some(w) => w, - _ => return, - }; - - touch_point.position = LogicalPosition::new(x, y); - - let scale_factor = window_handle.scale_factor(); - 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 window_id = wayland::make_wid(&touch_point.surface); - let window_handle = match winit_state.window_map.get(&window_id) { - Some(w) => w, - _ => return, - }; - - let scale_factor = window_handle.scale_factor(); - let location = touch_point.position.to_physical(scale_factor); - - 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 index 197e9ac9c50..5c10a7c4cef 100644 --- a/src/platform_impl/linux/wayland/seat/touch/mod.rs +++ b/src/platform_impl/linux/wayland/seat/touch/mod.rs @@ -3,76 +3,201 @@ 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 sctk::reexports::client::{Connection, Proxy, QueueHandle}; + +use sctk::compositor::SurfaceData; +use sctk::seat::touch::{TouchData, TouchHandler}; use crate::dpi::LogicalPosition; +use crate::event::{Touch, TouchPhase, WindowEvent}; -use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::state::WinitState; +use crate::platform_impl::wayland::{self, DeviceId}; -mod handlers; +impl TouchHandler for WinitState { + fn down( + &mut self, + _: &Connection, + _: &QueueHandle, + touch: &WlTouch, + _: u32, + _: u32, + surface: WlSurface, + id: i32, + position: (f64, f64), + ) { + let window_id = wayland::make_wid(&surface); + let scale_factor = surface + .data::() + .expect("failed to get surface data.") + .scale_factor() as f64; -/// Wrapper around touch to handle release. -pub struct Touch { - /// Proxy to touch. - touch: WlTouch, -} + let location = LogicalPosition::::from(position); -impl Touch { - pub fn new(seat: &Attached) -> Self { - let touch = seat.get_touch(); - let mut inner = TouchInner::new(); + let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); - touch.quick_assign(move |_, event, mut dispatch_data| { - let winit_state = dispatch_data.get::().unwrap(); - handlers::handle_touch(event, &mut inner, winit_state); - }); + // Update the state of the point. + seat_state + .touch_map + .insert(id, TouchPoint { surface, location }); - Self { - touch: touch.detach(), - } + self.events_sink.push_window_event( + WindowEvent::Touch(Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Started, + location: location.to_physical(scale_factor), + force: None, + id: id as u64, + }), + window_id, + ); } -} -impl Drop for Touch { - fn drop(&mut self) { - if self.touch.as_ref().version() >= 3 { - self.touch.release(); - } + fn up( + &mut self, + _: &Connection, + _: &QueueHandle, + touch: &WlTouch, + _: u32, + _: u32, + id: i32, + ) { + let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); + + // Remove the touch point. + let touch_point = match seat_state.touch_map.remove(&id) { + Some(touch_point) => touch_point, + None => return, + }; + + let window_id = wayland::make_wid(&touch_point.surface); + let scale_factor = touch_point + .surface + .data::() + .expect("failed to get surface data.") + .scale_factor() as f64; + + self.events_sink.push_window_event( + WindowEvent::Touch(Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Ended, + location: touch_point.location.to_physical(scale_factor), + force: None, + id: id as u64, + }), + window_id, + ); } -} -/// The data used by touch handlers. -pub(super) struct TouchInner { - /// Current touch points. - touch_points: Vec, -} + fn motion( + &mut self, + _: &Connection, + _: &QueueHandle, + touch: &WlTouch, + _: u32, + id: i32, + position: (f64, f64), + ) { + let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); + + // Remove the touch point. + let mut touch_point = match seat_state.touch_map.get_mut(&id) { + Some(touch_point) => touch_point, + None => return, + }; + + let window_id = wayland::make_wid(&touch_point.surface); + let scale_factor = touch_point + .surface + .data::() + .expect("failed to get surface data.") + .scale_factor() as f64; + + touch_point.location = LogicalPosition::::from(position); -impl TouchInner { - fn new() -> Self { - Self { - touch_points: Vec::new(), + self.events_sink.push_window_event( + WindowEvent::Touch(Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Cancelled, + location: touch_point.location.to_physical(scale_factor), + force: None, + id: id as u64, + }), + window_id, + ); + } + + fn cancel(&mut self, _: &Connection, _: &QueueHandle, touch: &WlTouch) { + let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); + + for (id, touch_point) in seat_state.touch_map.drain() { + let window_id = wayland::make_wid(&touch_point.surface); + let scale_factor = touch_point + .surface + .data::() + .expect("failed to get surface data.") + .scale_factor() as f64; + + let location = touch_point.location.to_physical(scale_factor); + + self.events_sink.push_window_event( + WindowEvent::Touch(Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Cancelled, + location, + force: None, + id: id as u64, + }), + window_id, + ); } } + + fn shape( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlTouch, + _: i32, + _: f64, + _: f64, + ) { + // Blank. + } + + fn orientation(&mut self, _: &Connection, _: &QueueHandle, _: &WlTouch, _: i32, _: f64) { + // Blank. + } } -/// Location of touch press. -pub(super) struct TouchPoint { - /// A surface where the touch point is located. - surface: WlSurface, +/// The state of the touch point. +#[derive(Debug)] +pub struct TouchPoint { + /// The surface on which the point is present. + pub surface: WlSurface, - /// Location of the touch point. - position: LogicalPosition, + /// The location of the point on the surface. + pub location: LogicalPosition, +} - /// Id. - id: i32, +pub trait TouchDataExt { + fn seat(&self) -> &WlSeat; } -impl TouchPoint { - pub fn new(surface: WlSurface, position: LogicalPosition, id: i32) -> Self { - Self { - surface, - position, - id, - } +impl TouchDataExt for WlTouch { + fn seat(&self) -> &WlSeat { + self.data::() + .expect("failed to get touch data.") + .seat() } } + +sctk::delegate_touch!(WinitState); diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs new file mode 100644 index 00000000000..f5c9f7dc653 --- /dev/null +++ b/src/platform_impl/linux/wayland/state.rs @@ -0,0 +1,392 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::error::Error; +use std::ops::Deref; +use std::sync::{Arc, Mutex}; + +use sctk::reexports::calloop::LoopHandle; +use sctk::reexports::client::backend::ObjectId; +use sctk::reexports::client::globals::BindError; +use sctk::reexports::client::globals::GlobalList; +use sctk::reexports::client::protocol::wl_output::WlOutput; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Dispatch; +use sctk::reexports::client::{delegate_dispatch, Proxy}; +use sctk::reexports::client::{Connection, QueueHandle}; +use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; +use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1; +use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1; + +use sctk::compositor::{CompositorHandler, CompositorState}; +use sctk::globals::GlobalData; +use sctk::output::{OutputHandler, OutputState}; +use sctk::registry::{ProvidesRegistryState, RegistryState}; +use sctk::seat::pointer::ThemedPointer; +use sctk::seat::SeatState; +use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler, XdgWindowState}; +use sctk::shell::xdg::XdgShellState; +use sctk::shm::{ShmHandler, ShmState}; + +use crate::dpi::LogicalSize; + +use super::event_loop::sink::EventSink; +use super::output::MonitorHandle; +use super::seat::{ + RelativePointerState, TextInputState, WinitPointerData, WinitPointerDataExt, WinitSeatState, +}; +use super::window::{WindowRequests, WindowState}; +use super::xdg_activation::XdgActivationState; +use super::WindowId; + +pub struct PointerConstraintsState { + pointer_constraints: ZwpPointerConstraintsV1, +} + +impl Deref for PointerConstraintsState { + type Target = ZwpPointerConstraintsV1; + fn deref(&self) -> &Self::Target { + &self.pointer_constraints + } +} + +impl Dispatch for PointerConstraintsState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpPointerConstraintsV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for PointerConstraintsState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpLockedPointerV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for PointerConstraintsState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpConfinedPointerV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl PointerConstraintsState { + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let pointer_constraints = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { + pointer_constraints, + }) + } +} + +delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState); +delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState); +delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState); + +/// Winit's Wayland state. +pub struct WinitState { + /// The WlRegistry. + pub registry_state: RegistryState, + + /// The state of the WlOutput handling. + pub output_state: OutputState, + + /// The compositor state which is used to create new windows and regions. + pub compositor_state: Arc, + + /// The seat state responsible for all sorts of input. + pub seat_state: SeatState, + + /// The shm for software buffers, such as cursors. + pub shm_state: ShmState, + + /// The XDG shell that is used for widnows. + pub xdg_shell_state: XdgShellState, + pub xdg_window_state: RefCell, + + /// The currently present windows. + pub windows: RefCell>>>, + + /// The requests from the `Window` to EventLoop, such as close operations and redraw requests. + pub window_requests: RefCell>>>, + + // TODO(kchibisov) consider using the calloop's channel. + /// The events that were generated directly from the window. + pub window_events_sink: Arc>, + + /// The update for the `windows` comming from the compositor. + pub window_compositor_updates: Vec, + + /// Currently handled seats. + pub seats: HashMap, + + /// Currently present cursor surfaces. + pub pointer_surfaces: HashMap>>, + + /// The state of the text input on the client. + pub text_input_state: Option, + + /// Observed monitors. + pub monitors: Arc>>, + + /// Sink to accumulate window events from the compositor, which is latter dispatched in + /// event loop run. + pub events_sink: EventSink, + + /// Xdg activation. + pub xdg_activation: Option, + + /// Relative pointer. + pub relative_pointer: Option, + + /// Pointer constraints to handle pointer locking and confining. + pub pointer_constraints: Option>, + + /// Loop handle to re-register event sources, such as keyboard repeat. + pub loop_handle: LoopHandle<'static, Self>, +} + +impl WinitState { + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + loop_handle: LoopHandle<'static, WinitState>, + ) -> Result> { + let registry_state = RegistryState::new(globals); + + let output_state = OutputState::new(globals, queue_handle); + let monitors = output_state.outputs().map(MonitorHandle::new).collect(); + + let seat_state = SeatState::new(globals, queue_handle); + + let mut seats = HashMap::new(); + for seat in seat_state.seats() { + seats.insert(seat.id(), WinitSeatState::new()); + } + + Ok(Self { + registry_state, + compositor_state: Arc::new(CompositorState::bind(globals, queue_handle)?), + output_state, + seat_state, + shm_state: ShmState::bind(globals, queue_handle)?, + + xdg_shell_state: XdgShellState::bind(globals, queue_handle)?, + xdg_window_state: RefCell::new(XdgWindowState::bind(globals, queue_handle)), + xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(), + + windows: Default::default(), + window_requests: Default::default(), + window_compositor_updates: Vec::new(), + window_events_sink: Default::default(), + + seats, + text_input_state: TextInputState::new(globals, queue_handle).ok(), + + relative_pointer: RelativePointerState::new(globals, queue_handle).ok(), + pointer_constraints: PointerConstraintsState::new(globals, queue_handle) + .map(Arc::new) + .ok(), + pointer_surfaces: Default::default(), + + monitors: Arc::new(Mutex::new(monitors)), + events_sink: EventSink::new(), + loop_handle, + }) + } +} + +impl ShmHandler for WinitState { + fn shm_state(&mut self) -> &mut ShmState { + &mut self.shm_state + } +} + +impl WindowHandler for WinitState { + fn request_close(&mut self, _: &Connection, _: &QueueHandle, window: &Window) { + let window_id = super::make_wid(window.wl_surface()); + let pos = if let Some(pos) = self + .window_compositor_updates + .iter() + .position(|update| update.window_id == window_id) + { + pos + } else { + self.window_compositor_updates + .push(WindowCompositorUpdate::new(window_id)); + self.window_compositor_updates.len() - 1 + }; + + self.window_compositor_updates[pos].close_window = true; + } + + fn configure( + &mut self, + _: &Connection, + _: &QueueHandle, + window: &Window, + configure: WindowConfigure, + _serial: u32, + ) { + let window_id = super::make_wid(window.wl_surface()); + + let pos = if let Some(pos) = self + .window_compositor_updates + .iter() + .position(|update| update.window_id == window_id) + { + pos + } else { + self.window_compositor_updates + .push(WindowCompositorUpdate::new(window_id)); + self.window_compositor_updates.len() - 1 + }; + + let new_size = configure.new_size.map(Into::into); + self.window_compositor_updates[pos].size = new_size; + + // Populate the configure to the window. + // + // XXX the size on the window will be updated right before dispatching the size to the user. + self.windows + .borrow_mut() + .get_mut(&window_id) + .expect("got configure for dead window.") + .lock() + .unwrap() + .configure(configure); + } +} + +impl OutputHandler for WinitState { + fn output_state(&mut self) -> &mut OutputState { + &mut self.output_state + } + + fn new_output(&mut self, _: &Connection, _: &QueueHandle, output: WlOutput) { + self.monitors + .lock() + .unwrap() + .push(MonitorHandle::new(output)); + } + + fn update_output(&mut self, _: &Connection, _: &QueueHandle, updated: WlOutput) { + let mut monitors = self.monitors.lock().unwrap(); + let updated = MonitorHandle::new(updated); + if let Some(pos) = monitors.iter().position(|output| output == &updated) { + monitors[pos] = updated + } else { + monitors.push(updated) + } + } + + fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle, removed: WlOutput) { + let mut monitors = self.monitors.lock().unwrap(); + let removed = MonitorHandle::new(removed); + if let Some(pos) = monitors.iter().position(|output| output == &removed) { + monitors.remove(pos); + } + } +} + +impl CompositorHandler for WinitState { + fn scale_factor_changed( + &mut self, + _: &Connection, + _: &QueueHandle, + surface: &WlSurface, + scale_factor: i32, + ) { + // Check if the cursor surface. + let window_id = super::make_wid(surface); + + if self.windows.borrow().contains_key(&window_id) { + // The scale factor change is for the window. + let pos = if let Some(pos) = self + .window_compositor_updates + .iter() + .position(|update| update.window_id == window_id) + { + pos + } else { + self.window_compositor_updates + .push(WindowCompositorUpdate::new(window_id)); + self.window_compositor_updates.len() - 1 + }; + + self.window_compositor_updates[pos].scale_factor = Some(scale_factor); + } else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) { + // Get the window, where the pointer resides right now. + let focused_window = match pointer.pointer().winit_data().focused_window() { + Some(focused_window) => focused_window, + None => return, + }; + + if let Some(window_state) = self.windows.borrow().get(&focused_window) { + window_state.lock().unwrap().reload_cursor() + } + } + } + + fn frame(&mut self, _: &Connection, _: &QueueHandle, _: &WlSurface, _: u32) {} +} + +impl ProvidesRegistryState for WinitState { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + + sctk::registry_handlers![OutputState, SeatState]; +} + +// The window update comming from the compositor. +#[derive(Debug, Clone, Copy)] +pub struct WindowCompositorUpdate { + /// The id of the window this updates belongs to. + pub window_id: WindowId, + + /// New window size. + pub size: Option>, + + /// New scale factor. + pub scale_factor: Option, + + /// Close the window. + pub close_window: bool, +} + +impl WindowCompositorUpdate { + fn new(window_id: WindowId) -> Self { + Self { + window_id, + size: None, + scale_factor: None, + close_window: false, + } + } +} + +sctk::delegate_compositor!(WinitState); +sctk::delegate_output!(WinitState); +sctk::delegate_registry!(WinitState); +sctk::delegate_shm!(WinitState); +sctk::delegate_xdg_shell!(WinitState); +sctk::delegate_xdg_window!(WinitState); diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index a775b307950..8a60add4175 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -1,82 +1,84 @@ -use std::collections::VecDeque; +//! The Wayland window. + 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 raw_window_handle::{ RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, }; -use sctk::window::Decorations; -use wayland_protocols::viewporter::client::wp_viewporter::WpViewporter; + +use sctk::reexports::calloop; +use sctk::reexports::client::protocol::wl_display::WlDisplay; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Proxy; +use sctk::reexports::client::QueueHandle; + +use sctk::compositor::{CompositorState, Region, SurfaceData}; +use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; +use sctk::shell::xdg::window::Window as SctkWindow; use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; +use crate::event::{Ime, WindowEvent}; use crate::platform_impl::{ - wayland::protocols::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, - wayland::protocols::wp_fractional_scale_v1, Fullscreen, MonitorHandle as PlatformMonitorHandle, - OsError, PlatformSpecificWindowBuilderAttributes as PlatformAttributes, + Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, + PlatformSpecificWindowBuilderAttributes as PlatformAttributes, }; use crate::window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, }; -use super::env::WindowingFeatures; -use super::event_loop::WinitState; -use super::output::{MonitorHandle, OutputManagerHandle}; +use super::event_loop::sink::EventSink; +use super::output::MonitorHandle; +use super::state::WinitState; +use super::xdg_activation::XdgActivationTokenData; use super::{EventLoopWindowTarget, WindowId}; -pub mod shim; +mod state; -use shim::{ - FractionalScalingState, WindowCompositorUpdate, WindowHandle, WindowRequest, WindowUserRequest, -}; +pub use state::WindowState; -#[cfg(feature = "sctk-adwaita")] -pub type WinitFrame = sctk_adwaita::AdwaitaFrame; -#[cfg(not(feature = "sctk-adwaita"))] -pub type WinitFrame = sctk::window::FallbackFrame; +// #[cfg(feature = "sctk-adwaita")] +// pub type WinitFrame = sctk_adwaita::AdwaitaFrame; +// #[cfg(not(feature = "sctk-adwaita"))] +// pub type WinitFrame = sctk::window::FallbackFrame; -#[cfg(feature = "sctk-adwaita")] -const WAYLAND_CSD_THEME_ENV_VAR: &str = "WINIT_WAYLAND_CSD_THEME"; +// #[cfg(feature = "sctk-adwaita")] +// const WAYLAND_CSD_THEME_ENV_VAR: &str = "WINIT_WAYLAND_CSD_THEME"; +/// The Wayland window. pub struct Window { + /// Reference to the underlying SCTK window. + window: SctkWindow, + /// Window id. window_id: WindowId, - /// The Wayland display. - display: Display, - - /// The underlying wl_surface. - surface: WlSurface, + /// The state of the window. + window_state: Arc>, - /// The scale factor. - scale_factor: Arc>, + /// Compositor to handle WlRegion stuff. + compositor: Arc, - /// The current window size. - size: Arc>>, + /// The wayland display used solely for raw window handle. + display: WlDisplay, - /// A handle to output manager. - output_manager_handle: OutputManagerHandle, - - /// Event loop proxy to wake it up. - event_loop_awakener: calloop::ping::Ping, + /// Xdg activation to request user attention. + xdg_activation: Option, - /// Fullscreen state. - fullscreen: Arc, + /// The state of the requested attention from the `xdg_activation`. + attention_requested: Arc, - /// Maximized state. - maximized: Arc, + /// Handle to the main queue to perform requests. + queue_handle: QueueHandle, - /// Available windowing features. - windowing_features: WindowingFeatures, + min_size: Mutex>>, + max_size: Mutex>>, - /// Requests that SCTK window should perform. - window_requests: Arc>>, + // TODO(kchibisov) (truncate the title). + /// The title of the window from the Window::set_title. + title: Mutex, /// Whether the window is resizeable. resizeable: AtomicBool, @@ -84,11 +86,17 @@ pub struct Window { /// Whether the window is decorated. decorated: AtomicBool, - /// Grabbing mode. - cursor_grab_mode: Mutex, + /// Window requests to the event loop. + window_requests: Arc>, - /// Whether the window has keyboard focus. - has_focus: Arc, + /// Observed monitors. + monitors: Arc>>, + + /// Source to wake-up the event-loop for window requests. + event_loop_awakener: calloop::ping::Ping, + + /// The event sink to deliver sythetic events. + window_events_sink: Arc>, } impl Window { @@ -97,263 +105,134 @@ impl Window { attributes: WindowAttributes, platform_attributes: PlatformAttributes, ) -> Result { - let viewporter = event_loop_window_target.env.get_global::(); - let fractional_scale_manager = event_loop_window_target - .env - .get_global::(); - - // Create surface and register callback for the scale factor changes. - let mut scale_factor = 1.; - let (surface, fractional_scaling_state) = - if let (Some(viewporter), Some(fractional_scale_manager)) = - (viewporter, fractional_scale_manager) - { - let surface = event_loop_window_target.env.create_surface().detach(); - let fractional_scale = fractional_scale_manager.get_fractional_scale(&surface); - - let window_id = super::make_wid(&surface); - fractional_scale.quick_assign(move |_, event, mut dispatch_data| { - let wp_fractional_scale_v1::Event::PreferredScale { scale } = event; - let winit_state = dispatch_data.get::().unwrap(); - apply_scale(window_id, scale as f64 / 120., winit_state); - }); + let queue_handle = event_loop_window_target.queue_handle.clone(); + let state = event_loop_window_target.state.borrow(); - let fractional_scale = fractional_scale.detach(); - let viewport = viewporter.get_viewport(&surface).detach(); - let fractional_scaling_state = - FractionalScalingState::new(viewport, fractional_scale); + let monitors = state.monitors.clone(); - (surface, Some(fractional_scaling_state)) - } else { - 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(); + let surface = state.compositor_state.create_surface(&queue_handle); + let compositor = state.compositor_state.clone(); + let xdg_activation = state + .xdg_activation + .as_ref() + .map(|activation_state| activation_state.global().clone()); + let display = event_loop_window_target.connection.display(); - // Get the window that received the event. - let window_id = super::make_wid(&surface); - apply_scale(window_id, scale as f64, winit_state); - surface.set_buffer_scale(scale); - }) - .detach(); + // XXX The initial scale factor must be 1, but it might cause sizing issues on HiDPI. + let size: LogicalSize = attributes + .inner_size + .map(|size| size.to_logical::(1.)) + .unwrap_or((800, 600).into()); - scale_factor = sctk::get_surface_scale_factor(&surface) as _; + let title = Mutex::new(attributes.title.clone()); + let mut window_builder = SctkWindow::builder().title(attributes.title); - (surface, None) - }; + let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.)); - let window_id = super::make_wid(&surface); - let maximized = Arc::new(AtomicBool::new(false)); - let maximized_clone = maximized.clone(); - let fullscreen = Arc::new(AtomicBool::new(false)); - let fullscreen_clone = fullscreen.clone(); + let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.)); - let (width, height) = attributes - .inner_size - .map(|size| size.to_logical::(scale_factor).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_compositor_update = winit_state - .window_compositor_updates - .get_mut(&window_id) - .unwrap(); - - let mut window_user_requests = winit_state - .window_user_requests - .get_mut(&window_id) - .unwrap(); - - match event { - Event::Refresh => { - window_user_requests.refresh_frame = true; - } - Event::Configure { new_size, states } => { - let is_maximized = states.contains(&State::Maximized); - maximized_clone.store(is_maximized, Ordering::Relaxed); - let is_fullscreen = states.contains(&State::Fullscreen); - fullscreen_clone.store(is_fullscreen, Ordering::Relaxed); - - window_user_requests.refresh_frame = true; - if let Some((w, h)) = new_size { - window_compositor_update.size = Some(LogicalSize::new(w, h)); - } - } - Event::Close => { - window_compositor_update.close_window = true; - } - } - }, - ) - .map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?; - - // Set CSD frame config from theme if specified, - // otherwise use upstream automatic selection. - #[cfg(feature = "sctk-adwaita")] - if let Some(theme) = attributes.preferred_theme.or_else(|| { - std::env::var(WAYLAND_CSD_THEME_ENV_VAR) - .ok() - .and_then(|s| s.as_str().try_into().ok()) - }) { - window.set_frame_config(theme.into()); - } + if attributes.resizable { + if let Some((min_width, min_height)) = min_size.map(Into::into) { + window_builder = window_builder.min_size((min_width, min_height)); + } - // Set decorations. - if attributes.decorations { - window.set_decorate(Decorations::FollowServer); + if let Some((max_width, max_height)) = max_size.map(Into::into) { + window_builder = window_builder.max_size((max_width, max_height)); + } } else { - window.set_decorate(Decorations::None); + // Non-resizable implies that the min and max sizes are set to the same value. + window_builder = window_builder.min_size(size.into()); + window_builder = window_builder.max_size(size.into()); } - // Min dimensions. - let min_size = attributes - .min_inner_size - .map(|size| size.to_logical::(scale_factor).into()); - window.set_min_size(min_size); - - // Max dimensions. - let max_size = attributes - .max_inner_size - .map(|size| size.to_logical::(scale_factor).into()); - window.set_max_size(max_size); - - // Set Wayland specific window attributes. - if let Some(name) = platform_attributes.name { - window.set_app_id(name.general); + if let Some(name) = platform_attributes.name.map(|name| name.general) { + window_builder = window_builder.app_id(name); } - // 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.map(Into::into) { - Some(Fullscreen::Exclusive(_)) => { - warn!("`Fullscreen::Exclusive` is ignored on Wayland") - } - Some(Fullscreen::Borderless(monitor)) => { - let monitor = monitor.and_then(|monitor| match monitor { - PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), - #[cfg(x11_platform)] - PlatformMonitorHandle::X(_) => None, - }); + let window_id = super::make_wid(&surface); + let window = window_builder + .map( + &queue_handle, + &state.xdg_shell_state, + &mut state.xdg_window_state.borrow_mut(), + surface, + ) + .map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?; - window.set_fullscreen(monitor.as_ref()); - } - None => { - if attributes.maximized { - window.set_maximized(); - } - } - } + let window_state = Arc::new(Mutex::new(WindowState::new( + event_loop_window_target.connection.clone(), + &event_loop_window_target.queue_handle, + &event_loop_window_target.state.borrow(), + size, + window.clone(), + ))); - // Without this commit here at least on kwin 5.23.3 the initial configure - // will have a size (1,1), the second configure including the decoration - // mode will have the min_size as its size. With this commit the initial - // configure will have no size, the application will draw it's content - // with the initial size and everything works as expected afterwards. - // - // The window commit must be after setting on top level properties, but right before any - // buffer attachments commits. - window.surface().commit(); + state + .windows + .borrow_mut() + .insert(window_id, window_state.clone()); - let size = Arc::new(Mutex::new(LogicalSize::new(width, height))); - let has_focus = Arc::new(AtomicBool::new(true)); + let window_requests = WindowRequests { + redraw_requested: true, + ..Default::default() + }; + let window_requests = Arc::new(Mutex::new(window_requests)); - // We should trigger redraw and commit the surface for the newly created window. - let mut window_user_request = WindowUserRequest::new(); - window_user_request.refresh_frame = true; - window_user_request.redraw_requested = true; + let window_events_sink = state.window_events_sink.clone(); - let window_id = super::make_wid(&surface); - let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64))); + state + .window_requests + .borrow_mut() + .insert(window_id, window_requests.clone()); - // Create a handle that performs all the requests on underlying sctk a window. - let window_handle = WindowHandle::new( - &event_loop_window_target.env, - window, - size.clone(), - has_focus.clone(), - fractional_scaling_state, - scale_factor, - window_requests.clone(), - ); - - // Set opaque region. - window_handle.set_transparent(attributes.transparent); - - // Set resizable state, so we can determine how to handle `Window::set_inner_size`. - window_handle.is_resizable.set(attributes.resizable); - - let mut winit_state = event_loop_window_target.state.borrow_mut(); - - winit_state.window_map.insert(window_id, window_handle); - - // On Wayland window doesn't have Focus by default and it'll get it later on. So be - // explicit here. - winit_state - .event_sink - .push_window_event(crate::event::WindowEvent::Focused(false), window_id); - - // Add state for the window. - winit_state - .window_user_requests - .insert(window_id, window_user_request); - winit_state - .window_compositor_updates - .insert(window_id, WindowCompositorUpdate::new()); - - let windowing_features = event_loop_window_target.windowing_features; - - // 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. { + // XXX Release the immutable borrow. + std::mem::drop(state); let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut(); + let mut state = event_loop_window_target.state.borrow_mut(); let event_queue = wayland_source.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(); + // Do a roundtrip. + event_queue.roundtrip(&mut state).map_err(|_| { + os_error!(OsError::WaylandMisc( + "failed to do initial roundtrip for the window." + )) + })?; + + // XXX Wait for the initial configure to arrive. + while !window_state.lock().unwrap().is_configured() { + event_queue.blocking_dispatch(&mut state).map_err(|_| { + os_error!(OsError::WaylandMisc( + "failed to dispatch waiting for initial configure." + )) + })?; + } + } - let output_manager_handle = event_loop_window_target.output_manager.handle(); + // Wake-up event loop, so it'll send initial redraw requested. + let event_loop_awakener = event_loop_window_target.event_loop_awakener.clone(); + event_loop_awakener.ping(); - let window = Self { + Ok(Self { + title, + window, + display, + monitors, 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, - maximized, - windowing_features, + compositor, + window_state, + queue_handle, + xdg_activation, + min_size: Mutex::new(min_size), + max_size: Mutex::new(max_size), resizeable: AtomicBool::new(attributes.resizable), + attention_requested: Arc::new(AtomicBool::new(false)), + // XXX decorations are not supported yet. decorated: AtomicBool::new(attributes.decorations), - cursor_grab_mode: Mutex::new(CursorGrabMode::None), - has_focus, - scale_factor: window_handle.scale_factor.clone(), - }; - - Ok(window) + event_loop_awakener, + window_requests, + window_events_sink, + }) } } @@ -365,12 +244,8 @@ impl Window { #[inline] pub fn set_title(&self, title: &str) { - self.send_request(WindowRequest::Title(title.to_owned())); - } - - #[inline] - pub fn set_transparent(&self, transparent: bool) { - self.send_request(WindowRequest::Transparent(transparent)); + *self.title.lock().unwrap() = title.into(); + self.window.set_title(title) } #[inline] @@ -398,44 +273,51 @@ impl Window { // Not possible on Wayland. } + #[inline] pub fn inner_size(&self) -> PhysicalSize { - self.size.lock().unwrap().to_physical(self.scale_factor()) + let size = self.window_state.lock().unwrap().size; + let scale_factor = self.scale_factor(); + size.to_physical(scale_factor) } #[inline] pub fn request_redraw(&self) { - self.send_request(WindowRequest::Redraw); + self.window_requests.lock().unwrap().redraw_requested = true; + self.event_loop_awakener.ping(); } #[inline] pub fn outer_size(&self) -> PhysicalSize { - self.size.lock().unwrap().to_physical(self.scale_factor()) + let size = self.window_state.lock().unwrap().size; + let scale_factor = self.scale_factor(); + size.to_physical(scale_factor) } #[inline] pub fn set_inner_size(&self, size: Size) { let scale_factor = self.scale_factor(); - let size = size.to_logical::(scale_factor); - *self.size.lock().unwrap() = size; - - self.send_request(WindowRequest::FrameSize(size)); + self.window_state.lock().unwrap().size = size; + // TODO(kchibisov) CSD set_geometry + self.request_redraw(); } #[inline] - pub fn set_min_inner_size(&self, dimensions: Option) { + pub fn set_min_inner_size(&self, min_size: Option) { let scale_factor = self.scale_factor(); - let size = dimensions.map(|size| size.to_logical::(scale_factor)); - - self.send_request(WindowRequest::MinSize(size)); + let min_size = min_size.map(|size| size.to_logical(scale_factor)); + *self.min_size.lock().unwrap() = min_size; + self.window + .set_min_size(min_size.map(|size| (size.width, size.height))) } #[inline] - pub fn set_max_inner_size(&self, dimensions: Option) { + pub fn set_max_inner_size(&self, max_size: Option) { let scale_factor = self.scale_factor(); - let size = dimensions.map(|size| size.to_logical::(scale_factor)); - - self.send_request(WindowRequest::MaxSize(size)); + let max_size = max_size.map(|size| size.to_logical(scale_factor)); + *self.max_size.lock().unwrap() = max_size; + self.window + .set_max_size(max_size.map(|size| (size.width, size.height))) } #[inline] @@ -448,10 +330,46 @@ impl Window { warn!("`set_resize_increments` is not implemented for Wayland"); } + #[inline] + pub fn set_transparent(&self, transparent: bool) { + self.window_state + .lock() + .unwrap() + .set_transparent(transparent); + } + + #[inline] + pub fn has_focus(&self) -> bool { + self.window_state.lock().unwrap().has_focus() + } + + #[inline] + pub fn is_minimized(&self) -> Option { + None + } + + #[inline] + pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { + self.window_state + .lock() + .unwrap() + .drag_resize_window(direction) + } + #[inline] pub fn set_resizable(&self, resizable: bool) { self.resizeable.store(resizable, Ordering::Relaxed); - self.send_request(WindowRequest::Resizeable(resizable)); + if resizable { + // Restore min/max sizes of the window. + let min_size = *self.min_size.lock().unwrap(); + let max_size = *self.max_size.lock().unwrap(); + self.set_min_inner_size(min_size.map(Into::into)); + self.set_max_inner_size(max_size.map(Into::into)); + } else { + let size = self.window_state.lock().unwrap().size.into(); + self.set_min_inner_size(Some(size)); + self.set_max_inner_size(Some(size)); + } } #[inline] @@ -460,22 +378,30 @@ impl Window { } #[inline] - pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} + pub fn set_enabled_buttons(&self, _buttons: WindowButtons) { + // TODO(kchibisov) v5 of the xdg_shell allows that. + } #[inline] pub fn enabled_buttons(&self) -> WindowButtons { + // TODO(kchibisov) v5 of the xdg_shell allows that. WindowButtons::all() } #[inline] pub fn scale_factor(&self) -> f64 { - *self.scale_factor.lock().unwrap() + let data = self + .window + .wl_surface() + .data::() + .expect("failed to get surface data."); + + data.scale_factor() as f64 } #[inline] - pub fn set_decorations(&self, decorate: bool) { - self.decorated.store(decorate, Ordering::Relaxed); - self.send_request(WindowRequest::Decorate(decorate)); + pub fn set_decorations(&self, _decorate: bool) { + // TODO(kchibibov) CSD are not there yet. } #[inline] @@ -483,36 +409,50 @@ impl Window { self.decorated.load(Ordering::Relaxed) } - #[inline] - pub fn is_minimized(&self) -> Option { - None - } - #[inline] pub fn set_minimized(&self, minimized: bool) { // You can't unminimize the window on Wayland. if !minimized { + warn!("Unminimizing is ignored on Wayland."); return; } - self.send_request(WindowRequest::Minimize); + self.window.set_mimimized(); } #[inline] pub fn is_maximized(&self) -> bool { - self.maximized.load(Ordering::Relaxed) + self.window_state + .lock() + .unwrap() + .last_configure + .as_ref() + .map(|last_configure| last_configure.is_maximized()) + .unwrap_or_default() } #[inline] pub fn set_maximized(&self, maximized: bool) { - self.send_request(WindowRequest::Maximize(maximized)); + if maximized { + self.window.set_maximized() + } else { + self.window.unset_maximized() + } } #[inline] pub(crate) fn fullscreen(&self) -> Option { - if self.fullscreen.load(Ordering::Relaxed) { + let is_fullscreen = self + .window_state + .lock() + .unwrap() + .last_configure + .as_ref() + .map(|last_configure| last_configure.is_fullscreen()) + .unwrap_or_default(); + + if is_fullscreen { let current_monitor = self.current_monitor().map(PlatformMonitorHandle::Wayland); - Some(Fullscreen::Borderless(current_monitor)) } else { None @@ -521,195 +461,223 @@ impl Window { #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { - let fullscreen_request = match fullscreen { + match fullscreen { Some(Fullscreen::Exclusive(_)) => { warn!("`Fullscreen::Exclusive` is ignored on Wayland"); - return; } Some(Fullscreen::Borderless(monitor)) => { - let monitor = monitor.and_then(|monitor| match monitor { + let output = monitor.and_then(|monitor| match monitor { PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), #[cfg(x11_platform)] PlatformMonitorHandle::X(_) => None, }); - WindowRequest::Fullscreen(monitor) + self.window.set_fullscreen(output.as_ref()) } - None => WindowRequest::UnsetFullscreen, - }; - - self.send_request(fullscreen_request); + None => self.window.unset_fullscreen(), + } } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - self.send_request(WindowRequest::NewCursorIcon(cursor)); + self.window_state.lock().unwrap().set_cursor(cursor); } #[inline] pub fn set_cursor_visible(&self, visible: bool) { - self.send_request(WindowRequest::ShowCursor(visible)); + self.window_state + .lock() + .unwrap() + .set_cursor_visible(visible); } - #[inline] - pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { - if !self.windowing_features.pointer_constraints() { - if mode == CursorGrabMode::None { - return Ok(()); + pub fn request_user_attention(&self, request_type: Option) { + let xdg_activation = match self.xdg_activation.as_ref() { + Some(xdg_activation) => xdg_activation, + None => { + warn!("`request_user_attention` isn't supported"); + return; } + }; - return Err(ExternalError::NotSupported(NotSupportedError::new())); + // Urgency is only removed by the compositor and there's no need to raise urgency when it + // was already raised. + if request_type.is_none() || self.attention_requested.load(Ordering::Relaxed) { + return; } - *self.cursor_grab_mode.lock().unwrap() = mode; - self.send_request(WindowRequest::SetCursorGrabMode(mode)); - - Ok(()) + self.attention_requested.store(true, Ordering::Relaxed); + let surface = self.surface().clone(); + let data = + XdgActivationTokenData::new(surface.clone(), Arc::downgrade(&self.attention_requested)); + let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); + xdg_activation_token.set_surface(&surface); + xdg_activation_token.commit(); } - pub fn request_user_attention(&self, request_type: Option) { - if !self.windowing_features.xdg_activation() { - warn!("`request_user_attention` isn't supported"); - return; - } - - self.send_request(WindowRequest::Attention(request_type)); + #[inline] + pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + self.window_state.lock().unwrap().set_cursor_grab(mode) } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { - // Positon can be set only for locked cursor. - if *self.cursor_grab_mode.lock().unwrap() != CursorGrabMode::Locked { - return Err(ExternalError::Os(os_error!(OsError::WaylandMisc( - "cursor position can be set only for locked cursor." - )))); - } - let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); - self.send_request(WindowRequest::SetLockedCursorPosition(position)); - - Ok(()) + self.window_state + .lock() + .unwrap() + .set_cursor_position(position) + // Request redraw on success, since the state is double buffered. + .map(|_| self.request_redraw()) } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { - self.send_request(WindowRequest::DragWindow); - - Ok(()) - } - - #[inline] - pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) + self.window_state.lock().unwrap().drag_window() } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { - self.send_request(WindowRequest::PassthroughMouseInput(!hittest)); + let surface = self.window.wl_surface(); - Ok(()) + if hittest { + surface.set_input_region(None); + Ok(()) + } else { + let region = Region::new(&*self.compositor).map_err(|_| { + ExternalError::Os(os_error!(OsError::WaylandMisc( + "failed to set input region." + ))) + })?; + region.add(0, 0, 0, 0); + surface.set_input_region(Some(region.wl_region())); + Ok(()) + } } #[inline] pub fn set_ime_position(&self, position: Position) { let scale_factor = self.scale_factor(); - let position = position.to_logical(scale_factor); - self.send_request(WindowRequest::ImePosition(position)); + let window_state = self.window_state.lock().unwrap(); + if window_state.ime_allowed() { + let position = position.to_logical(scale_factor); + window_state.set_ime_position(position); + } } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { - self.send_request(WindowRequest::AllowIme(allowed)); + let window_state = self.window_state.lock().unwrap(); + + if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed) { + let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled }); + self.window_events_sink + .lock() + .unwrap() + .push_window_event(event, self.window_id); + self.event_loop_awakener.ping(); + } } #[inline] pub fn set_ime_purpose(&self, purpose: ImePurpose) { - self.send_request(WindowRequest::ImePurpose(purpose)); + self.window_state.lock().unwrap().set_ime_purpose(purpose); } #[inline] - pub fn display(&self) -> &Display { + pub fn display(&self) -> &WlDisplay { &self.display } #[inline] pub fn surface(&self) -> &WlSurface { - &self.surface + self.window.wl_surface() } #[inline] pub fn current_monitor(&self) -> Option { - let output = sctk::get_surface_outputs(&self.surface).last()?.clone(); - Some(MonitorHandle::new(output)) + let data = self.window.wl_surface().data::()?; + + data.outputs().next().map(MonitorHandle::new) } #[inline] - pub fn available_monitors(&self) -> VecDeque { - self.output_manager_handle.available_outputs() + pub fn available_monitors(&self) -> Vec { + self.monitors.lock().unwrap().clone() } #[inline] pub fn primary_monitor(&self) -> Option { + // XXX there's no such concept on Wayland. None } #[inline] pub fn raw_window_handle(&self) -> RawWindowHandle { let mut window_handle = WaylandWindowHandle::empty(); - window_handle.surface = self.surface.as_ref().c_ptr() as *mut _; + window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _; RawWindowHandle::Wayland(window_handle) } #[inline] pub fn raw_display_handle(&self) -> RawDisplayHandle { let mut display_handle = WaylandDisplayHandle::empty(); - display_handle.display = self.display.get_display_ptr() as *mut _; + display_handle.display = self.display.id().as_ptr() as *mut _; RawDisplayHandle::Wayland(display_handle) } #[inline] - fn send_request(&self, request: WindowRequest) { - self.window_requests.lock().unwrap().push(request); - self.event_loop_awakener.ping(); - } - - #[inline] - pub fn set_theme(&self, theme: Option) { - self.send_request(WindowRequest::Theme(theme)); + pub fn set_theme(&self, _theme: Option) { + // TODO(kchibisov) CSD } #[inline] pub fn theme(&self) -> Option { + // TODO(kchibisov) CSD None } #[inline] - pub fn has_focus(&self) -> bool { - self.has_focus.load(Ordering::Relaxed) - } - pub fn title(&self) -> String { - String::new() + self.title.lock().unwrap().clone() } } impl Drop for Window { fn drop(&mut self) { - self.send_request(WindowRequest::Close); + self.window_requests.lock().unwrap().closed = true; + self.event_loop_awakener.ping(); } } -#[cfg(feature = "sctk-adwaita")] -impl From for sctk_adwaita::FrameConfig { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => sctk_adwaita::FrameConfig::light(), - Theme::Dark => sctk_adwaita::FrameConfig::dark(), - } +/// The request from the window to the event loop. +#[derive(Clone, Copy, Default)] +pub struct WindowRequests { + /// The window was closed. + pub closed: bool, + + /// Redraw Requested. + pub redraw_requested: bool, +} + +impl WindowRequests { + /// Take the current request, leaving `Default`. + pub fn take(&mut self) -> Self { + std::mem::take(self) } } +// #[cfg(feature = "sctk-adwaita")] +// impl From for sctk_adwaita::FrameConfig { +// fn from(theme: Theme) -> Self { +// match theme { +// Theme::Light => sctk_adwaita::FrameConfig::light(), +// Theme::Dark => sctk_adwaita::FrameConfig::dark(), +// } +// } +// } + impl TryFrom<&str> for Theme { type Error = (); @@ -729,20 +697,3 @@ impl TryFrom<&str> for Theme { } } } - -/// Set pending scale for the provided `window_id`. -fn apply_scale(window_id: WindowId, scale: f64, winit_state: &mut WinitState) { - // Set pending scale factor. - winit_state - .window_compositor_updates - .get_mut(&window_id) - .unwrap() - .scale_factor = Some(scale); - - // Mark that we need a frame refresh on the DPI change. - winit_state - .window_user_requests - .get_mut(&window_id) - .unwrap() - .refresh_frame = true; -} diff --git a/src/platform_impl/linux/wayland/window/shim.rs b/src/platform_impl/linux/wayland/window/shim.rs deleted file mode 100644 index 1cc0a12459a..00000000000 --- a/src/platform_impl/linux/wayland/window/shim.rs +++ /dev/null @@ -1,666 +0,0 @@ -use std::cell::Cell; -use std::mem::ManuallyDrop; -use std::sync::atomic::AtomicBool; -use std::sync::{Arc, Mutex}; - -use sctk::reexports::client::protocol::wl_compositor::WlCompositor; -use sctk::reexports::client::protocol::wl_output::WlOutput; -use sctk::reexports::client::Attached; -use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1; -use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1; - -use sctk::environment::Environment; -use sctk::window::{Decorations, Window}; -use wayland_protocols::viewporter::client::wp_viewport::WpViewport; - -use crate::dpi::{LogicalPosition, LogicalSize}; - -use crate::event::{Ime, WindowEvent}; -use crate::platform_impl::wayland; -use crate::platform_impl::wayland::env::WinitEnv; -use crate::platform_impl::wayland::event_loop::{EventSink, WinitState}; -use crate::platform_impl::wayland::protocols::wp_fractional_scale_v1::WpFractionalScaleV1; -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::{CursorGrabMode, CursorIcon, ImePurpose, Theme, UserAttentionType}; - -use super::WinitFrame; - -/// 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), - - /// Change cursor grabbing mode. - SetCursorGrabMode(CursorGrabMode), - - /// Set cursor position. - SetLockedCursorPosition(LogicalPosition), - - /// Drag window. - DragWindow, - - /// 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), - - /// Enable IME on the given window. - AllowIme(bool), - - /// Set the IME purpose. - ImePurpose(ImePurpose), - - /// Mark the window as opaque. - Transparent(bool), - - /// Request Attention. - /// - /// `None` unsets the attention request. - Attention(Option), - - /// Passthrough mouse input to underlying windows. - PassthroughMouseInput(bool), - - /// Redraw was requested. - Redraw, - - /// Window should be closed. - Close, - - /// Change window theme. - Theme(Option), -} - -// The window update comming from the compositor. -#[derive(Default, Debug, Clone, Copy)] -pub struct WindowCompositorUpdate { - /// New window size. - pub size: Option>, - - /// New scale factor. - pub scale_factor: Option, - - /// Close the window. - pub close_window: bool, -} - -impl WindowCompositorUpdate { - pub fn new() -> Self { - Default::default() - } -} - -/// Pending update to a window requested by the user. -#[derive(Default, Debug, Clone, Copy)] -pub struct WindowUserRequest { - /// Whether `redraw` was requested. - pub redraw_requested: bool, - - /// Wether the frame should be refreshed. - pub refresh_frame: bool, -} - -impl WindowUserRequest { - pub fn new() -> Self { - Default::default() - } -} - -/// A handle to perform operations on SCTK window -/// and react to events. -pub struct WindowHandle { - /// An actual window. - pub window: ManuallyDrop>, - - /// The state of the fractional scaling handlers for the window. - pub fractional_scaling_state: Option, - - /// The scale factor of the window. - pub scale_factor: Arc>, - - /// 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, - - /// Whether the window is resizable. - pub is_resizable: Cell, - - /// Whether the window has keyboard focus. - pub has_focus: Arc, - - /// Allow IME events for that window. - pub ime_allowed: Cell, - - /// IME purpose for that window. - pub ime_purpose: Cell, - - /// Wether the window is transparent. - pub transparent: Cell, - - /// Visible cursor or not. - cursor_visible: Cell, - - /// Cursor confined to the surface. - cursor_grab_mode: Cell, - - /// Pointers over the current surface. - pointers: Vec, - - /// Text inputs on the current surface. - text_inputs: Vec, - - /// XdgActivation object. - xdg_activation: Option>, - - /// Indicator whether user attention is requested. - attention_requested: Cell, - - /// Compositor - compositor: Attached, -} - -impl WindowHandle { - pub fn new( - env: &Environment, - window: Window, - size: Arc>>, - has_focus: Arc, - fractional_scaling_state: Option, - scale_factor: f64, - pending_window_requests: Arc>>, - ) -> Self { - let xdg_activation = env.get_global::(); - // Unwrap is safe, since we can't create window without compositor anyway and won't be - // here. - let compositor = env.get_global::().unwrap(); - - Self { - window: ManuallyDrop::new(window), - fractional_scaling_state, - scale_factor: Arc::new(Mutex::new(scale_factor)), - size, - pending_window_requests, - cursor_icon: Cell::new(CursorIcon::Default), - is_resizable: Cell::new(true), - transparent: Cell::new(false), - cursor_grab_mode: Cell::new(CursorGrabMode::None), - cursor_visible: Cell::new(true), - pointers: Vec::new(), - text_inputs: Vec::new(), - xdg_activation, - attention_requested: Cell::new(false), - compositor, - ime_allowed: Cell::new(false), - ime_purpose: Cell::new(ImePurpose::default()), - has_focus, - } - } - - pub fn scale_factor(&self) -> f64 { - *self.scale_factor.lock().unwrap() - } - - pub fn set_cursor_grab(&self, mode: CursorGrabMode) { - // The new requested state matches the current confine status, return. - let old_mode = self.cursor_grab_mode.replace(mode); - if old_mode == mode { - return; - } - - // Clear old pointer data. - match old_mode { - CursorGrabMode::None => (), - CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()), - CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()), - } - - let surface = self.window.surface(); - match mode { - CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)), - CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)), - CursorGrabMode::None => { - // Current lock/confine was already removed. - } - } - } - - pub fn set_locked_cursor_position(&self, position: LogicalPosition) { - // XXX the cursor locking is ensured inside `Window`. - self.pointers - .iter() - .for_each(|p| p.set_cursor_position(position.x, position.y)); - } - - pub fn set_user_attention(&self, request_type: Option) { - let xdg_activation = match self.xdg_activation.as_ref() { - None => return, - Some(xdg_activation) => xdg_activation, - }; - - // Urgency is only removed by the compositor and there's no need to raise urgency when it - // was already raised. - if request_type.is_none() || self.attention_requested.get() { - return; - } - - let xdg_activation_token = xdg_activation.get_activation_token(); - let surface = self.window.surface(); - let window_id = wayland::make_wid(surface); - let xdg_activation = xdg_activation.clone(); - - xdg_activation_token.quick_assign(move |xdg_token, event, mut dispatch_data| { - let token = match event { - xdg_activation_token_v1::Event::Done { token } => token, - _ => return, - }; - - let winit_state = dispatch_data.get::().unwrap(); - let window_handle = match winit_state.window_map.get_mut(&window_id) { - Some(window_handle) => window_handle, - None => return, - }; - - let surface = window_handle.window.surface(); - xdg_activation.activate(token, surface); - - // Mark that attention request was done and drop the token. - window_handle.attention_requested.replace(false); - xdg_token.destroy(); - }); - - xdg_activation_token.set_surface(surface); - xdg_activation_token.commit(); - self.attention_requested.replace(true); - } - - /// 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() { - let surface = self.window.surface(); - match self.cursor_grab_mode.get() { - CursorGrabMode::None => (), - CursorGrabMode::Locked => pointer.lock(surface), - CursorGrabMode::Confined => 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 grabbing mode. - match self.cursor_grab_mode.get() { - CursorGrabMode::None => (), - CursorGrabMode::Locked => pointer.unlock(), - CursorGrabMode::Confined => pointer.unconfine(), - } - } - } - - pub fn text_input_entered(&mut self, text_input: TextInputHandler) { - if !self.text_inputs.iter().any(|t| *t == text_input) { - 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 passthrough_mouse_input(&self, passthrough_mouse_input: bool) { - if passthrough_mouse_input { - let region = self.compositor.create_region(); - region.add(0, 0, 0, 0); - self.window - .surface() - .set_input_region(Some(®ion.detach())); - region.destroy(); - } else { - // Using `None` results in the entire window being clickable. - self.window.surface().set_input_region(None); - } - } - - pub fn set_transparent(&self, transparent: bool) { - self.transparent.set(transparent); - let surface = self.window.surface(); - if transparent { - surface.set_opaque_region(None); - } else { - let region = self.compositor.create_region(); - region.add(0, 0, i32::MAX, i32::MAX); - surface.set_opaque_region(Some(®ion.detach())); - region.destroy(); - } - } - - pub fn set_ime_allowed(&self, allowed: bool, event_sink: &mut EventSink) { - if self.ime_allowed.get() == allowed { - return; - } - - self.ime_allowed.replace(allowed); - let window_id = wayland::make_wid(self.window.surface()); - - let purpose = allowed.then(|| self.ime_purpose.get()); - for text_input in self.text_inputs.iter() { - text_input.set_input_allowed(purpose); - } - - let event = if allowed { - WindowEvent::Ime(Ime::Enabled) - } else { - WindowEvent::Ime(Ime::Disabled) - }; - - event_sink.push_window_event(event, window_id); - } - - pub fn set_ime_purpose(&self, purpose: ImePurpose) { - if self.ime_purpose.get() == purpose { - return; - } - - self.ime_purpose.replace(purpose); - - if self.ime_allowed.get() { - for text_input in self.text_inputs.iter() { - text_input.set_content_type_by_purpose(purpose); - } - } - } - - 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)); - } - } - - pub fn drag_window(&self) { - for pointer in self.pointers.iter() { - pointer.drag_window(&self.window); - } - } -} - -#[inline] -pub fn handle_window_requests(winit_state: &mut WinitState) { - let window_map = &mut winit_state.window_map; - let window_user_requests = &mut winit_state.window_user_requests; - let window_compositor_updates = &mut winit_state.window_compositor_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(); - let requests = requests.drain(..); - for request in requests { - 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::AllowIme(allow) => { - let event_sink = &mut winit_state.event_sink; - window_handle.set_ime_allowed(allow, event_sink); - } - WindowRequest::ImePurpose(purpose) => { - window_handle.set_ime_purpose(purpose); - } - WindowRequest::SetCursorGrabMode(mode) => { - window_handle.set_cursor_grab(mode); - } - WindowRequest::SetLockedCursorPosition(position) => { - window_handle.set_locked_cursor_position(position); - } - WindowRequest::DragWindow => { - window_handle.drag_window(); - } - WindowRequest::Maximize(maximize) => { - if maximize { - window_handle.window.set_maximized(); - } else { - window_handle.window.unset_maximized(); - } - } - WindowRequest::Minimize => { - window_handle.window.set_minimized(); - } - WindowRequest::Transparent(transparent) => { - window_handle.set_transparent(transparent); - - // This requires surface commit. - let window_request = window_user_requests.get_mut(window_id).unwrap(); - window_request.redraw_requested = true; - } - 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_request = window_user_requests.get_mut(window_id).unwrap(); - window_request.refresh_frame = true; - } - WindowRequest::Resizeable(resizeable) => { - window_handle.window.set_resizable(resizeable); - - // We should refresh the frame to update button state. - let window_request = window_user_requests.get_mut(window_id).unwrap(); - window_request.refresh_frame = true; - } - WindowRequest::Title(title) => { - window_handle.window.set_title(title); - - // We should refresh the frame to draw new title. - let window_request = window_user_requests.get_mut(window_id).unwrap(); - window_request.refresh_frame = true; - } - WindowRequest::MinSize(size) => { - let size = size.map(|size| (size.width, size.height)); - window_handle.window.set_min_size(size); - - let window_request = window_user_requests.get_mut(window_id).unwrap(); - window_request.refresh_frame = true; - } - WindowRequest::MaxSize(size) => { - let size = size.map(|size| (size.width, size.height)); - window_handle.window.set_max_size(size); - - let window_request = window_user_requests.get_mut(window_id).unwrap(); - window_request.refresh_frame = true; - } - WindowRequest::FrameSize(size) => { - if !window_handle.is_resizable.get() { - // On Wayland non-resizable window is achieved by setting both min and max - // size of the window to the same value. - let size = Some((size.width, size.height)); - window_handle.window.set_max_size(size); - window_handle.window.set_min_size(size); - } - - window_handle.window.resize(size.width, size.height); - - // We should refresh the frame after resize. - let window_request = window_user_requests.get_mut(window_id).unwrap(); - window_request.refresh_frame = true; - } - WindowRequest::PassthroughMouseInput(passthrough) => { - window_handle.passthrough_mouse_input(passthrough); - - let window_request = window_user_requests.get_mut(window_id).unwrap(); - window_request.refresh_frame = true; - } - WindowRequest::Attention(request_type) => { - window_handle.set_user_attention(request_type); - } - WindowRequest::Redraw => { - let window_request = window_user_requests.get_mut(window_id).unwrap(); - window_request.redraw_requested = 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); - } - WindowRequest::Theme(_theme) => { - #[cfg(feature = "sctk-adwaita")] - { - window_handle.window.set_frame_config(match _theme { - Some(theme) => theme.into(), - None => sctk_adwaita::FrameConfig::auto(), - }); - - let window_requst = window_user_requests.get_mut(window_id).unwrap(); - window_requst.refresh_frame = true; - } - } - }; - } - } - - // Close the windows. - for window in windows_to_close { - let _ = window_map.remove(&window); - let _ = window_user_requests.remove(&window); - let _ = window_compositor_updates.remove(&window); - } -} - -impl Drop for WindowHandle { - fn drop(&mut self) { - // Drop the fractional scaling before the surface. - let _ = self.fractional_scaling_state.take(); - - unsafe { - let surface = self.window.surface().clone(); - // The window must be destroyed before wl_surface. - ManuallyDrop::drop(&mut self.window); - surface.destroy(); - } - } -} - -/// Fractional scaling objects. -pub struct FractionalScalingState { - /// The wp-viewport of the window. - pub viewport: WpViewport, - - /// The wp-fractional-scale of the window surface. - pub fractional_scale: WpFractionalScaleV1, -} - -impl FractionalScalingState { - pub fn new(viewport: WpViewport, fractional_scale: WpFractionalScaleV1) -> Self { - Self { - viewport, - fractional_scale, - } - } -} - -impl Drop for FractionalScalingState { - fn drop(&mut self) { - self.viewport.destroy(); - self.fractional_scale.destroy(); - } -} diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs new file mode 100644 index 00000000000..89cdadbbf18 --- /dev/null +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -0,0 +1,469 @@ +//! The state of the window, which is shared with the event-loop. + +use std::mem::ManuallyDrop; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex, Weak}; + +use log::warn; + +use sctk::reexports::client::protocol::wl_shm::WlShm; +use sctk::reexports::client::{Connection, Proxy, QueueHandle}; +use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; +use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge; + +use sctk::compositor::{CompositorState, Region, SurfaceData}; +use sctk::seat::pointer::ThemedPointer; +use sctk::shell::xdg::window::{Window, WindowConfigure}; + +use crate::dpi::{LogicalPosition, LogicalSize}; +use crate::error::{ExternalError, NotSupportedError}; +use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection}; + +use crate::platform_impl::wayland::seat::{ + WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext, +}; +use crate::platform_impl::wayland::state::{PointerConstraintsState, WinitState}; + +/// The state of the window which is being updated from the [`WinitState`]. +pub struct WindowState { + /// The connection to Wayland server. + pub connection: Connection, + + /// The underlying SCTK window. + pub window: ManuallyDrop, + + /// The `Shm` to set cursor. + pub shm: WlShm, + + /// The inner size of the window. + pub size: LogicalSize, + + /// The last received configure. + pub last_configure: Option, + + /// The pointers observed on the window. + pub pointers: Vec>>, + + /// Cursor icon. + pub cursor_icon: Mutex, + + /// Wether the cursor is visible. + pub cursor_visible: AtomicBool, + + /// Pointer constraints to lock/confine pointer. + pub pointer_constraints: Option>, + + /// Queue handle. + pub queue_handle: QueueHandle, + + /// Whether the window has focus. + pub has_focus: AtomicBool, + + /// Whether the window is transparent. + transparent: AtomicBool, + + /// The state of the compositor to create WlRegions. + compositor: Arc, + + /// The current cursor grabbing mode. + cursor_grab_mode: Mutex, + + /// Whether the IME input is allowed for that window. + ime_allowed: AtomicBool, + + /// The current IME purpose. + ime_purpose: Mutex, + + /// The text inputs observed on the window. + text_inputs: Vec, +} + +/// The state of the cursor grabs. +#[derive(Clone, Copy)] +struct GrabState { + /// The grab mode requested by the user. + user_grab_mode: CursorGrabMode, + + /// The current grab mode. + current_grab_mode: CursorGrabMode, +} + +impl GrabState { + fn new() -> Self { + Self { + user_grab_mode: CursorGrabMode::None, + current_grab_mode: CursorGrabMode::None, + } + } +} + +impl WindowState { + pub fn new( + connection: Connection, + queue_handle: &QueueHandle, + winit_state: &WinitState, + size: LogicalSize, + window: Window, + ) -> Self { + let compositor = winit_state.compositor_state.clone(); + let pointer_constraints = winit_state.pointer_constraints.clone(); + Self { + connection, + cursor_icon: Mutex::new(CursorIcon::Default), + cursor_visible: AtomicBool::new(true), + queue_handle: queue_handle.clone(), + compositor, + last_configure: None, + pointers: Default::default(), + shm: winit_state.shm_state.wl_shm().clone(), + size, + window: ManuallyDrop::new(window), + pointer_constraints, + cursor_grab_mode: Mutex::new(GrabState::new()), + text_inputs: Vec::new(), + ime_purpose: Mutex::new(ImePurpose::Normal), + has_focus: AtomicBool::new(false), + ime_allowed: AtomicBool::new(false), + transparent: AtomicBool::new(false), + } + } + + pub fn configure(&mut self, configure: WindowConfigure) { + self.last_configure = Some(configure); + } + + pub fn is_configured(&self) -> bool { + self.last_configure.is_some() + } + + pub fn pointer_entered(&mut self, added: Weak>) { + self.pointers.push(added); + self.reload_cursor(); + // Release the lock before calling the set mode. + let mode = self.cursor_grab_mode.lock().unwrap().user_grab_mode; + let _ = self.set_cursor_grab_inner(mode); + } + + pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) { + if !self.text_inputs.iter().any(|t| t == text_input) { + self.text_inputs.push(text_input.clone()); + } + } + + pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) { + if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) { + self.text_inputs.remove(position); + } + } + + pub fn pointer_left(&mut self, removed: Weak>) { + let mut new_pointers = Vec::new(); + for pointer in self.pointers.drain(..) { + if let Some(pointer) = pointer.upgrade() { + if pointer.pointer() != removed.upgrade().unwrap().pointer() { + new_pointers.push(Arc::downgrade(&pointer)); + } + } + } + self.pointers = new_pointers; + } + + pub fn reload_cursor(&self) { + let cursor_visible = self.cursor_visible.load(Ordering::Relaxed); + if cursor_visible { + let cursor_icon = *self.cursor_icon.lock().unwrap(); + self.set_cursor(cursor_icon); + } else { + self.set_cursor_visible(cursor_visible); + } + } + + pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + // Replace the user grabbing mode. + self.cursor_grab_mode.lock().unwrap().user_grab_mode = mode; + self.set_cursor_grab_inner(mode) + } + + fn set_cursor_grab_inner(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + let pointer_constraints = match self.pointer_constraints.as_ref() { + Some(pointer_constraints) => pointer_constraints, + None if mode == CursorGrabMode::None => return Ok(()), + None => return Err(ExternalError::NotSupported(NotSupportedError::new())), + }; + + // Replace the current mode. + let old_mode = std::mem::replace( + &mut self.cursor_grab_mode.lock().unwrap().current_grab_mode, + mode, + ); + + match old_mode { + CursorGrabMode::None => (), + CursorGrabMode::Confined => self.apply_on_poiner(|_, data| { + data.unconfine_pointer(); + }), + CursorGrabMode::Locked => { + self.apply_on_poiner(|_, data| data.unlock_pointer()); + } + } + + let surface = self.window.wl_surface(); + match mode { + CursorGrabMode::Locked => self.apply_on_poiner(|pointer, data| { + let pointer = pointer.pointer(); + data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle) + }), + CursorGrabMode::Confined => self.apply_on_poiner(|pointer, data| { + let pointer = pointer.pointer(); + data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle) + }), + CursorGrabMode::None => { + // Current lock/confine was already removed. + } + } + + Ok(()) + } + + pub fn ime_allowed(&self) -> bool { + self.ime_allowed.load(Ordering::Relaxed) + } + + pub fn set_transparent(&self, transparent: bool) { + let surface = self.window.wl_surface(); + self.transparent.store(transparent, Ordering::Relaxed); + + if transparent { + surface.set_opaque_region(None); + } else if let Ok(region) = Region::new(&*self.compositor) { + region.add(0, 0, i32::MAX, i32::MAX); + surface.set_opaque_region(Some(region.wl_region())); + } else { + warn!("Failed to mark window opaque."); + } + } + + pub fn transparent(&self) -> bool { + self.transparent.load(Ordering::Relaxed) + } + + /// Returns `true` if the requested state was applied. + pub fn set_ime_allowed(&self, allowed: bool) -> bool { + self.ime_allowed.store(allowed, Ordering::Relaxed); + let purpose = *self.ime_purpose.lock().unwrap(); + + let mut applied = false; + for text_input in &self.text_inputs { + applied = true; + if allowed { + text_input.enable(); + text_input.set_content_type_by_purpose(purpose); + } else { + text_input.disable(); + } + text_input.commit(); + } + + applied + } + + /// Set the IME purpose. + pub fn set_ime_purpose(&self, purpose: ImePurpose) { + *self.ime_purpose.lock().unwrap() = purpose; + + for text_input in &self.text_inputs { + text_input.set_content_type_by_purpose(purpose); + text_input.commit(); + } + } + + /// Set the IME 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_cursor_rectangle(x, y, 0, 0); + text_input.commit(); + } + } + + pub fn has_focus(&self) -> bool { + self.has_focus.load(Ordering::Relaxed) + } + + /// Mark that the window has focus. + /// + /// Should be used from routine that sends focused event. + pub fn set_has_focus(&self, has_focus: bool) { + self.has_focus.store(has_focus, Ordering::Relaxed); + } + + pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> { + if self.pointer_constraints.is_none() { + return Err(ExternalError::NotSupported(NotSupportedError::new())); + } + + // Positon can be set only for locked cursor. + if self.cursor_grab_mode.lock().unwrap().current_grab_mode != CursorGrabMode::Locked { + return Err(ExternalError::Os(os_error!( + crate::platform_impl::OsError::WaylandMisc( + "cursor position can be set only for locked cursor." + ) + ))); + } + + self.apply_on_poiner(|_, data| { + data.set_locked_cursor_position(position.x, position.y); + }); + + Ok(()) + } + + pub fn drag_window(&self) -> Result<(), ExternalError> { + let xdg_toplevel = self.window.xdg_toplevel(); + // TODO(kchibisov) handle touch serials. + self.apply_on_poiner(|_, data| { + let serial = data.latest_button_serial(); + let seat = data.seat(); + xdg_toplevel._move(seat, serial); + }); + + Ok(()) + } + + pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { + let xdg_toplevel = self.window.xdg_toplevel(); + + // TODO(kchibisov) handle touch serials. + self.apply_on_poiner(|_, data| { + let serial = data.latest_button_serial(); + let seat = data.seat(); + xdg_toplevel.resize(seat, serial, direction.into()); + }); + + Ok(()) + } + + fn apply_on_poiner, &WinitPointerData)>( + &self, + callback: F, + ) { + self.pointers + .iter() + .filter_map(Weak::upgrade) + .for_each(|pointer| { + let data = pointer.pointer().winit_data(); + callback(pointer.as_ref(), data); + }) + } + + /// Set the visibility state of the cursor. + pub fn set_cursor_visible(&self, cursor_visible: bool) { + self.cursor_visible.store(cursor_visible, Ordering::Relaxed); + + for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) { + let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial(); + + pointer + .pointer() + .set_cursor(latest_enter_serial, None, 0, 0); + } + } + + /// Set the cursor icon. + /// + /// Providing `None` will hide the cursor. + pub fn set_cursor(&self, cursor_icon: CursorIcon) { + *self.cursor_icon.lock().unwrap() = cursor_icon; + + if !self.cursor_visible.load(Ordering::Relaxed) { + 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 => &["hand2", "hand1"], + 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_fdiag"], + CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"], + 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"], + }; + + self.apply_on_poiner(|pointer, data| { + let surface = data.cursor_surface(); + let scale_factor = surface.data::().unwrap().scale_factor(); + + for cursor in cursors { + if pointer + .set_cursor(&self.connection, cursor, &self.shm, surface, scale_factor) + .is_ok() + { + return; + } + } + + warn!("Failed to set cursor to {:?}", cursor_icon); + }) + } +} + +impl Drop for WindowState { + fn drop(&mut self) { + let surface = self.window.wl_surface().clone(); + unsafe { + ManuallyDrop::drop(&mut self.window); + } + surface.destroy(); + } +} + +impl From for ResizeEdge { + fn from(value: ResizeDirection) -> Self { + match value { + ResizeDirection::North => ResizeEdge::Top, + ResizeDirection::West => ResizeEdge::Left, + ResizeDirection::NorthWest => ResizeEdge::TopLeft, + ResizeDirection::NorthEast => ResizeEdge::TopRight, + ResizeDirection::East => ResizeEdge::Right, + ResizeDirection::SouthWest => ResizeEdge::BottomLeft, + ResizeDirection::SouthEast => ResizeEdge::BottomRight, + ResizeDirection::South => ResizeEdge::Bottom, + } + } +} diff --git a/src/platform_impl/linux/wayland/xdg_activation.rs b/src/platform_impl/linux/wayland/xdg_activation.rs new file mode 100644 index 00000000000..5d85bfbbba8 --- /dev/null +++ b/src/platform_impl/linux/wayland/xdg_activation.rs @@ -0,0 +1,103 @@ +//! Handling of xdg activation, which is used for user attention requests. + +use std::sync::atomic::AtomicBool; +use std::sync::Weak; + +use sctk::reexports::client::delegate_dispatch; +use sctk::reexports::client::globals::BindError; +use sctk::reexports::client::globals::GlobalList; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Dispatch; +use sctk::reexports::client::{Connection, Proxy, QueueHandle}; +use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_token_v1::{ + Event as ActivationTokenEvent, XdgActivationTokenV1, +}; +use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; + +use sctk::globals::GlobalData; + +use super::state::WinitState; + +pub struct XdgActivationState { + xdg_activation: XdgActivationV1, +} + +impl XdgActivationState { + pub fn bind( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let xdg_activation = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { xdg_activation }) + } + + pub fn global(&self) -> &XdgActivationV1 { + &self.xdg_activation + } +} + +impl Dispatch for XdgActivationState { + fn event( + _state: &mut WinitState, + _proxy: &XdgActivationV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for XdgActivationState { + fn event( + state: &mut WinitState, + proxy: &XdgActivationTokenV1, + event: ::Event, + data: &XdgActivationTokenData, + _: &Connection, + _: &QueueHandle, + ) { + let token = match event { + ActivationTokenEvent::Done { token } => token, + _ => return, + }; + + state + .xdg_activation + .as_ref() + .expect("got xdg_activation event without global.") + .global() + .activate(token, &data.surface); + + // Mark that no request attention is in process. + if let Some(attention_requested) = data.attention_requested.upgrade() { + attention_requested.store(false, std::sync::atomic::Ordering::Relaxed); + } + + proxy.destroy(); + } +} + +/// The data associated with the activation request. +pub struct XdgActivationTokenData { + /// The surface we're raising. + surface: WlSurface, + + /// Flag to throttle attention requests. + attention_requested: Weak, +} + +impl XdgActivationTokenData { + /// Create a new data. + /// + /// The `attenteion_requested` is marked as `false` on complition. + pub fn new(surface: WlSurface, attention_requested: Weak) -> Self { + Self { + surface, + attention_requested, + } + } +} + +delegate_dispatch!(WinitState: [ XdgActivationV1: GlobalData] => XdgActivationState); +delegate_dispatch!(WinitState: [ XdgActivationTokenV1: XdgActivationTokenData] => XdgActivationState);