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,11 +1,13 @@
mod localize;
use cosmic::iced::widget;
use cosmic::iced_native::alignment::Horizontal;
use cosmic::iced_native::layout::Limits;
use cosmic::iced::Limits;
use cosmic::iced_runtime::core::alignment::Horizontal;
use cosmic::theme::Svg;
use cosmic::applet::{CosmicAppletHelper, APPLET_BUTTON_THEME};
use cosmic::widget::{button, divider, icon};
use cosmic::Renderer;
use cosmic_applet::{applet_button_theme, CosmicAppletHelper};
use cosmic::iced::{
self,
@ -20,12 +22,16 @@ use iced::widget::container;
use iced::Color;
mod pulse;
use crate::localize::localize;
use crate::pulse::DeviceInfo;
use libpulse_binding::volume::VolumeLinear;
pub fn main() -> cosmic::iced::Result {
pretty_env_logger::init();
// Prepare i18n
localize();
let helper = CosmicAppletHelper::default();
Audio::run(helper.window_settings())
}
@ -43,7 +49,7 @@ struct Audio {
theme: Theme,
popup: Option<window::Id>,
show_media_controls_in_top_panel: bool,
id_ctr: u32,
id_ctr: u128,
}
#[derive(Debug, PartialEq, Eq)]
@ -93,7 +99,7 @@ impl Application for Audio {
}
fn theme(&self) -> Theme {
self.theme
self.theme.clone()
}
fn close_requested(&self, _id: window::Id) -> Self::Message {
@ -101,10 +107,10 @@ impl Application for Audio {
}
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 update(&mut self, message: Message) -> Command<Message> {
@ -117,21 +123,21 @@ impl Application for Audio {
conn.send(pulse::Message::UpdateConnection);
}
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_height(1)
.min_width(1)
.max_width(400)
.max_height(1080);
.min_height(1.0)
.min_width(1.0)
.max_width(400.0)
.max_height(1080.0);
if let Some(conn) = self.pulse_state.connection() {
conn.send(pulse::Message::GetDefaultSink);
@ -268,7 +274,7 @@ impl Application for Audio {
}
fn view(&self, id: window::Id) -> Element<Message> {
if id == window::Id::new(0) {
if id == window::Id(0) {
self.applet_helper
.icon_button(&self.icon_name)
.on_press(Message::TogglePopup)
@ -291,20 +297,18 @@ impl Application for Audio {
.0 * 100.0;
let audio_content = if audio_disabled {
column![text("PulseAudio Disconnected")
column![text(fl!("disconnected"))
.width(Length::Fill)
.horizontal_alignment(Horizontal::Center)
.size(24),]
} else {
column![
row![
icon("audio-volume-high-symbolic", 32)
.width(Length::Units(24))
.height(Length::Units(24))
.style(Svg::Symbolic),
icon("audio-volume-high-symbolic", 24).style(Svg::Symbolic),
slider(0.0..=100.0, out_f64, Message::SetOutputVolume)
.width(Length::FillPortion(5)),
text(format!("{}%", out_f64.round()))
.size(16)
.width(Length::FillPortion(1))
.horizontal_alignment(Horizontal::Right)
]
@ -312,13 +316,11 @@ impl Application for Audio {
.align_items(Alignment::Center)
.padding([8, 24]),
row![
icon("audio-input-microphone-symbolic", 32)
.width(Length::Units(24))
.height(Length::Units(24))
.style(Svg::Symbolic),
icon("audio-input-microphone-symbolic", 24).style(Svg::Symbolic),
slider(0.0..=100.0, in_f64, Message::SetInputVolume)
.width(Length::FillPortion(5)),
text(format!("{}%", in_f64.round()))
.size(16)
.width(Length::FillPortion(1))
.horizontal_alignment(Horizontal::Right)
]
@ -330,7 +332,7 @@ impl Application for Audio {
.width(Length::Fill),
revealer(
self.is_open == IsOpen::Output,
"Output",
fl!("output"),
match &self.current_output {
Some(output) => pretty_name(output.description.clone()),
None => String::from("No device selected"),
@ -348,10 +350,10 @@ impl Application for Audio {
),
revealer(
self.is_open == IsOpen::Input,
"Input",
fl!("input"),
match &self.current_input {
Some(input) => pretty_name(input.description.clone()),
None => String::from("No device selected"),
None => fl!("no-device"),
},
self.inputs
.clone()
@ -372,17 +374,20 @@ impl Application for Audio {
container(divider::horizontal::light())
.padding([12, 24])
.width(Length::Fill),
container(toggler(
Some("Show Media Controls on Top Panel".into()),
self.show_media_controls_in_top_panel,
Message::ToggleMediaControlsInTopPanel,
))
container(
toggler(
Some(fl!("show-media-controls")),
self.show_media_controls_in_top_panel,
Message::ToggleMediaControlsInTopPanel,
)
.text_size(14)
)
.padding([0, 24]),
container(divider::horizontal::light())
.padding([12, 24])
.width(Length::Fill),
button(APPLET_BUTTON_THEME)
.text("Sound Settings...")
button(applet_button_theme())
.custom(vec![text(fl!("sound-settings")).size(14).into()])
.padding([8, 24])
.width(Length::Fill)
]
@ -398,19 +403,19 @@ impl Application for Audio {
fn revealer(
open: bool,
title: &str,
title: String,
selected: String,
options: Vec<(String, String)>,
toggle: Message,
mut change: impl FnMut(String) -> Message + 'static,
) -> widget::Column<Message, Renderer> {
) -> widget::Column<'static, Message, Renderer> {
if open {
options.iter().fold(
column![revealer_head(open, title, selected, toggle)].width(Length::Fill),
|col, (id, name)| {
col.push(
button(APPLET_BUTTON_THEME)
.custom(vec![text(name).into()])
button(applet_button_theme())
.custom(vec![text(name).size(14).into()])
.on_press(change(id.clone()))
.width(Length::Fill)
.padding([8, 48]),
@ -424,14 +429,14 @@ fn revealer(
fn revealer_head(
_open: bool,
title: &str,
title: String,
selected: String,
toggle: Message,
) -> widget::Button<Message, Renderer> {
button(APPLET_BUTTON_THEME)
) -> widget::Button<'static, Message, Renderer> {
button(applet_button_theme())
.custom(vec![
text(title).width(Length::Fill).into(),
text(selected).into(),
text(title).width(Length::Fill).size(14).into(),
text(selected).size(10).into(),
])
.padding([8, 24])
.width(Length::Fill)

View file

@ -1,8 +1,8 @@
use cosmic::iced_native::subscription::{self, Subscription};
use std::cell::RefCell;
use std::{rc::Rc, thread};
extern crate libpulse_binding as pulse;
use cosmic::iced::{subscription, Subscription};
//use futures::channel::mpsc;
use libpulse_binding::{
callbacks::ListResult,
@ -15,71 +15,80 @@ use libpulse_binding::{
proplist::Proplist,
volume::ChannelVolumes,
};
pub fn connect() -> Subscription<Event> {
struct Connect;
subscription::unfold(
std::any::TypeId::of::<Connect>(),
State::Init,
|state| async move {
match state {
State::Init => {
let PulseHandle {
to_pulse,
from_pulse,
} = PulseHandle::new();
(
Some(Event::Init(Connection(to_pulse))),
State::Connecting(from_pulse),
)
}
// Waiting for Connection to succeed
// The GUI doesn't have to monitor this state, as it is never sent to the GUI
State::Connecting(mut from_pulse) => match from_pulse.recv().await {
Some(Message::Connected) => {
(Some(Event::Connected), State::Connected(from_pulse))
}
Some(Message::Disconnected) => {
(Some(Event::Disconnected), State::Connecting(from_pulse))
}
Some(m) => {
panic!("Unexpected message: {:?}", m);
}
None => {
panic!("Pulse Sender dropped, something has gone wrong!");
}
},
State::Connected(mut from_pulse) => {
// This is where we match messages from the pulse server to pass to the gui
match from_pulse.recv().await {
Some(Message::SetSinks(sinks)) => (
Some(Event::MessageReceived(Message::SetSinks(sinks))),
State::Connected(from_pulse),
),
Some(Message::SetSources(sources)) => (
Some(Event::MessageReceived(Message::SetSources(sources))),
State::Connected(from_pulse),
),
Some(Message::SetDefaultSink(sink)) => (
Some(Event::MessageReceived(Message::SetDefaultSink(sink))),
State::Connected(from_pulse),
),
Some(Message::SetDefaultSource(source)) => (
Some(Event::MessageReceived(Message::SetDefaultSource(source))),
State::Connected(from_pulse),
),
Some(Message::Disconnected) => {
(Some(Event::Disconnected), State::Connecting(from_pulse))
}
None => (Some(Event::Disconnected), State::Connecting(from_pulse)),
_ => (None, State::Connected(from_pulse)),
}
|mut state| async move {
loop {
let (update, new_state) = connection(state).await;
state = new_state;
if let Some(update) = update {
return (update, state);
}
}
},
)
}
async fn connection(state: State) -> (Option<Event>, State) {
match state {
State::Init => {
let PulseHandle {
to_pulse,
from_pulse,
} = PulseHandle::new();
(
Some(Event::Init(Connection(to_pulse))),
State::Connecting(from_pulse),
)
}
// Waiting for Connection to succeed
// The GUI doesn't have to monitor this state, as it is never sent to the GUI
State::Connecting(mut from_pulse) => match from_pulse.recv().await {
Some(Message::Connected) => (Some(Event::Connected), State::Connected(from_pulse)),
Some(Message::Disconnected) => {
(Some(Event::Disconnected), State::Connecting(from_pulse))
}
Some(m) => {
panic!("Unexpected message: {:?}", m);
}
None => {
panic!("Pulse Sender dropped, something has gone wrong!");
}
},
State::Connected(mut from_pulse) => {
// This is where we match messages from the pulse server to pass to the gui
match from_pulse.recv().await {
Some(Message::SetSinks(sinks)) => (
Some(Event::MessageReceived(Message::SetSinks(sinks))),
State::Connected(from_pulse),
),
Some(Message::SetSources(sources)) => (
Some(Event::MessageReceived(Message::SetSources(sources))),
State::Connected(from_pulse),
),
Some(Message::SetDefaultSink(sink)) => (
Some(Event::MessageReceived(Message::SetDefaultSink(sink))),
State::Connected(from_pulse),
),
Some(Message::SetDefaultSource(source)) => (
Some(Event::MessageReceived(Message::SetDefaultSource(source))),
State::Connected(from_pulse),
),
Some(Message::Disconnected) => {
(Some(Event::Disconnected), State::Connecting(from_pulse))
}
None => (Some(Event::Disconnected), State::Connecting(from_pulse)),
_ => (None, State::Connected(from_pulse)),
}
}
}
}
// #[derive(Debug)]
enum State {
Init,