From 91810f5baabd850b6f78afda61933971b96dcf4b Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 14 Oct 2023 17:43:59 +0400 Subject: [PATCH] Use `wayland-csd-frame` as a part of public API (#28) This new crate is used to provide common interface for the CSD libraries. This also updates to the newer SCTK 0.18. --- CHANGELOG.md | 2 + Cargo.toml | 6 +-- examples/window.rs | 105 ++++++++++++++++++++++++++++++++------------- src/buttons.rs | 6 +-- src/lib.rs | 73 ++++++++++++++++++++++++------- src/parts.rs | 16 ++++--- src/pointer.rs | 39 ++++++++--------- src/wl_typed.rs | 1 + 8 files changed, 167 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab18321..90a4835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +- **Breaking:** `wayland-csd-frame` is now used as a part of the public interface. + ## 0.6.1 - Bump tiny-skia to v0.11 (#32) - cleanup: Remove debug println (#29) diff --git a/Cargo.toml b/Cargo.toml index 730846a..c0d0399 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,13 @@ documentation = "https://docs.rs/sctk-adwaita" description = "Adwaita-like SCTK Frame" [dependencies] -smithay-client-toolkit = { version = "0.17.0", default_features = false } +log = "0.4" +memmap2 = { version = "0.9.0", optional = true } tiny-skia = { version = "0.11", default-features = false, features = [ "std", "simd", ] } -log = "0.4" -memmap2 = { version = "0.5.8", optional = true } +smithay-client-toolkit = { version = "0.18.0", default_features = false } # Draw title text using crossfont `--features crossfont` crossfont = { version = "0.5.0", features = [ diff --git a/examples/window.rs b/examples/window.rs index d2977ce..61e9ed6 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -1,6 +1,7 @@ // Based on https://github.com/Smithay/client-toolkit/blob/master/examples/themed_window.rs. use std::sync::Arc; +use std::time::Duration; use std::{convert::TryInto, num::NonZeroU32}; use smithay_client_toolkit::reexports::client::{ @@ -8,6 +9,10 @@ use smithay_client_toolkit::reexports::client::{ protocol::{wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, Connection, Proxy, QueueHandle, }; +use smithay_client_toolkit::reexports::csd_frame::{ + CursorIcon, DecorationsFrame, FrameAction, FrameClick, ResizeEdge, +}; +use smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_output, delegate_pointer, delegate_registry, delegate_seat, @@ -23,7 +28,6 @@ use smithay_client_toolkit::{ }, shell::{ xdg::{ - frame::{DecorationsFrame, FrameAction, FrameClick}, window::{DecorationMode, Window, WindowConfigure, WindowDecorations, WindowHandler}, XdgShell, XdgSurface, }, @@ -60,7 +64,6 @@ fn main() { .expect("Failed to create pool"); let window_surface = compositor_state.create_surface(&qh); - let pointer_surface = compositor_state.create_surface(&qh); let window = xdg_shell_state.create_window(window_surface, WindowDecorations::ServerDefault, &qh); @@ -81,7 +84,7 @@ fn main() { registry_state, seat_state, output_state, - _compositor_state: compositor_state, + compositor_state, subcompositor_state: Arc::new(subcompositor_state), shm_state, _xdg_shell_state: xdg_shell_state, @@ -95,10 +98,9 @@ fn main() { buffer: None, window, window_frame: None, - pointer_surface, themed_pointer: None, set_cursor: false, - cursor_icon: String::from("diamond_cross"), + cursor_icon: CursorIcon::Crosshair, }; // We don't draw immediately, the configure will notify us when to first draw. @@ -118,7 +120,7 @@ struct SimpleWindow { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, - _compositor_state: CompositorState, + compositor_state: CompositorState, subcompositor_state: Arc, shm_state: Shm, _xdg_shell_state: XdgShell, @@ -132,10 +134,9 @@ struct SimpleWindow { buffer: Option, window: Window, window_frame: Option>, - pointer_surface: wl_surface::WlSurface, themed_pointer: Option, set_cursor: bool, - cursor_icon: String, + cursor_icon: CursorIcon, } impl CompositorHandler for SimpleWindow { @@ -149,6 +150,16 @@ impl CompositorHandler for SimpleWindow { // Not needed for this example. } + fn transform_changed( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_surface::WlSurface, + _: wl_output::Transform, + ) { + // Not needed for this example. + } + fn frame( &mut self, conn: &Connection, @@ -305,9 +316,16 @@ impl SeatHandler for SimpleWindow { if capability == Capability::Pointer && self.themed_pointer.is_none() { println!("Set pointer capability"); println!("Creating pointer theme"); + let surface = self.compositor_state.create_surface(qh); let themed_pointer = self .seat_state - .get_pointer_with_theme(qh, &seat, ThemeSpec::default()) + .get_pointer_with_theme( + qh, + &seat, + self.shm_state.wl_shm(), + surface, + ThemeSpec::default(), + ) .expect("Failed to create pointer"); self.themed_pointer.replace(themed_pointer); } @@ -346,8 +364,10 @@ impl PointerHandler for SimpleWindow { self.cursor_icon = self .window_frame .as_mut() - .and_then(|frame| frame.click_point_moved(&event.surface, x, y)) - .unwrap_or("diamond_cross") + .and_then(|frame| { + frame.click_point_moved(Duration::ZERO, &event.surface.id(), x, y) + }) + .unwrap_or(CursorIcon::Crosshair) .to_owned(); if &event.surface == self.window.wl_surface() { @@ -362,17 +382,29 @@ impl PointerHandler for SimpleWindow { } println!("Pointer left"); } - Motion { .. } => { - if let Some(new_cursor) = self - .window_frame - .as_mut() - .and_then(|frame| frame.click_point_moved(&event.surface, x, y)) - { + Motion { time } => { + if let Some(new_cursor) = self.window_frame.as_mut().and_then(|frame| { + frame.click_point_moved( + Duration::from_millis(time as u64), + &event.surface.id(), + x, + y, + ) + }) { self.set_cursor = true; self.cursor_icon = new_cursor.to_owned(); } } - Press { button, serial, .. } | Release { button, serial, .. } => { + Press { + button, + serial, + time, + } + | Release { + button, + serial, + time, + } => { let pressed = if matches!(event.kind, Press { .. }) { true } else { @@ -385,11 +417,9 @@ impl PointerHandler for SimpleWindow { _ => continue, }; - if let Some(action) = self - .window_frame - .as_mut() - .and_then(|frame| frame.on_click(click, pressed)) - { + if let Some(action) = self.window_frame.as_mut().and_then(|frame| { + frame.on_click(Duration::from_millis(time as u64), click, pressed) + }) { self.frame_action(pointer, serial, action); } } else if pressed { @@ -420,8 +450,23 @@ impl SimpleWindow { FrameAction::Maximize => self.window.set_maximized(), FrameAction::UnMaximize => self.window.unset_maximized(), FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)), - 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, + }; + self.window.resize(seat, serial, edge); + } FrameAction::Move => self.window.move_(seat, serial), + _ => (), } } } @@ -435,13 +480,11 @@ impl ShmHandler for SimpleWindow { impl SimpleWindow { pub fn draw(&mut self, conn: &Connection, qh: &QueueHandle) { if self.set_cursor { - let _ = self.themed_pointer.as_mut().unwrap().set_cursor( - conn, - &self.cursor_icon, - self.shm_state.wl_shm(), - &self.pointer_surface, - 1, - ); + let _ = self + .themed_pointer + .as_mut() + .unwrap() + .set_cursor(conn, self.cursor_icon); self.set_cursor = false; } diff --git a/src/buttons.rs b/src/buttons.rs index 18bb63f..bf17d5f 100644 --- a/src/buttons.rs +++ b/src/buttons.rs @@ -1,8 +1,7 @@ use log::{debug, warn}; +use smithay_client_toolkit::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use tiny_skia::{FillRule, PathBuilder, PixmapMut, Rect, Stroke, Transform}; -use smithay_client_toolkit::shell::xdg::window::{WindowManagerCapabilities, WindowState}; - use crate::{theme::ColorMap, Location, SkiaResult}; /// The size of the button on the header bar in logical points. @@ -113,6 +112,7 @@ impl Buttons { self.buttons_left.last().map(|button| button.end_x()) } + #[allow(clippy::too_many_arguments)] pub fn draw( &self, start_x: f32, @@ -375,7 +375,7 @@ pub enum ButtonKind { Minimize, } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Side { Left, Right, diff --git a/src/lib.rs b/src/lib.rs index 1cb52d4..92a3de5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,24 @@ use std::error::Error; +use std::mem; use std::num::NonZeroU32; use std::sync::Arc; +use std::time::Duration; use tiny_skia::{ Color, FillRule, Mask, Path, PathBuilder, Pixmap, PixmapMut, PixmapPaint, Point, Rect, Transform, }; +use smithay_client_toolkit::reexports::client::backend::ObjectId; use smithay_client_toolkit::reexports::client::protocol::wl_shm; use smithay_client_toolkit::reexports::client::protocol::wl_subsurface::WlSubsurface; use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface; use smithay_client_toolkit::reexports::client::{Dispatch, Proxy, QueueHandle}; +use smithay_client_toolkit::reexports::csd_frame::{ + CursorIcon, DecorationsFrame, FrameAction, FrameClick, WindowManagerCapabilities, WindowState, +}; use smithay_client_toolkit::compositor::SurfaceData; -use smithay_client_toolkit::shell::xdg::frame::{DecorationsFrame, FrameAction, FrameClick}; -use smithay_client_toolkit::shell::xdg::window::{WindowManagerCapabilities, WindowState}; use smithay_client_toolkit::shell::WaylandSurface; use smithay_client_toolkit::shm::{slot::SlotPool, Shm}; use smithay_client_toolkit::subcompositor::SubcompositorState; @@ -63,6 +67,12 @@ pub struct AdwaitaFrame { /// Whether the frame should be redrawn. dirty: bool, + /// Whether the drawing should be synced with the main surface. + should_sync: bool, + + /// Scale factor used for the surface. + scale_factor: u32, + /// Wether the frame is resizable. resizable: bool, @@ -105,6 +115,8 @@ where subcompositor, queue_handle, dirty: true, + scale_factor: 1, + should_sync: true, title: None, title_text: TitleText::new(theme.active.font_color), theme, @@ -147,16 +159,17 @@ where } } - fn redraw_inner(&mut self) -> SkiaResult { + fn redraw_inner(&mut self) -> Option { let decorations = self.decorations.as_mut()?; // Reset the dirty bit. self.dirty = false; + let should_sync = mem::take(&mut self.should_sync); // Don't draw borders if the frame explicitly hidden or fullscreened. if self.state.contains(WindowState::FULLSCREEN) { decorations.hide(); - return Some(()); + return Some(true); } let colors = if self.state.contains(WindowState::ACTIVATED) { @@ -179,7 +192,7 @@ where .parts() .filter(|(idx, _)| *idx == DecorationParts::HEADER || draw_borders) { - let scale = part.scale(); + let scale = self.scale_factor; // XXX to perfectly align the visible borders we draw them with // the header, otherwise rounded corners won't look 'smooth' at the @@ -278,6 +291,12 @@ where } }; + if should_sync { + part.subsurface.set_sync(); + } else { + part.subsurface.set_desync(); + } + part.surface.set_buffer_scale(scale as i32); part.subsurface.set_position(pos.0, pos.1); @@ -292,7 +311,7 @@ where part.surface.commit(); } - Some(()) + Some(should_sync) } } @@ -329,6 +348,7 @@ where &self.queue_handle, )); self.dirty = true; + self.should_sync = true; } } @@ -347,10 +367,11 @@ where self.buttons .arrange(width.get(), get_margin_h_lp(&self.state)); self.dirty = true; + self.should_sync = true; } - fn draw(&mut self) { - self.redraw_inner(); + fn draw(&mut self) -> bool { + self.redraw_inner().unwrap_or(true) } fn subtract_borders( @@ -394,21 +415,41 @@ where self.dirty = true; } - fn on_click(&mut self, click: FrameClick, pressed: bool) -> Option { + fn on_click( + &mut self, + timestamp: Duration, + click: FrameClick, + pressed: bool, + ) -> Option { match click { - FrameClick::Normal => { - self.mouse - .click(pressed, self.resizable, &self.state, &self.wm_capabilities) - } + FrameClick::Normal => self.mouse.click( + timestamp, + pressed, + self.resizable, + &self.state, + &self.wm_capabilities, + ), FrameClick::Alternate => self.mouse.alternate_click(pressed, &self.wm_capabilities), + _ => None, } } - fn click_point_moved(&mut self, surface: &WlSurface, x: f64, y: f64) -> Option<&str> { - let surface = WlTyped::wrap::(surface.clone()); + fn set_scaling_factor(&mut self, scale_factor: f64) { + // NOTE: Clamp it just in case to some ok-ish range. + self.scale_factor = scale_factor.clamp(0.1, 64.).ceil() as u32; + self.dirty = true; + self.should_sync = true; + } + fn click_point_moved( + &mut self, + _timestamp: Duration, + surface: &ObjectId, + x: f64, + y: f64, + ) -> Option { let decorations = self.decorations.as_ref()?; - let location = decorations.find_surface(&surface); + let location = decorations.find_surface(surface); if location == Location::None { return None; } diff --git a/src/parts.rs b/src/parts.rs index 16101e2..2eb783b 100644 --- a/src/parts.rs +++ b/src/parts.rs @@ -1,6 +1,7 @@ use smithay_client_toolkit::reexports::client::{ + backend::ObjectId, protocol::{wl_subsurface::WlSubsurface, wl_surface::WlSurface}, - Dispatch, QueueHandle, + Dispatch, Proxy, QueueHandle, }; use smithay_client_toolkit::{ @@ -94,6 +95,7 @@ impl DecorationParts { pub fn hide(&self) { for part in self.parts.iter() { + part.subsurface.set_sync(); part.surface.attach(None, 0, 0); part.surface.commit(); } @@ -124,8 +126,12 @@ impl DecorationParts { &self.parts[Self::HEADER] } - pub fn find_surface(&self, surface: &WlTyped) -> Location { - let pos = match self.parts.iter().position(|part| &part.surface == surface) { + pub fn find_surface(&self, surface: &ObjectId) -> Location { + let pos = match self + .parts + .iter() + .position(|part| &part.surface.id() == surface) + { Some(pos) => pos, None => return Location::None, }; @@ -181,10 +187,6 @@ impl Part { pos, } } - - pub fn scale(&self) -> u32 { - self.surface.data().scale_factor() as u32 - } } impl Drop for Part { diff --git a/src/pointer.rs b/src/pointer.rs index df82e48..9a512c7 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -1,11 +1,7 @@ -use std::time::{Duration, Instant}; +use std::time::Duration; -use smithay_client_toolkit::{ - reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge, - shell::xdg::{ - frame::FrameAction, - window::{WindowManagerCapabilities, WindowState}, - }, +use smithay_client_toolkit::reexports::csd_frame::{ + CursorIcon, FrameAction, ResizeEdge, WindowManagerCapabilities, WindowState, }; use crate::{ @@ -27,13 +23,14 @@ pub(crate) struct MouseState { position: (f64, f64), /// The instant of the last click. - last_normal_click: Option, + last_normal_click: Option, } impl MouseState { /// The normal click on decorations frame was made. pub fn click( &mut self, + timestamp: Duration, pressed: bool, resizable: bool, state: &WindowState, @@ -60,8 +57,8 @@ impl MouseState { Location::Head if pressed && wm_capabilities.contains(WindowManagerCapabilities::MAXIMIZE) => { - match self.last_normal_click.replace(std::time::Instant::now()) { - Some(now) if now.elapsed() < DOUBLE_CLICK_DURATION => { + match self.last_normal_click.replace(timestamp) { + Some(last) if timestamp.saturating_sub(last) < DOUBLE_CLICK_DURATION => { if maximized { FrameAction::UnMaximize } else { @@ -104,20 +101,20 @@ impl MouseState { } /// The mouse moved inside the decorations frame. - pub fn moved(&mut self, location: Location, x: f64, y: f64, resizable: bool) -> &'static str { + pub fn moved(&mut self, location: Location, x: f64, y: f64, resizable: bool) -> CursorIcon { self.location = location; self.position = (x, y); match self.location { - _ if !resizable => "left_ptr", - Location::Top => "top_side", - Location::TopRight => "top_right_corner", - Location::Right => "right_side", - Location::BottomRight => "bottom_right_corner", - Location::Bottom => "bottom_side", - Location::BottomLeft => "bottom_left_corner", - Location::Left => "left_side", - Location::TopLeft => "top_left_corner", - _ => "left_ptr", + _ if !resizable => CursorIcon::Default, + Location::Top => CursorIcon::NResize, + Location::TopRight => CursorIcon::NeResize, + Location::Right => CursorIcon::EResize, + Location::BottomRight => CursorIcon::SeResize, + Location::Bottom => CursorIcon::SResize, + Location::BottomLeft => CursorIcon::SwResize, + Location::Left => CursorIcon::WResize, + Location::TopLeft => CursorIcon::NwResize, + _ => CursorIcon::Default, } } diff --git a/src/wl_typed.rs b/src/wl_typed.rs index 9728fb3..26b6c11 100644 --- a/src/wl_typed.rs +++ b/src/wl_typed.rs @@ -22,6 +22,7 @@ where &self.0 } + #[allow(dead_code)] pub fn data(&self) -> &DATA { // Generic on Self::wrap makes sure that this will never panic #[allow(clippy::unwrap_used)]