diff --git a/CHANGELOG.md b/CHANGELOG.md index 124f723533f..a6025d2163f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,8 @@ And please only add new entries to the top of this list, right below the `# Unre - On Android, fix `DeviceId` to contain device id's. - Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now. - On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time. -- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Windows for now. +- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now. +- On Wayland, support `Occluded` event with xdg-shell v6 # 0.29.1-beta diff --git a/Cargo.toml b/Cargo.toml index 1e189755a2e..462333c248e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"] -wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "fnv", "memmap2"] +wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"] wayland-dlopen = ["wayland-backend/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"] @@ -140,22 +140,22 @@ features = [ ] [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] +ahash = { version = "0.8.3", features = ["no-rng"], optional = true } bytemuck = { version = "1.13.1", default-features = false, optional = true } +calloop = "0.12.3" libc = "0.2.64" +memmap2 = { version = "0.9.0", optional = true } percent-encoding = { version = "2.0", optional = true } -fnv = { version = "1.0.3", optional = true } -sctk = { package = "smithay-client-toolkit", version = "0.17.0", default-features = false, features = ["calloop"], 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 } -wayland-protocols-plasma = { version = "0.1.0", features = [ "client" ], optional = true } -calloop = "0.10.5" rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] } +sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true } +sctk-adwaita = { git = "https://github.com/kchibisov/sctk-adwaita", branch = "wayland-csd-frame", default_features = false, optional = true } +wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true } +wayland-client = { version = "0.31.1", optional = true } +wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true } +wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true } x11-dl = { version = "2.18.5", optional = true } x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true } xkbcommon-dl = "0.4.0" -memmap2 = { version = "0.5.0", optional = true } [target.'cfg(target_os = "redox")'.dependencies] orbclient = { version = "0.3.42", default-features = false } diff --git a/deny.toml b/deny.toml index eb551db8e7e..59fe8e34b98 100644 --- a/deny.toml +++ b/deny.toml @@ -32,9 +32,6 @@ wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny deny = [] skip = [ { name = "bitflags" }, # the ecosystem is in the process of migrating. - { 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 = "memmap2" }, # sctk uses a different version until the next update { name = "libloading" }, # x11rb uses a different version until the next update { name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392 { name = "num_enum"}, # See above ^, waiting for release diff --git a/src/event.rs b/src/event.rs index 963fdb099d9..5a3998c537f 100644 --- a/src/event.rs +++ b/src/event.rs @@ -574,7 +574,7 @@ pub enum WindowEvent { /// ### Others /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. - /// - **Android / Wayland / Windows / Orbital:** Unsupported. + /// - **Android / Windows / Orbital:** Unsupported. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding diff --git a/src/platform_impl/linux/common/xkb_state.rs b/src/platform_impl/linux/common/xkb_state.rs index d8bfc9e0bb6..7c4449af628 100644 --- a/src/platform_impl/linux/common/xkb_state.rs +++ b/src/platform_impl/linux/common/xkb_state.rs @@ -13,7 +13,7 @@ use xkbcommon_dl::{ XkbCommonCompose, }; #[cfg(feature = "wayland")] -use {memmap2::MmapOptions, wayland_backend::io_lifetimes::OwnedFd}; +use {memmap2::MmapOptions, std::os::unix::io::OwnedFd}; #[cfg(feature = "x11")] use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle}; diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 8fb148564a1..9159aa8176e 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -445,7 +445,9 @@ impl Window { } #[inline] - pub fn show_window_menu(&self, _position: Position) {} + pub fn show_window_menu(&self, position: Position) { + x11_or_wayland!(match self; Window(w) => w.show_window_menu(position)) + } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index c0baead6a14..d8c9509c04d 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -13,8 +13,9 @@ use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle}; use sctk::reexports::calloop; use sctk::reexports::calloop::Error as CalloopError; +use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::reexports::client::globals; -use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource}; +use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use crate::dpi::{LogicalSize, PhysicalSize}; use crate::error::{EventLoopError, OsError as RootOsError}; @@ -102,7 +103,7 @@ impl EventLoop { )?; // Register Wayland source. - let wayland_source = map_err!(WaylandSource::new(event_queue), WaylandError::Wire)?; + let wayland_source = WaylandSource::new(connection.clone(), event_queue); let wayland_dispatcher = calloop::Dispatcher::new(wayland_source, |_, queue, winit_state: &mut WinitState| { let result = queue.dispatch_pending(winit_state); @@ -254,46 +255,7 @@ impl EventLoop { let cause = loop { let start = Instant::now(); - // TODO(rib): remove this workaround and instead make sure that the calloop - // WaylandSource correctly implements the cooperative prepare_read protocol - // that support multithreaded wayland clients that may all read from the - // same socket. - // - // During the run of the user callback, some other code monitoring and reading the - // Wayland socket may have been run (mesa for example does this with vsync), if that - // is the case, some events may have been enqueued in our event queue. - // - // If some messages are there, the event loop needs to behave as if it was instantly - // woken up by messages arriving from the Wayland socket, to avoid delaying the - // dispatch of these events until we're woken up again. - let instant_wakeup = { - let mut wayland_source = self.wayland_dispatcher.as_source_mut(); - let queue = wayland_source.queue(); - let state = match &mut self.window_target.p { - PlatformEventLoopWindowTarget::Wayland(window_target) => { - window_target.state.get_mut() - } - #[cfg(x11_platform)] - _ => unreachable!(), - }; - - match queue.dispatch_pending(state) { - Ok(dispatched) => { - state.dispatched_events |= !state.events_sink.is_empty() - || !state.window_compositor_updates.is_empty(); - dispatched > 0 - } - Err(error) => { - error!("Error dispatching wayland queue: {}", error); - self.set_exit_code(1); - return; - } - } - }; - - timeout = if instant_wakeup { - Some(Duration::ZERO) - } else { + timeout = { let control_flow_timeout = match self.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Duration::ZERO), @@ -307,7 +269,13 @@ impl EventLoop { // NOTE Ideally we should flush as the last thing we do before polling // to wait for events, and this should be done by the calloop // WaylandSource but we currently need to flush writes manually. - let _ = self.connection.flush(); + // + // Checking for flush error is essential to perform an exit with error, since + // once we have a protocol error, we could get stuck retrying... + if self.connection.flush().is_err() { + self.set_exit_code(1); + return; + } if let Err(error) = self.loop_dispatch(timeout) { // NOTE We exit on errors from dispatches, since if we've got protocol error diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index 531399368d4..975e81d1c6d 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -67,7 +67,11 @@ impl Dispatch for WinitState { }; // Drop the repeat, if there were any. - seat_state.keyboard_state.as_mut().unwrap().current_repeat = None; + let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); + keyboard_state.current_repeat = None; + if let Some(token) = keyboard_state.repeat_token.take() { + keyboard_state.loop_handle.remove(token); + } // The keyboard focus is considered as general focus. state @@ -89,7 +93,11 @@ impl Dispatch for WinitState { // NOTE: we should drop the repeat regardless whethere it was for the present // window of for the window which just went gone. - seat_state.keyboard_state.as_mut().unwrap().current_repeat = None; + let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); + keyboard_state.current_repeat = None; + if let Some(token) = keyboard_state.repeat_token.take() { + keyboard_state.loop_handle.remove(token); + } // NOTE: The check whether the window exists is essential as we might get a // nil surface, regardless of what protocol says. @@ -204,6 +212,9 @@ impl Dispatch for WinitState { && Some(key) == keyboard_state.current_repeat { keyboard_state.current_repeat = None; + if let Some(token) = keyboard_state.repeat_token.take() { + keyboard_state.loop_handle.remove(token); + } } } WlKeyboardEvent::Modifiers { diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 1832e2af6ac..f3562421cb5 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -2,7 +2,7 @@ use std::sync::Arc; -use fnv::FnvHashMap; +use ahash::AHashMap; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_touch::WlTouch; @@ -29,7 +29,7 @@ use keyboard::{KeyboardData, KeyboardState}; use text_input::TextInputData; use touch::TouchPoint; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct WinitSeatState { /// The pointer bound on the seat. pointer: Option>>, @@ -38,7 +38,7 @@ pub struct WinitSeatState { touch: Option, /// The mapping from touched points to the surfaces they're present. - touch_map: FnvHashMap, + touch_map: AHashMap, /// The text input bound on the seat. text_input: Option>, @@ -58,16 +58,7 @@ pub struct WinitSeatState { impl WinitSeatState { pub fn new() -> Self { - Self { - pointer: None, - touch: None, - relative_pointer: None, - text_input: None, - touch_map: Default::default(), - keyboard_state: None, - modifiers: ModifiersState::empty(), - modifiers_pending: false, - } + Default::default() } } @@ -97,12 +88,14 @@ impl SeatHandler for WinitState { 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 pointer_data = WinitPointerData::new(seat.clone()); let themed_pointer = self .seat_state .get_pointer_with_theme_and_data( queue_handle, &seat, + self.shm.wl_shm(), + surface, ThemeSpec::System, pointer_data, ) @@ -167,7 +160,7 @@ impl SeatHandler for WinitState { let pointer_data = pointer.pointer().winit_data(); // Remove the cursor from the mapping. - let surface_id = pointer_data.cursor_surface().id(); + let surface_id = pointer.surface().id(); let _ = self.pointer_surfaces.remove(&surface_id); // Remove the inner locks/confines before dropping the pointer. diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index 4d65e1fa0ec..3b62bae10d7 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use std::sync::{Arc, Mutex}; +use std::time::Duration; use sctk::reexports::client::delegate_dispatch; use sctk::reexports::client::protocol::wl_pointer::WlPointer; @@ -10,15 +11,17 @@ use sctk::reexports::client::protocol::wl_surface::WlSurface; 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::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; +use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1; use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1}; use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::csd_frame::FrameClick; use sctk::compositor::SurfaceData; use sctk::globals::GlobalData; use sctk::seat::pointer::{PointerData, PointerDataExt}; use sctk::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler}; use sctk::seat::SeatState; -use sctk::shell::xdg::frame::FrameClick; use crate::dpi::{LogicalPosition, PhysicalPosition}; use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}; @@ -67,35 +70,31 @@ impl PointerHandler for WinitState { PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. } if parent_surface != surface => { - if let Some(icon) = - window.frame_point_moved(seat, surface, event.position.0, event.position.1) - { + if let Some(icon) = window.frame_point_moved( + seat, + surface, + Duration::ZERO, + event.position.0, + event.position.1, + ) { if let Some(pointer) = seat_state.pointer.as_ref() { - let surface = pointer - .pointer() - .data::() - .unwrap() - .cursor_surface(); - let scale_factor = - surface.data::().unwrap().scale_factor(); - - let _ = pointer.set_cursor( - connection, - icon, - self.shm.wl_shm(), - surface, - scale_factor, - ); + let _ = pointer.set_cursor(connection, icon); } } } 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 => - { + ref kind @ PointerEventKind::Press { + button, + serial, + time, + } + | ref kind @ PointerEventKind::Release { + button, + serial, + time, + } if parent_surface != surface => { let click = match wayland_button_to_winit(button) { MouseButton::Left => FrameClick::Normal, MouseButton::Right => FrameClick::Alternate, @@ -109,6 +108,7 @@ impl PointerHandler for WinitState { pressed, seat, serial, + Duration::from_millis(time as u64), window_id, &mut self.window_compositor_updates, ); @@ -238,9 +238,6 @@ impl PointerHandler for WinitState { #[derive(Debug)] pub struct WinitPointerData { - /// The surface associated with this pointer, which is used for icons. - cursor_surface: WlSurface, - /// The inner winit data associated with the pointer. inner: Mutex, @@ -249,9 +246,8 @@ pub struct WinitPointerData { } impl WinitPointerData { - pub fn new(seat: WlSeat, surface: WlSurface) -> Self { + pub fn new(seat: WlSeat) -> Self { Self { - cursor_surface: surface, inner: Mutex::new(WinitPointerDataInner::default()), sctk_data: PointerData::new(seat), } @@ -313,11 +309,6 @@ impl WinitPointerData { 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 @@ -325,7 +316,7 @@ impl WinitPointerData { /// Last button serial. pub fn latest_button_serial(&self) -> u32 { - self.inner.lock().unwrap().latest_button_serial + self.sctk_data.latest_button_serial().unwrap_or_default() } /// Last enter serial. @@ -341,12 +332,6 @@ impl WinitPointerData { } } -impl Drop for WinitPointerData { - fn drop(&mut self) { - self.cursor_surface.destroy(); - } -} - impl PointerDataExt for WinitPointerData { fn pointer_data(&self) -> &PointerData { &self.sctk_data @@ -486,7 +471,35 @@ impl Dispatch for PointerConstrain } } +impl Dispatch for SeatState { + fn event( + _: &mut WinitState, + _: &WpCursorShapeDeviceV1, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + unreachable!("wp_cursor_shape_manager has no events") + } +} + +impl Dispatch for SeatState { + fn event( + _: &mut WinitState, + _: &WpCursorShapeManagerV1, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + unreachable!("wp_cursor_device_manager has no events") + } +} + delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState); +delegate_dispatch!(WinitState: [ WpCursorShapeManagerV1: GlobalData] => SeatState); +delegate_dispatch!(WinitState: [ WpCursorShapeDeviceV1: GlobalData] => 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/state.rs b/src/platform_impl/linux/wayland/state.rs index 2c885ee503d..afc126bdc00 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; -use fnv::FnvHashMap; +use ahash::AHashMap; use sctk::reexports::calloop::LoopHandle; use sctk::reexports::client::backend::ObjectId; @@ -62,10 +62,10 @@ pub struct WinitState { pub xdg_shell: XdgShell, /// The currently present windows. - pub windows: RefCell>>>, + pub windows: RefCell>>>, /// The requests from the `Window` to EventLoop, such as close operations and redraw requests. - pub window_requests: RefCell>>, + pub window_requests: RefCell>>, /// The events that were generated directly from the window. pub window_events_sink: Arc>, @@ -74,10 +74,10 @@ pub struct WinitState { pub window_compositor_updates: Vec, /// Currently handled seats. - pub seats: FnvHashMap, + pub seats: AHashMap, /// Currently present cursor surfaces. - pub pointer_surfaces: FnvHashMap>>, + pub pointer_surfaces: AHashMap>>, /// The state of the text input on the client. pub text_input_state: Option, @@ -136,7 +136,7 @@ impl WinitState { let seat_state = SeatState::new(globals, queue_handle); - let mut seats = FnvHashMap::default(); + let mut seats = AHashMap::default(); for seat in seat_state.seats() { seats.insert(seat.id(), WinitSeatState::new()); } @@ -287,7 +287,12 @@ impl WindowHandler for WinitState { .expect("got configure for dead window.") .lock() .unwrap() - .configure(configure, &self.shm, &self.subcompositor_state); + .configure( + configure, + &self.shm, + &self.subcompositor_state, + &mut self.events_sink, + ); self.window_compositor_updates[pos].size = Some(new_size); } @@ -325,6 +330,16 @@ impl OutputHandler for WinitState { } impl CompositorHandler for WinitState { + fn transform_changed( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wayland_client::protocol::wl_surface::WlSurface, + _: wayland_client::protocol::wl_output::Transform, + ) { + // TODO(kchibisov) we need to expose it somehow in winit. + } + fn scale_factor_changed( &mut self, _: &Connection, diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index d7b5bbe3969..a75ac81b8e3 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -377,6 +377,13 @@ impl Window { None } + #[inline] + pub fn show_window_menu(&self, position: Position) { + let scale_factor = self.scale_factor(); + let position = position.to_logical(scale_factor); + self.window_state.lock().unwrap().show_window_menu(position); + } + #[inline] pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { self.window_state diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index 470d2665cf9..67b705ab4d2 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -3,6 +3,7 @@ use std::mem::ManuallyDrop; use std::num::NonZeroU32; use std::sync::{Arc, Weak}; +use std::time::Duration; use log::{info, warn}; @@ -10,14 +11,16 @@ 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::csd_frame::{ + DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowState as XdgWindowState, +}; 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::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge; -use sctk::compositor::{CompositorState, Region, SurfaceData}; +use sctk::compositor::{CompositorState, Region}; 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; @@ -27,6 +30,9 @@ use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; use crate::dpi::{LogicalPosition, LogicalSize}; use crate::error::{ExternalError, NotSupportedError}; +use crate::event::WindowEvent; +use crate::platform_impl::wayland::event_loop::sink::EventSink; +use crate::platform_impl::wayland::make_wid; use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager; use crate::platform_impl::WindowId; use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme}; @@ -39,7 +45,7 @@ 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; +pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame; // Minimum window inner size. const MIN_WINDOW_SIZE: LogicalSize = LogicalSize::new(2, 1); @@ -245,6 +251,7 @@ impl WindowState { configure: WindowConfigure, shm: &Shm, subcompositor: &Arc, + event_sink: &mut EventSink, ) -> LogicalSize { if configure.decoration_mode == DecorationMode::Client && self.frame.is_none() @@ -260,6 +267,7 @@ impl WindowState { ) { Ok(mut frame) => { frame.set_title(&self.title); + frame.set_scaling_factor(self.scale_factor); // Hide the frame if we were asked to not decorate. frame.set_hidden(!self.decorate); self.frame = Some(frame); @@ -276,6 +284,19 @@ impl WindowState { let stateless = Self::is_stateless(&configure); + // Emit `Occluded` event on suspension change. + let occluded = configure.state.contains(XdgWindowState::SUSPENDED); + if self + .last_configure + .as_ref() + .map(|c| c.state.contains(XdgWindowState::SUSPENDED)) + .unwrap_or(false) + != occluded + { + let window_id = make_wid(self.window.wl_surface()); + event_sink.push_window_event(WindowEvent::Occluded(occluded), window_id); + } + let new_size = if let Some(frame) = self.frame.as_mut() { // Configure the window states. frame.update_state(configure.state); @@ -342,23 +363,40 @@ impl WindowState { } /// Tells whether the window should be closed. + #[allow(clippy::too_many_arguments)] pub fn frame_click( &mut self, click: FrameClick, pressed: bool, seat: &WlSeat, serial: u32, + timestamp: Duration, window_id: WindowId, updates: &mut Vec, ) -> Option { - match self.frame.as_mut()?.on_click(click, pressed)? { + match self.frame.as_mut()?.on_click(timestamp, 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.has_pending_move = Some(serial), - FrameAction::Resize(edge) => self.window.resize(seat, serial, edge), + FrameAction::Resize(edge) => { + let edge = match edge { + ResizeEdge::None => XdgResizeEdge::None, + ResizeEdge::Top => XdgResizeEdge::Top, + ResizeEdge::Bottom => XdgResizeEdge::Bottom, + ResizeEdge::Left => XdgResizeEdge::Left, + ResizeEdge::TopLeft => XdgResizeEdge::TopLeft, + ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft, + ResizeEdge::Right => XdgResizeEdge::Right, + ResizeEdge::TopRight => XdgResizeEdge::TopRight, + ResizeEdge::BottomRight => XdgResizeEdge::BottomRight, + _ => return None, + }; + self.window.resize(seat, serial, edge); + } FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)), + _ => (), }; Some(false) @@ -375,14 +413,15 @@ impl WindowState { &mut self, seat: &WlSeat, surface: &WlSurface, + timestamp: Duration, x: f64, y: f64, - ) -> Option<&str> { + ) -> Option { // Take the serial if we had any, so it doesn't stick around. let serial = self.has_pending_move.take(); if let Some(frame) = self.frame.as_mut() { - let cursor = frame.click_point_moved(surface, x, y); + let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y); // If we have a cursor change, that means that cursor is over the decorations, // so try to apply move. if let Some(serial) = cursor.is_some().then_some(serial).flatten() { @@ -498,14 +537,12 @@ impl WindowState { /// 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(); + if frame.is_dirty() { + return frame.draw(); } - dirty - } else { - false } + + false } /// Reload the cursor style on the given window. @@ -592,20 +629,8 @@ impl WindowState { return; } - self.apply_on_poiner(|pointer, data| { - let surface = data.cursor_surface(); - let scale_factor = surface.data::().unwrap().scale_factor(); - - if pointer - .set_cursor( - &self.connection, - cursor_icon.name(), - &self.shm, - surface, - scale_factor, - ) - .is_err() - { + self.apply_on_poiner(|pointer, _| { + if pointer.set_cursor(&self.connection, cursor_icon).is_err() { warn!("Failed to set cursor to {:?}", cursor_icon); } }) @@ -709,6 +734,15 @@ impl WindowState { Ok(()) } + pub fn show_window_menu(&self, position: LogicalPosition) { + // TODO(kchibisov) handle touch serials. + self.apply_on_poiner(|_, data| { + let serial = data.latest_button_serial(); + let seat = data.seat(); + self.window.show_window_menu(seat, serial, position.into()); + }); + } + /// Set the position of the cursor. pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> { if self.pointer_constraints.is_none() { @@ -844,6 +878,10 @@ impl WindowState { if self.fractional_scale.is_none() { let _ = self.window.set_buffer_scale(self.scale_factor as _); } + + if let Some(frame) = self.frame.as_mut() { + frame.set_scaling_factor(scale_factor); + } } /// Make window background blurred @@ -926,10 +964,20 @@ impl Drop for WindowState { ManuallyDrop::drop(&mut self.window); } - if let Some(blur) = &self.blur { + // Cleanup objects. + + if let Some(blur) = self.blur.take() { blur.release(); } + if let Some(fs) = self.fractional_scale.take() { + fs.destroy(); + } + + if let Some(viewport) = self.viewport.take() { + viewport.destroy(); + } + surface.destroy(); } } @@ -965,17 +1013,17 @@ pub enum FrameCallbackState { Received, } -impl From for ResizeEdge { +impl From for XdgResizeEdge { 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, + ResizeDirection::North => XdgResizeEdge::Top, + ResizeDirection::West => XdgResizeEdge::Left, + ResizeDirection::NorthWest => XdgResizeEdge::TopLeft, + ResizeDirection::NorthEast => XdgResizeEdge::TopRight, + ResizeDirection::East => XdgResizeEdge::Right, + ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft, + ResizeDirection::SouthEast => XdgResizeEdge::BottomRight, + ResizeDirection::South => XdgResizeEdge::Bottom, } } } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 037426a0369..0bb4c1c225d 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -32,7 +32,7 @@ use std::{ ops::Deref, os::{ raw::*, - unix::io::{AsRawFd, RawFd}, + unix::io::{AsRawFd, BorrowedFd}, }, ptr, rc::Rc, @@ -83,7 +83,7 @@ use crate::{ const ALL_DEVICES: u16 = 0; const ALL_MASTER_DEVICES: u16 = 1; -type X11Source = Generic; +type X11Source = Generic>; struct WakeSender { sender: Sender, @@ -269,7 +269,8 @@ impl EventLoop { // Create the X11 event dispatcher. let source = X11Source::new( - xconn.xcb_connection().as_raw_fd(), + // SAFETY: xcb owns the FD and outlives the source. + unsafe { BorrowedFd::borrow_raw(xconn.xcb_connection().as_raw_fd()) }, calloop::Interest::READ, calloop::Mode::Level, ); diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 8dcf2382d2e..fab29ddb370 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1604,6 +1604,9 @@ impl UnownedWindow { self.drag_initiate(util::MOVERESIZE_MOVE) } + #[inline] + pub fn show_window_menu(&self, _position: Position) {} + /// Resizes the window while it is being dragged. pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { self.drag_initiate(match direction {