wip: update libcosmic (#93)

* wip: update libcosmic

* fix: damge issue resolved by updating iced

* fix: high cpu usage by time applet and app-list

* refactor subscriptions to produce fewer events

* refactor network applet to use less cpu

* fix: text size

* refactor: i18n for audio applet

* refactor: power applet i18n setup

* fix (battery): always send profile update

* fix (battery): set toggler width to layout correctly

* fix (app-list): backoff for restarts of toplevel subscription

* fix (network): alignment

* feat: ask for comfirmation before applying power applet actions

* wip: integrate cosmic-config

* update zbus

* feat: update to use latest libcosmic

* update iced

* udpate deps

* update deps

* refactor: move applet helpers to this repo, outside of libcosmic.

this should help alleviate some dependency hell

* chore update deps

* update deps

* cleanup
This commit is contained in:
Ashley Wulber 2023-06-01 12:23:12 -04:00 committed by GitHub
parent 8b46cc209f
commit 9ebd9b511a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 2841 additions and 1681 deletions

View file

@ -0,0 +1,47 @@
// SPDX-License-Identifier: MPL-2.0-only
use i18n_embed::{
fluent::{fluent_language_loader, FluentLanguageLoader},
DefaultLocalizer, LanguageLoader, Localizer,
};
use once_cell::sync::Lazy;
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "i18n/"]
struct Localizations;
pub static LANGUAGE_LOADER: Lazy<FluentLanguageLoader> = Lazy::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<dyn Localizer> {
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);
}
}

View file

@ -1,18 +1,28 @@
use std::collections::HashMap;
use std::process;
use std::time::Duration;
use cosmic::applet::{CosmicAppletHelper, APPLET_BUTTON_THEME};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::event::wayland::{self, LayerEvent};
use cosmic::iced::event::PlatformSpecific;
use cosmic::iced::subscription::events_with;
use cosmic::iced::wayland::actions::layer_surface::SctkLayerSurfaceSettings;
use cosmic::iced::wayland::popup::{destroy_popup, get_popup};
use cosmic::iced_native::layout::Limits;
use cosmic::iced_native::widget::Space;
use cosmic::iced_runtime::core::layout::Limits;
use cosmic::iced_sctk::commands::layer_surface::{
destroy_layer_surface, get_layer_surface, Anchor, KeyboardInteractivity,
};
use cosmic::iced_widget::mouse_area;
use cosmic::widget::{button, divider, icon};
use cosmic::Renderer;
use cosmic_applet::{applet_button_theme, CosmicAppletHelper};
use cosmic::iced::Color;
use cosmic::iced::{
widget::{self, column, container, row, Row},
widget::{self, column, container, row, space::Space, text, Row},
window, Alignment, Application, Command, Length, Subscription,
};
use cosmic::iced_style::application::{self, Appearance};
use cosmic::iced_style::Color;
use cosmic::theme::{self, Svg};
use cosmic::{Element, Theme};
@ -20,9 +30,11 @@ use logind_zbus::manager::ManagerProxy;
use logind_zbus::session::{SessionProxy, SessionType};
use logind_zbus::user::UserProxy;
use nix::unistd::getuid;
use tokio::time::sleep;
use zbus::Connection;
pub mod cosmic_session;
mod localize;
pub mod session_manager;
use crate::cosmic_session::CosmicSessionProxy;
@ -39,19 +51,28 @@ struct Power {
icon_name: String,
theme: Theme,
popup: Option<window::Id>,
id_ctr: u32,
id_ctr: u128,
action_to_confirm: Option<(window::Id, PowerAction)>,
}
#[derive(Debug, Clone)]
enum Message {
#[derive(Debug, Clone, Copy)]
enum PowerAction {
Lock,
LogOut,
Suspend,
Restart,
Shutdown,
}
#[derive(Debug, Clone)]
enum Message {
Timeout(window::Id),
Action(PowerAction),
TogglePopup,
Settings,
Ignore,
Confirm,
Cancel,
Closed(window::Id),
Zbus(Result<(), zbus::Error>),
}
@ -72,26 +93,34 @@ impl Application for Power {
}
fn title(&self) -> String {
String::from("Power")
fl!("power")
}
fn theme(&self) -> Theme {
self.theme
self.theme.clone()
}
fn close_requested(&self, _id: window::Id) -> Self::Message {
Message::Ignore
fn close_requested(&self, id: window::Id) -> Self::Message {
Message::Closed(id)
}
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
<Self::Theme as application::StyleSheet>::Style::Custom(|theme| Appearance {
<Self::Theme as application::StyleSheet>::Style::Custom(Box::new(|theme| Appearance {
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
text_color: theme.cosmic().on_bg_color().into(),
})
}))
}
fn subscription(&self) -> Subscription<Message> {
Subscription::none()
events_with(|e, _status| match e {
cosmic::iced::Event::PlatformSpecific(PlatformSpecific::Wayland(
wayland::Event::Layer(LayerEvent::Unfocused, ..),
)) => Some(Message::Cancel),
// cosmic::iced::Event::PlatformSpecific(PlatformSpecific::Wayland(
// wayland::Event::Seat(wayland::SeatEvent::Leave, _),
// )) => Some(Message::Cancel),
_ => None,
})
}
fn update(&mut self, message: Message) -> Command<Message> {
@ -101,21 +130,21 @@ impl Application for Power {
destroy_popup(p)
} else {
self.id_ctr += 1;
let new_id = window::Id::new(self.id_ctr);
let new_id = window::Id(self.id_ctr);
self.popup.replace(new_id);
let mut popup_settings = self.applet_helper.get_popup_settings(
window::Id::new(0),
window::Id(0),
new_id,
None,
None,
None,
);
popup_settings.positioner.size_limits = Limits::NONE
.min_width(100)
.min_height(100)
.max_height(400)
.max_width(500);
.min_width(100.0)
.min_height(100.0)
.max_height(400.0)
.max_width(500.0);
get_popup(popup_settings)
}
}
@ -123,51 +152,117 @@ impl Application for Power {
let _ = process::Command::new("cosmic-settings").spawn();
Command::none()
}
Message::Lock => Command::perform(lock(), Message::Zbus),
Message::LogOut => Command::perform(log_out(), Message::Zbus),
Message::Suspend => Command::perform(suspend(), Message::Zbus),
Message::Restart => Command::perform(restart(), Message::Zbus),
Message::Shutdown => Command::perform(shutdown(), Message::Zbus),
Message::Action(action) => {
self.id_ctr += 1;
let id = window::Id(self.id_ctr);
self.action_to_confirm = Some((id, action));
return Command::batch(vec![
Command::perform(sleep(Duration::from_secs(60)), move |_| {
Message::Timeout(id)
}),
get_layer_surface(SctkLayerSurfaceSettings {
id,
keyboard_interactivity: KeyboardInteractivity::None,
anchor: Anchor::all(),
namespace: "dialog".into(),
size: Some((None, None)),
size_limits: Limits::NONE.min_width(1.0).min_height(1.0),
..Default::default()
}),
]);
}
Message::Zbus(result) => {
if let Err(e) = result {
eprintln!("cosmic-applet-power ERROR: '{}'", e);
}
Command::none()
}
Message::Ignore => Command::none(),
Message::Confirm => {
if let Some((id, a)) = self.action_to_confirm.take() {
Command::batch(vec![
destroy_layer_surface(id),
match a {
PowerAction::Lock => Command::perform(lock(), Message::Zbus),
PowerAction::LogOut => Command::perform(log_out(), Message::Zbus),
PowerAction::Suspend => Command::perform(suspend(), Message::Zbus),
PowerAction::Restart => Command::perform(restart(), Message::Zbus),
PowerAction::Shutdown => Command::perform(shutdown(), Message::Zbus),
},
])
} else {
Command::none()
}
}
Message::Cancel => {
if let Some((id, _)) = self.action_to_confirm.take() {
return destroy_layer_surface(id);
}
Command::none()
}
Message::Closed(id) => {
if let Some((surface_id, _)) = self.action_to_confirm {
if id == surface_id {
self.action_to_confirm = None;
return destroy_layer_surface(id);
}
}
if id == window::Id(0) {
process::exit(0);
}
Command::none()
}
Message::Timeout(id) => {
if let Some((surface_id, a)) = self.action_to_confirm {
if id == surface_id {
self.action_to_confirm = None;
return Command::batch(vec![
destroy_layer_surface(id),
match a {
PowerAction::Lock => Command::perform(lock(), Message::Zbus),
PowerAction::LogOut => Command::perform(log_out(), Message::Zbus),
PowerAction::Suspend => Command::perform(suspend(), Message::Zbus),
PowerAction::Restart => Command::perform(restart(), Message::Zbus),
PowerAction::Shutdown => {
Command::perform(shutdown(), Message::Zbus)
}
},
]);
}
}
Command::none()
}
}
}
fn view(&self, id: window::Id) -> Element<Message> {
if id == window::Id::new(0) {
self.applet_helper
.icon_button(&self.icon_name)
.on_press(Message::TogglePopup)
.into()
} else {
let settings = row_button(vec!["Settings...".into()]).on_press(Message::Settings);
if matches!(self.popup, Some(p) if p == id) {
let settings =
row_button(vec![text(fl!("settings")).size(14).into()]).on_press(Message::Settings);
let session = column![
row_button(vec![
text_icon("system-lock-screen-symbolic", 24).into(),
"Lock Screen".into(),
text(fl!("lock-screen")).size(14).into(),
Space::with_width(Length::Fill).into(),
"Super + Escape".into(),
text(fl!("lock-screen-shortcut")).size(14).into(),
])
.on_press(Message::Lock),
.on_press(Message::Action(PowerAction::Lock)),
row_button(vec![
text_icon("system-log-out-symbolic", 24).into(),
"Log Out".into(),
text(fl!("log-out")).size(14).into(),
Space::with_width(Length::Fill).into(),
"Ctrl + Alt + Delete".into(),
text(fl!("log-out-shortcut")).size(14).into(),
])
.on_press(Message::LogOut),
.on_press(Message::Action(PowerAction::LogOut)),
];
let power = row![
power_buttons("system-lock-screen-symbolic", "Suspend").on_press(Message::Suspend),
power_buttons("system-restart-symbolic", "Restart").on_press(Message::Restart),
power_buttons("system-shutdown-symbolic", "Shutdown").on_press(Message::Shutdown),
power_buttons("system-lock-screen-symbolic", fl!("suspend"))
.on_press(Message::Action(PowerAction::Suspend)),
power_buttons("system-restart-symbolic", fl!("restart"))
.on_press(Message::Action(PowerAction::Restart)),
power_buttons("system-shutdown-symbolic", fl!("shutdown"))
.on_press(Message::Action(PowerAction::Shutdown)),
]
.spacing(24)
.padding([0, 24]);
@ -188,6 +283,65 @@ impl Application for Power {
.padding([8, 0]);
self.applet_helper.popup_container(content).into()
} else if matches!(self.action_to_confirm, Some((c_id, _)) if c_id == id) {
let action = match self.action_to_confirm.as_ref().unwrap().1 {
PowerAction::Lock => "lock-screen",
PowerAction::LogOut => "log-out",
PowerAction::Suspend => "suspend",
PowerAction::Restart => "restart",
PowerAction::Shutdown => "shutdown",
};
// TODO actual countdown
let content = column![
text(fl!(
"confirm-question",
HashMap::from_iter(vec![("action", action), ("countdown", "60")])
))
.size(16),
row![
button(theme::Button::Primary)
.custom(vec![text(fl!("confirm")).size(14).into()])
.on_press(Message::Confirm),
button(theme::Button::Primary)
.custom(vec![text(fl!("cancel")).size(14).into()])
.on_press(Message::Cancel),
]
.spacing(24)
]
.align_items(Alignment::Center)
.spacing(12)
.padding(24);
mouse_area(
container(
container(content)
.style(cosmic::theme::Container::custom(|theme| {
cosmic::iced_style::container::Appearance {
text_color: Some(theme.cosmic().background.on.into()),
background: Some(
Color::from(theme.cosmic().background.base).into(),
),
border_radius: 12.0.into(),
border_width: 2.0,
border_color: theme.cosmic().bg_divider().into(),
}
}))
.width(Length::Shrink)
.height(Length::Shrink),
)
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
.width(Length::Fill)
.height(Length::Fill),
)
.on_press(Message::Cancel)
.on_right_press(Message::Cancel)
.on_middle_press(Message::Cancel)
.into()
} else {
self.applet_helper
.icon_button(&self.icon_name)
.on_press(Message::TogglePopup)
.into()
}
}
}
@ -195,7 +349,7 @@ impl Application for Power {
// ### UI Helplers
fn row_button(content: Vec<Element<Message>>) -> widget::Button<Message, Renderer> {
button(APPLET_BUTTON_THEME)
button(applet_button_theme())
.custom(vec![Row::with_children(content)
.spacing(4)
.align_items(Alignment::Center)
@ -204,14 +358,14 @@ fn row_button(content: Vec<Element<Message>>) -> widget::Button<Message, Rendere
.padding([8, 24])
}
fn power_buttons<'a>(name: &'a str, text: &'a str) -> widget::Button<'a, Message, Renderer> {
fn power_buttons<'a>(name: &'a str, msg: String) -> widget::Button<'a, Message, Renderer> {
widget::button(
column![text_icon(name, 40), text]
column![text_icon(name, 40), text(msg).size(14)]
.spacing(4)
.align_items(Alignment::Center),
)
.width(Length::Fill)
.height(Length::Units(76))
.height(Length::Fixed(76.0))
.style(theme::Button::Text)
}