From fb6af67b17d845858bfb6832edee9480b933953d 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 #2164. Fixes #2128. Fixes #1760. Fixes #725. --- .github/workflows/ci.yml | 6 +- CHANGELOG.md | 6 + Cargo.toml | 24 +- build.rs | 40 - src/platform/wayland.rs | 8 +- src/platform_impl/linux/wayland/env.rs | 183 ---- .../linux/wayland/event_loop/mod.rs | 457 ++++------ .../linux/wayland/event_loop/sink.rs | 14 + .../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 | 256 ++++-- src/platform_impl/linux/wayland/seat/mod.rs | 362 ++++---- .../linux/wayland/seat/pointer/data.rs | 82 -- .../linux/wayland/seat/pointer/handlers.rs | 327 ------- .../linux/wayland/seat/pointer/mod.rs | 715 ++++++++++------ .../wayland/seat/pointer/relative_pointer.rs | 80 ++ .../linux/wayland/seat/text_input/handlers.rs | 122 --- .../linux/wayland/seat/text_input/mod.rs | 247 ++++-- .../linux/wayland/seat/touch/handlers.rs | 144 ---- .../linux/wayland/seat/touch/mod.rs | 221 +++-- src/platform_impl/linux/wayland/state.rs | 366 ++++++++ src/platform_impl/linux/wayland/types/mod.rs | 5 + .../wayland/types/wp_fractional_scaling.rs | 81 ++ .../linux/wayland/types/wp_viewporter.rs | 67 ++ .../linux/wayland/types/xdg_activation.rs | 103 +++ src/platform_impl/linux/wayland/window/mod.rs | 801 ++++++++---------- .../linux/wayland/window/shim.rs | 666 --------------- .../linux/wayland/window/state.rs | 788 +++++++++++++++++ wayland_protocols/fractional-scale-v1.xml | 102 --- 33 files changed, 3332 insertions(+), 3371 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 create mode 100644 src/platform_impl/linux/wayland/types/mod.rs create mode 100644 src/platform_impl/linux/wayland/types/wp_fractional_scaling.rs create mode 100644 src/platform_impl/linux/wayland/types/wp_viewporter.rs create mode 100644 src/platform_impl/linux/wayland/types/xdg_activation.rs delete mode 100644 src/platform_impl/linux/wayland/window/shim.rs create mode 100644 src/platform_impl/linux/wayland/window/state.rs delete mode 100644 wayland_protocols/fractional-scale-v1.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54e62843f57..126e99a619e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,7 @@ jobs: env: RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 + PKG_CONFIG_ALLOW_CROSS: 1 RUSTFLAGS: "-C debuginfo=0 --deny warnings" OPTIONS: ${{ matrix.platform.options }} FEATURES: ${{ format(',{0}', matrix.platform.features ) }} @@ -67,9 +68,12 @@ jobs: targets: ${{ matrix.platform.target }} components: clippy + - name: Install Linux dependencies + if: (matrix.platform.os == 'ubuntu-latest') + run: sudo apt-get update && sudo apt-get install pkg-config libxkbcommon-dev - name: Install GCC Multilib if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') - run: sudo apt-get update && sudo apt-get install gcc-multilib + run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install g++-multilib gcc-multilib libxkbcommon-dev:i386 - name: Install cargo-apk if: contains(matrix.platform.target, 'android') run: cargo install cargo-apk diff --git a/CHANGELOG.md b/CHANGELOG.md index e250fe6152e..6f1030c2d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- On Wayland, `Window::outer_size` now accounts for **client side** decorations. +- On Wayland, fix window not checking that it actually got initial configure event. +- On Wayland, fix maximized window creation and window geometry handling. +- On Wayland, fix forward compatibility issues. +- On Wayland, add `Window::drag_resize_window` method. + # 0.28.2 - Implement `HasRawDisplayHandle` for `EventLoop`. diff --git a/Cargo.toml b/Cargo.toml index 679b044ab35..1e9859fa7f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,13 +35,14 @@ 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", "fnv"] +wayland-dlopen = ["wayland-backend/dlopen"] +# TODO(kchibisov) bring back sctk-adwaita +wayland-csd-adwaita = [] +wayland-csd-adwaita-crossfont = [] +wayland-csd-adwaita-notitle = [] android-native-activity = [ "android-activity/native-activity" ] android-game-activity = [ "android-activity/game-activity" ] @@ -107,11 +108,12 @@ 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 } +fnv = { version = "1.0.3", 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..2e476703c6b 100644 --- a/build.rs +++ b/build.rs @@ -1,35 +1,8 @@ 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; - - 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"); // Setup cfg aliases cfg_aliases! { @@ -48,17 +21,4 @@ fn main() { wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) }, orbital_platform: { redox }, } - - // 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(); } 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 84540f6c819..063b42b9032 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -1,23 +1,20 @@ +//! The event-loop routines. + use std::cell::RefCell; -use std::collections::HashMap; use std::error::Error; use std::io::Result as IOResult; +use std::marker::PhantomData; use std::mem; use std::process; use std::rc::Rc; +use std::sync::atomic::Ordering; 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::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::dpi::{LogicalSize, PhysicalSize}; use crate::event::{Event, StartCause, WindowEvent}; @@ -25,135 +22,68 @@ use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindo 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, +use sink::EventSink; - /// 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::{WindowCompositorUpdate, 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. + // XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which + // we don't really want, since it'll break public API by a lot. + /// 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, _, _| { @@ -164,52 +94,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, }, }; @@ -229,7 +137,11 @@ impl EventLoop { F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), { let mut control_flow = ControlFlow::Poll; - let pending_user_events = self.pending_user_events.clone(); + + // XXX preallocate certian structures to avoid allocating on each loop iteration. + let mut window_ids = Vec::::new(); + let mut compositor_updates = Vec::::new(); + let mut buffer_sink = EventSink::new(); callback( Event::NewEvents(StartCause::Init), @@ -237,23 +149,19 @@ impl EventLoop { &mut control_flow, ); - // NB: For consistency all platforms must emit a 'resumed' event even though Wayland + // XXX 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 + // XXX We break on errors from dispatches, since if we've got protocol error // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not // really an option. Instead we inform that the event loop got destroyed. We may // communicate an error that something was terminated, but winit doesn't provide us // with an API to do that via some event. // 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 @@ -273,9 +181,12 @@ 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(error) => { + error!("Error dispatching wayland queue: {}", error); + break 1; + } } }; @@ -283,7 +194,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); } @@ -296,7 +207,7 @@ impl EventLoop { } ControlFlow::Wait => { let timeout = if instant_wakeup { - Some(Duration::from_millis(0)) + Some(Duration::ZERO) } else { None }; @@ -321,7 +232,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)) { @@ -354,7 +265,7 @@ impl EventLoop { // Handle pending user events. We don't need back buffer, since we can't dispatch // user events indirectly via callback to the user. - for user_event in pending_user_events.borrow_mut().drain(..) { + for user_event in self.pending_user_events.borrow_mut().drain(..) { sticky_exit_callback( Event::UserEvent(user_event), &self.window_target, @@ -363,35 +274,31 @@ impl EventLoop { ); } - // Process 'new' pending updates from compositor. + // Drain the pending compositor updates. 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))), - ); + compositor_updates.append(&mut state.window_compositor_updates) }); - for (window_id, window_compositor_update) in window_compositor_updates.iter_mut() { - if let Some(scale_factor) = window_compositor_update.scale_factor { + for mut compositor_update in compositor_updates.drain(..) { + let window_id = compositor_update.window_id; + if let Some(scale_factor) = compositor_update.scale_factor { 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 windows = state.windows.get_mut(); + let mut window = windows.get(&window_id).unwrap().lock().unwrap(); - let mut size = window_handle.size.lock().unwrap(); + // Set the new scale factor. + window.set_scale_factor(scale_factor); - // Update the new logical size if it was changed. - let window_size = window_compositor_update.size.unwrap_or(*size); - *size = window_size; + // Resize the window. + let window_size = compositor_update.size.unwrap_or(window.inner_size()); + window.resize(window_size); logical_to_physical_rounded(window_size, 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, @@ -405,58 +312,39 @@ 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 windows = state.windows.get_mut(); + let mut window = windows.get(&window_id).unwrap().lock().unwrap(); + let old_size = window.inner_size(); - let mut window_size = window_handle.size.lock().unwrap(); + // Always update the size to refresh the decorations. + window.resize(size); // 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 = logical_to_physical_rounded(size, 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()); - - // 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. + let physical_size = + if compositor_update.scale_factor.is_none() && old_size == size { + // The size hasn't changed, don't inform downstream about that. + None + } else { + let scale_factor = window.scale_factor(); + Some(logical_to_physical_rounded(size, scale_factor)) + }; + + // Reapply the tranparency state. + window.reload_transparency_hint(); + + // Mark the window is needed a redraw. state - .window_user_requests - .get_mut(window_id) + .window_requests + .get_mut() + .get_mut(&window_id) .unwrap() - .redraw_requested = true; + .redraw_requested + .store(true, Ordering::Relaxed); physical_size }); @@ -464,7 +352,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, @@ -474,11 +362,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, @@ -488,18 +375,20 @@ 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. + // Push the events directly from the window. self.with_state(|state| { - std::mem::swap( - &mut event_sink_back_buffer, - &mut state.event_sink.window_events, - ) + buffer_sink.append(&mut state.window_events_sink.lock().unwrap()); }); + for event in buffer_sink.drain() { + 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. + self.with_state(|state| { + buffer_sink.append(&mut state.events_sink); + }); + for event in buffer_sink.drain() { let event = event.map_nonuser_event().unwrap(); sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); } @@ -512,42 +401,41 @@ 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. + // Collect the window ids self.with_state(|state| { - shim::handle_window_requests(state); + window_ids.extend(state.window_requests.get_mut().keys()); }); - // 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))), - ); - }); + for window_id in window_ids.drain(..) { + let request_redraw = self.with_state(|state| { + let window_requests = state.window_requests.get_mut(); + if window_requests.get(&window_id).unwrap().take_closed() { + mem::drop(window_requests.remove(&window_id)); + mem::drop(state.windows.get_mut().remove(&window_id)); + false + } else { + let mut redraw_requested = window_requests + .get(&window_id) + .unwrap() + .take_redraw_requested(); - // Handle RedrawRequested events. - for (window_id, mut window_request) in window_user_requests.iter() { - // Handle refresh of the frame. - if window_request.refresh_frame { - self.with_state(|state| { - let window_handle = state.window_map.get_mut(window_id).unwrap(); - window_handle.window.refresh(); - }); + // Redraw the frames while at it. + redraw_requested |= state + .windows + .get_mut() + .get_mut(&window_id) + .unwrap() + .lock() + .unwrap() + .refresh_frame(); - // In general refreshing the frame requires surface commit, those force user - // to redraw. - window_request.redraw_requested = true; - } + redraw_requested + } + }); - // Handle redraw request. - if window_request.redraw_requested { + if request_redraw { 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, @@ -578,26 +466,55 @@ impl EventLoop { &self.window_target } - fn with_state U>(&mut self, f: F) -> U { + fn with_state<'a, U: 'a, F: FnOnce(&'a mut WinitState) -> U>(&'a 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!(), }; - self.event_loop - .dispatch(timeout, state) - .map_err(|error| error.into()) + self.event_loop.dispatch(timeout, state).map_err(|error| { + error!("Error dispatching event loop: {}", 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/sink.rs b/src/platform_impl/linux/wayland/event_loop/sink.rs index 26a895e569a..0bf2ae5833e 100644 --- a/src/platform_impl/linux/wayland/event_loop/sink.rs +++ b/src/platform_impl/linux/wayland/event_loop/sink.rs @@ -1,5 +1,7 @@ //! An event loop's sink to deliver events from the Wayland event callbacks. +use std::vec::Drain; + use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent}; use crate::platform_impl::platform::DeviceId as PlatformDeviceId; use crate::window::WindowId as RootWindowId; @@ -19,6 +21,7 @@ impl EventSink { } /// Add new device event to a queue. + #[inline] pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) { self.window_events.push(Event::DeviceEvent { event, @@ -27,10 +30,21 @@ impl EventSink { } /// Add new window event to a queue. + #[inline] pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) { self.window_events.push(Event::WindowEvent { event, window_id: RootWindowId(window_id), }); } + + #[inline] + pub fn append(&mut self, other: &mut Self) { + self.window_events.append(&mut other.window_events); + } + + #[inline] + pub fn drain(&mut self) -> Drain<'_, Event<'static, ()>> { + self.window_events.drain(..) + } } 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..36439821646 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 types; mod window; +/// 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..6f59733b3e6 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -1,85 +1,231 @@ -//! 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.get_mut().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.get_mut().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); + } - /// 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, + 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; - /// Current state of modifiers keys. - modifiers_state: Rc>, + // 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); + } +} + +/// 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>, + + /// 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 { + type State = WinitState; + + 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 +234,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..94b28ec6005 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -1,208 +1,234 @@ -//! Seat handling and managing. +//! Seat handling. -use std::cell::RefCell; -use std::rc::Rc; +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 fnv::FnvHashMap; +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::{PointerConstraintsState, 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: FnvHashMap, - /// 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 = self + .seat_state + .get_keyboard_with_repeat_with_data( + queue_handle, + &seat, + WinitKeyboardData::new(seat.clone()), + self.loop_handle.clone(), + Box::new(|state, keyboard, event| { + state.handle_key_input(keyboard, event, ElementState::Pressed); + }), + ) + .expect("failed to create keyboard with present capability."); + + seat_state.keyboard_state = Some(KeyboardState { keyboard }); + } + 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() { + 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, - - /// Touch handling. - touch: Option, - - /// Text input handling aka IME. - text_input: Option, + fn new_seat( + &mut self, + _connection: &Connection, + _queue_handle: &QueueHandle, + seat: WlSeat, + ) { + self.seats.insert(seat.id(), WinitSeatState::new()); + } - /// 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>, + fn remove_seat( + &mut self, + _connection: &Connection, + _queue_handle: &QueueHandle, + seat: WlSeat, + ) { + let _ = self.seats.remove(&seat.id()); + } } -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())), - } - } +#[derive(Debug)] +pub struct KeyboardState { + /// The underlying WlKeyboard. + pub keyboard: WlKeyboard, } + +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..8104af53f48 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -1,343 +1,502 @@ -//! All pointer related handling. +//! The pointer events. -use std::cell::{Cell, RefCell}; -use std::rc::{Rc, Weak}; +use std::ops::Deref; +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>>, +use sctk::reexports::client::{Connection, Proxy, QueueHandle, Dispatch}; +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, ZwpPointerConstraintsV1}; +use sctk::reexports::client::globals::{BindError, GlobalList}; + +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 sctk::shell::xdg::frame::FrameClick; + +use crate::dpi::{LogicalPosition, PhysicalPosition}; +use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}; + +use crate::platform_impl::wayland::state::WinitState; +use crate::platform_impl::wayland::{self, DeviceId, WindowId}; + +pub mod relative_pointer; + +impl PointerHandler for WinitState { + fn pointer_frame( + &mut self, + connection: &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; + + // Ensure that the cached surface is alive. + if !surface.is_alive() { + continue; + } - /// Cursor to handle locked requests. - locked_pointer: Weak>>, + // The parent surface. + let parent_surface = event + .surface + .data::() + .unwrap() + .parent_surface() + .unwrap_or(surface); + + let window_id = wayland::make_wid(parent_surface); + + // Ensure that window exists. + let mut window = match self.windows.get_mut().get_mut(&window_id) { + Some(window) => window.lock().unwrap(), + None => continue, + }; + + let scale_factor = window.scale_factor(); + let position: PhysicalPosition = + LogicalPosition::new(event.position.0, event.position.1).to_physical(scale_factor); + + match event.kind { + // Pointer movements on decorations. + PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. } + if parent_surface != surface => + { + if let Some(icon) = + window.frame_point_moved(surface, event.position.0, event.position.1) + { + if let Some(pointer) = seat_state.pointer.as_ref() { + let surface = pointer + .pointer() + .data::() + .unwrap() + .cursor_surface(); + let scale_factor = + surface.data::().unwrap().scale_factor(); + + let _ = pointer.set_cursor( + connection, + icon, + self.shm.wl_shm(), + surface, + scale_factor, + ); + } + } + } + PointerEventKind::Leave { .. } if parent_surface != surface => { + window.frame_point_left(); + } + ref kind @ PointerEventKind::Press { button, serial, .. } + | ref kind @ PointerEventKind::Release { button, serial, .. } + if parent_surface != surface => + { + let click = match wayland_button_to_winit(button) { + MouseButton::Left => FrameClick::Normal, + MouseButton::Right => FrameClick::Alternate, + _ => continue, + }; + let pressed = if matches!(kind, PointerEventKind::Press { .. }) { + true + } else { + false + }; + + // Emulate click on the frame. + window.frame_click( + click, + pressed, + seat, + serial, + window_id, + &mut self.window_compositor_updates, + ); + } + // Regular events on the main surface. + 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) { + 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) { + 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), + } } -} -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 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, + )); } - 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, - }; + pub fn unlock_pointer(&self) { + let mut inner = self.inner.lock().unwrap(); + if let Some(locked_pointer) = inner.locked_pointer.take() { + locked_pointer.destroy(); + } + } - *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(); +impl Default for WinitPointerDataInner { + fn default() -> Self { + Self { + surface: None, + locked_pointer: None, + confined_pointer: None, + latest_button_serial: 0, + phase: TouchPhase::Ended, } + } +} - // Drop the pointer itself in case it's possible. - if self.pointer.as_ref().version() >= 3 { - self.pointer.release(); - } +/// 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_relative_pointer( - relative_pointer_manager: &ZwpRelativePointerManagerV1, - pointer: &WlPointer, -) -> ZwpRelativePointerV1 { - let relative_pointer = relative_pointer_manager.get_relative_pointer(pointer); - relative_pointer.quick_assign(move |_, event, mut dispatch_data| { - let winit_state = dispatch_data.get::().unwrap(); - handlers::handle_relative_pointer(event, winit_state); - }); - - relative_pointer.detach() +pub trait WinitPointerDataExt { + fn winit_data(&self) -> &WinitPointerData; +} + +impl WinitPointerDataExt for WlPointer { + fn winit_data(&self) -> &WinitPointerData { + self.data::() + .expect("failed to get pointer data.") + } } -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); +pub struct PointerConstraintsState { + pointer_constraints: ZwpPointerConstraintsV1, +} - confined_pointer.quick_assign(move |_, _, _| {}); +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, + }) + } +} - confined_pointer.detach() +impl Deref for PointerConstraintsState { + type Target = ZwpPointerConstraintsV1; + fn deref(&self) -> &Self::Target { + &self.pointer_constraints + } } -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); +impl Dispatch for PointerConstraintsState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpPointerConstraintsV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} - locked_pointer.quick_assign(move |_, _, _| {}); +impl Dispatch for PointerConstraintsState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpLockedPointerV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} - locked_pointer.detach() +impl Dispatch for PointerConstraintsState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpConfinedPointerV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } } + +delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState); +delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState); +delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState); +delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState); 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..4b425930c1c 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,209 @@ -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 sctk::reexports::client::delegate_dispatch; +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Dispatch; +use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; +use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::Event as TextInputEvent; +use sctk::reexports::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, -} - -trait ZwpTextInputV3Ext { - fn set_content_type_by_purpose(&self, purpose: ImePurpose); +pub struct TextInputState { + text_input_manager: ZwpTextInputManagerV3, } -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.get_mut(); + 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..502f2e8cce3 100644 --- a/src/platform_impl/linux/wayland/seat/touch/mod.rs +++ b/src/platform_impl/linux/wayland/seat/touch/mod.rs @@ -3,76 +3,197 @@ 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::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 = match self.windows.get_mut().get(&window_id) { + Some(window) => window.lock().unwrap().scale_factor(), + None => return, + }; -/// 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 = match self.windows.get_mut().get(&window_id) { + Some(window) => window.lock().unwrap().scale_factor(), + None => return, + }; + + 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 = match self.windows.get_mut().get(&window_id) { + Some(window) => window.lock().unwrap().scale_factor(), + None => return, + }; + + 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 = match self.windows.get_mut().get(&window_id) { + Some(window) => window.lock().unwrap().scale_factor(), + None => return, + }; + + 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..bd94a4faad7 --- /dev/null +++ b/src/platform_impl/linux/wayland/state.rs @@ -0,0 +1,366 @@ +use std::cell::RefCell; +use std::error::Error; +use std::sync::{Arc, Mutex}; + +use fnv::FnvHashMap; + +use sctk::reexports::calloop::LoopHandle; +use sctk::reexports::client::backend::ObjectId; +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::{Connection, Proxy, QueueHandle}; + +use sctk::compositor::{CompositorHandler, CompositorState}; +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}; +use sctk::shell::xdg::XdgShell; +use sctk::shell::WaylandSurface; +use sctk::shm::{Shm, ShmHandler}; +use sctk::subcompositor::SubcompositorState; + +use crate::dpi::LogicalSize; + +use super::event_loop::sink::EventSink; +use super::output::MonitorHandle; +use super::seat::{ + PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData, + WinitPointerDataExt, WinitSeatState, +}; +use super::types::wp_fractional_scaling::FractionalScalingManager; +use super::types::wp_viewporter::ViewporterState; +use super::types::xdg_activation::XdgActivationState; +use super::window::{WindowRequests, WindowState}; +use super::WindowId; + +/// 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 state of the subcompositor. + pub subcompositor_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: Shm, + + /// The XDG shell that is used for widnows. + pub xdg_shell: XdgShell, + + /// 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>>, + + /// 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: FnvHashMap, + + /// Currently present cursor surfaces. + pub pointer_surfaces: FnvHashMap>>, + + /// 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>, + + /// Viewporter state on the given window. + pub viewporter_state: Option, + + /// Fractional scaling manager. + pub fractional_scaling_manager: 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 compositor_state = CompositorState::bind(globals, queue_handle)?; + let subcompositor_state = SubcompositorState::bind( + compositor_state.wl_compositor().clone(), + globals, + queue_handle, + )?; + + 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 = FnvHashMap::default(); + for seat in seat_state.seats() { + seats.insert(seat.id(), WinitSeatState::new()); + } + + let (viewporter_state, fractional_scaling_manager) = + if let Ok(fsm) = FractionalScalingManager::new(globals, queue_handle) { + (ViewporterState::new(globals, queue_handle).ok(), Some(fsm)) + } else { + (None, None) + }; + + Ok(Self { + registry_state, + compositor_state: Arc::new(compositor_state), + subcompositor_state: Arc::new(subcompositor_state), + output_state, + seat_state, + shm: Shm::bind(globals, queue_handle)?, + + xdg_shell: XdgShell::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(), + viewporter_state, + fractional_scaling_manager, + + 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, + }) + } + + pub fn scale_factor_changed( + &mut self, + surface: &WlSurface, + scale_factor: f64, + is_legacy: bool, + ) { + // Check if the cursor surface. + let window_id = super::make_wid(surface); + + if self.windows.get_mut().contains_key(&window_id) { + // Don't update the scaling factor, when legacy method is used. + if is_legacy && self.fractional_scaling_manager.is_some() { + return; + } + + // 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.get_mut().get(&focused_window) { + window_state.lock().unwrap().reload_cursor_style() + } + } + } + + pub fn queue_close(updates: &mut Vec, window_id: WindowId) { + let pos = if let Some(pos) = updates + .iter() + .position(|update| update.window_id == window_id) + { + pos + } else { + updates.push(WindowCompositorUpdate::new(window_id)); + updates.len() - 1 + }; + + updates[pos].close_window = true; + } +} + +impl ShmHandler for WinitState { + fn shm_state(&mut self) -> &mut Shm { + &mut self.shm + } +} + +impl WindowHandler for WinitState { + fn request_close(&mut self, _: &Connection, _: &QueueHandle, window: &Window) { + let window_id = super::make_wid(window.wl_surface()); + Self::queue_close(&mut self.window_compositor_updates, window_id); + } + + 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 + }; + + // Populate the configure to the window. + // + // XXX the size on the window will be updated right before dispatching the size to the user. + let new_size = self + .windows + .get_mut() + .get_mut(&window_id) + .expect("got configure for dead window.") + .lock() + .unwrap() + .configure(configure, &self.shm, &self.subcompositor_state); + + self.window_compositor_updates[pos].size = Some(new_size); + } +} + +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, + ) { + self.scale_factor_changed(surface, scale_factor as f64, true) + } + + 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_subcompositor!(WinitState); +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/types/mod.rs b/src/platform_impl/linux/wayland/types/mod.rs new file mode 100644 index 00000000000..8e92eb1d03d --- /dev/null +++ b/src/platform_impl/linux/wayland/types/mod.rs @@ -0,0 +1,5 @@ +//! Wayland protocol implementation boilerplate. + +pub mod wp_fractional_scaling; +pub mod wp_viewporter; +pub mod xdg_activation; diff --git a/src/platform_impl/linux/wayland/types/wp_fractional_scaling.rs b/src/platform_impl/linux/wayland/types/wp_fractional_scaling.rs new file mode 100644 index 00000000000..ead1e06b2d4 --- /dev/null +++ b/src/platform_impl/linux/wayland/types/wp_fractional_scaling.rs @@ -0,0 +1,81 @@ +//! Handling of the fractional scaling. + +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Dispatch; +use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle}; +use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; +use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::Event as FractionalScalingEvent; +use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; + +use sctk::globals::GlobalData; + +use crate::platform_impl::wayland::state::WinitState; + +/// The scaling factor denominator. +const SCALE_DENOMINATOR: f64 = 120.; + +/// Fractional scaling manager. +#[derive(Debug)] +pub struct FractionalScalingManager { + manager: WpFractionalScaleManagerV1, +} + +pub struct FractionalScaling { + /// The surface used for scaling. + surface: WlSurface, +} + +impl FractionalScalingManager { + /// Create new viewporter. + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { manager }) + } + + pub fn fractional_scaling( + &self, + surface: &WlSurface, + queue_handle: &QueueHandle, + ) -> WpFractionalScaleV1 { + let data = FractionalScaling { + surface: surface.clone(), + }; + self.manager + .get_fractional_scale(surface, queue_handle, data) + } +} + +impl Dispatch for FractionalScalingManager { + fn event( + _: &mut WinitState, + _: &WpFractionalScaleManagerV1, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + // No events. + } +} + +impl Dispatch for FractionalScalingManager { + fn event( + state: &mut WinitState, + _: &WpFractionalScaleV1, + event: ::Event, + data: &FractionalScaling, + _: &Connection, + _: &QueueHandle, + ) { + if let FractionalScalingEvent::PreferredScale { scale } = event { + state.scale_factor_changed(&data.surface, scale as f64 / SCALE_DENOMINATOR, false); + } + } +} + +delegate_dispatch!(WinitState: [WpFractionalScaleManagerV1: GlobalData] => FractionalScalingManager); +delegate_dispatch!(WinitState: [WpFractionalScaleV1: FractionalScaling] => FractionalScalingManager); diff --git a/src/platform_impl/linux/wayland/types/wp_viewporter.rs b/src/platform_impl/linux/wayland/types/wp_viewporter.rs new file mode 100644 index 00000000000..a8f399c3a74 --- /dev/null +++ b/src/platform_impl/linux/wayland/types/wp_viewporter.rs @@ -0,0 +1,67 @@ +//! Handling of the wp-viewporter. + +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Dispatch; +use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle}; +use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; +use sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter; + +use sctk::globals::GlobalData; + +use crate::platform_impl::wayland::state::WinitState; + +/// Viewporter. +#[derive(Debug)] +pub struct ViewporterState { + viewporter: WpViewporter, +} + +impl ViewporterState { + /// Create new viewporter. + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let viewporter = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { viewporter }) + } + + /// Get the viewport for the given object. + pub fn get_viewport( + &self, + surface: &WlSurface, + queue_handle: &QueueHandle, + ) -> WpViewport { + self.viewporter + .get_viewport(surface, queue_handle, GlobalData) + } +} + +impl Dispatch for ViewporterState { + fn event( + _: &mut WinitState, + _: &WpViewporter, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + // No events. + } +} +impl Dispatch for ViewporterState { + fn event( + _: &mut WinitState, + _: &WpViewport, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + // No events. + } +} + +delegate_dispatch!(WinitState: [WpViewporter: GlobalData] => ViewporterState); +delegate_dispatch!(WinitState: [WpViewport: GlobalData] => ViewporterState); diff --git a/src/platform_impl/linux/wayland/types/xdg_activation.rs b/src/platform_impl/linux/wayland/types/xdg_activation.rs new file mode 100644 index 00000000000..1befb5ff0ac --- /dev/null +++ b/src/platform_impl/linux/wayland/types/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 crate::platform_impl::wayland::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); diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 14c317889ad..e8d82925a7f 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -1,94 +1,94 @@ -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 sctk::shell::xdg::window::WindowDecorations; +use sctk::shell::WaylandSurface; 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::types::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 scale factor. - scale_factor: Arc>, + /// The state of the window. + window_state: Arc>, - /// The current window size. - size: Arc>>, + /// Compositor to handle WlRegion stuff. + compositor: Arc, - /// A handle to output manager. - output_manager_handle: OutputManagerHandle, - - /// Event loop proxy to wake it up. - event_loop_awakener: calloop::ping::Ping, + /// The wayland display used solely for raw window handle. + display: WlDisplay, - /// Fullscreen state. - fullscreen: Arc, + /// Xdg activation to request user attention. + xdg_activation: Option, - /// Maximized state. - maximized: Arc, + /// The state of the requested attention from the `xdg_activation`. + attention_requested: Arc, - /// Available windowing features. - windowing_features: WindowingFeatures, - - /// Requests that SCTK window should perform. - window_requests: Arc>>, + /// Handle to the main queue to perform requests. + queue_handle: QueueHandle, /// Whether the window is resizeable. resizeable: AtomicBool, - /// Whether the window is decorated. - decorated: AtomicBool, + /// Window requests to the event loop. + window_requests: Arc, + + /// Observed monitors. + monitors: Arc>>, - /// Grabbing mode. - cursor_grab_mode: Mutex, + /// Source to wake-up the event-loop for window requests. + event_loop_awakener: calloop::ping::Ping, - /// Whether the window has keyboard focus. - has_focus: Arc, + /// The event sink to deliver sythetic events. + window_events_sink: Arc>, } impl Window { @@ -97,269 +97,125 @@ 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 mut state = event_loop_window_target.state.borrow_mut(); - 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| { - // While I'm not sure how this could happen, we can safely ignore it - // for now as a quickfix. - if !surface.as_ref().is_alive() { - return; - } - - let winit_state = dispatch_data.get::().unwrap(); - - // 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(); - - scale_factor = sctk::get_surface_scale_factor(&surface) as _; - - (surface, None) - }; + 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(); - 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 (width, height) = attributes + // 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::(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()); - } - - // Set decorations. - if attributes.decorations { - window.set_decorate(Decorations::FollowServer); - } else { - window.set_decorate(Decorations::None); - } - - // Min dimensions. - let min_size = attributes - .min_inner_size - .map(|size| size.to_logical::(scale_factor).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); - } - - // 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, - }); + .map(|size| size.to_logical::(1.)) + .unwrap_or((800, 600).into()); - window.set_fullscreen(monitor.as_ref()); - } - None => { - if attributes.maximized { - window.set_maximized(); - } - } - } - - // 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(); - - let size = Arc::new(Mutex::new(LogicalSize::new(width, height))); - let has_focus = Arc::new(AtomicBool::new(true)); - - // 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_id = super::make_wid(&surface); - let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64))); - - // Create a handle that performs all the requests on underlying sctk a window. - let window_handle = WindowHandle::new( - &event_loop_window_target.env, - window, - size.clone(), - has_focus.clone(), - fractional_scaling_state, - scale_factor, - window_requests.clone(), + let window = state.xdg_shell.create_window( + surface.clone(), + WindowDecorations::ServerDefault, + &queue_handle, ); - // Set opaque region. - window_handle.set_transparent(attributes.transparent); + let mut window_state = WindowState::new( + event_loop_window_target.connection.clone(), + &event_loop_window_target.queue_handle, + &state, + size, + window.clone(), + ); - // Set resizable state, so we can determine how to handle `Window::set_inner_size`. - window_handle.is_resizable.set(attributes.resizable); + // Set the window title. + window_state.set_title(attributes.title); - let mut winit_state = event_loop_window_target.state.borrow_mut(); + let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.)); + let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.)); - winit_state.window_map.insert(window_id, window_handle); + // Set the min and max sizes. + { + let (min_size, max_size) = if attributes.resizable { + (min_size.map(Into::into), max_size.map(Into::into)) + } else { + // Non-resizable implies that the min and max sizes are set to the same value. + (Some(size.into()), Some(size.into())) + }; - // 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); + window_state.set_min_inner_size(min_size); + window_state.set_min_inner_size(max_size); + } - // 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()); + // Set the app_id. + if let Some(name) = platform_attributes.name.map(|name| name.general) { + window.set_app_id(name); + } - let windowing_features = event_loop_window_target.windowing_features; + // XXX Do initial commit. + window.commit(); - // 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. - { - let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut(); - let event_queue = wayland_source.queue(); - let _ = event_queue.sync_roundtrip(&mut *winit_state, |_, _, _| unreachable!()); + // Add the window and window requests into the state. + let window_state = Arc::new(Mutex::new(window_state)); + let window_id = super::make_wid(&surface); + state + .windows + .get_mut() + .insert(window_id, window_state.clone()); + + let window_requests = WindowRequests { + redraw_requested: AtomicBool::new(true), + closed: AtomicBool::new(false), + }; + let window_requests = Arc::new(window_requests); + state + .window_requests + .get_mut() + .insert(window_id, window_requests.clone()); + + // Setup the event sync to insert `WindowEvents` right from the window. + let window_events_sink = state.window_events_sink.clone(); + + let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut(); + let event_queue = wayland_source.queue(); + + // 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 queue while waiting for initial configure." + )) + })?; } - // We all praise GNOME for these 3 lines of pure magic. If we don't do that, - // GNOME will shrink our window a bit for the size of the decorations. I guess it - // happens because we haven't committed them with buffers to the server. - let window_handle = winit_state.window_map.get_mut(&window_id).unwrap(); - window_handle.window.refresh(); - - let output_manager_handle = event_loop_window_target.output_manager.handle(); + // 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 { + 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, resizeable: AtomicBool::new(attributes.resizable), - decorated: AtomicBool::new(attributes.decorations), - cursor_grab_mode: Mutex::new(CursorGrabMode::None), - has_focus, - scale_factor: window_handle.scale_factor.clone(), - }; - - Ok(window) + attention_requested: Arc::new(AtomicBool::new(false)), + event_loop_awakener, + window_requests, + window_events_sink, + }) } } @@ -370,13 +226,9 @@ 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)); + pub fn set_title(&self, title: impl ToString) { + let new_title = title.to_string(); + self.window_state.lock().unwrap().set_title(new_title); } #[inline] @@ -404,44 +256,58 @@ impl Window { // Not possible on Wayland. } + #[inline] pub fn inner_size(&self) -> PhysicalSize { - self.size.lock().unwrap().to_physical(self.scale_factor()) + let window_state = self.window_state.lock().unwrap(); + let scale_factor = window_state.scale_factor(); + window_state.inner_size().to_physical(scale_factor) } #[inline] pub fn request_redraw(&self) { - self.send_request(WindowRequest::Redraw); + self.window_requests + .redraw_requested + .store(true, Ordering::Relaxed); + self.event_loop_awakener.ping(); } #[inline] pub fn outer_size(&self) -> PhysicalSize { - self.size.lock().unwrap().to_physical(self.scale_factor()) + let window_state = self.window_state.lock().unwrap(); + let scale_factor = window_state.scale_factor(); + window_state.outer_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; + // TODO should we issue the resize event? I don't think other platforms do so. + let mut window_state = self.window_state.lock().unwrap(); + let scale_factor = window_state.scale_factor(); + window_state.resize(size.to_logical::(scale_factor)); - self.send_request(WindowRequest::FrameSize(size)); + self.request_redraw(); } + /// Set the minimum inner size for the window. #[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.window_state + .lock() + .unwrap() + .set_min_inner_size(min_size) } + /// Set the maximum inner size for the window. #[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.window_state + .lock() + .unwrap() + .set_max_inner_size(max_size) } #[inline] @@ -454,10 +320,44 @@ 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 { + // XXX clients don't know whether they are minimized or not. + 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. + self.window_state.lock().unwrap().reload_min_max_hints(); + } else { + let size = self.window_state.lock().unwrap().inner_size().into(); + self.set_min_inner_size(Some(size)); + self.set_max_inner_size(Some(size)); + } } #[inline] @@ -466,59 +366,75 @@ 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() + self.window_state.lock().unwrap().scale_factor() } #[inline] pub fn set_decorations(&self, decorate: bool) { - self.decorated.store(decorate, Ordering::Relaxed); - self.send_request(WindowRequest::Decorate(decorate)); + self.window_state.lock().unwrap().set_decorate(decorate) } #[inline] pub fn is_decorated(&self) -> bool { - self.decorated.load(Ordering::Relaxed) - } - - #[inline] - pub fn is_minimized(&self) -> Option { - None + self.window_state.lock().unwrap().is_decorated() } #[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_minimized(); } #[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 @@ -527,195 +443,225 @@ 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 scale_factor = window_state.scale_factor(); + 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 mut 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) sctk-adwaita } #[inline] pub fn theme(&self) -> Option { - None + // TODO(kchibisov) sctk-adwaita + Some(Theme::Dark) } #[inline] - pub fn has_focus(&self) -> bool { - self.has_focus.load(Ordering::Relaxed) - } - pub fn title(&self) -> String { - String::new() + self.window_state.lock().unwrap().title().to_owned() } } impl Drop for Window { fn drop(&mut self) { - self.send_request(WindowRequest::Close); + self.window_requests.closed.store(true, Ordering::Relaxed); + 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(Debug)] +pub struct WindowRequests { + /// The window was closed. + pub closed: AtomicBool, + + /// Redraw Requested. + pub redraw_requested: AtomicBool, +} + +impl WindowRequests { + pub fn take_closed(&self) -> bool { + self.closed.swap(false, Ordering::Relaxed) + } + + pub fn take_redraw_requested(&self) -> bool { + self.redraw_requested.swap(false, Ordering::Relaxed) } } +// #[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 = (); @@ -735,20 +681,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..47922bce7b3 --- /dev/null +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -0,0 +1,788 @@ +//! The state of the window, which is shared with the event-loop. + +use std::mem::ManuallyDrop; +use std::sync::{Arc, Weak}; + +use log::warn; + +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::protocol::wl_shm::WlShm; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::{Connection, Proxy, QueueHandle}; +use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; +use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; +use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; +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::frame::fallback_frame::FallbackFrame; +use sctk::shell::xdg::frame::{DecorationsFrame, FrameAction, FrameClick}; +use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure}; +use sctk::shell::xdg::XdgSurface; +use sctk::shell::WaylandSurface; +use sctk::shm::Shm; +use sctk::subcompositor::SubcompositorState; + +use crate::dpi::{LogicalPosition, LogicalSize}; +use crate::error::{ExternalError, NotSupportedError}; +use crate::platform_impl::WindowId; +use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection}; + +use crate::platform_impl::wayland::seat::{ + PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext, +}; +use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState}; + +// Minimum window inner size. +const MIN_WINDOW_SIZE: LogicalSize = LogicalSize::new(2, 1); + +/// 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 window frame, which is created from the configure request. + frame: Option>, + + /// The `Shm` to set cursor. + pub shm: WlShm, + + /// The last received configure. + pub last_configure: Option, + + /// The pointers observed on the window. + pub pointers: Vec>>, + + /// Cursor icon. + pub cursor_icon: CursorIcon, + + /// Wether the cursor is visible. + pub cursor_visible: bool, + + /// Pointer constraints to lock/confine pointer. + pub pointer_constraints: Option>, + + /// Queue handle. + pub queue_handle: QueueHandle, + + /// The current window title. + title: String, + + /// Whether the window has focus. + has_focus: bool, + + /// The scale factor of the window. + scale_factor: f64, + + /// Whether the window is transparent. + transparent: bool, + + /// The state of the compositor to create WlRegions. + compositor: Arc, + + /// The current cursor grabbing mode. + cursor_grab_mode: GrabState, + + /// Whether the IME input is allowed for that window. + ime_allowed: bool, + + /// The current IME purpose. + ime_purpose: ImePurpose, + + /// The text inputs observed on the window. + text_inputs: Vec, + + /// The inner size of the window, as in without client side decorations. + size: LogicalSize, + + /// Whether the CSD fail to create, so we don't try to create them on each iteration. + csd_fails: bool, + + /// Min size. + min_inner_size: LogicalSize, + max_inner_size: Option>, + + viewport: Option, + fractional_scale: Option, +} + +/// 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 { + /// Apply closure on the given pointer. + 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); + }) + } + + pub fn configure( + &mut self, + configure: WindowConfigure, + shm: &Shm, + subcompositor: &Arc, + ) -> LogicalSize { + if configure.decoration_mode == DecorationMode::Client + && self.frame.is_none() + && !self.csd_fails + { + match FallbackFrame::new( + &*self.window, + shm, + subcompositor.clone(), + self.queue_handle.clone(), + ) { + Ok(frame) => { + self.frame = Some(frame); + } + Err(err) => { + warn!("Failed to create client side decorations frame: {err}"); + self.csd_fails = true; + } + } + } + + let new_size = if let Some(frame) = self.frame.as_mut() { + // Unhide the frame. + frame.set_hidden(false); + + // Configure the window states. + frame.configure_state(&configure.states); + + match configure.new_size { + Some((width, height)) => frame.subtract_borders(width, height).into(), + None => self.size, + } + } else { + if let Some(frame) = self.frame.as_mut() { + frame.set_hidden(true); + } + configure.new_size.map(Into::into).unwrap_or(self.size) + }; + + self.last_configure = Some(configure); + new_size + } + + /// Start interacting drag resize. + 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(()) + } + + /// Start the window drag. + 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(()) + } + + /// Tells whether the window should be closed. + pub fn frame_click( + &mut self, + click: FrameClick, + pressed: bool, + seat: &WlSeat, + serial: u32, + window_id: WindowId, + updates: &mut Vec, + ) -> Option { + match self.frame.as_mut()?.on_click(click, pressed)? { + FrameAction::Minimize => self.window.set_minimized(), + FrameAction::Maximize => self.window.set_maximized(), + FrameAction::UnMaximize => self.window.unset_maximized(), + FrameAction::Close => WinitState::queue_close(updates, window_id), + FrameAction::Move => self.window.r#move(seat, serial), + FrameAction::Resize(edge) => self.window.resize(seat, serial, edge), + FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)), + }; + + Some(false) + } + + pub fn frame_point_left(&mut self) { + if let Some(frame) = self.frame.as_mut() { + frame.click_point_left(); + } + } + + // Move the point over decorations. + pub fn frame_point_moved(&mut self, surface: &WlSurface, x: f64, y: f64) -> Option<&str> { + if let Some(frame) = self.frame.as_mut() { + frame.click_point_moved(surface, x, y) + } else { + None + } + } + + /// Whether the window is focused. + #[inline] + pub fn has_focus(&self) -> bool { + self.has_focus + } + + /// Whether the IME is allowed. + #[inline] + pub fn ime_allowed(&self) -> bool { + self.ime_allowed + } + + /// Get the size of the window. + #[inline] + pub fn inner_size(&self) -> LogicalSize { + self.size + } + + /// Whether the window received initial configure event from the compositor. + #[inline] + pub fn is_configured(&self) -> bool { + self.last_configure.is_some() + } + + #[inline] + pub fn is_decorated(&mut self) -> bool { + let csd = self + .last_configure + .as_ref() + .map(|configure| configure.decoration_mode == DecorationMode::Client) + .unwrap_or(false); + if let Some(frame) = csd.then(|| self.frame.as_ref()).flatten() { + frame.is_hidden() + } else { + // Server side decorations. + true + } + } + + /// Create new window state. + 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(); + let viewport = winit_state + .viewporter_state + .as_ref() + .map(|state| state.get_viewport(window.wl_surface(), queue_handle)); + let fractional_scale = winit_state + .fractional_scaling_manager + .as_ref() + .map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle)); + + Self { + compositor, + connection, + csd_fails: false, + cursor_grab_mode: GrabState::new(), + cursor_icon: CursorIcon::Default, + cursor_visible: true, + fractional_scale, + frame: None, + has_focus: false, + ime_allowed: false, + ime_purpose: ImePurpose::Normal, + last_configure: None, + max_inner_size: None, + min_inner_size: MIN_WINDOW_SIZE, + pointer_constraints, + pointers: Default::default(), + queue_handle: queue_handle.clone(), + scale_factor: 1., + shm: winit_state.shm.wl_shm().clone(), + size, + text_inputs: Vec::new(), + title: String::default(), + transparent: false, + viewport, + window: ManuallyDrop::new(window), + } + } + + /// Get the outer size of the window. + #[inline] + pub fn outer_size(&self) -> LogicalSize { + self.frame + .as_ref() + .map(|frame| frame.add_borders(self.size.width, self.size.height).into()) + .unwrap_or(self.size) + } + + /// Register pointer on the top-level. + pub fn pointer_entered(&mut self, added: Weak>) { + self.pointers.push(added); + self.reload_cursor_style(); + + let mode = self.cursor_grab_mode.user_grab_mode; + let _ = self.set_cursor_grab_inner(mode); + } + + /// Pointer has left the top-level. + 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; + } + + /// Refresh the decorations frame if it's present returning whether the client should redraw. + pub fn refresh_frame(&mut self) -> bool { + if let Some(frame) = self.frame.as_mut() { + let dirty = frame.is_dirty(); + if dirty { + frame.draw(); + } + dirty + } else { + false + } + } + + /// Reload the cursor style on the given window. + pub fn reload_cursor_style(&mut self) { + if self.cursor_visible { + self.set_cursor(self.cursor_icon); + } else { + self.set_cursor_visible(self.cursor_visible); + } + } + + /// Reissue the transparency hint to the compositor. + pub fn reload_transparency_hint(&self) { + let surface = self.window.wl_surface(); + + if self.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."); + } + } + + /// Resize the window to the new inner size. + pub fn resize(&mut self, inner_size: LogicalSize) { + self.size = inner_size; + + // Update the inner frame. + let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() { + // Resize only visible frame. + if !frame.is_hidden() { + frame.resize(self.size.width, self.size.height); + } + + ( + frame.location(), + frame.add_borders(self.size.width, self.size.height).into(), + ) + } else { + ((0, 0), self.size) + }; + + // Set the window geometry. + self.window.xdg_surface().set_window_geometry( + x, + y, + outer_size.width as i32, + outer_size.height as i32, + ); + + // Update the target viewport, this is used if and only if fractional scaling is in use. + if let Some(viewport) = self.viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination(self.size.width as _, self.size.height as _); + } + } + + /// Get the scale factor of the window. + #[inline] + pub fn scale_factor(&self) -> f64 { + self.scale_factor + } + + /// Set the cursor icon. + /// + /// Providing `None` will hide the cursor. + pub fn set_cursor(&mut self, cursor_icon: CursorIcon) { + self.cursor_icon = cursor_icon; + + if !self.cursor_visible { + 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); + }) + } + + /// Set maximum inner window size. + pub fn set_min_inner_size(&mut self, size: Option>) { + // Ensure that the window has the right minimum size. + let mut size = size.unwrap_or(MIN_WINDOW_SIZE); + size.width = size.width.max(MIN_WINDOW_SIZE.width); + size.height = size.height.max(MIN_WINDOW_SIZE.height); + + // Add the borders. + let size = self + .frame + .as_ref() + .map(|frame| frame.add_borders(size.width, size.height).into()) + .unwrap_or(size); + + self.min_inner_size = size; + self.window.set_min_size(Some(size.into())); + } + + /// Set maximum inner window size. + pub fn set_max_inner_size(&mut self, size: Option>) { + let size = size.map(|size| { + self.frame + .as_ref() + .map(|frame| frame.add_borders(size.width, size.height).into()) + .unwrap_or(size) + }); + + self.max_inner_size = size; + self.window.set_max_size(size.map(Into::into)); + } + + /// Set the cursor grabbing state on the top-level. + pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> { + // Replace the user grabbing mode. + self.cursor_grab_mode.user_grab_mode = mode; + self.set_cursor_grab_inner(mode) + } + + /// Reload the hints for minimum and maximum sizes. + pub fn reload_min_max_hints(&mut self) { + self.set_min_inner_size(Some(self.min_inner_size)); + self.set_max_inner_size(self.max_inner_size); + } + + /// Set the grabbing state on the surface. + fn set_cursor_grab_inner(&mut 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.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(()) + } + + /// Set the position of the cursor. + 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.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(()) + } + + /// Set the visibility state of the cursor. + pub fn set_cursor_visible(&mut self, cursor_visible: bool) { + self.cursor_visible = cursor_visible; + + 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); + } + } + + /// Whether show or hide client side decorations. + #[inline] + pub fn set_decorate(&mut self, decorate: bool) { + if let Some(frame) = self.frame.as_mut() { + frame.set_hidden(!decorate); + } + } + + /// Mark that the window has focus. + /// + /// Should be used from routine that sends focused event. + #[inline] + pub fn set_has_focus(&mut self, has_focus: bool) { + self.has_focus = has_focus; + } + + /// Returns `true` if the requested state was applied. + pub fn set_ime_allowed(&mut self, allowed: bool) -> bool { + self.ime_allowed = allowed; + + 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(self.ime_purpose); + } else { + text_input.disable(); + } + text_input.commit(); + } + + applied + } + + /// 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(); + } + } + + /// Set the IME purpose. + pub fn set_ime_purpose(&mut self, purpose: ImePurpose) { + self.ime_purpose = purpose; + + for text_input in &self.text_inputs { + text_input.set_content_type_by_purpose(purpose); + text_input.commit(); + } + } + + /// Set the scale factor for the given window. + #[inline] + pub fn set_scale_factor(&mut self, scale_factor: f64) { + self.scale_factor = scale_factor; + + // XXX when fractional scaling is not used update the buffer scale. + if self.fractional_scale.is_none() { + let _ = self.window.set_buffer_scale(self.scale_factor as _); + } + } + + /// Set the window title to a new value. + /// + /// This will autmatically truncate the title to something meaningfull. + pub fn set_title(&mut self, mut title: String) { + // Truncate the title to at most 1024 bytes, so that it does not blow up the protocol + // messages + if title.len() > 1024 { + let mut new_len = 1024; + while !title.is_char_boundary(new_len) { + new_len -= 1; + } + title.truncate(new_len); + } + + // Update the CSD title. + if let Some(frame) = self.frame.as_mut() { + frame.set_title(&title); + } + + self.window.set_title(&title); + self.title = title; + } + + /// Mark the window as transparent. + #[inline] + pub fn set_transparent(&mut self, transparent: bool) { + self.transparent = transparent; + self.reload_transparency_hint(); + } + + /// Register text input on the top-level. + #[inline] + 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()); + } + } + + /// The text input left the top-level. + #[inline] + 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); + } + } + + /// Get the cached title. + #[inline] + pub fn title(&self) -> &str { + &self.title + } +} + +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/wayland_protocols/fractional-scale-v1.xml b/wayland_protocols/fractional-scale-v1.xml deleted file mode 100644 index 350bfc01eaf..00000000000 --- a/wayland_protocols/fractional-scale-v1.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - Copyright © 2022 Kenny Levinsen - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - This protocol allows a compositor to suggest for surfaces to render at - fractional scales. - - A client can submit scaled content by utilizing wp_viewport. This is done by - creating a wp_viewport object for the surface and setting the destination - rectangle to the surface size before the scale factor is applied. - - The buffer size is calculated by multiplying the surface size by the - intended scale. - - The wl_surface buffer scale should remain set to 1. - - If a surface has a surface-local size of 100 px by 50 px and wishes to - submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should - be used and the wp_viewport destination rectangle should be 100 px by 50 px. - - For toplevel surfaces, the size is rounded halfway away from zero. The - rounding algorithm for subsurface position and size is not defined. - - - - - A global interface for requesting surfaces to use fractional scales. - - - - - Informs the server that the client will not be using this protocol - object anymore. This does not affect any other objects, - wp_fractional_scale_v1 objects included. - - - - - - - - - - Create an add-on object for the the wl_surface to let the compositor - request fractional scales. If the given wl_surface already has a - wp_fractional_scale_v1 object associated, the fractional_scale_exists - protocol error is raised. - - - - - - - - - An additional interface to a wl_surface object which allows the compositor - to inform the client of the preferred scale. - - - - - Destroy the fractional scale object. When this object is destroyed, - preferred_scale events will no longer be sent. - - - - - - Notification of a new preferred scale for this surface that the - compositor suggests that the client should use. - - The sent scale is the numerator of a fraction with a denominator of 120. - - - - -