diff --git a/src/hooks.rs b/src/hooks.rs new file mode 100644 index 00000000..c5307043 --- /dev/null +++ b/src/hooks.rs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::shell::element::stack::{ + CosmicStackInternal, DefaultDecorations as DefaultStackDecorations, Message as StackMessage, +}; +use crate::shell::element::window::{ + CosmicWindowInternal, DefaultDecorations as DefaultWindowDecorations, Message as WindowMessage, +}; +use std::sync::{Arc, OnceLock}; + +/// An _unstable_ interface to customize cosmic-comp at compile-time by providing +/// hooks to be run in specific code paths. +#[derive(Default, Debug, Clone)] +pub struct Hooks { + pub window_decorations: + Option + Send + Sync>>, + pub stack_decorations: + Option + Send + Sync>>, +} + +pub static HOOKS: OnceLock = OnceLock::new(); + +pub trait Decorations: std::fmt::Debug { + fn view(&self, state: &Internal) -> cosmic::Element<'_, Message>; +} + +impl Decorations + for Option + Send + Sync>> +{ + fn view(&self, window: &CosmicWindowInternal) -> cosmic::Element<'_, WindowMessage> { + match self { + None => DefaultWindowDecorations.view(window), + Some(deco) => deco.view(window), + } + } +} + +impl Decorations + for Option + Send + Sync>> +{ + fn view(&self, window: &CosmicStackInternal) -> cosmic::Element<'_, StackMessage> { + match self { + None => DefaultStackDecorations.view(window), + Some(deco) => deco.view(window), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index ef14496e..0fbb38d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ pub mod config; pub mod dbus; #[cfg(feature = "debug")] pub mod debug; +pub mod hooks; pub mod input; mod logger; pub mod session; @@ -103,7 +104,7 @@ impl State { } } -pub fn run() -> Result<(), Box> { +pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box> { let raw_args = RawArgs::from_args(); let mut cursor = raw_args.cursor(); let git_hash = option_env!("GIT_HASH").unwrap_or("unknown"); @@ -137,6 +138,10 @@ pub fn run() -> Result<(), Box> { utils::rlimit::increase_nofile_limit(); + // init hook globals + hooks::HOOKS.set(hooks) + .expect("Hooks global has already been initialized. Running multiple instances of COSMIC in one process is not supported."); + // init event loop let mut event_loop = EventLoop::try_new().with_context(|| "Failed to initialize event loop")?; // init wayland diff --git a/src/main.rs b/src/main.rs index c26487f4..7ae05991 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only fn main() { - if let Err(err) = cosmic_comp::run() { + if let Err(err) = cosmic_comp::run(Default::default()) { tracing::error!("Error occured in main(): {}", err); std::process::exit(1); } diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index ceb566a5..6ddb3458 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -4,6 +4,7 @@ use super::{ }; use crate::{ backend::render::cursor::CursorState, + hooks::{Decorations, HOOKS}, shell::{ focus::target::PointerFocusTarget, grabs::{ReleaseMode, ResizeEdge}, @@ -1007,12 +1008,57 @@ impl Program for CosmicStackInternal { } fn view(&self) -> CosmicElement<'_, Self::Message> { - let windows = self.windows.lock().unwrap(); - if self.geometry.lock().unwrap().is_none() { + HOOKS.get().unwrap().stack_decorations.view(self) + } + + fn foreground( + &self, + pixels: &mut tiny_skia::PixmapMut<'_>, + damage: &[Rectangle], + scale: f32, + theme: &Theme, + ) { + if self.group_focused.load(Ordering::SeqCst) { + let border = Rectangle::new( + (0, ((TAB_HEIGHT as f32 * scale) - scale).floor() as i32).into(), + (pixels.width() as i32, scale.ceil() as i32).into(), + ); + + let mut paint = tiny_skia::Paint::default(); + let (b, g, r, a) = theme.cosmic().accent_color().into_components(); + paint.set_color(tiny_skia::Color::from_rgba(r, g, b, a).unwrap()); + + for rect in damage { + if let Some(overlap) = rect.intersection(border) { + pixels.fill_rect( + tiny_skia::Rect::from_xywh( + overlap.loc.x as f32, + overlap.loc.y as f32, + overlap.size.w as f32, + overlap.size.h as f32, + ) + .unwrap(), + &paint, + Default::default(), + None, + ) + } + } + } + } +} + +#[derive(Debug)] +pub struct DefaultDecorations; + +impl Decorations for DefaultDecorations { + fn view(&self, stack: &CosmicStackInternal) -> cosmic::Element<'_, Message> { + let windows = stack.windows.lock().unwrap(); + if stack.geometry.lock().unwrap().is_none() { return iced_widget::row(Vec::new()).into(); }; - let active = self.active.load(Ordering::SeqCst); - let group_focused = self.group_focused.load(Ordering::SeqCst); + let active = stack.active.load(Ordering::SeqCst); + let group_focused = stack.group_focused.load(Ordering::SeqCst); let elements = vec![ cosmic_widget::icon::from_name("window-stack-symbolic") @@ -1057,7 +1103,8 @@ impl Program for CosmicStackInternal { ) .id(SCROLLABLE_ID.clone()) .force_visible( - self.scroll_to_focus + stack + .scroll_to_focus .load(Ordering::SeqCst) .then_some(active), ) @@ -1079,7 +1126,7 @@ impl Program for CosmicStackInternal { } else { Radius::from([8.0, 8.0, 0.0, 0.0]) }; - let group_focused = self.group_focused.load(Ordering::SeqCst); + let group_focused = stack.group_focused.load(Ordering::SeqCst); iced_widget::row(elements) .height(TAB_HEIGHT as u16) @@ -1109,42 +1156,6 @@ impl Program for CosmicStackInternal { })) .into() } - - fn foreground( - &self, - pixels: &mut tiny_skia::PixmapMut<'_>, - damage: &[Rectangle], - scale: f32, - theme: &Theme, - ) { - if self.group_focused.load(Ordering::SeqCst) { - let border = Rectangle::new( - (0, ((TAB_HEIGHT as f32 * scale) - scale).floor() as i32).into(), - (pixels.width() as i32, scale.ceil() as i32).into(), - ); - - let mut paint = tiny_skia::Paint::default(); - let (b, g, r, a) = theme.cosmic().accent_color().into_components(); - paint.set_color(tiny_skia::Color::from_rgba(r, g, b, a).unwrap()); - - for rect in damage { - if let Some(overlap) = rect.intersection(border) { - pixels.fill_rect( - tiny_skia::Rect::from_xywh( - overlap.loc.x as f32, - overlap.loc.y as f32, - overlap.size.w as f32, - overlap.size.h as f32, - ) - .unwrap(), - &paint, - Default::default(), - None, - ) - } - } - } - } } impl IsAlive for CosmicStack { diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 22ee024c..43b9c816 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1,5 +1,6 @@ use crate::{ backend::render::cursor::CursorState, + hooks::{Decorations, HOOKS}, shell::{ focus::target::PointerFocusTarget, grabs::{ReleaseMode, ResizeEdge}, @@ -554,11 +555,20 @@ impl Program for CosmicWindowInternal { } fn view(&self) -> cosmic::Element<'_, Self::Message> { + HOOKS.get().unwrap().window_decorations.view(self) + } +} + +#[derive(Debug)] +pub struct DefaultDecorations; + +impl Decorations for DefaultDecorations { + fn view(&self, win: &CosmicWindowInternal) -> cosmic::Element<'_, Message> { let mut header = cosmic::widget::header_bar() - .title(self.last_title.lock().unwrap().clone()) + .title(win.last_title.lock().unwrap().clone()) .on_drag(Message::DragStart) .on_close(Message::Close) - .focused(self.window.is_activated(false)) + .focused(win.window.is_activated(false)) .on_double_click(Message::Maximize) .on_right_click(Message::Menu) .is_ssd(true);