2024-09-09 19:08:21 +02:00
|
|
|
// Copyright 2023 System76 <info@system76.com>
|
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
|
|
|
|
|
|
use config::{CosmicPanelButtonConfig, IndividualConfig, Override};
|
2025-08-12 21:38:51 +02:00
|
|
|
use cosmic::desktop::fde::{self, DesktopEntry, get_languages_from_env};
|
2024-09-09 19:08:21 +02:00
|
|
|
use cosmic::{
|
2025-08-12 21:38:51 +02:00
|
|
|
Task, app,
|
2024-09-09 19:08:21 +02:00
|
|
|
applet::{
|
|
|
|
|
Size,
|
2025-08-12 21:38:51 +02:00
|
|
|
cosmic_panel_config::{PanelAnchor, PanelSize},
|
2024-09-09 19:08:21 +02:00
|
|
|
},
|
2024-10-30 22:51:08 -04:00
|
|
|
iced::{self, Length},
|
2024-09-09 19:08:21 +02:00
|
|
|
iced_widget::row,
|
2025-03-14 13:14:51 -04:00
|
|
|
surface,
|
2025-08-12 21:38:51 +02:00
|
|
|
widget::{Id, autosize, vertical_space},
|
2024-09-09 19:08:21 +02:00
|
|
|
};
|
|
|
|
|
use cosmic_config::{Config, CosmicConfigEntry};
|
2025-08-12 19:59:56 +02:00
|
|
|
use std::{env, fs, process::Command, sync::LazyLock};
|
2024-09-09 19:08:21 +02:00
|
|
|
|
|
|
|
|
mod config;
|
|
|
|
|
|
2025-08-12 19:59:56 +02:00
|
|
|
static AUTOSIZE_MAIN_ID: LazyLock<Id> = LazyLock::new(|| Id::new("autosize-main"));
|
2024-10-30 22:51:08 -04:00
|
|
|
|
2024-09-09 19:08:21 +02:00
|
|
|
#[derive(Debug, Clone, Default)]
|
|
|
|
|
struct Desktop {
|
|
|
|
|
name: String,
|
|
|
|
|
icon: Option<String>,
|
|
|
|
|
exec: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Button {
|
|
|
|
|
core: cosmic::app::Core,
|
|
|
|
|
desktop: Desktop,
|
|
|
|
|
config: IndividualConfig,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
enum Msg {
|
|
|
|
|
Press,
|
|
|
|
|
ConfigUpdated(CosmicPanelButtonConfig),
|
2025-03-14 13:14:51 -04:00
|
|
|
Surface(surface::Action),
|
2024-09-09 19:08:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl cosmic::Application for Button {
|
|
|
|
|
type Message = Msg;
|
|
|
|
|
type Executor = cosmic::SingleThreadExecutor;
|
|
|
|
|
type Flags = Desktop;
|
|
|
|
|
const APP_ID: &'static str = "com.system76.CosmicPanelButton";
|
|
|
|
|
|
2024-10-30 22:51:08 -04:00
|
|
|
fn init(core: cosmic::app::Core, desktop: Desktop) -> (Self, app::Task<Msg>) {
|
2024-09-09 19:08:21 +02:00
|
|
|
let config = Config::new(Self::APP_ID, CosmicPanelButtonConfig::VERSION)
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|c| CosmicPanelButtonConfig::get_entry(&c).ok())
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.configs
|
|
|
|
|
.get(&core.applet.panel_type.to_string())
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
(
|
|
|
|
|
Self {
|
|
|
|
|
core,
|
|
|
|
|
desktop,
|
|
|
|
|
config,
|
|
|
|
|
},
|
2024-10-30 22:51:08 -04:00
|
|
|
Task::none(),
|
2024-09-09 19:08:21 +02:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn core(&self) -> &cosmic::app::Core {
|
|
|
|
|
&self.core
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn core_mut(&mut self) -> &mut cosmic::app::Core {
|
|
|
|
|
&mut self.core
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-30 22:51:08 -04:00
|
|
|
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
|
2024-09-09 19:08:21 +02:00
|
|
|
Some(cosmic::applet::style())
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-30 22:51:08 -04:00
|
|
|
fn update(&mut self, message: Msg) -> app::Task<Msg> {
|
2024-09-09 19:08:21 +02:00
|
|
|
match message {
|
|
|
|
|
Msg::Press => {
|
2024-10-30 22:51:08 -04:00
|
|
|
let _ = Command::new("sh")
|
|
|
|
|
.arg("-c")
|
|
|
|
|
.arg(&self.desktop.exec)
|
|
|
|
|
.spawn()
|
|
|
|
|
.unwrap();
|
2024-09-09 19:08:21 +02:00
|
|
|
}
|
|
|
|
|
Msg::ConfigUpdated(conf) => {
|
|
|
|
|
self.config = conf
|
|
|
|
|
.configs
|
|
|
|
|
.get(&self.core.applet.panel_type.to_string())
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
}
|
2025-03-14 13:14:51 -04:00
|
|
|
Msg::Surface(a) => {
|
|
|
|
|
return cosmic::task::message(cosmic::Action::Cosmic(
|
|
|
|
|
cosmic::app::Action::Surface(a),
|
|
|
|
|
));
|
|
|
|
|
}
|
2024-09-09 19:08:21 +02:00
|
|
|
}
|
2024-10-30 22:51:08 -04:00
|
|
|
Task::none()
|
2024-09-09 19:08:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn view(&self) -> cosmic::Element<Msg> {
|
|
|
|
|
// currently, panel being anchored to the left or right is a hard
|
|
|
|
|
// override for icon, later if text is updated to wrap, we may
|
|
|
|
|
// use Override::Text to override this behavior
|
2024-10-30 22:51:08 -04:00
|
|
|
autosize::autosize(
|
|
|
|
|
if self.desktop.icon.is_some()
|
|
|
|
|
&& matches!(
|
|
|
|
|
self.core.applet.anchor,
|
|
|
|
|
PanelAnchor::Left | PanelAnchor::Right
|
2024-09-09 19:08:21 +02:00
|
|
|
)
|
2024-10-30 22:51:08 -04:00
|
|
|
|| matches!(self.config.force_presentation, Some(Override::Icon))
|
|
|
|
|
|| matches!(
|
|
|
|
|
(&self.core.applet.size, &self.config.force_presentation),
|
|
|
|
|
(
|
2025-07-03 13:14:40 -06:00
|
|
|
Size::PanelSize(PanelSize::S | PanelSize::M | PanelSize::L | PanelSize::XL),
|
2024-10-30 22:51:08 -04:00
|
|
|
None
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
{
|
2025-02-27 20:32:13 -05:00
|
|
|
cosmic::Element::from(
|
2025-03-14 13:14:51 -04:00
|
|
|
self.core.applet.applet_tooltip::<Msg>(
|
2025-02-27 20:32:13 -05:00
|
|
|
self.core
|
|
|
|
|
.applet
|
|
|
|
|
.icon_button_from_handle(
|
|
|
|
|
cosmic::widget::icon::from_name(self.desktop.icon.clone().unwrap())
|
|
|
|
|
.handle(),
|
|
|
|
|
)
|
|
|
|
|
.on_press_down(Msg::Press),
|
|
|
|
|
self.desktop.name.clone(),
|
|
|
|
|
false,
|
2025-03-14 13:14:51 -04:00
|
|
|
Msg::Surface,
|
2025-04-14 09:48:59 -04:00
|
|
|
None,
|
2025-02-27 20:32:13 -05:00
|
|
|
),
|
2024-10-30 22:51:08 -04:00
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
let content = row!(
|
|
|
|
|
self.core.applet.text(&self.desktop.name),
|
|
|
|
|
vertical_space().height(Length::Fixed(
|
|
|
|
|
(self.core.applet.suggested_size(true).1
|
|
|
|
|
+ 2 * self.core.applet.suggested_padding(true))
|
|
|
|
|
as f32
|
|
|
|
|
))
|
|
|
|
|
)
|
|
|
|
|
.align_y(iced::Alignment::Center);
|
|
|
|
|
cosmic::widget::button::custom(content)
|
|
|
|
|
.padding([0, self.core.applet.suggested_padding(true)])
|
|
|
|
|
.class(cosmic::theme::Button::AppletIcon)
|
2025-02-27 20:32:13 -05:00
|
|
|
.on_press_down(Msg::Press)
|
|
|
|
|
.into()
|
|
|
|
|
},
|
2024-10-30 22:51:08 -04:00
|
|
|
AUTOSIZE_MAIN_ID.clone(),
|
|
|
|
|
)
|
2024-09-09 19:08:21 +02:00
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn subscription(&self) -> iced::Subscription<Self::Message> {
|
|
|
|
|
self.core.watch_config(Self::APP_ID).map(|u| {
|
|
|
|
|
for why in u.errors {
|
|
|
|
|
tracing::error!(why = why.to_string(), "Error watching config");
|
|
|
|
|
}
|
|
|
|
|
Msg::ConfigUpdated(u.config)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run() -> iced::Result {
|
|
|
|
|
let id = env::args()
|
|
|
|
|
.nth(1)
|
|
|
|
|
.expect("Requires desktop file id as argument.");
|
|
|
|
|
let filename = format!("{id}.desktop");
|
|
|
|
|
let mut desktop = None;
|
|
|
|
|
let locales = get_languages_from_env();
|
|
|
|
|
|
2025-04-02 17:26:07 +02:00
|
|
|
for mut path in fde::default_paths() {
|
2024-09-09 19:08:21 +02:00
|
|
|
path.push(&filename);
|
|
|
|
|
if let Ok(bytes) = fs::read_to_string(&path) {
|
|
|
|
|
if let Ok(entry) = DesktopEntry::from_str(&path, &bytes, Some(&locales)) {
|
|
|
|
|
desktop = Some(Desktop {
|
|
|
|
|
name: entry
|
|
|
|
|
.name(&locales)
|
|
|
|
|
.map(|x| x.to_string())
|
|
|
|
|
.unwrap_or_else(|| panic!("Desktop file '{filename}' doesn't have `Name`")),
|
|
|
|
|
icon: entry.icon().map(|x| x.to_string()),
|
|
|
|
|
exec: entry
|
|
|
|
|
.exec()
|
|
|
|
|
.map(|x| x.to_string())
|
|
|
|
|
.unwrap_or_else(|| panic!("Desktop file '{filename}' doesn't have `Exec`")),
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let desktop = desktop.unwrap_or_else(|| {
|
|
|
|
|
panic!("Failed to find valid desktop file '{filename}' in search paths")
|
|
|
|
|
});
|
2024-10-30 22:51:08 -04:00
|
|
|
cosmic::applet::run::<Button>(desktop)
|
2024-09-09 19:08:21 +02:00
|
|
|
}
|