diff --git a/Cargo.lock b/Cargo.lock index 9b160617a..36c99f489 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1340,6 +1340,23 @@ dependencies = [ "zbus 5.12.0", ] +[[package]] +name = "cosmic-applet-theme" +version = "0.1.0" +dependencies = [ + "dirs 5.0.1", + "i18n-embed", + "i18n-embed-fl", + "libcosmic", + "notify 6.1.1", + "rust-embed", + "tokio", + "tracing", + "tracing-log", + "tracing-subscriber", + "zbus 5.12.0", +] + [[package]] name = "cosmic-applet-tiling" version = "0.1.0" @@ -1413,6 +1430,7 @@ dependencies = [ "cosmic-applet-notifications", "cosmic-applet-power", "cosmic-applet-status-area", + "cosmic-applet-theme", "cosmic-applet-tiling", "cosmic-applet-time", "cosmic-applet-workspaces", @@ -1462,11 +1480,11 @@ dependencies = [ "atomicwrites", "cosmic-config-derive", "cosmic-settings-daemon", - "dirs", + "dirs 6.0.0", "futures-util", "iced_futures", "known-folders", - "notify", + "notify 8.2.0", "ron", "serde", "tokio", @@ -1510,7 +1528,7 @@ name = "cosmic-freedesktop-icons" version = "0.4.0" source = "git+https://github.com/pop-os/freedesktop-icons#689c60d428f46dc59316eafa22297e196afa4b15" dependencies = [ - "dirs", + "dirs 6.0.0", "ini_core", "memmap2 0.9.9", "thiserror 2.0.17", @@ -1660,7 +1678,7 @@ dependencies = [ "almost", "cosmic-config", "csscolorparser", - "dirs", + "dirs 6.0.0", "palette", "ron", "serde", @@ -1695,6 +1713,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -1972,13 +1999,34 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", ] [[package]] @@ -1989,7 +2037,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.2", "windows-sys 0.61.2", ] @@ -2296,6 +2344,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.60.2", +] + [[package]] name = "find-crate" version = "0.6.3" @@ -3705,6 +3765,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + [[package]] name = "inotify" version = "0.11.0" @@ -4356,6 +4427,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.1.0" @@ -4515,6 +4598,25 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.10.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify 0.9.6", + "kqueue", + "libc", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "notify" version = "8.2.0" @@ -4523,11 +4625,11 @@ checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ "bitflags 2.10.0", "fsevent-sys", - "inotify", + "inotify 0.11.0", "kqueue", "libc", "log", - "mio", + "mio 1.1.0", "notify-types", "walkdir", "windows-sys 0.60.2", @@ -5533,6 +5635,17 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -6530,7 +6643,7 @@ checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ "bytes", "libc", - "mio", + "mio 1.1.0", "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", diff --git a/Cargo.toml b/Cargo.toml index 6aaf267b4..f31689a7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "cosmic-applet-status-area", "cosmic-applet-tiling", "cosmic-applet-time", + "cosmic-applet-theme", "cosmic-applet-workspaces", "cosmic-panel-button", "cosmic-applet-input-sources", diff --git a/cosmic-applet-theme/Cargo.toml b/cosmic-applet-theme/Cargo.toml new file mode 100644 index 000000000..00c9c4653 --- /dev/null +++ b/cosmic-applet-theme/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cosmic-applet-theme" +version = "0.1.0" +edition = "2024" +license = "GPL-3.0-only" + +[dependencies] +dirs = "5.0" +i18n-embed-fl.workspace = true +i18n-embed.workspace = true +libcosmic.workspace = true +rust-embed.workspace = true +tokio.workspace = true +tracing-log.workspace = true +tracing-subscriber.workspace = true +tracing.workspace = true +zbus.workspace = true +notify = "6.1" diff --git a/cosmic-applet-theme/data/com.system76.CosmicAppletTheme.desktop b/cosmic-applet-theme/data/com.system76.CosmicAppletTheme.desktop new file mode 100644 index 000000000..c185dfb87 --- /dev/null +++ b/cosmic-applet-theme/data/com.system76.CosmicAppletTheme.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] +Type=Application +Name=Theme Toggle Applet +Comment=Toggle system light/dark theme +Exec=cosmic-applet-theme +Icon=com.system76.CosmicAppletTheme-symbolic +Terminal=false +Categories=COSMIC; +Keywords=COSMIC;Iced; +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +StartupNotify=true +NoDisplay=true +X-CosmicApplet=true +X-CosmicShrinkable=true +X-CosmicHoverPopup=Auto +X-OverflowPriority=10 diff --git a/cosmic-applet-theme/data/icons/scalable/apps/com.system76.CosmicAppletTheme-symbolic.svg b/cosmic-applet-theme/data/icons/scalable/apps/com.system76.CosmicAppletTheme-symbolic.svg new file mode 100644 index 000000000..20074917f --- /dev/null +++ b/cosmic-applet-theme/data/icons/scalable/apps/com.system76.CosmicAppletTheme-symbolic.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/cosmic-applet-theme/i18n.toml b/cosmic-applet-theme/i18n.toml new file mode 100644 index 000000000..76f7c3103 --- /dev/null +++ b/cosmic-applet-theme/i18n.toml @@ -0,0 +1,4 @@ +fallback_language = "en" + +[fluent] +assets_dir = "i18n" diff --git a/cosmic-applet-theme/i18n/be/cosmic_applet_theme.ftl b/cosmic-applet-theme/i18n/be/cosmic_applet_theme.ftl new file mode 100644 index 000000000..fbbe44cfa --- /dev/null +++ b/cosmic-applet-theme/i18n/be/cosmic_applet_theme.ftl @@ -0,0 +1,4 @@ +applet-theme-title = Theme Toggle +applet-theme-description = An applet to toggle between light and dark themes. + +toggle-theme = Пераключыць тэму diff --git a/cosmic-applet-theme/i18n/en/cosmic_applet_theme.ftl b/cosmic-applet-theme/i18n/en/cosmic_applet_theme.ftl new file mode 100644 index 000000000..d5a1ec7c6 --- /dev/null +++ b/cosmic-applet-theme/i18n/en/cosmic_applet_theme.ftl @@ -0,0 +1,4 @@ +applet-theme-title = Theme Toggle +applet-theme-description = An applet to toggle between light and dark themes. + +toggle-theme = Toggle Theme diff --git a/cosmic-applet-theme/src/lib.rs b/cosmic-applet-theme/src/lib.rs new file mode 100644 index 000000000..a38fa8a72 --- /dev/null +++ b/cosmic-applet-theme/src/lib.rs @@ -0,0 +1,194 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic::{ + Element, Task, app, + cosmic_theme::Spacing, + iced::{ + Alignment, Length, + widget::{self, column, row}, + window, + }, + theme, + widget::{icon, text}, +}; +use std::{fs, path::PathBuf}; + +use crate::localize::localize; + +pub mod localize; +mod theme_subscription; + +pub fn run() -> cosmic::iced::Result { + localize(); + + cosmic::applet::run::(()) +} + +struct Theme { + core: cosmic::app::Core, + icon_name: String, + popup: Option, + is_dark: bool, + theme_file: PathBuf, +} + +#[derive(Debug, Clone)] +enum ThemeAction { + ToggleTheme, +} + +#[derive(Debug, Clone)] +enum Message { + Action(ThemeAction), + Closed(window::Id), + ThemeUpdate(theme_subscription::ThemeUpdate), +} + +impl cosmic::Application for Theme { + type Executor = cosmic::SingleThreadExecutor; + type Flags = (); + type Message = Message; + const APP_ID: &'static str = "com.system76.CosmicAppletTheme"; + + fn core(&self) -> &cosmic::app::Core { + &self.core + } + + fn core_mut(&mut self) -> &mut cosmic::app::Core { + &mut self.core + } + + fn init(core: cosmic::app::Core, _flags: ()) -> (Self, app::Task) { + let theme_file = dirs::config_dir() + .expect("Failed to find config dir") + .join("cosmic/com.system76.CosmicTheme.Mode/v1/is_dark"); + + let is_dark = fs::read_to_string(&theme_file) + .map(|s| s.trim() == "true") + .unwrap_or(true); // Default to dark mode + + // Ensure file exists + if !theme_file.exists() { + if let Some(parent) = theme_file.parent() { + fs::create_dir_all(parent).expect("Failed to create theme config directory"); + } + fs::write(&theme_file, is_dark.to_string()) + .expect("Failed to write initial theme state"); + } + + let icon_name = if is_dark { + "weather-clear-night-symbolic" + } else { + "weather-sunny-symbolic" + } + .to_string(); + + ( + Self { + core, + icon_name, + popup: Option::default(), + is_dark, + theme_file, + }, + Task::none(), + ) + } + + fn on_close_requested(&self, id: window::Id) -> Option { + Some(Message::Closed(id)) + } + + fn update(&mut self, message: Message) -> app::Task { + match message { + Message::Action(action) => match action { + ThemeAction::ToggleTheme => { + self.is_dark = !self.is_dark; + fs::write(&self.theme_file, self.is_dark.to_string()) + .expect("Failed to write theme state"); + self.icon_name = if self.is_dark { + "weather-clear-night-symbolic" + } else { + "weather-sunny-symbolic" + } + .to_string(); + Task::none() + } + }, + + Message::Closed(id) => { + if self.popup == Some(id) { + self.popup = None; + } + Task::none() + } + + Message::ThemeUpdate(update) => match update { + theme_subscription::ThemeUpdate::Changed(is_dark) => { + self.is_dark = is_dark; + self.icon_name = if is_dark { + "weather-clear-night-symbolic" + } else { + "weather-sunny-symbolic" + } + .to_string(); + Task::none() + } + }, + } + } + + fn view(&self) -> Element<'_, Message> { + self.core + .applet + .icon_button(&self.icon_name) + .on_press_down(Message::Action(ThemeAction::ToggleTheme)) + .into() + } + + fn view_window(&self, id: window::Id) -> Element<'_, Message> { + let Spacing { space_xxs, .. } = theme::active().cosmic().spacing; + + if matches!(self.popup, Some(p) if p == id) { + let toggle = cosmic::widget::button::custom( + widget::container( + row![ + icon::from_name(if self.is_dark { + "weather-sunny-symbolic" + } else { + "weather-clear-night-symbolic" + }) + .size(24) + .symbolic(true) + .icon(), + text::body(fl!("toggle-theme")), + ] + .align_y(Alignment::Center) + .spacing(space_xxs), + ) + .center(Length::Fill), + ) + .on_press(Message::Action(ThemeAction::ToggleTheme)) + .height(Length::Fixed(40.0)) + .class(theme::Button::Text); + + let content = column![toggle].align_x(Alignment::Start).padding([8, 0]); + + self.core.applet.popup_container(content).into() + } else { + widget::text("").into() + } + } + + fn style(&self) -> Option { + Some(cosmic::applet::style()) + } + + fn subscription(&self) -> cosmic::iced::Subscription { + cosmic::iced::Subscription::batch(vec![ + theme_subscription::theme_subscription(self.theme_file.clone()) + .map(Message::ThemeUpdate), + ]) + } +} diff --git a/cosmic-applet-theme/src/localize.rs b/cosmic-applet-theme/src/localize.rs new file mode 100644 index 000000000..c1fca2531 --- /dev/null +++ b/cosmic-applet-theme/src/localize.rs @@ -0,0 +1,48 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: GPL-3.0-only + +use i18n_embed::{ + DefaultLocalizer, LanguageLoader, Localizer, + fluent::{FluentLanguageLoader, fluent_language_loader}, +}; +use rust_embed::RustEmbed; +use std::sync::LazyLock; + +#[derive(RustEmbed)] +#[folder = "i18n/"] +struct Localizations; + +pub static LANGUAGE_LOADER: LazyLock = LazyLock::new(|| { + let loader: FluentLanguageLoader = fluent_language_loader!(); + + loader + .load_fallback_language(&Localizations) + .expect("Error while loading fallback language"); + + loader +}); + +#[macro_export] +macro_rules! fl { + ($message_id:literal) => {{ + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id) + }}; + + ($message_id:literal, $($args:expr),*) => {{ + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *) + }}; +} + +// Get the `Localizer` to be used for localizing this library. +pub fn localizer() -> Box { + Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations)) +} + +pub fn localize() { + let localizer = localizer(); + let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages(); + + if let Err(error) = localizer.select(&requested_languages) { + eprintln!("Error while loading language for App List {error}"); + } +} diff --git a/cosmic-applet-theme/src/main.rs b/cosmic-applet-theme/src/main.rs new file mode 100644 index 000000000..d1a3fb534 --- /dev/null +++ b/cosmic-applet-theme/src/main.rs @@ -0,0 +1,13 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: GPL-3.0-only + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn main() -> cosmic::iced::Result { + tracing_subscriber::fmt::init(); + let _ = tracing_log::LogTracer::init(); + + tracing::info!("Starting theme applet with version {VERSION}"); + + cosmic_applet_theme::run() +} diff --git a/cosmic-applet-theme/src/theme_subscription.rs b/cosmic-applet-theme/src/theme_subscription.rs new file mode 100644 index 000000000..70801a9c9 --- /dev/null +++ b/cosmic-applet-theme/src/theme_subscription.rs @@ -0,0 +1,62 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic::iced::{Subscription, futures::SinkExt, stream}; +use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; +use std::path::PathBuf; +use tokio::sync::mpsc; + +#[derive(Debug, Clone)] +pub enum ThemeUpdate { + Changed(bool), // is_dark +} + +pub fn theme_subscription(theme_file: PathBuf) -> Subscription { + Subscription::run_with_id( + "theme-file-watch", + stream::channel(1, move |mut output| async move { + let (tx, mut rx) = mpsc::channel(20); + + let mut watcher = match RecommendedWatcher::new( + move |res: Result| { + if let Ok(event) = res { + if matches!(event.kind, notify::EventKind::Modify(_)) { + let _ = tx.try_send(()); + } + } + }, + Config::default(), + ) { + Ok(watcher) => watcher, + Err(e) => { + tracing::error!("Failed to create file watcher: {}", e); + return; + } + }; + + if let Err(e) = watcher.watch(&theme_file, RecursiveMode::NonRecursive) { + tracing::error!("Failed to watch theme file: {}", e); + return; + } + + // Send initial state + if let Ok(content) = std::fs::read_to_string(&theme_file) { + let is_dark = content.trim() == "true"; + tracing::debug!("Theme subscription: initial state is_dark={}", is_dark); + let _ = output.send(ThemeUpdate::Changed(is_dark)).await; + } else { + tracing::warn!("Theme subscription: failed to read initial theme file"); + } + + while let Some(_) = rx.recv().await { + if let Ok(content) = std::fs::read_to_string(&theme_file) { + let is_dark = content.trim() == "true"; + tracing::debug!("Theme subscription: detected change, is_dark={}", is_dark); + let _ = output.send(ThemeUpdate::Changed(is_dark)).await; + } else { + tracing::warn!("Theme subscription: failed to read theme file on change"); + } + } + }), + ) +} diff --git a/cosmic-applets/Cargo.toml b/cosmic-applets/Cargo.toml index c39ad7208..cf7429134 100644 --- a/cosmic-applets/Cargo.toml +++ b/cosmic-applets/Cargo.toml @@ -19,6 +19,7 @@ cosmic-applet-tiling = { path = "../cosmic-applet-tiling" } cosmic-applet-time = { path = "../cosmic-applet-time" } cosmic-applet-workspaces = { path = "../cosmic-applet-workspaces" } cosmic-applet-input-sources = { path = "../cosmic-applet-input-sources" } +cosmic-applet-theme = { path = "../cosmic-applet-theme" } cosmic-panel-button = { path = "../cosmic-panel-button" } libcosmic.workspace = true tracing.workspace = true diff --git a/cosmic-applets/src/main.rs b/cosmic-applets/src/main.rs index f62bbf210..f5c8ee293 100644 --- a/cosmic-applets/src/main.rs +++ b/cosmic-applets/src/main.rs @@ -31,6 +31,7 @@ fn main() -> cosmic::iced::Result { "cosmic-applet-time" => cosmic_applet_time::run(), "cosmic-applet-workspaces" => cosmic_applet_workspaces::run(), "cosmic-applet-input-sources" => cosmic_applet_input_sources::run(), + "cosmic-applet-theme" => cosmic_applet_theme::run(), "cosmic-panel-button" => cosmic_panel_button::run(), _ => Ok(()), } diff --git a/justfile b/justfile index 54e110267..591baff46 100644 --- a/justfile +++ b/justfile @@ -58,7 +58,7 @@ _install_metainfo: install -Dm0644 {{metainfo-src}} {{metainfo-dst}} # Installs files into the system -install: (_install_bin 'cosmic-applets') (_link_applet 'cosmic-panel-button') (_install_applet 'com.system76.CosmicAppList' 'cosmic-app-list') (_install_default_schema 'cosmic-app-list') (_install_applet 'com.system76.CosmicAppletA11y' 'cosmic-applet-a11y') (_install_applet 'com.system76.CosmicAppletAudio' 'cosmic-applet-audio') (_install_applet 'com.system76.CosmicAppletInputSources' 'cosmic-applet-input-sources') (_install_applet 'com.system76.CosmicAppletBattery' 'cosmic-applet-battery') (_install_applet 'com.system76.CosmicAppletBluetooth' 'cosmic-applet-bluetooth') (_install_applet 'com.system76.CosmicAppletMinimize' 'cosmic-applet-minimize') (_install_applet 'com.system76.CosmicAppletNetwork' 'cosmic-applet-network') (_install_applet 'com.system76.CosmicAppletNotifications' 'cosmic-applet-notifications') (_install_applet 'com.system76.CosmicAppletPower' 'cosmic-applet-power') (_install_applet 'com.system76.CosmicAppletStatusArea' 'cosmic-applet-status-area') (_install_applet 'com.system76.CosmicAppletTiling' 'cosmic-applet-tiling') (_install_applet 'com.system76.CosmicAppletTime' 'cosmic-applet-time') (_install_applet 'com.system76.CosmicAppletWorkspaces' 'cosmic-applet-workspaces') (_install_button 'com.system76.CosmicPanelAppButton' 'cosmic-panel-app-button') (_install_button 'com.system76.CosmicPanelLauncherButton' 'cosmic-panel-launcher-button') (_install_button 'com.system76.CosmicPanelWorkspacesButton' 'cosmic-panel-workspaces-button') (_install_metainfo) +install: (_install_bin 'cosmic-applets') (_link_applet 'cosmic-panel-button') (_install_applet 'com.system76.CosmicAppList' 'cosmic-app-list') (_install_default_schema 'cosmic-app-list') (_install_applet 'com.system76.CosmicAppletA11y' 'cosmic-applet-a11y') (_install_applet 'com.system76.CosmicAppletAudio' 'cosmic-applet-audio') (_install_applet 'com.system76.CosmicAppletInputSources' 'cosmic-applet-input-sources') (_install_applet 'com.system76.CosmicAppletBattery' 'cosmic-applet-battery') (_install_applet 'com.system76.CosmicAppletBluetooth' 'cosmic-applet-bluetooth') (_install_applet 'com.system76.CosmicAppletMinimize' 'cosmic-applet-minimize') (_install_applet 'com.system76.CosmicAppletNetwork' 'cosmic-applet-network') (_install_applet 'com.system76.CosmicAppletNotifications' 'cosmic-applet-notifications') (_install_applet 'com.system76.CosmicAppletPower' 'cosmic-applet-power') (_install_applet 'com.system76.CosmicAppletStatusArea' 'cosmic-applet-status-area') (_install_applet 'com.system76.CosmicAppletTiling' 'cosmic-applet-tiling') (_install_applet 'com.system76.CosmicAppletTime' 'cosmic-applet-time') (_install_applet 'com.system76.CosmicAppletTheme' 'cosmic-applet-theme') (_install_applet 'com.system76.CosmicAppletWorkspaces' 'cosmic-applet-workspaces') (_install_button 'com.system76.CosmicPanelAppButton' 'cosmic-panel-app-button') (_install_button 'com.system76.CosmicPanelLauncherButton' 'cosmic-panel-launcher-button') (_install_button 'com.system76.CosmicPanelWorkspacesButton' 'cosmic-panel-workspaces-button') (_install_metainfo) # Vendor Cargo dependencies locally vendor: