diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4ceca80..02a5930c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,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 ) }} @@ -97,9 +98,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 d597b896..31bc55b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- On Wayland, fix maximized startup not taking full size on GNOME. +- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window. +- 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. +- On Wayland, drop `WINIT_WAYLAND_CSD_THEME` variable. - Bump MSRV from `1.60` to `1.64`. - On macOS, fixed potential panic when getting refresh rate. - On macOS, fix crash when calling `Window::set_ime_position` from another thread. diff --git a/Cargo.toml b/Cargo.toml index c86287cf..11d2a1d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,13 +37,13 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] x11 = ["x11-dl", "mio", "percent-encoding"] -wayland = ["wayland-client", "wayland-protocols", "sctk", "wayland-commons"] -wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"] +wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv"] +wayland-dlopen = ["wayland-backend/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"] wayland-csd-adwaita-notitle = ["sctk-adwaita"] -android-native-activity = [ "android-activity/native-activity" ] -android-game-activity = [ "android-activity/game-activity" ] +android-native-activity = ["android-activity/native-activity"] +android-game-activity = ["android-activity/game-activity"] [build-dependencies] cfg_aliases = "0.1.1" @@ -107,20 +107,19 @@ 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", version = "0.17.0", optional = true } +sctk-adwaita = { version = "0.6.0", default_features = false, 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] orbclient = { version = "0.3.42", default-features = false } redox_syscall = "0.3" -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.build-dependencies] -wayland-scanner = "0.29.5" - [target.'cfg(target_family = "wasm")'.dependencies.web_sys] package = "web-sys" version = "0.3.22" diff --git a/build.rs b/build.rs index fc0b6003..00d706d3 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/deny.toml b/deny.toml index f6b6b641..025537c4 100644 --- a/deny.toml +++ b/deny.toml @@ -32,6 +32,9 @@ wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny deny = [] skip = [ { name = "nix" }, # differing version - as of 2023-03-02 whis can be solved with `cargo update && cargo update -p calloop --precise 0.10.2` + { name = "memoffset"}, # due to different nix versions. + { name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392 and https://github.com/rustwasm/wasm-bindgen/issues/3390 + { name = "miniz_oxide"}, # https://github.com/rust-lang/flate2-rs/issues/340 { name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46 ] skip-tree = [] diff --git a/src/platform/wayland.rs b/src/platform/wayland.rs index 7bbbd991..3e59184d 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 e88b1bad..00000000 --- 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 84540f6c..6ddb4571 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,72 @@ 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; +use sink::EventSink; -type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>; +use super::state::{WindowCompositorUpdate, WinitState}; +use super::{DeviceId, WindowId}; -pub struct EventLoopWindowTarget { - /// Wayland display. - pub display: Display, - - /// Environment to handle object creation, etc. - pub env: Environment, - - /// Event loop handle. - pub event_loop_handle: calloop::LoopHandle<'static, WinitState>, - - /// Output manager. - pub output_manager: OutputManager, - - /// State that we share across callbacks. - pub state: RefCell, - - /// Dispatcher of Wayland events. - pub wayland_dispatcher: WinitDispatcher, - - /// A proxy to wake up event loop. - pub event_loop_awakener: calloop::ping::Ping, - - /// The available windowing features. - pub windowing_features: WindowingFeatures, - - /// Theme manager to manage cursors. - /// - /// It's being shared between all windows to avoid loading - /// multiple similar themes. - pub theme_manager: ThemeManager, - - _marker: std::marker::PhantomData, -} - -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, - - /// Pending user events. - pending_user_events: Rc>>, - /// Sender of user events. user_events_sender: calloop::channel::Sender, - /// Window target. + // 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>>, + + /// The Wayland dispatcher to has raw access to the queue when needed, such as + /// when creating a new window. + wayland_dispatcher: WaylandDispatcher, + + /// 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()); + let connection = Connection::connect_to_env()?; - // Setup environment. - let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?; + let (globals, mut event_queue) = globals::registry_queue_init(&connection)?; + let queue_handle = event_queue.handle(); - // Create event loop. - let event_loop = calloop::EventLoop::<'static, WinitState>::try_new()?; - // Build windowing features. - let windowing_features = WindowingFeatures::new(&env); + let event_loop = calloop::EventLoop::::try_new()?; - // Create a theme manager. - let compositor = env.require_global::(); - let shm = env.require_global::(); - let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm); + let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?; - // Setup theme seat and output managers. - let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone()); - let output_manager = OutputManager::new(&env); + // NOTE: do a roundtrip after binding the globals to prevent potential + // races with the server. + event_queue.roundtrip(&mut winit_state)?; - // A source of events that we plug into our event loop. - let wayland_source = WaylandSource::new(event_queue); + // 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 +98,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 +141,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 +153,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 +185,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 +198,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 +211,7 @@ impl EventLoop { } ControlFlow::Wait => { let timeout = if instant_wakeup { - Some(Duration::from_millis(0)) + Some(Duration::ZERO) } else { None }; @@ -321,7 +236,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 +269,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 +278,30 @@ 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 mut size = window_handle.size.lock().unwrap(); - - // Update the new logical size if it was changed. - let window_size = window_compositor_update.size.unwrap_or(*size); - *size = window_size; + let windows = state.windows.get_mut(); + let mut window = windows.get(&window_id).unwrap().lock().unwrap(); + // Set the new scale factor. + window.set_scale_factor(scale_factor); + let window_size = compositor_update.size.unwrap_or(window.inner_size()); logical_to_physical_rounded(window_size, scale_factor) }); + // Stash the old window size. + let old_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::ScaleFactorChanged { scale_factor, new_inner_size: &mut physical_size, @@ -402,83 +312,58 @@ impl EventLoop { &mut callback, ); - // We don't update size on a window handle since we'll do that later - // when handling size update. let new_logical_size = physical_size.to_logical(scale_factor); - window_compositor_update.size = Some(new_logical_size); + + // Resize the window when user altered the size. + if old_physical_size != physical_size { + self.with_state(|state| { + let windows = state.windows.get_mut(); + let mut window = windows.get(&window_id).unwrap().lock().unwrap(); + window.resize(new_logical_size); + }); + } + + // Make it queue resize. + 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(); + let windows = state.windows.get_mut(); + let window = windows.get(&window_id).unwrap().lock().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 scale_factor = window.scale_factor(); + let physical_size = logical_to_physical_rounded(size, scale_factor); - let mut window_size = window_handle.size.lock().unwrap(); + // TODO could probably bring back size reporting optimization. - // 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. + // Mark the window as needed a redraw. state - .window_user_requests - .get_mut(window_id) + .window_requests + .get_mut() + .get_mut(&window_id) .unwrap() - .refresh_frame = false; - - // Queue redraw requested. - state - .window_user_requests - .get_mut(window_id) - .unwrap() - .redraw_requested = true; + .redraw_requested + .store(true, Ordering::Relaxed); physical_size }); - if let Some(physical_size) = physical_size { - sticky_exit_callback( - Event::WindowEvent { - window_id: crate::window::WindowId(*window_id), - event: WindowEvent::Resized(physical_size), - }, - &self.window_target, - &mut control_flow, - &mut callback, - ); - } - } - - // If the close is requested, send it here. - if window_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::Resized(physical_size), + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + + if compositor_update.close_window { + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId(window_id), event: WindowEvent::CloseRequested, }, &self.window_target, @@ -488,18 +373,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 +399,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 +464,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 26a895e5..0bf2ae58 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 0cf1c668..00000000 --- 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 fce6d8ec..36439821 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 f796643d..a7878511 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, - } +impl EventLoopWindowTarget { + #[inline] + pub fn available_monitors(&self) -> Vec { + self.state + .borrow() + .output_state + .outputs() + .map(MonitorHandle::new) + .collect() } - pub fn handle(&self) -> OutputManagerHandle { - self.handle.clone() - } -} - -/// A handle to output manager. -#[derive(Debug, Clone)] -pub struct OutputManagerHandle { - outputs: Arc>>, -} - -impl OutputManagerHandle { - fn new() -> Self { - let outputs = Arc::new(Mutex::new(VecDeque::new())); - Self { outputs } - } - - /// Handle addition of the output. - fn add_output(&self, output: WlOutput) { - let mut outputs = self.outputs.lock().unwrap(); - let position = outputs.iter().position(|handle| handle.proxy == output); - if position.is_none() { - outputs.push_back(MonitorHandle::new(output)); - } - } - - /// Handle removal of the output. - fn remove_output(&self, output: WlOutput) { - let mut outputs = self.outputs.lock().unwrap(); - let position = outputs.iter().position(|handle| handle.proxy == output); - if let Some(position) = position { - outputs.remove(position); - } - } - - /// Get all observed outputs. - pub fn available_outputs(&self) -> VecDeque { - self.outputs.lock().unwrap().clone() + #[inline] + pub fn primary_monitor(&self) -> Option { + // There's no primary monitor on Wayland. + None } } @@ -101,6 +33,80 @@ pub struct MonitorHandle { pub(crate) proxy: WlOutput, } +impl MonitorHandle { + #[inline] + pub(crate) fn new(proxy: WlOutput) -> Self { + Self { proxy } + } + + #[inline] + pub fn name(&self) -> Option { + let output_data = self.proxy.data::().unwrap(); + output_data.with_output_info(|info| info.name.clone()) + } + + #[inline] + pub fn native_identifier(&self) -> u32 { + let output_data = self.proxy.data::().unwrap(); + output_data.with_output_info(|info| info.id) + } + + #[inline] + pub fn size(&self) -> PhysicalSize { + let output_data = self.proxy.data::().unwrap(); + let dimensions = output_data.with_output_info(|info| { + info.modes + .iter() + .find_map(|mode| mode.current.then_some(mode.dimensions)) + }); + + match dimensions { + Some((width, height)) => (width as u32, height as u32), + _ => (0, 0), + } + .into() + } + + #[inline] + pub fn position(&self) -> PhysicalPosition { + let output_data = self.proxy.data::().unwrap(); + output_data.with_output_info(|info| info.location).into() + } + + #[inline] + pub fn refresh_rate_millihertz(&self) -> Option { + let output_data = self.proxy.data::().unwrap(); + output_data.with_output_info(|info| { + info.modes + .iter() + .find_map(|mode| mode.current.then_some(mode.refresh_rate as u32)) + }) + } + + #[inline] + pub fn scale_factor(&self) -> i32 { + let output_data = self.proxy.data::().unwrap(); + output_data.scale_factor() + } + + #[inline] + pub fn video_modes(&self) -> impl Iterator { + let output_data = self.proxy.data::().unwrap(); + let modes = output_data.with_output_info(|info| info.modes.clone()); + + let monitor = self.clone(); + + modes.into_iter().map(move |mode| { + PlatformVideoMode::Wayland(VideoMode { + size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(), + refresh_rate_millihertz: mode.refresh_rate as u32, + bit_depth: 32, + monitor: monitor.clone(), + }) + }) + } +} + impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { self.native_identifier() == other.native_identifier() @@ -127,78 +133,6 @@ impl std::hash::Hash for MonitorHandle { } } -impl MonitorHandle { - #[inline] - pub(crate) fn new(proxy: WlOutput) -> Self { - Self { proxy } - } - - #[inline] - pub fn name(&self) -> Option { - sctk::output::with_output_info(&self.proxy, |info| { - format!("{} ({})", info.model, info.make) - }) - } - - #[inline] - pub fn native_identifier(&self) -> u32 { - sctk::output::with_output_info(&self.proxy, |info| info.id).unwrap_or(0) - } - - #[inline] - pub fn size(&self) -> PhysicalSize { - match sctk::output::with_output_info(&self.proxy, |info| { - info.modes - .iter() - .find(|mode| mode.is_current) - .map(|mode| mode.dimensions) - }) { - Some(Some((w, h))) => (w as u32, h as u32), - _ => (0, 0), - } - .into() - } - - #[inline] - pub fn position(&self) -> PhysicalPosition { - sctk::output::with_output_info(&self.proxy, |info| info.location) - .unwrap_or((0, 0)) - .into() - } - - #[inline] - pub fn refresh_rate_millihertz(&self) -> Option { - sctk::output::with_output_info(&self.proxy, |info| { - info.modes - .iter() - .find_map(|mode| mode.is_current.then_some(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) - } - - #[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 monitor = self.clone(); - - modes.into_iter().map(move |mode| { - PlatformVideoMode::Wayland(VideoMode { - size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(), - refresh_rate_millihertz: mode.refresh_rate as u32, - bit_depth: 32, - monitor: monitor.clone(), - }) - }) - } -} - #[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 7612ff67..00000000 --- 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 36fda96b..00000000 --- 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 991afff2..e13488e2 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 262a014b..daede709 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -1,85 +1,235 @@ -//! 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, + }; -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); + let virtual_keycode = keymap::keysym_to_vkey(event.keysym); + + let seat_state = self.seats.get(&keyboard.seat().id()).unwrap(); + let modifiers = seat_state.modifiers; + + 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, + ); - Some(Self { keyboard }) - } -} + // Don't send utf8 chars on release. + if state == ElementState::Released { + return; + } -impl Drop for Keyboard { - fn drop(&mut self) { - if self.keyboard.as_ref().version() >= 3 { - self.keyboard.release(); + if let Some(txt) = event.utf8 { + for ch in txt.chars() { + self.events_sink + .push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); + } } } } -struct KeyboardInner { - /// Currently focused surface. - target_window_id: Option, +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); - /// 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, + // Mark the window as focused. + match self.windows.get_mut().get(&window_id) { + Some(window) => window.lock().unwrap().set_has_focus(true), + None => return, + }; - /// Current state of modifiers keys. - modifiers_state: Rc>, + // Window gained focus. + self.events_sink + .push_window_event(WindowEvent::Focused(true), window_id); + + *keyboard.winit_data().window_id.lock().unwrap() = Some(window_id); + + // NOTE: GNOME still hasn't violates the specification wrt ordering of such + // events. See https://gitlab.gnome.org/GNOME/mutter/-/issues/2231. + 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, + ); + } + } + + fn leave( + &mut self, + _: &Connection, + _: &QueueHandle, + keyboard: &WlKeyboard, + surface: &WlSurface, + _serial: u32, + ) { + let window_id = wayland::make_wid(surface); + + // XXX The check whether the window exists is essential as we might get a nil surface... + 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); + } + + // FIXME(kchibisov): recent spec suggested that this event could be sent + // without any focus to indicate modifiers for the pointer, so update + // for will be required once https://github.com/rust-windowing/winit/issues/2768 + // wrt winit design of the event is resolved. + 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; + + // NOTE: part of the workaround from `fn enter`, see it above. + 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); + } } -impl KeyboardInner { - fn new(modifiers_state: Rc>) -> Self { +/// 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 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 +238,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 2d7b7533..63900512 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; + +#[derive(Debug)] +pub struct WinitSeatState { + /// The pointer bound on the seat. + pointer: Option>>, + + /// The touch bound on the seat. + touch: Option, + + /// The mapping from touched points to the surfaces they're present. + touch_map: FnvHashMap, + + /// The text input bound on the seat. + text_input: Option>, + + /// The relative pointer bound on the seat. + relative_pointer: Option, + + /// The keyboard bound on the seat. + keyboard_state: Option, + + /// The current modifiers state on the seat. + modifiers: ModifiersState, + + /// Wether we have pending modifiers. + modifiers_pending: bool, } -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); - } - - let seat_listener = env.listen_for_seats(move |seat, seat_data, _| { - inner.process_seat_update(&seat, seat_data); - }); - +impl WinitSeatState { + pub fn new() -> Self { Self { - _seat_listener: seat_listener, - } - } -} - -/// Inner state of the seat manager. -struct SeatManagerInner { - /// Currently observed seats. - seats: Vec, - - /// Loop handle. - loop_handle: LoopHandle<'static, WinitState>, - - /// Relative pointer manager. - relative_pointer_manager: Option>, - - /// Pointer constraints. - pointer_constraints: Option>, - - /// Text input manager. - text_input_manager: Option>, - - /// A theme manager. - theme_manager: ThemeManager, -} - -impl SeatManagerInner { - fn new( - theme_manager: ThemeManager, - relative_pointer_manager: Option>, - pointer_constraints: Option>, - text_input_manager: Option>, - loop_handle: LoopHandle<'static, WinitState>, - ) -> Self { - Self { - seats: Vec::new(), - loop_handle, - relative_pointer_manager, - pointer_constraints, - text_input_manager, - theme_manager, - } - } - - /// Handle seats update from the `SeatListener`. - pub fn process_seat_update(&mut self, seat: &Attached, seat_data: &SeatData) { - let detached_seat = seat.detach(); - - let position = self.seats.iter().position(|si| si.seat == detached_seat); - let index = position.unwrap_or_else(|| { - self.seats.push(SeatInfo::new(detached_seat)); - self.seats.len() - 1 - }); - - let seat_info = &mut self.seats[index]; - - // Pointer handling. - if seat_data.has_pointer && !seat_data.defunct { - if seat_info.pointer.is_none() { - seat_info.pointer = Some(Pointers::new( - seat, - &self.theme_manager, - &self.relative_pointer_manager, - &self.pointer_constraints, - seat_info.modifiers_state.clone(), - )); - } - } else { - seat_info.pointer = None; - } - - // Handle keyboard. - if seat_data.has_keyboard && !seat_data.defunct { - if seat_info.keyboard.is_none() { - seat_info.keyboard = Keyboard::new( - seat, - self.loop_handle.clone(), - seat_info.modifiers_state.clone(), - ); - } - } else { - seat_info.keyboard = None; - } - - // Handle touch. - if seat_data.has_touch && !seat_data.defunct { - if seat_info.touch.is_none() { - seat_info.touch = Some(Touch::new(seat)); - } - } else { - seat_info.touch = None; - } - - // Handle text input. - if let Some(text_input_manager) = self.text_input_manager.as_ref() { - if seat_data.defunct { - seat_info.text_input = None; - } else if seat_info.text_input.is_none() { - seat_info.text_input = Some(TextInput::new(seat, text_input_manager)); - } - } - } -} - -/// Resources associtated with a given seat. -struct SeatInfo { - /// Seat to which this `SeatInfo` belongs. - seat: WlSeat, - - /// A keyboard handle with its repeat rate handling. - keyboard: Option, - - /// All pointers we're using on a seat. - pointer: Option, - - /// Touch handling. - touch: Option, - - /// Text input handling aka IME. - text_input: Option, - - /// The current state of modifiers observed in keyboard handler. - /// - /// We keep modifiers state on a seat, since it's being used by pointer events as well. - modifiers_state: Rc>, -} - -impl SeatInfo { - pub fn new(seat: WlSeat) -> Self { - Self { - seat, - keyboard: None, pointer: None, touch: None, + relative_pointer: None, text_input: None, - modifiers_state: Rc::new(RefCell::new(ModifiersState::default())), + 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 + } + + 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(); + } + 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); + } + _ => (), + } + + if let Some(text_input_state) = seat_state + .text_input + .is_none() + .then_some(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(), + ))); + } + } + + 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(); + } + } + } + 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(); + } + } + } + _ => (), + } + + if let Some(text_input) = seat_state.text_input.take() { + text_input.destroy(); + } + } + + fn new_seat( + &mut self, + _connection: &Connection, + _queue_handle: &QueueHandle, + seat: WlSeat, + ) { + self.seats.insert(seat.id(), WinitSeatState::new()); + } + + fn remove_seat( + &mut self, + _connection: &Connection, + _queue_handle: &QueueHandle, + seat: WlSeat, + ) { + let _ = self.seats.remove(&seat.id()); + } +} + +#[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 17e7a57a..00000000 --- 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 945443f8..00000000 --- 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 04ab0119..3af7e8f0 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -1,343 +1,491 @@ -//! 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::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::seat::pointer::{ThemeManager, ThemedPointer}; -use sctk::window::Window; +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::event::ModifiersState; -use crate::platform_impl::wayland::event_loop::WinitState; -use crate::platform_impl::wayland::window::WinitFrame; -use crate::window::CursorIcon; +use crate::dpi::{LogicalPosition, PhysicalPosition}; +use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}; -mod data; -mod handlers; +use crate::platform_impl::wayland::state::WinitState; +use crate::platform_impl::wayland::{self, DeviceId, WindowId}; -use data::PointerData; +pub mod relative_pointer; -/// A proxy to Wayland pointer, which serves requests from a `WindowHandle`. -pub struct WinitPointer { - pointer: ThemedPointer, +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; - /// Create confined pointers. - pointer_constraints: Option>, + let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); - /// Cursor to handle confine requests. - confined_pointer: Weak>>, + for event in events { + let surface = &event.surface; - /// Cursor to handle locked requests. - locked_pointer: Weak>>, + // The parent surface. + let parent_surface = match event.surface.data::() { + Some(data) => data.parent_surface().unwrap_or(surface), + None => continue, + }; - /// Latest observed serial in pointer events. - /// used by Window::start_interactive_move() - latest_serial: Rc>, + let window_id = wayland::make_wid(parent_surface); - /// Latest observed serial in pointer enter events. - /// used by Window::set_cursor() - latest_enter_serial: Rc>, + // Ensure that window exists. + let mut window = match self.windows.get_mut().get_mut(&window_id) { + Some(window) => window.lock().unwrap(), + None => continue, + }; - /// Seat. - seat: WlSeat, -} + let scale_factor = window.scale_factor(); + let position: PhysicalPosition = + LogicalPosition::new(event.position.0, event.position.1).to_physical(scale_factor); -impl PartialEq for WinitPointer { - fn eq(&self, other: &Self) -> bool { - *self.pointer == *other.pointer - } -} + 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(); -impl Eq for WinitPointer {} + 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 = matches!(kind, PointerEventKind::Press { .. }); -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; - } - }; + // 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); - 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"], + if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) { + window.pointer_entered(pointer); + } - CursorIcon::NoDrop => &["no-drop", "circle"], - CursorIcon::NotAllowed => &["crossed_circle"], + // Set the currently focused surface. + pointer.winit_data().inner.lock().unwrap().surface = Some(window_id); - // 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"], + 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); + } - CursorIcon::Wait => &["watch"], + // Remove the active surface. + pointer.winit_data().inner.lock().unwrap().surface = None; - CursorIcon::ZoomIn => &["zoom-in"], - CursorIcon::ZoomOut => &["zoom-out"], - }; + 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 serial = Some(self.latest_enter_serial.get()); - for cursor in cursors { - if self.pointer.set_cursor(cursor, serial).is_ok() { - return; + 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, + ) + } } } - warn!("Failed to set cursor to {:?}", cursor_icon); - } - - /// Confine the pointer to a surface. - pub fn confine(&self, surface: &WlSurface) { - let pointer_constraints = match &self.pointer_constraints { - Some(pointer_constraints) => pointer_constraints, - None => return, - }; - - let confined_pointer = match self.confined_pointer.upgrade() { - Some(confined_pointer) => confined_pointer, - // A pointer is gone. - None => return, - }; - - *confined_pointer.borrow_mut() = Some(init_confined_pointer( - pointer_constraints, - surface, - &self.pointer, - )); - } - - /// Tries to unconfine the pointer if the current pointer is confined. - pub fn unconfine(&self) { - let confined_pointer = match self.confined_pointer.upgrade() { - Some(confined_pointer) => confined_pointer, - // A pointer is gone. - None => return, - }; - - let mut confined_pointer = confined_pointer.borrow_mut(); - - if let Some(confined_pointer) = confined_pointer.take() { - confined_pointer.destroy(); - } - } - - 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, - )); - } - - pub fn unlock(&self) { - let locked_pointer = match self.locked_pointer.upgrade() { - Some(locked_pointer) => locked_pointer, - // A pointer is gone. - None => return, - }; - - let mut locked_pointer = locked_pointer.borrow_mut(); - - if let Some(locked_pointer) = locked_pointer.take() { - locked_pointer.destroy(); - } - } - - 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, - }; - - 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 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()); } } -/// A pointer wrapper for easy releasing and managing pointers. -pub(super) struct Pointers { - /// A pointer itself. - pointer: ThemedPointer, +#[derive(Debug)] +pub struct WinitPointerData { + /// The surface associated with this pointer, which is used for icons. + cursor_surface: WlSurface, - /// A relative pointer handler. - relative_pointer: Option, + /// The inner winit data associated with the pointer. + inner: Mutex, - /// Confined pointer. - confined_pointer: Rc>>, - - /// Locked pointer. - locked_pointer: Rc>>, + /// The data required by the sctk. + sctk_data: PointerData, } -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) - }); - +impl WinitPointerData { + pub fn new(seat: WlSeat, surface: WlSurface) -> Self { Self { + cursor_surface: surface, + inner: Mutex::new(WinitPointerDataInner::default()), + sctk_data: PointerData::new(seat), + } + } + + pub fn lock_pointer( + &self, + pointer_constraints: &PointerConstraintsState, + surface: &WlSurface, + pointer: &WlPointer, + queue_handle: &QueueHandle, + ) { + let mut inner = self.inner.lock().unwrap(); + if inner.locked_pointer.is_none() { + inner.locked_pointer = Some(pointer_constraints.lock_pointer( + surface, + pointer, + None, + Lifetime::Persistent, + queue_handle, + GlobalData, + )); + } + } + + pub fn unlock_pointer(&self) { + let mut inner = self.inner.lock().unwrap(); + if let Some(locked_pointer) = inner.locked_pointer.take() { + locked_pointer.destroy(); + } + } + + 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, pointer, - relative_pointer, - confined_pointer, - locked_pointer, + None, + Lifetime::Persistent, + queue_handle, + GlobalData, + )); + } + + pub fn unconfine_pointer(&self) { + let inner = self.inner.lock().unwrap(); + if let Some(confined_pointer) = inner.confined_pointer.as_ref() { + confined_pointer.destroy(); + } + } + + /// Seat associated with this pointer. + pub fn seat(&self) -> &WlSeat { + self.sctk_data.seat() + } + + /// The WlSurface used to set cursor theme. + pub fn cursor_surface(&self) -> &WlSurface { + &self.cursor_surface + } + + /// Active window. + pub fn focused_window(&self) -> Option { + self.inner.lock().unwrap().surface + } + + /// Last button serial. + pub fn latest_button_serial(&self) -> u32 { + self.inner.lock().unwrap().latest_button_serial + } + + /// Last enter serial. + pub fn latest_enter_serial(&self) -> u32 { + self.sctk_data.latest_enter_serial().unwrap_or_default() + } + + 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); } } } -impl Drop for Pointers { +impl Drop for WinitPointerData { fn drop(&mut self) { - // Drop relative pointer. - if let Some(relative_pointer) = self.relative_pointer.take() { - relative_pointer.destroy(); - } + self.cursor_surface.destroy(); + } +} - // Drop confined pointer. - if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() { - confined_pointer.destroy(); - } +impl PointerDataExt for WinitPointerData { + fn pointer_data(&self) -> &PointerData { + &self.sctk_data + } +} - // Drop lock ponter. - if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() { +#[derive(Debug)] +pub struct WinitPointerDataInner { + /// The associated locked pointer. + locked_pointer: Option, + + /// The associated confined pointer. + confined_pointer: Option, + + /// Serial of the last button event. + latest_button_serial: u32, + + /// Currently focused window. + surface: Option, + + /// Current axis phase. + phase: TouchPhase, +} + +impl Drop for WinitPointerDataInner { + fn drop(&mut self) { + if let Some(locked_pointer) = self.locked_pointer.take() { locked_pointer.destroy(); } - // Drop the pointer itself in case it's possible. - if self.pointer.as_ref().version() >= 3 { - self.pointer.release(); + if let Some(confined_pointer) = self.confined_pointer.take() { + confined_pointer.destroy(); } } } -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() +impl Default for WinitPointerDataInner { + fn default() -> Self { + Self { + surface: None, + locked_pointer: None, + confined_pointer: None, + latest_button_serial: 0, + phase: TouchPhase::Ended, + } + } } -pub(super) fn init_confined_pointer( - pointer_constraints: &Attached, - surface: &WlSurface, - pointer: &WlPointer, -) -> ZwpConfinedPointerV1 { - let confined_pointer = - pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent); +/// 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; - confined_pointer.quick_assign(move |_, _, _| {}); - - confined_pointer.detach() + match button { + BTN_LEFT => MouseButton::Left, + BTN_RIGHT => MouseButton::Right, + BTN_MIDDLE => MouseButton::Middle, + button => MouseButton::Other(button as u16), + } } -pub(super) fn init_locked_pointer( - pointer_constraints: &Attached, - surface: &WlSurface, - pointer: &WlPointer, -) -> ZwpLockedPointerV1 { - let locked_pointer = - pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent); - - locked_pointer.quick_assign(move |_, _, _| {}); - - locked_pointer.detach() +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 struct PointerConstraintsState { + pointer_constraints: ZwpPointerConstraintsV1, +} + +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, + }) + } +} + +impl Deref for PointerConstraintsState { + type Target = ZwpPointerConstraintsV1; + fn deref(&self) -> &Self::Target { + &self.pointer_constraints + } +} + +impl Dispatch for PointerConstraintsState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpPointerConstraintsV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for PointerConstraintsState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpLockedPointerV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for PointerConstraintsState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpConfinedPointerV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +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 00000000..23a6e219 --- /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 bb94ed13..00000000 --- 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_some(idx)); - let cursor_end = usize::try_from(cursor_end) - .ok() - .and_then(|idx| text.is_char_boundary(idx).then_some(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 60219c70..cff18dff 100644 --- a/src/platform_impl/linux/wayland/seat/text_input/mod.rs +++ b/src/platform_impl/linux/wayland/seat/text_input/mod.rs @@ -1,23 +1,174 @@ -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, +pub struct TextInputState { + text_input_manager: ZwpTextInputManagerV3, } -trait ZwpTextInputV3Ext { +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 Deref for TextInputState { + type Target = ZwpTextInputManagerV3; + + fn deref(&self) -> &Self::Target { + &self.text_input_manager + } +} + +impl Dispatch for TextInputState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpTextInputManagerV3, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +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.set_content_type_by_purpose(window.ime_purpose()); + 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); + + // XXX this check is essential, because `leave` could have a + // refence to nil 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_some(idx)); + let cursor_end = usize::try_from(cursor_end) + .ok() + .and_then(|idx| text.is_char_boundary(idx).then_some(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. + } + _ => {} + } + } +} + +pub trait ZwpTextInputV3Ext { fn set_content_type_by_purpose(&self, purpose: ImePurpose); } @@ -32,81 +183,30 @@ impl ZwpTextInputV3Ext for ZwpTextInputV3 { } } -impl TextInputHandler { - #[inline] - pub fn set_ime_position(&self, x: i32, y: i32) { - self.text_input.set_cursor_rectangle(x, y, 0, 0); - self.text_input.commit(); - } - - #[inline] - pub fn set_content_type_by_purpose(&self, purpose: ImePurpose) { - self.text_input.set_content_type_by_purpose(purpose); - self.text_input.commit(); - } - - #[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(); - } - - self.text_input.commit(); - } +/// The Data associated with the text input. +#[derive(Default)] +pub struct TextInputData { + inner: std::sync::Mutex, } -/// A wrapper around text input to automatically destroy the object on `Drop`. -pub struct TextInput { - text_input: Attached, -} +#[derive(Default)] +pub struct TextInputDataInner { + /// The `WlSurface` we're performing input to. + surface: Option, -impl TextInput { - pub fn new(seat: &Attached, text_input_manager: &ZwpTextInputManagerV3) -> Self { - let text_input = text_input_manager.get_text_input(seat); - let mut text_input_inner = TextInputInner::new(); - text_input.quick_assign(move |text_input, event, mut dispatch_data| { - let winit_state = dispatch_data.get::().unwrap(); - handlers::handle_text_input(text_input, &mut text_input_inner, event, winit_state); - }); - - let text_input: Attached = text_input.into(); - - Self { text_input } - } -} - -impl Drop for TextInput { - fn drop(&mut self) { - self.text_input.destroy(); - } -} - -struct TextInputInner { - /// Currently focused surface. - target_window_id: Option, - - /// Pending 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 190b5770..00000000 --- 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 197e9ac9..502f2e8c 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, + ); + } + + 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, + ); + } + + 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); + + 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. + } } -impl Drop for Touch { - fn drop(&mut self) { - if self.touch.as_ref().version() >= 3 { - self.touch.release(); - } +/// The state of the touch point. +#[derive(Debug)] +pub struct TouchPoint { + /// The surface on which the point is present. + pub surface: WlSurface, + + /// The location of the point on the surface. + pub location: LogicalPosition, +} + +pub trait TouchDataExt { + fn seat(&self) -> &WlSeat; +} + +impl TouchDataExt for WlTouch { + fn seat(&self) -> &WlSeat { + self.data::() + .expect("failed to get touch data.") + .seat() } } -/// The data used by touch handlers. -pub(super) struct TouchInner { - /// Current touch points. - touch_points: Vec, -} - -impl TouchInner { - fn new() -> Self { - Self { - touch_points: Vec::new(), - } - } -} - -/// Location of touch press. -pub(super) struct TouchPoint { - /// A surface where the touch point is located. - surface: WlSurface, - - /// Location of the touch point. - position: LogicalPosition, - - /// Id. - id: i32, -} - -impl TouchPoint { - pub fn new(surface: WlSurface, position: LogicalPosition, id: i32) -> Self { - Self { - surface, - position, - id, - } - } -} +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 00000000..f9b7c4af --- /dev/null +++ b/src/platform_impl/linux/wayland/state.rs @@ -0,0 +1,368 @@ +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 let Some(window) = self.windows.get_mut().get(&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 + }; + + // Update the scale factor right away. + window.lock().unwrap().set_scale_factor(scale_factor); + 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 00000000..8e92eb1d --- /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 00000000..ead1e06b --- /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 00000000..a8f399c3 --- /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 00000000..1befb5ff --- /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 14c31788..76168f36 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -1,94 +1,83 @@ -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, -}; - -#[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"; +pub use state::WindowState; +/// 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 state of the window. + window_state: Arc>, - /// The underlying wl_surface. - surface: WlSurface, + /// Compositor to handle WlRegion stuff. + compositor: Arc, - /// The scale factor. - scale_factor: Arc>, + /// The wayland display used solely for raw window handle. + display: WlDisplay, - /// The current window size. - size: Arc>>, + /// Xdg activation to request user attention. + xdg_activation: Option, - /// A handle to output manager. - output_manager_handle: OutputManagerHandle, + /// The state of the requested attention from the `xdg_activation`. + attention_requested: Arc, - /// Event loop proxy to wake it up. + /// Handle to the main queue to perform requests. + queue_handle: QueueHandle, + + /// Window requests to the event loop. + window_requests: Arc, + + /// Observed monitors. + monitors: Arc>>, + + /// Source to wake-up the event-loop for window requests. event_loop_awakener: calloop::ping::Ping, - /// Fullscreen state. - fullscreen: Arc, - - /// Maximized state. - maximized: Arc, - - /// Available windowing features. - windowing_features: WindowingFeatures, - - /// Requests that SCTK window should perform. - window_requests: Arc>>, - - /// Whether the window is resizeable. - resizeable: AtomicBool, - - /// Whether the window is decorated. - decorated: AtomicBool, - - /// Grabbing mode. - cursor_grab_mode: Mutex, - - /// Whether the window has keyboard focus. - has_focus: Arc, + /// The event sink to deliver sythetic events. + window_events_sink: Arc>, } impl Window { @@ -97,269 +86,136 @@ 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::(); + let queue_handle = event_loop_window_target.queue_handle.clone(); + let mut state = event_loop_window_target.state.borrow_mut(); - // 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 monitors = state.monitors.clone(); - 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 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 fractional_scale = fractional_scale.detach(); - let viewport = viewporter.get_viewport(&surface).detach(); - let fractional_scaling_state = - FractionalScalingState::new(viewport, fractional_scale); - - (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 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)); + .map(|size| size.to_logical::(1.)) + .unwrap_or((800, 600).into()); - 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 window = state.xdg_shell.create_window( + surface.clone(), + WindowDecorations::ServerDefault, + &queue_handle, + ); - 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_state = WindowState::new( + event_loop_window_target.connection.clone(), + &event_loop_window_target.queue_handle, + &state, + size, + window.clone(), + attributes.preferred_theme, + ); - 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 the app_id. + if let Some(name) = platform_attributes.name.map(|name| name.general) { + window.set_app_id(name); } - // Set decorations. - if attributes.decorations { - window.set_decorate(Decorations::FollowServer); - } else { - window.set_decorate(Decorations::None); - } + // Set the window title. + window_state.set_title(attributes.title); - // Min dimensions. - let min_size = attributes - .min_inner_size - .map(|size| size.to_logical::(scale_factor).into()); - window.set_min_size(min_size); + // Set the min and max sizes. + 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.)); + window_state.set_min_inner_size(min_size); + window_state.set_max_inner_size(max_size); - // Max dimensions. - let max_size = attributes - .max_inner_size - .map(|size| size.to_logical::(scale_factor).into()); - window.set_max_size(max_size); + // Non-resizable implies that the min and max sizes are set to the same value. + window_state.set_resizable(attributes.resizable); - // 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. + // Set startup mode. match attributes.fullscreen.map(Into::into) { Some(Fullscreen::Exclusive(_)) => { - warn!("`Fullscreen::Exclusive` is ignored on Wayland") + warn!("`Fullscreen::Exclusive` is ignored on Wayland"); } 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, }); - window.set_fullscreen(monitor.as_ref()); + window.set_fullscreen(output.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(), - ); - - // Set opaque region. - window_handle.set_transparent(attributes.transparent); - - // Set resizable state, so we can determine how to handle `Window::set_inner_size`. - window_handle.is_resizable.set(attributes.resizable); - - let mut winit_state = event_loop_window_target.state.borrow_mut(); - - winit_state.window_map.insert(window_id, window_handle); - - // On Wayland window doesn't have Focus by default and it'll get it later on. So be - // explicit here. - winit_state - .event_sink - .push_window_event(crate::event::WindowEvent::Focused(false), window_id); - - // Add state for the window. - winit_state - .window_user_requests - .insert(window_id, window_user_request); - winit_state - .window_compositor_updates - .insert(window_id, WindowCompositorUpdate::new()); - - let windowing_features = event_loop_window_target.windowing_features; - - // To make our window usable for drawing right away we must `ack` a `configure` - // from the server, the acking part here is done by SCTK window frame, so we just - // need to sync with server so it'll be done automatically for us. - { - 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!()); - } - - // We all praise GNOME for these 3 lines of pure magic. If we don't do that, - // GNOME will shrink our window a bit for the size of the decorations. I guess it - // happens because we haven't committed them with buffers to the server. - let window_handle = winit_state.window_map.get_mut(&window_id).unwrap(); - window_handle.window.refresh(); - - let output_manager_handle = event_loop_window_target.output_manager.handle(); - - let window = Self { - window_id, - surface, - display: event_loop_window_target.display.clone(), - output_manager_handle, - size, - window_requests, - event_loop_awakener: event_loop_window_target.event_loop_awakener.clone(), - fullscreen, - maximized, - windowing_features, - 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(), + _ if attributes.maximized => window.set_maximized(), + _ => (), }; - Ok(window) + // XXX Do initial commit. + window.commit(); + + // 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." + )) + })?; + } + + // 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(); + + Ok(Self { + window, + display, + monitors, + window_id, + compositor, + window_state, + queue_handle, + xdg_activation, + 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(); + // 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)); - let size = size.to_logical::(scale_factor); - *self.size.lock().unwrap() = size; - - 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,71 +320,113 @@ 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)); + self.window_state.lock().unwrap().set_resizable(resizable); } #[inline] pub fn is_resizable(&self) -> bool { - self.resizeable.load(Ordering::Relaxed) + self.window_state.lock().unwrap().resizable() } #[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 current_monitor = self.current_monitor().map(PlatformMonitorHandle::Wayland); + 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,192 +435,211 @@ 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); + } + + 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; + } + }; + + // 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.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(); } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { - if !self.windowing_features.pointer_constraints() { - if mode == CursorGrabMode::None { - return Ok(()); - } - - return Err(ExternalError::NotSupported(NotSupportedError::new())); - } - - *self.cursor_grab_mode.lock().unwrap() = mode; - self.send_request(WindowRequest::SetCursorGrabMode(mode)); - - Ok(()) - } - - 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)); + 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)); + self.window_state.lock().unwrap().set_theme(theme) } #[inline] pub fn theme(&self) -> Option { - None + self.window_state.lock().unwrap().theme() } #[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) } } @@ -735,20 +662,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 1cc0a124..00000000 --- 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 00000000..f968866b --- /dev/null +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -0,0 +1,905 @@ +//! The state of the window, which is shared with the event-loop. + +use std::mem::ManuallyDrop; +use std::num::NonZeroU32; +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::{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, Theme}; + +use crate::platform_impl::wayland::seat::{ + PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext, +}; +use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState}; + +#[cfg(feature = "sctk-adwaita")] +pub type WinitFrame = sctk_adwaita::AdwaitaFrame; +#[cfg(not(feature = "sctk-adwaita"))] +pub type WinitFrame = sctk::shell::xdg::frame::fallback_frame::FallbackFrame; + +// 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, + + /// Theme varaint. + theme: Option, + + /// The current window title. + title: String, + + /// Whether the frame is resizable. + resizable: bool, + + /// 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>, + + /// The size of the window when no states were applied to it. The primary use for it + /// is to fallback to original window size, before it was maximized, if the compositor + /// sends `None` for the new size in the configure. + stateless_size: LogicalSize, + + 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 WinitFrame::new( + &*self.window, + shm, + subcompositor.clone(), + self.queue_handle.clone(), + #[cfg(feature = "sctk-adwaita")] + into_sctk_adwaita_config(self.theme), + ) { + Ok(mut frame) => { + frame.set_title(&self.title); + // Ensure that the frame is not hidden. + frame.set_hidden(false); + self.frame = Some(frame); + } + Err(err) => { + warn!("Failed to create client side decorations frame: {err}"); + self.csd_fails = true; + } + } + } else if configure.decoration_mode == DecorationMode::Server { + // Drop the frame for server side decorations to save resources. + self.frame = None; + } + + let stateless = Self::is_stateless(&configure); + + let new_size = if let Some(frame) = self.frame.as_mut() { + // Configure the window states. + frame.update_state(configure.state); + + match configure.new_size { + (Some(width), Some(height)) => { + let (width, height) = frame.subtract_borders(width, height); + ( + width.map(|w| w.get()).unwrap_or(1), + height.map(|h| h.get()).unwrap_or(1), + ) + .into() + } + (_, _) if stateless => self.stateless_size, + _ => self.size, + } + } else { + match configure.new_size { + (Some(width), Some(height)) => (width.get(), height.get()).into(), + _ if stateless => self.stateless_size, + _ => self.size, + } + }; + + // XXX Set the configure before doing a resize. + self.last_configure = Some(configure); + + // XXX Update the new size right away. + self.resize(new_size); + + new_size + } + + #[inline] + fn is_stateless(configure: &WindowConfigure) -> bool { + !(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled()) + } + + /// 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.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 + } + } + + /// Get the stored resizable state. + #[inline] + pub fn resizable(&self) -> bool { + self.resizable + } + + /// Set the resizable state on the window. + #[inline] + pub fn set_resizable(&mut self, resizable: bool) { + if self.resizable == resizable { + return; + } + + self.resizable = resizable; + if resizable { + // Restore min/max sizes of the window. + self.reload_min_max_hints(); + } else { + self.set_min_inner_size(Some(self.size)); + self.set_max_inner_size(Some(self.size)); + } + + // Reload the state on the frame as well. + if let Some(frame) = self.frame.as_mut() { + frame.set_resizable(resizable); + } + } + + /// 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_some(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, + theme: Option, + ) -> 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, + theme, + 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, + stateless_size: size, + text_inputs: Vec::new(), + title: String::default(), + transparent: false, + resizable: true, + 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 stateless size. + if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) { + self.stateless_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( + NonZeroU32::new(self.size.width).unwrap(), + NonZeroU32::new(self.size.height).unwrap(), + ); + } + + ( + frame.location(), + frame.add_borders(self.size.width, self.size.height).into(), + ) + } else { + ((0, 0), self.size) + }; + + // Reload the hint. + self.reload_transparency_hint(); + + // 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 CSD theme. + pub fn set_theme(&mut self, theme: Option) { + self.theme = theme; + #[cfg(feature = "sctk-adwaita")] + if let Some(frame) = self.frame.as_mut() { + frame.set_config(into_sctk_adwaita_config(theme)) + } + } + + /// The current theme for CSD decorations. + #[inline] + pub fn theme(&self) -> Option { + self.theme + } + + /// 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); + // Force the resize. + self.resize(self.size); + } + } + + /// 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(); + } + } + + /// Get the IME purpose. + pub fn ime_purpose(&self) -> ImePurpose { + self.ime_purpose + } + + /// 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, + } + } +} + +// XXX rust doesn't allow from `Option`. +#[cfg(feature = "sctk-adwaita")] +fn into_sctk_adwaita_config(theme: Option) -> sctk_adwaita::FrameConfig { + match theme { + Some(Theme::Light) => sctk_adwaita::FrameConfig::light(), + Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(), + None => sctk_adwaita::FrameConfig::auto(), + } +} diff --git a/wayland_protocols/fractional-scale-v1.xml b/wayland_protocols/fractional-scale-v1.xml deleted file mode 100644 index 350bfc01..00000000 --- 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. - - - - -