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)]
#[version = 1]
pub struct CosmicCompConfig {
@ -104,6 +111,7 @@ pub struct CosmicCompConfig {
/// Briefly magnify the cursor when the pointer is shaken, to help locate it
pub cursor_shake_to_find: bool,
pub activation_policy: ActivationPolicy,
pub decoration_preference: DecorationPreference,
}
impl Default for CosmicCompConfig {
@ -143,6 +151,7 @@ impl Default for CosmicCompConfig {
cursor_hide_timeout: None,
cursor_shake_to_find: true,
activation_policy: ActivationPolicy::default(),
decoration_preference: DecorationPreference::default(),
}
}
}

View file

@ -49,8 +49,8 @@ mod types;
use cosmic::config::CosmicTk;
pub use cosmic_comp_config::EdidProduct;
use cosmic_comp_config::{
ActivationPolicy, AppearanceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, XkbConfig,
XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
ActivationPolicy, AppearanceConfig, CosmicCompConfig, DecorationPreference, KeyboardConfig,
TileBehavior, XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride},
output::comp::{
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;
}
}
"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 cosmic_comp_config::DecorationPreference;
use smithay::{
desktop::Window,
reexports::{
@ -20,7 +21,7 @@ use smithay::{
};
use wayland_backend::protocol::WEnum;
use crate::state::State;
use crate::{shell::CosmicMapped, state::State};
pub struct PreferredDecorationMode(RefCell<Option<XdgMode>>);
@ -29,7 +30,7 @@ impl PreferredDecorationMode {
window
.user_data()
.get::<PreferredDecorationMode>()
.is_none()
.is_none_or(|mode| mode.0.borrow().is_none())
}
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>;
#[derive(Debug, Default)]
pub struct KdeDecorationSurfaceState {
@ -67,7 +135,7 @@ impl XdgDecorationHandler for State {
let mode = if mapped.is_stack() {
XdgMode::ServerSide
} else {
XdgMode::ClientSide
XdgMode::from_preference(self.default_decoration())
};
if let Some((window, _)) = mapped
@ -80,6 +148,10 @@ impl XdgDecorationHandler for State {
});
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 {
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()))
&& 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);
toplevel.with_pending_state(|state| {
state.decoration_mode = None;
state.decoration_mode = Some(mode);
});
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() {
KdeMode::Server
} else {
KdeMode::Client
KdeMode::from_preference(self.default_decoration())
}
} else {
KdeMode::Client
KdeMode::from_preference(self.default_decoration())
};
with_states(surface, |states| {