Support xdg decorations

This commit is contained in:
bbb651 2025-03-20 15:49:35 +02:00 committed by Shawn Wallace
parent b2613aec05
commit 0559ace758
5 changed files with 328 additions and 4 deletions

View file

@ -19,6 +19,8 @@ use wayland_protocols::wp::relative_pointer::zv1::client::{
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
zwp_relative_pointer_v1::ZwpRelativePointerV1,
};
use wayland_protocols::xdg::decoration::zv1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1;
use wayland_protocols::{
wp::{
linux_dmabuf::zv1::client::{
@ -140,6 +142,8 @@ delegate_noop!(Globals: ZxdgOutputManagerV1);
delegate_noop!(Globals: ZwpPointerConstraintsV1);
delegate_noop!(Globals: ZwpTabletManagerV2);
delegate_noop!(Globals: XdgActivationV1);
delegate_noop!(Globals: ZxdgDecorationManagerV1);
delegate_noop!(Globals: ignore ZxdgToplevelDecorationV1);
impl Dispatch<WlRegistry, GlobalListContents> for Globals {
fn event(

View file

@ -6,7 +6,7 @@ mod tests;
use self::event::*;
use crate::clientside::*;
use crate::xstate::{WindowDims, WmHints, WmName, WmNormalHints};
use crate::xstate::{Decorations, WindowDims, WmHints, WmName, WmNormalHints};
use crate::{X11Selection, XConnection};
use log::{debug, warn};
use rustix::event::{poll, PollFd, PollFlags};
@ -22,6 +22,10 @@ use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::net::UnixStream;
use std::rc::{Rc, Weak};
use wayland_client::{globals::Global, protocol as client, Proxy};
use wayland_protocols::xdg::decoration::zv1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::{
self, ZxdgToplevelDecorationV1,
};
use wayland_protocols::{
wp::{
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
@ -87,6 +91,7 @@ pub struct WindowAttributes {
pub title: Option<WmName>,
pub class: Option<String>,
pub group: Option<x::Window>,
pub decorations: Option<Decorations>,
}
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)]
@ -244,6 +249,7 @@ struct ToplevelData {
toplevel: XdgToplevel,
xdg: XdgSurfaceData,
fullscreen: bool,
decoration: Option<ZxdgToplevelDecorationV1>,
}
#[derive(Debug)]
@ -509,6 +515,7 @@ pub struct ServerState<C: XConnection> {
activation_state: Option<ActivationState>,
global_output_offset: GlobalOutputOffset,
global_offset_updated: bool,
decoration_manager: Option<ZxdgDecorationManagerV1>,
}
impl<C: XConnection> ServerState<C> {
@ -542,6 +549,14 @@ impl<C: XConnection> ServerState<C> {
})
.ok();
let decoration_manager = clientside
.global_list
.bind::<ZxdgDecorationManagerV1, _, _>(&qh, 1..=1, ())
.inspect_err(|e| {
warn!("Could not bind xdg decoration ({e:?}). Windows might not have decorations.")
})
.ok();
dh.create_global::<Self, XwaylandShellV1, _>(1, ());
clientside
.global_list
@ -578,6 +593,7 @@ impl<C: XConnection> ServerState<C> {
},
},
global_offset_updated: false,
decoration_manager,
}
}
@ -717,6 +733,35 @@ impl<C: XConnection> ServerState<C> {
}
}
pub fn set_win_decorations(&mut self, window: x::Window, decorations: Decorations) {
if self.decoration_manager.is_none() {
return;
};
let Some(win) = self.windows.get_mut(&window) else {
debug!("not setting decorations for unknown window {window:?}");
return;
};
if win.attrs.decorations != Some(decorations) {
debug!("setting {window:?} decorations {decorations:?}");
if let Some(key) = win.surface_key {
if let Some(object) = self.objects.get(key) {
let surface: &SurfaceData = object.as_ref();
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
data.decoration
.as_ref()
.unwrap()
.set_mode(decorations.into());
}
} else {
warn!("could not set decorations on {window:?}: stale surface")
}
}
win.attrs.decorations = Some(decorations);
}
}
pub fn set_window_serial(&mut self, window: x::Window, serial: [u32; 2]) {
let Some(win) = self.windows.get_mut(&window) else {
warn!("Tried to set serial for unknown window {window:?}");
@ -1224,6 +1269,17 @@ impl<C: XConnection> ServerState<C> {
toplevel.set_fullscreen(None);
}
let decoration = self.decoration_manager.as_ref().map(|decoration_manager| {
let decoration = decoration_manager.get_toplevel_decoration(&toplevel, &self.qh, ());
decoration.set_mode(
window
.attrs
.decorations
.map_or(zxdg_toplevel_decoration_v1::Mode::ServerSide, From::from),
);
decoration
});
let surface: &SurfaceData = self.objects[surface_key].as_ref();
if let (Some(activation_state), Some(token)) = (
self.activation_state.as_ref(),
@ -1240,6 +1296,7 @@ impl<C: XConnection> ServerState<C> {
},
toplevel,
fullscreen: false,
decoration,
}
}

View file

@ -1,5 +1,6 @@
mod selection;
use selection::{Selection, SelectionData};
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1;
use crate::{server::WindowAttributes, XConnection};
use bitflags::bitflags;
@ -246,7 +247,11 @@ impl XState {
self.set_root_property(self.atoms.wm_check, x::ATOM_WINDOW, &[self.wm_window]);
self.set_root_property(self.atoms.active_win, x::ATOM_WINDOW, &[x::Window::none()]);
self.set_root_property(self.atoms.supported, x::ATOM_ATOM, &[self.atoms.active_win]);
self.set_root_property(
self.atoms.supported,
x::ATOM_ATOM,
&[self.atoms.active_win, self.atoms.motif_wm_hints],
);
self.connection
.send_and_check_request(&x::ChangeProperty {
@ -491,6 +496,7 @@ impl XState {
let class = self.get_wm_class(window);
let wm_hints = self.get_wm_hints(window);
let size_hints = self.get_wm_size_hints(window);
let motif_wm_hints = self.get_motif_wm_hints(window);
let geometry = self.connection.wait_for_reply(geometry)?;
debug!("{window:?} geometry: {geometry:?}");
@ -503,6 +509,7 @@ impl XState {
let class = class.resolve()?;
let wm_hints = wm_hints.resolve()?;
let size_hints = size_hints.resolve()?;
let motif_wm_hints = motif_wm_hints.resolve()?;
Ok(WindowAttributes {
override_redirect: attrs.override_redirect(),
@ -517,6 +524,7 @@ impl XState {
class,
group: wm_hints.and_then(|h| h.window_group),
size_hints,
decorations: motif_wm_hints.and_then(|h| h.decorations),
})
}
@ -535,6 +543,9 @@ impl XState {
if let Some(hints) = attrs.size_hints {
server_state.set_size_hints(window, hints);
}
if let Some(decorations) = attrs.decorations {
server_state.set_win_decorations(window, decorations);
}
}
fn get_property_cookie(
@ -660,6 +671,28 @@ impl XState {
}
}
fn get_motif_wm_hints(
&self,
window: x::Window,
) -> PropertyCookieWrapper<impl PropertyResolver<Output = MotifWmHints>> {
let cookie = self.get_property_cookie(
window,
self.atoms.motif_wm_hints,
self.atoms.motif_wm_hints,
5,
);
let resolver = |reply: x::GetPropertyReply| {
let data: &[u32] = reply.value();
MotifWmHints::from(data)
};
PropertyCookieWrapper {
connection: &self.connection,
cookie,
resolver,
}
}
fn get_pid(&self, window: x::Window) -> Option<u32> {
let Some(pid) = self
.connection
@ -718,6 +751,13 @@ impl XState {
unwrap_or_skip_bad_window!(self.get_wm_class(window).resolve()).unwrap();
server_state.set_win_class(window, class);
}
x if x == self.atoms.motif_wm_hints => {
let motif_hints =
unwrap_or_skip_bad_window!(self.get_motif_wm_hints(window).resolve()).unwrap();
if let Some(decorations) = motif_hints.decorations {
server_state.set_win_decorations(window, decorations);
}
}
_ => {
if !self.handle_selection_property_change(&event)
&& log::log_enabled!(log::Level::Debug)
@ -750,6 +790,7 @@ xcb::atoms_struct! {
active_win => b"_NET_ACTIVE_WINDOW" only_if_exists = false,
client_list => b"_NET_CLIENT_LIST" only_if_exists = false,
supported => b"_NET_SUPPORTED" only_if_exists = false,
motif_wm_hints => b"_MOTIF_WM_HINTS" only_if_exists = false,
utf8_string => b"UTF8_STRING" only_if_exists = false,
clipboard => b"CLIPBOARD" only_if_exists = false,
targets => b"TARGETS" only_if_exists = false,
@ -795,6 +836,12 @@ bitflags! {
}
}
bitflags! {
pub struct MotifWmHintsFlags: u32 {
const Decorations = 2;
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct WinSize {
pub width: i32,
@ -849,6 +896,52 @@ impl From<&[u32]> for WmHints {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Decorations {
Client = 0,
Server = 1,
}
impl TryFrom<u32> for Decorations {
type Error = ();
fn try_from(value: u32) -> Result<Self, ()> {
match value {
0 => Ok(Self::Client),
1 => Ok(Self::Server),
_ => Err(()),
}
}
}
impl From<Decorations> for zxdg_toplevel_decoration_v1::Mode {
fn from(value: Decorations) -> Self {
match value {
Decorations::Client => zxdg_toplevel_decoration_v1::Mode::ClientSide,
Decorations::Server => zxdg_toplevel_decoration_v1::Mode::ServerSide,
}
}
}
#[derive(Default, Debug, PartialEq, Eq)]
pub struct MotifWmHints {
pub decorations: Option<Decorations>,
}
impl From<&[u32]> for MotifWmHints {
fn from(value: &[u32]) -> Self {
let mut ret = Self::default();
let flags = MotifWmHintsFlags::from_bits_truncate(value[0]);
if flags.contains(MotifWmHintsFlags::Decorations) {
ret.decorations = value[2].try_into().ok();
}
ret
}
}
#[derive(Debug, Clone, Copy)]
pub enum SetState {
Remove,