From 4dcd231209db80a49b3403c01966026cee975b00 Mon Sep 17 00:00:00 2001 From: Chooooo Date: Fri, 27 Sep 2024 04:01:06 +0900 Subject: [PATCH] fix(wayland): add client side decorations & fix error protocol 71 (#979) --- .changes/improve-wayland-display.md | 5 ++ .changes/wayland-dragging-header.md | 5 ++ .changes/wayland-resize-window.md | 5 ++ .changes/wayland-titlebar.md | 5 ++ examples/window_debug.rs | 7 +++ src/platform_impl/linux/event_loop.rs | 60 ++++++++++++----------- src/platform_impl/linux/mod.rs | 1 + src/platform_impl/linux/util.rs | 52 +++++++++++++++++--- src/platform_impl/linux/wayland/header.rs | 36 ++++++++++++++ src/platform_impl/linux/wayland/mod.rs | 5 ++ src/platform_impl/linux/window.rs | 28 ++++++++--- 11 files changed, 167 insertions(+), 42 deletions(-) create mode 100644 .changes/improve-wayland-display.md create mode 100644 .changes/wayland-dragging-header.md create mode 100644 .changes/wayland-resize-window.md create mode 100644 .changes/wayland-titlebar.md create mode 100644 src/platform_impl/linux/wayland/header.rs create mode 100644 src/platform_impl/linux/wayland/mod.rs diff --git a/.changes/improve-wayland-display.md b/.changes/improve-wayland-display.md new file mode 100644 index 000000000..cf6eb7c08 --- /dev/null +++ b/.changes/improve-wayland-display.md @@ -0,0 +1,5 @@ +--- +"tao": patch +--- + +On Linux Wayland, changed the event handling for maximizing to process events sequentially to avoid "Error 71(Protocol error): dispatching to Wayland display". diff --git a/.changes/wayland-dragging-header.md b/.changes/wayland-dragging-header.md new file mode 100644 index 000000000..348acafa3 --- /dev/null +++ b/.changes/wayland-dragging-header.md @@ -0,0 +1,5 @@ +--- +"tao": patch +--- + +On Linux Wayland, fixed an issue where the window was not moving when dragging the header bar area. \ No newline at end of file diff --git a/.changes/wayland-resize-window.md b/.changes/wayland-resize-window.md new file mode 100644 index 000000000..0f749e8f1 --- /dev/null +++ b/.changes/wayland-resize-window.md @@ -0,0 +1,5 @@ +--- +"tao": patch +--- + +On Linux Wayland, fixed an issue where the window was not resizing when dragging the window borders. \ No newline at end of file diff --git a/.changes/wayland-titlebar.md b/.changes/wayland-titlebar.md new file mode 100644 index 000000000..21b56871b --- /dev/null +++ b/.changes/wayland-titlebar.md @@ -0,0 +1,5 @@ +--- +"tao": patch +--- + +On Linux Wayland, added buttons for maximize and minimize in the title bar. diff --git a/examples/window_debug.rs b/examples/window_debug.rs index 461295146..f65002bbe 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -33,6 +33,7 @@ fn main() { eprintln!(" (T) Toggle always on top"); eprintln!(" (B) Toggle always on bottom"); eprintln!(" (C) Toggle content protection"); + eprintln!(" (R) Toggle resizable"); eprintln!(" (M) Toggle minimized"); eprintln!(" (X) Toggle maximized"); eprintln!(" (Q) Quit event loop"); @@ -44,6 +45,7 @@ fn main() { let mut always_on_top = false; let mut visible = true; let mut content_protection = false; + let mut resizable = false; event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -119,6 +121,11 @@ fn main() { window.set_fullscreen(Some(Fullscreen::Borderless(None))); } } + "r" => { + resizable = !resizable; + window.set_resizable(resizable); + println!("Resizable: {}", resizable); + } "m" => { window.set_minimized(!window.is_minimized()); } diff --git a/src/platform_impl/linux/event_loop.rs b/src/platform_impl/linux/event_loop.rs index 6d84587b5..d5ce7e21b 100644 --- a/src/platform_impl/linux/event_loop.rs +++ b/src/platform_impl/linux/event_loop.rs @@ -262,6 +262,7 @@ impl EventLoop { }; let mut taskbar = TaskbarIndicator::new(); + let is_wayland = window_target.is_wayland(); // Window Request window_requests_rx.attach(Some(&context), move |(id, request)| { @@ -292,9 +293,13 @@ impl EventLoop { window.deiconify(); } } - WindowRequest::Maximized(maximized) => { + WindowRequest::Maximized(maximized, resizable) => { if maximized { - window.maximize(); + let maximize_process = util::WindowMaximizeProcess::new(window.clone(), resizable); + glib::idle_add_local_full(glib::Priority::DEFAULT_IDLE, move || { + let mut maximize_process = maximize_process.borrow_mut(); + maximize_process.next_step() + }); } else { window.unmaximize(); } @@ -462,35 +467,34 @@ impl EventLoop { glib::Propagation::Proceed }); window.connect_button_press_event(move |window, event| { - if !window.is_decorated() + const LMB: u32 = 1; + if (is_wayland || !window.is_decorated()) && window.is_resizable() && !window.is_maximized() - && event.button() == 1 + && event.button() == LMB { - if let Some(window) = window.window() { - let (cx, cy) = event.root(); - let (left, top) = window.position(); - let (w, h) = (window.width(), window.height()); - let (right, bottom) = (left + w, top + h); - let border = window.scale_factor() * 5; - let edge = crate::window::hit_test( - (left, top, right, bottom), - cx as _, - cy as _, - border, - border, - ) - .map(|d| d.to_gtk_edge()) - // we return `WindowEdge::__Unknown` to be ignored later. - // we must return 8 or bigger, otherwise it will be the same as one of the other 7 variants of `WindowEdge` enum. - .unwrap_or(WindowEdge::__Unknown(8)); - // Ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges. - match edge { - WindowEdge::__Unknown(_) => (), - _ => { - // FIXME: calling `window.begin_resize_drag` uses the default cursor, it should show a resizing cursor instead - window.begin_resize_drag(edge, 1, cx as i32, cy as i32, event.time()) - } + let (cx, cy) = event.root(); + let (left, top) = window.position(); + let (w, h) = window.size(); + let (right, bottom) = (left + w, top + h); + let border = window.scale_factor() * 5; + let edge = crate::window::hit_test( + (left, top, right, bottom), + cx as _, + cy as _, + border, + border, + ) + .map(|d| d.to_gtk_edge()) + // we return `WindowEdge::__Unknown` to be ignored later. + // we must return 8 or bigger, otherwise it will be the same as one of the other 7 variants of `WindowEdge` enum. + .unwrap_or(WindowEdge::__Unknown(8)); + // Ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges. + match edge { + WindowEdge::__Unknown(_) => (), + _ => { + // FIXME: calling `window.begin_resize_drag` uses the default cursor, it should show a resizing cursor instead + window.begin_resize_drag(edge, LMB as i32, cx as i32, cy as i32, event.time()) } } } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index a0b8c5d30..e641a79d1 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -20,6 +20,7 @@ mod util; mod window; pub mod taskbar; +pub mod wayland; pub mod x11; pub use self::keycode::{keycode_from_scancode, keycode_to_scancode}; diff --git a/src/platform_impl/linux/util.rs b/src/platform_impl/linux/util.rs index 5040a5092..393932c08 100644 --- a/src/platform_impl/linux/util.rs +++ b/src/platform_impl/linux/util.rs @@ -1,15 +1,19 @@ +use crate::{ + dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, + error::ExternalError, + window::WindowSizeConstraints, +}; use gtk::gdk::{ self, prelude::{DeviceExt, SeatExt}, Display, }; -use gtk::traits::{GtkWindowExt, WidgetExt}; - -use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, - error::ExternalError, - window::WindowSizeConstraints, +use gtk::{ + glib::{self}, + traits::{GtkWindowExt, WidgetExt}, }; +use std::cell::RefCell; +use std::rc::Rc; #[inline] pub fn cursor_position(is_wayland: bool) -> Result, ExternalError> { @@ -70,3 +74,39 @@ pub fn set_size_constraints( geom_mask, ) } + +pub struct WindowMaximizeProcess { + window: W, + resizable: bool, + step: u8, +} + +impl WindowMaximizeProcess { + pub fn new(window: W, resizable: bool) -> Rc> { + Rc::new(RefCell::new(Self { + window, + resizable, + step: 0, + })) + } + + pub fn next_step(&mut self) -> glib::ControlFlow { + match self.step { + 0 => { + self.window.set_resizable(true); + self.step += 1; + glib::ControlFlow::Continue + } + 1 => { + self.window.maximize(); + self.step += 1; + glib::ControlFlow::Continue + } + 2 => { + self.window.set_resizable(self.resizable); + glib::ControlFlow::Break + } + _ => glib::ControlFlow::Break, + } + } +} diff --git a/src/platform_impl/linux/wayland/header.rs b/src/platform_impl/linux/wayland/header.rs new file mode 100644 index 000000000..480eb4c85 --- /dev/null +++ b/src/platform_impl/linux/wayland/header.rs @@ -0,0 +1,36 @@ +use gtk::{prelude::*, ApplicationWindow, EventBox, HeaderBar}; + +pub struct WlHeader; + +impl WlHeader { + pub fn setup(window: &ApplicationWindow, title: &str) { + let header = HeaderBar::builder() + .show_close_button(true) + .decoration_layout("menu:minimize,maximize,close") + .title(title) + .build(); + + let event_box = EventBox::new(); + event_box.set_above_child(true); + event_box.set_visible(true); + event_box.set_can_focus(false); + event_box.add(&header); + + window.set_titlebar(Some(&event_box)); + Self::connect_resize_window(&header, window); + } + + fn connect_resize_window(header: &HeaderBar, window: &ApplicationWindow) { + let header_weak = header.downgrade(); + window.connect_resizable_notify(move |window| { + if let Some(header) = header_weak.upgrade() { + let is_resizable = window.is_resizable(); + header.set_decoration_layout(if !is_resizable { + Some("menu:minimize,close") + } else { + Some("menu:minimize,maximize,close") + }); + } + }); + } +} diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs new file mode 100644 index 000000000..a200d2603 --- /dev/null +++ b/src/platform_impl/linux/wayland/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +pub mod header; diff --git a/src/platform_impl/linux/window.rs b/src/platform_impl/linux/window.rs index a458f420e..5d988e93a 100644 --- a/src/platform_impl/linux/window.rs +++ b/src/platform_impl/linux/window.rs @@ -23,6 +23,7 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::MonitorHandle as RootMonitorHandle, + platform_impl::wayland::header::WlHeader, window::{ CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowSizeConstraints, @@ -78,6 +79,7 @@ impl Window { let app = &event_loop_window_target.app; let window_requests_tx = event_loop_window_target.window_requests_tx.clone(); let draw_tx = event_loop_window_target.draw_tx.clone(); + let is_wayland = event_loop_window_target.is_wayland(); let mut window_builder = gtk::ApplicationWindow::builder() .application(app) @@ -88,6 +90,10 @@ impl Window { let window = window_builder.build(); + if is_wayland { + WlHeader::setup(&window, &attributes.title); + } + let window_id = WindowId(window.id()); event_loop_window_target .windows @@ -104,10 +110,15 @@ impl Window { window.resize(width, height); if attributes.maximized { - window.maximize(); + let maximize_process = util::WindowMaximizeProcess::new(window.clone(), attributes.resizable); + glib::idle_add_local_full(glib::Priority::HIGH_IDLE, move || { + let mut maximize_process = maximize_process.borrow_mut(); + maximize_process.next_step() + }); + } else { + window.set_resizable(attributes.resizable); } - window.set_resizable(attributes.resizable); window.set_deletable(attributes.closable); // Set Min/Max Size @@ -578,10 +589,12 @@ impl Window { } pub fn set_maximized(&self, maximized: bool) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Maximized(maximized))) - { + let resizable = self.is_resizable(); + + if let Err(e) = self.window_requests_tx.send(( + self.window_id, + WindowRequest::Maximized(maximized, resizable), + )) { log::warn!("Fail to send maximized request: {}", e); } } @@ -609,7 +622,6 @@ impl Window { pub fn is_maximizable(&self) -> bool { true } - pub fn is_closable(&self) -> bool { self.window.is_deletable() } @@ -995,7 +1007,7 @@ pub enum WindowRequest { Resizable(bool), Closable(bool), Minimized(bool), - Maximized(bool), + Maximized(bool, bool), DragWindow, DragResizeWindow(ResizeDirection), Fullscreen(Option),