From dce901676a3cf539f3f7f6f9b86c991375293764 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 7 Mar 2023 10:04:12 +0300 Subject: [PATCH] Update to wayland-rs 0.30 This should update the sctk-adwaita to the latest smithay-client-toolkit and wayland-rs. --- CHANGELOG.md | 3 + Cargo.toml | 6 +- examples/simple.rs | 117 ------ examples/window.rs | 554 +++++++++++++++++++++++++ src/buttons.rs | 507 ++++++++++------------- src/lib.rs | 997 +++++++++++++++++++-------------------------- src/parts.rs | 281 +++++++------ src/pointer.rs | 333 +++++---------- src/surface.rs | 154 ------- src/theme.rs | 126 +++--- src/title.rs | 2 +- 11 files changed, 1500 insertions(+), 1580 deletions(-) delete mode 100644 examples/simple.rs create mode 100644 examples/window.rs delete mode 100644 src/surface.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9139167..dad3b20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## Unreleased +- Update the `smithay-client-toolkit` to `0.17.0` + ## 0.5.4 - Timeout dbus call to settings portal (100ms) diff --git a/Cargo.toml b/Cargo.toml index e281b56..81b0c69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,10 @@ documentation = "https://docs.rs/sctk-adwaita" description = "Adwaita-like SCTK Frame" [dependencies] -smithay-client-toolkit = "0.16" +smithay-client-toolkit = { version = "0.17.0", default_features = false } tiny-skia = { version = "0.8", features = ["std", "simd"] } log = "0.4" -memmap2 = "0.5.8" +memmap2 = { version = "0.5.8", optional = true } # Draw title text using crossfont `--features crossfont` crossfont = { version = "0.5.0", features = ["force_system_fontconfig"], optional = true } @@ -22,3 +22,5 @@ ab_glyph = { version = "0.2.17", optional = true } [features] default = ["ab_glyph"] +crossfont = ["dep:crossfont"] +ab_glyph = ["dep:ab_glyph", "memmap2"] diff --git a/examples/simple.rs b/examples/simple.rs deleted file mode 100644 index f1353d9..0000000 --- a/examples/simple.rs +++ /dev/null @@ -1,117 +0,0 @@ -extern crate smithay_client_toolkit as sctk; - -use sctk::reexports::calloop; -use sctk::reexports::client::protocol::{wl_shm, wl_surface}; -use sctk::shm::AutoMemPool; -use sctk::window::Event as WEvent; - -sctk::default_environment!(KbdInputExample, desktop); - -fn main() { - let (env, display, queue) = sctk::new_default_environment!(KbdInputExample, desktop) - .expect("Unable to connect to a Wayland compositor"); - - let mut event_loop = calloop::EventLoop::>::try_new().unwrap(); - - let mut dimensions = (380u32, 240u32); - - let surface = env.create_surface().detach(); - - let mut window = env - .create_window::( - surface, - None, - dimensions, - move |evt, mut dispatch_data| { - let next_action = dispatch_data.get::>().unwrap(); - // Keep last event in priority order : Close > Configure > Refresh - let replace = matches!( - (&evt, &*next_action), - (_, &None) - | (_, &Some(WEvent::Refresh)) - | (&WEvent::Configure { .. }, &Some(WEvent::Configure { .. })) - | (&WEvent::Close, _) - ); - if replace { - *next_action = Some(evt); - } - }, - ) - .expect("Failed to create a window !"); - - window.set_title("/usr/lib/xorg/modules/input".to_string()); - - // // uncomment to override automatic theme selection - // window.set_frame_config(sctk_adwaita::FrameConfig::light()); - - let mut pool = env - .create_auto_pool() - .expect("Failed to create a memory pool !"); - - if !env.get_shell().unwrap().needs_configure() { - // initial draw to bootstrap on wl_shell - redraw(&mut pool, window.surface(), dimensions).expect("Failed to draw"); - window.refresh(); - } - - println!("XDG_WINDOW: {:?}", window.surface()); - - let mut next_action = None; - - sctk::WaylandSource::new(queue) - .quick_insert(event_loop.handle()) - .unwrap(); - - loop { - match next_action.take() { - Some(WEvent::Close) => break, - Some(WEvent::Refresh) => { - window.refresh(); - window.surface().commit(); - } - Some(WEvent::Configure { new_size, .. }) => { - if let Some((w, h)) = new_size { - window.resize(w, h); - dimensions = (w, h) - } - window.refresh(); - redraw(&mut pool, window.surface(), dimensions).expect("Failed to draw"); - } - None => {} - } - - // always flush the connection before going to sleep waiting for events - display.flush().unwrap(); - - event_loop.dispatch(None, &mut next_action).unwrap(); - } -} - -fn redraw( - pool: &mut AutoMemPool, - surface: &wl_surface::WlSurface, - (buf_x, buf_y): (u32, u32), -) -> Result<(), ::std::io::Error> { - let (canvas, new_buffer) = pool.buffer( - buf_x as i32, - buf_y as i32, - 4 * buf_x as i32, - wl_shm::Format::Argb8888, - )?; - - for pixel in canvas.chunks_exact_mut(4) { - pixel[0] = 255; - pixel[1] = 255; - pixel[2] = 255; - pixel[3] = 255; - } - - surface.attach(Some(&new_buffer), 0, 0); - if surface.as_ref().version() >= 4 { - surface.damage_buffer(0, 0, buf_x as i32, buf_y as i32); - } else { - surface.damage(0, 0, buf_x as i32, buf_y as i32); - } - surface.commit(); - Ok(()) -} diff --git a/examples/window.rs b/examples/window.rs new file mode 100644 index 0000000..d2977ce --- /dev/null +++ b/examples/window.rs @@ -0,0 +1,554 @@ +// Based on https://github.com/Smithay/client-toolkit/blob/master/examples/themed_window.rs. + +use std::sync::Arc; +use std::{convert::TryInto, num::NonZeroU32}; + +use smithay_client_toolkit::reexports::client::{ + globals::registry_queue_init, + protocol::{wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, + Connection, Proxy, QueueHandle, +}; +use smithay_client_toolkit::{ + compositor::{CompositorHandler, CompositorState}, + delegate_compositor, delegate_output, delegate_pointer, delegate_registry, delegate_seat, + delegate_shm, delegate_subcompositor, delegate_xdg_shell, delegate_xdg_window, + output::{OutputHandler, OutputState}, + registry::{ProvidesRegistryState, RegistryState}, + registry_handlers, + seat::{ + pointer::{ + PointerData, PointerEvent, PointerEventKind, PointerHandler, ThemeSpec, ThemedPointer, + }, + Capability, SeatHandler, SeatState, + }, + shell::{ + xdg::{ + frame::{DecorationsFrame, FrameAction, FrameClick}, + window::{DecorationMode, Window, WindowConfigure, WindowDecorations, WindowHandler}, + XdgShell, XdgSurface, + }, + WaylandSurface, + }, + shm::{ + slot::{Buffer, SlotPool}, + Shm, ShmHandler, + }, + subcompositor::SubcompositorState, +}; + +use sctk_adwaita::{AdwaitaFrame, FrameConfig}; + +fn main() { + let conn = Connection::connect_to_env().unwrap(); + + let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); + let qh = event_queue.handle(); + let registry_state = RegistryState::new(&globals); + let seat_state = SeatState::new(&globals, &qh); + let output_state = OutputState::new(&globals, &qh); + let compositor_state = + CompositorState::bind(&globals, &qh).expect("wl_compositor not available"); + let subcompositor_state = + SubcompositorState::bind(compositor_state.wl_compositor().clone(), &globals, &qh) + .expect("wl_subcompositor not available"); + let shm_state = Shm::bind(&globals, &qh).expect("wl_shm not available"); + let xdg_shell_state = XdgShell::bind(&globals, &qh).expect("xdg shell not available"); + + let width = 256; + let height = 256; + let pool = SlotPool::new(width as usize * height as usize * 4, &shm_state) + .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); + window.set_title("A wayland window"); + // GitHub does not let projects use the `org.github` domain but the `io.github` domain is fine. + window.set_app_id("simple-window"); + window.set_min_size(Some((2, 1))); + + // In order for the window to be mapped, we need to perform an initial commit with no attached buffer. + // For more info, see WaylandSurface::commit + // + // The compositor will respond with an initial configure that we can then use to present to the window with + // the correct options. + window.commit(); + + let mut simple_window = SimpleWindow { + title: String::from("/usr/lib/xorg/modules/input"), + registry_state, + seat_state, + output_state, + _compositor_state: compositor_state, + subcompositor_state: Arc::new(subcompositor_state), + shm_state, + _xdg_shell_state: xdg_shell_state, + + exit: false, + first_configure: true, + pool, + width: NonZeroU32::new(width).unwrap(), + height: NonZeroU32::new(height).unwrap(), + shift: None, + buffer: None, + window, + window_frame: None, + pointer_surface, + themed_pointer: None, + set_cursor: false, + cursor_icon: String::from("diamond_cross"), + }; + + // We don't draw immediately, the configure will notify us when to first draw. + + loop { + event_queue.blocking_dispatch(&mut simple_window).unwrap(); + + if simple_window.exit { + println!("exiting example"); + break; + } + } +} + +struct SimpleWindow { + title: String, + registry_state: RegistryState, + seat_state: SeatState, + output_state: OutputState, + _compositor_state: CompositorState, + subcompositor_state: Arc, + shm_state: Shm, + _xdg_shell_state: XdgShell, + + exit: bool, + first_configure: bool, + pool: SlotPool, + width: NonZeroU32, + height: NonZeroU32, + shift: Option, + buffer: Option, + window: Window, + window_frame: Option>, + pointer_surface: wl_surface::WlSurface, + themed_pointer: Option, + set_cursor: bool, + cursor_icon: String, +} + +impl CompositorHandler for SimpleWindow { + fn scale_factor_changed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _new_factor: i32, + ) { + // Not needed for this example. + } + + fn frame( + &mut self, + conn: &Connection, + qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _time: u32, + ) { + self.draw(conn, qh); + } +} + +impl OutputHandler for SimpleWindow { + fn output_state(&mut self) -> &mut OutputState { + &mut self.output_state + } + + fn new_output( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + } + + fn update_output( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + } + + fn output_destroyed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + } +} + +impl WindowHandler for SimpleWindow { + fn request_close(&mut self, _: &Connection, _: &QueueHandle, _: &Window) { + self.exit = true; + } + + fn configure( + &mut self, + conn: &Connection, + qh: &QueueHandle, + window: &Window, + configure: WindowConfigure, + _serial: u32, + ) { + self.buffer = None; + + println!( + "Configure size {:?}, decorations: {:?}", + configure.new_size, configure.decoration_mode + ); + + let (width, height) = if configure.decoration_mode == DecorationMode::Client { + let window_frame = self.window_frame.get_or_insert_with(|| { + let mut frame = AdwaitaFrame::new( + &self.window, + &self.shm_state, + self.subcompositor_state.clone(), + qh.clone(), + FrameConfig::auto(), + ) + .expect("failed to create client side decorations frame."); + frame.set_title(self.title.clone()); + frame + }); + + // Un-hide the frame. + window_frame.set_hidden(false); + + // Configure state before touching any resizing. + window_frame.update_state(configure.state); + + // Configure the button state. + window_frame.update_wm_capabilities(configure.capabilities); + + let (width, height) = match configure.new_size { + (Some(width), Some(height)) => { + // The size could be 0. + window_frame.subtract_borders(width, height) + } + _ => { + // You might want to consider checking for configure bounds. + (Some(self.width), Some(self.height)) + } + }; + + // Clamp the size to at least one pixel. + let width = width.unwrap_or(NonZeroU32::new(1).unwrap()); + let height = height.unwrap_or(NonZeroU32::new(1).unwrap()); + + window_frame.resize(width, height); + + let (x, y) = window_frame.location(); + let outer_size = window_frame.add_borders(width.get(), height.get()); + window.xdg_surface().set_window_geometry( + x, + y, + outer_size.0 as i32, + outer_size.1 as i32, + ); + + (width, height) + } else { + // Hide the frame, if any. + if let Some(frame) = self.window_frame.as_mut() { + frame.set_hidden(true) + } + let width = configure.new_size.0.unwrap_or(self.width); + let height = configure.new_size.1.unwrap_or(self.height); + self.window.xdg_surface().set_window_geometry( + 0, + 0, + width.get() as i32, + height.get() as i32, + ); + (width, height) + }; + + // Update new width and height; + self.width = width; + self.height = height; + + // Initiate the first draw. + if self.first_configure { + self.first_configure = false; + self.draw(conn, qh); + } + } +} + +impl SeatHandler for SimpleWindow { + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat_state + } + + fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} + + fn new_capability( + &mut self, + _conn: &Connection, + qh: &QueueHandle, + seat: wl_seat::WlSeat, + capability: Capability, + ) { + if capability == Capability::Pointer && self.themed_pointer.is_none() { + println!("Set pointer capability"); + println!("Creating pointer theme"); + let themed_pointer = self + .seat_state + .get_pointer_with_theme(qh, &seat, ThemeSpec::default()) + .expect("Failed to create pointer"); + self.themed_pointer.replace(themed_pointer); + } + } + + fn remove_capability( + &mut self, + _conn: &Connection, + _: &QueueHandle, + _: wl_seat::WlSeat, + capability: Capability, + ) { + if capability == Capability::Pointer && self.themed_pointer.is_some() { + println!("Unset pointer capability"); + self.themed_pointer.take().unwrap().pointer().release(); + } + } + + fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} +} + +impl PointerHandler for SimpleWindow { + fn pointer_frame( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + pointer: &wl_pointer::WlPointer, + events: &[PointerEvent], + ) { + use PointerEventKind::*; + for event in events { + let (x, y) = event.position; + match event.kind { + Enter { .. } => { + self.set_cursor = true; + self.cursor_icon = self + .window_frame + .as_mut() + .and_then(|frame| frame.click_point_moved(&event.surface, x, y)) + .unwrap_or("diamond_cross") + .to_owned(); + + if &event.surface == self.window.wl_surface() { + println!("Pointer entered @{:?}", event.position); + } + } + Leave { .. } => { + if &event.surface != self.window.wl_surface() { + if let Some(window_frame) = self.window_frame.as_mut() { + window_frame.click_point_left(); + } + } + 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)) + { + self.set_cursor = true; + self.cursor_icon = new_cursor.to_owned(); + } + } + Press { button, serial, .. } | Release { button, serial, .. } => { + let pressed = if matches!(event.kind, Press { .. }) { + true + } else { + false + }; + if &event.surface != self.window.wl_surface() { + let click = match button { + 0x110 => FrameClick::Normal, + 0x111 => FrameClick::Alternate, + _ => continue, + }; + + if let Some(action) = self + .window_frame + .as_mut() + .and_then(|frame| frame.on_click(click, pressed)) + { + self.frame_action(pointer, serial, action); + } + } else if pressed { + println!("Press {:x} @ {:?}", button, event.position); + self.shift = self.shift.xor(Some(0)); + } + } + Axis { + horizontal, + vertical, + .. + } => { + if &event.surface == self.window.wl_surface() { + println!("Scroll H:{horizontal:?}, V:{vertical:?}"); + } + } + } + } + } +} +impl SimpleWindow { + fn frame_action(&mut self, pointer: &wl_pointer::WlPointer, serial: u32, action: FrameAction) { + let pointer_data = pointer.data::().unwrap(); + let seat = pointer_data.seat(); + match action { + FrameAction::Close => self.exit = true, + FrameAction::Minimize => self.window.set_minimized(), + 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::Move => self.window.move_(seat, serial), + } + } +} + +impl ShmHandler for SimpleWindow { + fn shm_state(&mut self) -> &mut Shm { + &mut self.shm_state + } +} + +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, + ); + self.set_cursor = false; + } + + let width = self.width.get(); + let height = self.height.get(); + let stride = width as i32 * 4; + + let buffer = self.buffer.get_or_insert_with(|| { + self.pool + .create_buffer( + width as i32, + height as i32, + stride, + wl_shm::Format::Argb8888, + ) + .expect("create buffer") + .0 + }); + + let canvas = match self.pool.canvas(buffer) { + Some(canvas) => canvas, + None => { + // This should be rare, but if the compositor has not released the previous + // buffer, we need double-buffering. + let (second_buffer, canvas) = self + .pool + .create_buffer( + width as i32, + height as i32, + stride, + wl_shm::Format::Argb8888, + ) + .expect("create buffer"); + *buffer = second_buffer; + canvas + } + }; + + // Draw to the window: + { + let shift = self.shift.unwrap_or(0); + canvas + .chunks_exact_mut(4) + .enumerate() + .for_each(|(index, chunk)| { + let x = ((index + shift as usize) % width as usize) as u32; + let y = (index / width as usize) as u32; + + let a = 0xFF; + let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height); + let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height); + let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height); + let color = (a << 24) + (r << 16) + (g << 8) + b; + + let array: &mut [u8; 4] = chunk.try_into().unwrap(); + *array = color.to_le_bytes(); + }); + + if let Some(shift) = &mut self.shift { + *shift = (*shift + 1) % width; + } + } + + // Draw the decorations frame. + self.window_frame.as_mut().map(|frame| { + if frame.is_dirty() && !frame.is_hidden() { + frame.draw(); + } + }); + + // Damage the entire window + self.window.wl_surface().damage_buffer( + 0, + 0, + self.width.get() as i32, + self.height.get() as i32, + ); + + // Request our next frame + self.window + .wl_surface() + .frame(qh, self.window.wl_surface().clone()); + + // Attach and commit to present. + buffer + .attach_to(self.window.wl_surface()) + .expect("buffer attach"); + self.window.wl_surface().commit(); + } +} + +delegate_compositor!(SimpleWindow); +delegate_subcompositor!(SimpleWindow); +delegate_output!(SimpleWindow); +delegate_shm!(SimpleWindow); + +delegate_seat!(SimpleWindow); +delegate_pointer!(SimpleWindow); + +delegate_xdg_shell!(SimpleWindow); +delegate_xdg_window!(SimpleWindow); + +delegate_registry!(SimpleWindow); + +impl ProvidesRegistryState for SimpleWindow { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + registry_handlers![OutputState, SeatState,]; +} diff --git a/src/buttons.rs b/src/buttons.rs index 67daf1b..6f03605 100644 --- a/src/buttons.rs +++ b/src/buttons.rs @@ -1,345 +1,268 @@ -use smithay_client_toolkit::window::ButtonState; use tiny_skia::{FillRule, PathBuilder, PixmapMut, Rect, Stroke, Transform}; -use crate::{ - theme::{ColorMap, BORDER_SIZE}, - Location, SkiaResult, -}; +use smithay_client_toolkit::shell::xdg::window::{WindowManagerCapabilities, WindowState}; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum ButtonKind { - Close, - Maximize, - Minimize, -} - -#[derive(Default, Debug)] -pub(crate) struct Button { - x: f32, - y: f32, - size: f32, -} - -impl Button { - pub fn radius(&self) -> f32 { - self.size / 2.0 - } +use crate::{theme::ColorMap, Location, SkiaResult}; - pub fn x(&self) -> f32 { - self.x - } +/// The size of the button on the header bar in logical points. +const BUTTON_SIZE: f32 = 24.; +const BUTTON_MARGIN: f32 = 5.; +const BUTTON_SPACING: f32 = 13.; - pub fn center_x(&self) -> f32 { - self.x + self.radius() - } - - pub fn center_y(&self) -> f32 { - self.y + self.radius() - } +#[derive(Debug)] +pub(crate) struct Buttons { + pub close: Button, + pub maximize: Option