On Wayland, provide option for better CSD

While most compositors provide server side decorations, the GNOME
does not, and won't provide them. Also Wayland clients must render
client side decorations.

Winit was already drawing some decorations, however they were bad
looking and provided no text rendering, so the title was missing.
However this commit makes use of the SCTK external frame similar to
GTK's Adwaita theme supporting text rendering and looking similar to
other GTK applications.

Fixes #1967.
This commit is contained in:
Bartłomiej Maryńczak 2022-05-20 02:09:23 +02:00 committed by GitHub
parent f04fa5d54f
commit 829a140d9b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 118 additions and 14 deletions

View file

@ -42,6 +42,7 @@ jobs:
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
PKG_CONFIG_ALLOW_CROSS: 1
RUSTFLAGS: "-C debuginfo=0 --deny warnings" RUSTFLAGS: "-C debuginfo=0 --deny warnings"
OPTIONS: ${{ matrix.platform.options }} OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }} FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
@ -62,9 +63,12 @@ jobs:
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }} rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }} targets: ${{ matrix.platform.target }}
- name: Install Linux dependencies
if: (matrix.platform.os == 'ubuntu-latest')
run: sudo apt-get update && sudo apt-get install pkg-config cmake libfreetype6-dev libfontconfig1-dev
- name: Install GCC Multilib - name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') 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 libfreetype6-dev:i386 libfontconfig1-dev:i386
- name: Install cargo-apk - name: Install cargo-apk
if: contains(matrix.platform.target, 'android') if: contains(matrix.platform.target, 'android')
run: cargo install cargo-apk run: cargo install cargo-apk

View file

@ -39,6 +39,12 @@ And please only add new entries to the top of this list, right below the `# Unre
sent a cancel or frame event. sent a cancel or frame event.
- On iOS, send `RedrawEventsCleared` even if there are no redraw events, consistent with other platforms. - On iOS, send `RedrawEventsCleared` even if there are no redraw events, consistent with other platforms.
- **Breaking:** Replaced `Window::with_app_id` and `Window::with_class` with `Window::with_name` on `WindowBuilderExtUnix`. - **Breaking:** Replaced `Window::with_app_id` and `Window::with_class` with `Window::with_name` on `WindowBuilderExtUnix`.
- On Wayland, fallback CSD was replaced with proper one:
- `WindowBuilderExtUnix::with_wayland_csd_theme` to set color theme in builder.
- `WindowExtUnix::wayland_set_csd_theme` to set color theme when creating a window.
- `WINIT_WAYLAND_CSD_THEME` env variable was added, it can be used to set "dark"/"light" theme in apps that don't expose theme setting.
- `wayland-csd-adwaita` feature that enables proper CSD with title rendering using FreeType system library.
- `wayland-csd-adwaita-notitle` feature that enables CSD but without title rendering.
- On Wayland and X11, fix window not resizing with `Window::set_inner_size` after calling `Window:set_resizable(false)`. - On Wayland and X11, fix window not resizing with `Window::set_inner_size` after calling `Window:set_resizable(false)`.
- On Windows, fix wrong fullscreen monitors being recognized when handling WM_WINDOWPOSCHANGING messages - On Windows, fix wrong fullscreen monitors being recognized when handling WM_WINDOWPOSCHANGING messages
- **Breaking:** Added new `WindowEvent::Ime` supported on desktop platforms. - **Breaking:** Added new `WindowEvent::Ime` supported on desktop platforms.

View file

@ -17,10 +17,12 @@ default-target = "x86_64-unknown-linux-gnu"
targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"] targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"]
[features] [features]
default = ["x11", "wayland", "wayland-dlopen"] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"] x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"]
wayland = ["wayland-client", "wayland-protocols", "sctk"] wayland = ["wayland-client", "wayland-protocols", "sctk"]
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"] wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/title"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
[dependencies] [dependencies]
instant = { version = "0.1", features = ["wasm-bindgen"] } instant = { version = "0.1", features = ["wasm-bindgen"] }
@ -90,6 +92,7 @@ features = [
wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true } wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true }
wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true } wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.15.4", default_features = false, features = ["calloop"], optional = true } sctk = { package = "smithay-client-toolkit", version = "0.15.4", default_features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.3.5", optional = true }
mio = { version = "0.8", features = ["os-ext"], optional = true } mio = { version = "0.8", features = ["os-ext"], optional = true }
x11-dl = { version = "2.18.5", optional = true } x11-dl = { version = "2.18.5", optional = true }
percent-encoding = { version = "2.0", optional = true } percent-encoding = { version = "2.0", optional = true }

View file

@ -173,7 +173,7 @@ Legend:
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ | |Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**| |Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**| |Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A**| |Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| |Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ | |Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| |Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|

View file

@ -32,6 +32,9 @@ pub use crate::platform_impl::x11;
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported}; pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
#[cfg(feature = "wayland")]
pub use crate::window::Theme;
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix. /// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
pub trait EventLoopWindowTargetExtUnix { pub trait EventLoopWindowTargetExtUnix {
/// True if the `EventLoopWindowTarget` uses Wayland. /// True if the `EventLoopWindowTarget` uses Wayland.
@ -70,7 +73,6 @@ impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
} }
#[inline] #[inline]
#[doc(hidden)]
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> { fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.p { match self.p {
@ -179,6 +181,13 @@ pub trait WindowExtUnix {
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void>; fn wayland_display(&self) -> Option<*mut raw::c_void>;
/// Updates [`Theme`] of window decorations.
///
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
/// Possible values for env variable are: "dark" and light"
#[cfg(feature = "wayland")]
fn wayland_set_csd_theme(&self, config: Theme);
/// Check if the window is ready for drawing /// Check if the window is ready for drawing
/// ///
/// It is a remnant of a previous implementation detail for the /// It is a remnant of a previous implementation detail for the
@ -221,7 +230,6 @@ impl WindowExtUnix for Window {
} }
#[inline] #[inline]
#[doc(hidden)]
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> { fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window { match self.window {
@ -261,6 +269,16 @@ impl WindowExtUnix for Window {
} }
} }
#[inline]
#[cfg(feature = "wayland")]
fn wayland_set_csd_theme(&self, theme: Theme) {
match self.window {
LinuxWindow::Wayland(ref w) => w.set_csd_theme(theme),
#[cfg(feature = "x11")]
_ => {}
}
}
#[inline] #[inline]
fn is_ready(&self) -> bool { fn is_ready(&self) -> bool {
true true
@ -299,6 +317,13 @@ pub trait WindowBuilderExtUnix {
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
fn with_gtk_theme_variant(self, variant: String) -> Self; fn with_gtk_theme_variant(self, variant: String) -> Self;
/// Build window with certain decoration [`Theme`]
///
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
/// Possible values for env variable are: "dark" and light"
#[cfg(feature = "wayland")]
fn with_wayland_csd_theme(self, theme: Theme) -> Self;
/// Build window with resize increment hint. Only implemented on X11. /// Build window with resize increment hint. Only implemented on X11.
/// ///
/// ``` /// ```
@ -375,6 +400,13 @@ impl WindowBuilderExtUnix for WindowBuilder {
self self
} }
#[inline]
#[cfg(feature = "wayland")]
fn with_wayland_csd_theme(mut self, theme: Theme) -> Self {
self.platform_specific.csd_theme = Some(theme);
self
}
#[inline] #[inline]
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self { fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {

View file

@ -9,8 +9,11 @@
#[cfg(all(not(feature = "x11"), not(feature = "wayland")))] #[cfg(all(not(feature = "x11"), not(feature = "wayland")))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
#[cfg(feature = "wayland")]
use crate::window::Theme;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
use std::error::Error; use std::error::Error;
use std::{collections::VecDeque, env, fmt}; use std::{collections::VecDeque, env, fmt};
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc}; use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc};
@ -101,6 +104,8 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub x11_window_types: Vec<XWindowType>, pub x11_window_types: Vec<XWindowType>,
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
pub gtk_theme_variant: Option<String>, pub gtk_theme_variant: Option<String>,
#[cfg(feature = "wayland")]
pub csd_theme: Option<Theme>,
} }
impl Default for PlatformSpecificWindowBuilderAttributes { impl Default for PlatformSpecificWindowBuilderAttributes {
@ -121,6 +126,8 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
x11_window_types: vec![XWindowType::Normal], x11_window_types: vec![XWindowType::Normal],
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
gtk_theme_variant: None, gtk_theme_variant: None,
#[cfg(feature = "wayland")]
csd_theme: None,
} }
} }
} }

View file

@ -13,10 +13,11 @@ use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_p
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::seat::pointer::{ThemeManager, ThemedPointer}; use sctk::seat::pointer::{ThemeManager, ThemedPointer};
use sctk::window::{FallbackFrame, Window}; use sctk::window::Window;
use crate::event::ModifiersState; use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState; use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::window::WinitFrame;
use crate::window::CursorIcon; use crate::window::CursorIcon;
mod data; mod data;
@ -156,7 +157,7 @@ impl WinitPointer {
} }
} }
pub fn drag_window(&self, window: &Window<FallbackFrame>) { pub fn drag_window(&self, window: &Window<WinitFrame>) {
// WlPointer::setart_interactive_move() expects the last serial of *any* // WlPointer::setart_interactive_move() expects the last serial of *any*
// pointer event (compare to set_cursor()). // pointer event (compare to set_cursor()).
window.start_interactive_move(&self.seat, self.latest_serial.get()); window.start_interactive_move(&self.seat, self.latest_serial.get());

View file

@ -8,7 +8,7 @@ use sctk::reexports::client::Display;
use sctk::reexports::calloop; use sctk::reexports::calloop;
use raw_window_handle::WaylandHandle; use raw_window_handle::WaylandHandle;
use sctk::window::{Decorations, FallbackFrame}; use sctk::window::Decorations;
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
@ -17,7 +17,7 @@ use crate::platform_impl::{
MonitorHandle as PlatformMonitorHandle, OsError, MonitorHandle as PlatformMonitorHandle, OsError,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes, PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
}; };
use crate::window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}; use crate::window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes};
use super::env::WindowingFeatures; use super::env::WindowingFeatures;
use super::event_loop::WinitState; use super::event_loop::WinitState;
@ -28,6 +28,14 @@ pub mod shim;
use shim::{WindowHandle, WindowRequest, WindowUpdate}; use shim::{WindowHandle, WindowRequest, WindowUpdate};
#[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 struct Window { pub struct Window {
/// Window id. /// Window id.
window_id: WindowId, window_id: WindowId,
@ -105,7 +113,7 @@ impl Window {
let theme_manager = event_loop_window_target.theme_manager.clone(); let theme_manager = event_loop_window_target.theme_manager.clone();
let mut window = event_loop_window_target let mut window = event_loop_window_target
.env .env
.create_window::<FallbackFrame, _>( .create_window::<WinitFrame, _>(
surface.clone(), surface.clone(),
Some(theme_manager), Some(theme_manager),
(width, height), (width, height),
@ -139,6 +147,20 @@ impl Window {
) )
.map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?; .map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?;
// Set CSD frame config
#[cfg(feature = "sctk-adwaita")]
{
let theme = platform_attributes.csd_theme.unwrap_or_else(|| {
let env = std::env::var(WAYLAND_CSD_THEME_ENV_VAR).unwrap_or_default();
match env.to_lowercase().as_str() {
"dark" => Theme::Dark,
_ => Theme::Light,
}
});
window.set_frame_config(theme.into());
}
// Set decorations. // Set decorations.
if attributes.decorations { if attributes.decorations {
window.set_decorate(Decorations::FollowServer); window.set_decorate(Decorations::FollowServer);
@ -380,6 +402,11 @@ impl Window {
self.decorated.load(Ordering::Relaxed) self.decorated.load(Ordering::Relaxed)
} }
#[inline]
pub fn set_csd_theme(&self, theme: Theme) {
self.send_request(WindowRequest::CsdThemeVariant(theme));
}
#[inline] #[inline]
pub fn set_minimized(&self, minimized: bool) { pub fn set_minimized(&self, minimized: bool) {
// You can't unminimize the window on Wayland. // You can't unminimize the window on Wayland.
@ -550,3 +577,13 @@ impl Drop for Window {
self.send_request(WindowRequest::Close); self.send_request(WindowRequest::Close);
} }
} }
#[cfg(feature = "sctk-adwaita")]
impl From<Theme> for sctk_adwaita::FrameConfig {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => sctk_adwaita::FrameConfig::light(),
Theme::Dark => sctk_adwaita::FrameConfig::dark(),
}
}
}

View file

@ -8,7 +8,7 @@ use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activat
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1; use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::environment::Environment; use sctk::environment::Environment;
use sctk::window::{Decorations, FallbackFrame, Window}; use sctk::window::{Decorations, Window};
use crate::dpi::{LogicalPosition, LogicalSize}; use crate::dpi::{LogicalPosition, LogicalSize};
@ -19,7 +19,9 @@ use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
use crate::platform_impl::wayland::seat::pointer::WinitPointer; use crate::platform_impl::wayland::seat::pointer::WinitPointer;
use crate::platform_impl::wayland::seat::text_input::TextInputHandler; use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
use crate::platform_impl::wayland::WindowId; use crate::platform_impl::wayland::WindowId;
use crate::window::{CursorIcon, UserAttentionType}; use crate::window::{CursorIcon, Theme, UserAttentionType};
use super::WinitFrame;
/// A request to SCTK window from Winit window. /// A request to SCTK window from Winit window.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -53,6 +55,9 @@ pub enum WindowRequest {
/// Request decorations change. /// Request decorations change.
Decorate(bool), Decorate(bool),
/// Request decorations change.
CsdThemeVariant(Theme),
/// Make the window resizeable. /// Make the window resizeable.
Resizeable(bool), Resizeable(bool),
@ -146,7 +151,7 @@ impl WindowUpdate {
/// and react to events. /// and react to events.
pub struct WindowHandle { pub struct WindowHandle {
/// An actual window. /// An actual window.
pub window: Window<FallbackFrame>, pub window: Window<WinitFrame>,
/// The current size of the window. /// The current size of the window.
pub size: Arc<Mutex<LogicalSize<u32>>>, pub size: Arc<Mutex<LogicalSize<u32>>>,
@ -188,7 +193,7 @@ pub struct WindowHandle {
impl WindowHandle { impl WindowHandle {
pub fn new( pub fn new(
env: &Environment<WinitEnv>, env: &Environment<WinitEnv>,
window: Window<FallbackFrame>, window: Window<WinitFrame>,
size: Arc<Mutex<LogicalSize<u32>>>, size: Arc<Mutex<LogicalSize<u32>>>,
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>, pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
) -> Self { ) -> Self {
@ -450,6 +455,15 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
let window_update = window_updates.get_mut(window_id).unwrap(); let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true; window_update.refresh_frame = true;
} }
#[cfg(feature = "sctk-adwaita")]
WindowRequest::CsdThemeVariant(theme) => {
window_handle.window.set_frame_config(theme.into());
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
#[cfg(not(feature = "sctk-adwaita"))]
WindowRequest::CsdThemeVariant(_) => {}
WindowRequest::Resizeable(resizeable) => { WindowRequest::Resizeable(resizeable) => {
window_handle.window.set_resizable(resizeable); window_handle.window.set_resizable(resizeable);