wayland/decoration: Make default decoration mode configurable

This commit is contained in:
Victoria Brekenfeld 2026-08-14 18:25:41 +02:00 • committed by Jeremy Soller
parent a830784f11
commit b407ed560a
3 changed files with 120 additions and 8 deletions

View file

@ -66,6 +66,13 @@ impl Default for AppearanceConfig {
} }
} }
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum DecorationPreference {
#[default]
ClientSide,
ServerSide,
}
#[derive(Clone, Debug, PartialEq, CosmicConfigEntry)] #[derive(Clone, Debug, PartialEq, CosmicConfigEntry)]
#[version = 1] #[version = 1]
pub struct CosmicCompConfig { pub struct CosmicCompConfig {
@ -104,6 +111,7 @@ pub struct CosmicCompConfig {
/// Briefly magnify the cursor when the pointer is shaken, to help locate it /// Briefly magnify the cursor when the pointer is shaken, to help locate it
pub cursor_shake_to_find: bool, pub cursor_shake_to_find: bool,
pub activation_policy: ActivationPolicy, pub activation_policy: ActivationPolicy,
pub decoration_preference: DecorationPreference,
} }
impl Default for CosmicCompConfig { impl Default for CosmicCompConfig {
@ -143,6 +151,7 @@ impl Default for CosmicCompConfig {
cursor_hide_timeout: None, cursor_hide_timeout: None,
cursor_shake_to_find: true, cursor_shake_to_find: true,
activation_policy: ActivationPolicy::default(), activation_policy: ActivationPolicy::default(),
decoration_preference: DecorationPreference::default(),
} }
} }
} }

View file

@ -49,8 +49,8 @@ mod types;
use cosmic::config::CosmicTk; use cosmic::config::CosmicTk;
pub use cosmic_comp_config::EdidProduct; pub use cosmic_comp_config::EdidProduct;
use cosmic_comp_config::{ use cosmic_comp_config::{
ActivationPolicy, AppearanceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, XkbConfig, ActivationPolicy, AppearanceConfig, CosmicCompConfig, DecorationPreference, KeyboardConfig,
XwaylandDescaling, XwaylandEavesdropping, ZoomConfig, TileBehavior, XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride}, input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride},
output::comp::{ output::comp::{
OutputConfig, OutputInfo, OutputState, OutputsConfig, TransformDef, load_outputs, OutputConfig, OutputInfo, OutputState, OutputsConfig, TransformDef, load_outputs,
@ -1000,6 +1000,13 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
state.common.config.cosmic_conf.activation_policy = new; state.common.config.cosmic_conf.activation_policy = new;
} }
} }
"decoration_preference" => {
let new = get_config::<DecorationPreference>(&config, "decoration_preference");
if new != state.common.config.cosmic_conf.decoration_preference {
state.common.config.cosmic_conf.decoration_preference = new;
state.update_decorations();
}
}
_ => {} _ => {}
} }
} }

View file

@ -1,5 +1,6 @@
use std::{cell::RefCell, sync::Mutex}; use std::{cell::RefCell, sync::Mutex};
use cosmic_comp_config::DecorationPreference;
use smithay::{ use smithay::{
desktop::Window, desktop::Window,
reexports::{ reexports::{
@ -20,7 +21,7 @@ use smithay::{
}; };
use wayland_backend::protocol::WEnum; use wayland_backend::protocol::WEnum;
use crate::state::State; use crate::{shell::CosmicMapped, state::State};
pub struct PreferredDecorationMode(RefCell<Option<XdgMode>>); pub struct PreferredDecorationMode(RefCell<Option<XdgMode>>);
@ -29,7 +30,7 @@ impl PreferredDecorationMode {
window window
.user_data() .user_data()
.get::<PreferredDecorationMode>() .get::<PreferredDecorationMode>()
.is_none() .is_none_or(|mode| mode.0.borrow().is_none())
} }
pub fn mode(window: &Window) -> Option<XdgMode> { pub fn mode(window: &Window) -> Option<XdgMode> {
@ -53,6 +54,73 @@ impl PreferredDecorationMode {
} }
} }
impl State {
pub fn default_decoration(&self) -> DecorationPreference {
self.common.config.cosmic_conf.decoration_preference
}
pub fn update_decorations(&self) {
let shell = self.common.shell.read();
let update = |mapped: &CosmicMapped| {
let mode = if mapped.is_stack() {
XdgMode::ServerSide
} else {
XdgMode::from_preference(self.default_decoration())
};
for (window, _) in mapped.windows() {
if PreferredDecorationMode::is_unset(&window.0) {
if let Some(toplevel) = window.0.toplevel() {
if toplevel.with_committed_state(|state| {
state.is_some_and(|state| state.decoration_mode.is_some())
}) {
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(mode);
});
toplevel.send_configure();
}
}
}
}
};
for set in shell.workspaces.sets.values() {
set.sticky_layer.mapped().for_each(update);
}
for space in shell.workspaces.spaces() {
space.mapped().for_each(update);
space
.minimized_windows
.iter()
.filter_map(|m| m.mapped())
.for_each(update);
}
}
}
trait FromDecorationPreference {
fn from_preference(preference: DecorationPreference) -> Self;
}
impl FromDecorationPreference for XdgMode {
fn from_preference(preference: DecorationPreference) -> Self {
match preference {
DecorationPreference::ClientSide => XdgMode::ClientSide,
DecorationPreference::ServerSide => XdgMode::ServerSide,
}
}
}
impl FromDecorationPreference for KdeMode {
fn from_preference(preference: DecorationPreference) -> Self {
match preference {
DecorationPreference::ClientSide => KdeMode::Client,
DecorationPreference::ServerSide => KdeMode::Server,
}
}
}
pub type KdeDecorationData = Mutex<KdeDecorationSurfaceState>; pub type KdeDecorationData = Mutex<KdeDecorationSurfaceState>;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct KdeDecorationSurfaceState { pub struct KdeDecorationSurfaceState {
@ -67,7 +135,7 @@ impl XdgDecorationHandler for State {
let mode = if mapped.is_stack() { let mode = if mapped.is_stack() {
XdgMode::ServerSide XdgMode::ServerSide
} else { } else {
XdgMode::ClientSide XdgMode::from_preference(self.default_decoration())
}; };
if let Some((window, _)) = mapped if let Some((window, _)) = mapped
@ -80,6 +148,10 @@ impl XdgDecorationHandler for State {
}); });
toplevel.send_configure(); toplevel.send_configure();
} }
} else {
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(XdgMode::from_preference(self.default_decoration()))
})
} }
} }
@ -99,6 +171,13 @@ impl XdgDecorationHandler for State {
} }
} else { } else {
toplevel.with_pending_state(|state| state.decoration_mode = Some(mode)); toplevel.with_pending_state(|state| state.decoration_mode = Some(mode));
if let Some(pending) = shell
.pending_windows
.iter()
.find(|pending| pending.surface.0.toplevel().is_some_and(|t| t == &toplevel))
{
PreferredDecorationMode::update(&pending.surface.0, Some(mode));
}
} }
} }
@ -110,11 +189,28 @@ impl XdgDecorationHandler for State {
.find(|(window, _)| window.wl_surface().as_deref() == Some(toplevel.wl_surface())) .find(|(window, _)| window.wl_surface().as_deref() == Some(toplevel.wl_surface()))
&& let Some(toplevel) = window.0.toplevel() && let Some(toplevel) = window.0.toplevel()
{ {
let mode = if mapped.is_stack() {
XdgMode::ServerSide
} else {
XdgMode::from_preference(self.default_decoration())
};
PreferredDecorationMode::update(&window.0, None); PreferredDecorationMode::update(&window.0, None);
toplevel.with_pending_state(|state| { toplevel.with_pending_state(|state| {
state.decoration_mode = None; state.decoration_mode = Some(mode);
}); });
toplevel.send_configure(); toplevel.send_configure();
} else {
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(XdgMode::from_preference(self.default_decoration()))
});
if let Some(pending) = shell
.pending_windows
.iter()
.find(|pending| pending.surface.0.toplevel().is_some_and(|t| t == &toplevel))
{
PreferredDecorationMode::update(&pending.surface.0, None);
}
} }
} }
} }
@ -129,10 +225,10 @@ impl KdeDecorationHandler for State {
if mapped.is_stack() { if mapped.is_stack() {
KdeMode::Server KdeMode::Server
} else { } else {
KdeMode::Client KdeMode::from_preference(self.default_decoration())
} }
} else { } else {
KdeMode::Client KdeMode::from_preference(self.default_decoration())
}; };
with_states(surface, |states| { with_states(surface, |states| {