diff --git a/compositor/src/handlers/layer_shell.rs b/compositor/src/handlers/layer_shell.rs index 38f38f5..1bc458a 100644 --- a/compositor/src/handlers/layer_shell.rs +++ b/compositor/src/handlers/layer_shell.rs @@ -42,11 +42,24 @@ impl WlrLayerShellHandler for Ferese { } tracing::debug!(%namespace, output = %output.name(), "mapped layer surface"); + super::set_surface_tree_scale( + layer.wl_surface(), + output.current_scale().fractional_scale(), + ); self.relayout(); crate::backends::direct::render_all(self); } - fn new_popup(&mut self, _parent: LayerSurface, popup: PopupSurface) { + fn new_popup(&mut self, parent: LayerSurface, popup: PopupSurface) { + let scale = self.space.outputs().find_map(|output| { + layer_map_for_output(output) + .layers() + .any(|layer| layer.wl_surface() == parent.wl_surface()) + .then(|| output.current_scale().fractional_scale()) + }); + if let Some(scale) = scale { + super::set_surface_tree_scale(popup.wl_surface(), scale); + } self.unconstrain_popup(&popup); if let Err(error) = self.popups.track_popup(PopupKind::Xdg(popup)) { tracing::warn!(?error, "failed to track layer-shell popup"); diff --git a/compositor/src/handlers/mod.rs b/compositor/src/handlers/mod.rs index 827d38e..611f12c 100644 --- a/compositor/src/handlers/mod.rs +++ b/compositor/src/handlers/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod screencopy; mod xdg_shell; use smithay::{ + desktop::{PopupManager, layer_map_for_output}, input::{ Seat, SeatHandler, SeatState, pointer::{CursorImageStatus, PointerHandle}, @@ -159,18 +160,39 @@ impl Ferese { continue; }; - with_surface_tree_downward( - toplevel.wl_surface(), - (), - |_, _, &()| TraversalAction::DoChildren(()), - |_, states, &()| { - with_fractional_scale(states, |surface_scale| { - surface_scale.set_preferred_scale(scale); - }); - }, - |_, _, &()| true, - ); + set_surface_tree_scale(toplevel.wl_surface(), scale); } + + // Layer surfaces are not Space elements. Use their own output's scale, + // rather than broadcasting one output's density to every bar. + for output in self.space.outputs() { + let scale = output.current_scale().fractional_scale(); + let layers: Vec<_> = layer_map_for_output(output).layers().cloned().collect(); + for layer in layers { + set_surface_tree_scale(layer.wl_surface(), scale); + } + } + } +} + +pub(crate) fn set_surface_tree_scale(surface: &WlSurface, scale: f64) { + let update_tree = |root: &WlSurface| { + with_surface_tree_downward( + root, + (), + |_, _, &()| TraversalAction::DoChildren(()), + |_, states, &()| { + with_fractional_scale(states, |surface_scale| { + surface_scale.set_preferred_scale(scale); + }); + }, + |_, _, &()| true, + ); + }; + update_tree(surface); + // XDG popups are separate surface trees, not wl_subsurfaces. + for (popup, _) in PopupManager::popups_for_surface(surface) { + update_tree(popup.wl_surface()); } } diff --git a/shell/ferese-shell/src/main.rs b/shell/ferese-shell/src/main.rs index 014e779..02145d0 100644 --- a/shell/ferese-shell/src/main.rs +++ b/shell/ferese-shell/src/main.rs @@ -26,7 +26,7 @@ use cosmic::iced::{ window, }; use cosmic::theme; -use cosmic::widget::{button, container, icon, image, row, text}; +use cosmic::widget::{button, container, icon, image, row}; use ferese_protocols::effects::v1::client::{ ferese_effects_manager_v1::FereseEffectsManagerV1, ferese_surface_effects_v1::{self, FereseSurfaceEffectsV1}, @@ -43,6 +43,23 @@ use crate::control::{ShellControl, ShellSnapshot}; const APP_ID: &str = "dev.ferese.Shell"; +static SHELL_FONT: std::sync::OnceLock = std::sync::OnceLock::new(); + +fn configured_font(family: Option<&str>) -> cosmic::font::Font { + family.map_or_else(cosmic::font::default, |family| { + let family = Box::leak(family.to_owned().into_boxed_str()); + cosmic::font::Font::with_name(family) + }) +} + +// COSMIC's text helper explicitly selects its interface font, overriding +// Settings::default_font. Use this helper for every bar and menu label. +fn text<'a>( + content: impl Into> + 'a, +) -> cosmic::widget::Text<'a, cosmic::Theme, cosmic::Renderer> { + cosmic::widget::text(content).font(*SHELL_FONT.get_or_init(cosmic::font::default)) +} + #[derive(Clone, Copy)] struct BarMetrics { height: f32, @@ -87,6 +104,8 @@ fn main() -> cosmic::iced::Result { unsafe { std::env::set_var("ICED_BACKEND", "tiny-skia,wgpu") }; } let config = config::load(); + let shell_font = configured_font(config.font_family.as_deref()); + let _ = SHELL_FONT.set(shell_font); // Decode alongside toolkit/GPU initialization, never during a UI draw. let wallpaper = config.wallpaper.path.clone().map(|path| { let (sender, receiver) = cosmic::iced::futures::channel::oneshot::channel(); @@ -106,11 +125,7 @@ fn main() -> cosmic::iced::Result { .client_decorations(false) .transparent(true) .is_daemon(true); - if let Some(font_family) = config.font_family.clone() { - let font_family = Box::leak(font_family.into_boxed_str()); - - settings = settings.default_font(cosmic::font::Font::with_name(font_family)); - } + settings = settings.default_font(shell_font); cosmic::app::run::(settings, (config, wallpaper)) } @@ -1025,6 +1040,15 @@ mod tests { use super::*; use crate::control::{OutputSnapshot, WindowSnapshot}; + #[test] + fn shell_font_respects_configured_family() { + assert_eq!( + configured_font(Some("JetBrainsMono Nerd Font")), + cosmic::font::Font::with_name("JetBrainsMono Nerd Font") + ); + assert_eq!(configured_font(None), cosmic::font::default()); + } + #[test] fn icons_use_theme_accent_and_preserve_battery_status_colors() { for (source, accent) in [