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:
parent
8b46cc209f
commit
9ebd9b511a
48 changed files with 2841 additions and 1681 deletions
1740
Cargo.lock
generated
1740
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
|
"applet",
|
||||||
"cosmic-app-list",
|
"cosmic-app-list",
|
||||||
"cosmic-applet-audio",
|
"cosmic-applet-audio",
|
||||||
"cosmic-applet-battery",
|
"cosmic-applet-battery",
|
||||||
|
|
@ -15,4 +16,5 @@ members = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "fat"
|
lto = "thin"
|
||||||
|
# lto = "fat"
|
||||||
|
|
|
||||||
12
applet/Cargo.toml
Normal file
12
applet/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "cosmic-applet"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cosmic = { package = "libcosmic", git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["wayland", "tokio"] }
|
||||||
|
ron = { version = "0.8" }
|
||||||
|
serde = { version = "1.0" }
|
||||||
|
cosmic-panel-config = {git = "https://github.com/pop-os/cosmic-panel", rev = "11cfff0" }
|
||||||
203
applet/src/lib.rs
Normal file
203
applet/src/lib.rs
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
use cosmic::{
|
||||||
|
iced::{self, window, Limits},
|
||||||
|
iced_style, iced_widget, sctk,
|
||||||
|
theme::Button,
|
||||||
|
Renderer,
|
||||||
|
};
|
||||||
|
use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize};
|
||||||
|
use iced::{
|
||||||
|
alignment::{Horizontal, Vertical},
|
||||||
|
wayland::InitialSurface,
|
||||||
|
widget::{self, Container},
|
||||||
|
Color, Element, Length, Rectangle, Settings,
|
||||||
|
};
|
||||||
|
use iced_style::{button::StyleSheet, container::Appearance};
|
||||||
|
use iced_widget::runtime::command::platform_specific::wayland::{
|
||||||
|
popup::{SctkPopupSettings, SctkPositioner},
|
||||||
|
window::SctkWindowSettings,
|
||||||
|
};
|
||||||
|
use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity};
|
||||||
|
|
||||||
|
pub use cosmic_panel_config;
|
||||||
|
|
||||||
|
const APPLET_PADDING: u32 = 8;
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn applet_button_theme() -> Button {
|
||||||
|
Button::Custom {
|
||||||
|
active: Box::new(|t| iced_style::button::Appearance {
|
||||||
|
border_radius: 0.0,
|
||||||
|
..t.active(&Button::Text)
|
||||||
|
}),
|
||||||
|
hover: Box::new(|t| iced_style::button::Appearance {
|
||||||
|
border_radius: 0.0,
|
||||||
|
..t.hovered(&Button::Text)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CosmicAppletHelper {
|
||||||
|
pub size: Size,
|
||||||
|
pub anchor: PanelAnchor,
|
||||||
|
pub background: CosmicPanelBackground,
|
||||||
|
pub output_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Size {
|
||||||
|
PanelSize(PanelSize),
|
||||||
|
// (width, height)
|
||||||
|
Hardcoded((u16, u16)),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CosmicAppletHelper {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
size: Size::PanelSize(
|
||||||
|
std::env::var("COSMIC_PANEL_SIZE")
|
||||||
|
.ok()
|
||||||
|
.and_then(|size| ron::from_str(size.as_str()).ok())
|
||||||
|
.unwrap_or(PanelSize::S),
|
||||||
|
),
|
||||||
|
anchor: std::env::var("COSMIC_PANEL_ANCHOR")
|
||||||
|
.ok()
|
||||||
|
.and_then(|size| ron::from_str(size.as_str()).ok())
|
||||||
|
.unwrap_or(PanelAnchor::Top),
|
||||||
|
background: std::env::var("COSMIC_PANEL_BACKGROUND")
|
||||||
|
.ok()
|
||||||
|
.and_then(|size| ron::from_str(size.as_str()).ok())
|
||||||
|
.unwrap_or(CosmicPanelBackground::ThemeDefault),
|
||||||
|
output_name: std::env::var("COSMIC_PANEL_OUTPUT").unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CosmicAppletHelper {
|
||||||
|
#[must_use]
|
||||||
|
pub fn suggested_size(&self) -> (u16, u16) {
|
||||||
|
match &self.size {
|
||||||
|
Size::PanelSize(size) => match size {
|
||||||
|
PanelSize::XL => (64, 64),
|
||||||
|
PanelSize::L => (36, 36),
|
||||||
|
PanelSize::M => (24, 24),
|
||||||
|
PanelSize::S => (16, 16),
|
||||||
|
PanelSize::XS => (12, 12),
|
||||||
|
},
|
||||||
|
Size::Hardcoded((width, height)) => (*width, *height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the default window size. Helper for application init with hardcoded size.
|
||||||
|
pub fn window_size(&mut self, width: u16, height: u16) {
|
||||||
|
self.size = Size::Hardcoded((width, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn window_settings<F: Default>(&self) -> Settings<F> {
|
||||||
|
self.window_settings_with_flags(F::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
pub fn window_settings_with_flags<F>(&self, flags: F) -> Settings<F> {
|
||||||
|
let (width, height) = self.suggested_size();
|
||||||
|
let width = u32::from(width);
|
||||||
|
let height = u32::from(height);
|
||||||
|
Settings {
|
||||||
|
initial_surface: InitialSurface::XdgWindow(SctkWindowSettings {
|
||||||
|
size: (width + APPLET_PADDING * 2, height + APPLET_PADDING * 2),
|
||||||
|
size_limits: Limits::NONE
|
||||||
|
.min_height(height as f32 + APPLET_PADDING as f32 * 2.0)
|
||||||
|
.max_height(height as f32 + APPLET_PADDING as f32 * 2.0)
|
||||||
|
.min_width(width as f32 + APPLET_PADDING as f32 * 2.0)
|
||||||
|
.max_width(width as f32 + APPLET_PADDING as f32 * 2.0),
|
||||||
|
resizable: None,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..cosmic::settings_with_flags(flags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn icon_button<'a, Message: 'static>(
|
||||||
|
&self,
|
||||||
|
icon_name: &'a str,
|
||||||
|
) -> widget::Button<'a, Message, Renderer> {
|
||||||
|
cosmic::widget::button(cosmic::theme::Button::Text)
|
||||||
|
.icon(
|
||||||
|
cosmic::theme::Svg::Symbolic,
|
||||||
|
icon_name,
|
||||||
|
self.suggested_size().0,
|
||||||
|
)
|
||||||
|
.padding(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO popup container which tracks the size of itself and requests the popup to resize to match
|
||||||
|
pub fn popup_container<'a, Message: 'static>(
|
||||||
|
&self,
|
||||||
|
content: impl Into<Element<'a, Message, Renderer>>,
|
||||||
|
) -> Container<'a, Message, Renderer> {
|
||||||
|
let (vertical_align, horizontal_align) = match self.anchor {
|
||||||
|
PanelAnchor::Left => (Vertical::Center, Horizontal::Left),
|
||||||
|
PanelAnchor::Right => (Vertical::Center, Horizontal::Right),
|
||||||
|
PanelAnchor::Top => (Vertical::Top, Horizontal::Center),
|
||||||
|
PanelAnchor::Bottom => (Vertical::Bottom, Horizontal::Center),
|
||||||
|
};
|
||||||
|
|
||||||
|
Container::<Message, Renderer>::new(Container::<Message, Renderer>::new(content).style(
|
||||||
|
cosmic::theme::Container::custom(|theme| 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: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.height(Length::Shrink)
|
||||||
|
.align_x(horizontal_align)
|
||||||
|
.align_y(vertical_align)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::cast_possible_wrap)]
|
||||||
|
pub fn get_popup_settings(
|
||||||
|
&self,
|
||||||
|
parent: window::Id,
|
||||||
|
id: window::Id,
|
||||||
|
size: Option<(u32, u32)>,
|
||||||
|
width_padding: Option<i32>,
|
||||||
|
height_padding: Option<i32>,
|
||||||
|
) -> SctkPopupSettings {
|
||||||
|
let (width, height) = self.suggested_size();
|
||||||
|
let pixel_offset = 8;
|
||||||
|
let (offset, anchor, gravity) = match self.anchor {
|
||||||
|
PanelAnchor::Left => ((pixel_offset, 0), Anchor::Right, Gravity::Right),
|
||||||
|
PanelAnchor::Right => ((-pixel_offset, 0), Anchor::Left, Gravity::Left),
|
||||||
|
PanelAnchor::Top => ((0, pixel_offset), Anchor::Bottom, Gravity::Bottom),
|
||||||
|
PanelAnchor::Bottom => ((0, -pixel_offset), Anchor::Top, Gravity::Top),
|
||||||
|
};
|
||||||
|
SctkPopupSettings {
|
||||||
|
parent,
|
||||||
|
id,
|
||||||
|
positioner: SctkPositioner {
|
||||||
|
anchor,
|
||||||
|
gravity,
|
||||||
|
offset,
|
||||||
|
size,
|
||||||
|
anchor_rect: Rectangle {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: width_padding.unwrap_or(APPLET_PADDING as i32) * 2 + i32::from(width),
|
||||||
|
height: height_padding.unwrap_or(APPLET_PADDING as i32) * 2 + i32::from(height),
|
||||||
|
},
|
||||||
|
reactive: true,
|
||||||
|
constraint_adjustment: 15, // slide_y, slide_x, flip_x, flip_y
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
parent_size: None,
|
||||||
|
grab: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,16 +5,17 @@ edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit" }
|
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "f0cfe09" }
|
||||||
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = ["client"] }
|
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = ["client"], rev = "f0cfe09" }
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["wayland", "applet", "tokio"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["wayland", "tokio"] }
|
||||||
# libcosmic = { path = "../../libcosmic", default-features = false, features = ["wayland", "applet", "tokio"] }
|
cosmic-applet = { path = "../applet" }
|
||||||
|
# libcosmic = { path = "../../libcosmic", default-features = false, features = ["wayland", "tokio"] }
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
once_cell = "1.9"
|
once_cell = "1.9"
|
||||||
xdg = "2.4"
|
xdg = "2.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.5"
|
||||||
calloop = "0.10"
|
calloop = "0.10"
|
||||||
nix = "0.26"
|
nix = "0.26"
|
||||||
shlex = "1.1.0"
|
shlex = "1.1.0"
|
||||||
|
|
@ -29,3 +30,5 @@ i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester
|
||||||
i18n-embed-fl = "0.6"
|
i18n-embed-fl = "0.6"
|
||||||
rust-embed = "6.3"
|
rust-embed = "6.3"
|
||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
|
rust-embed-utils = "7.5.0"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::config::AppListConfig;
|
use crate::config::AppListConfig;
|
||||||
|
use crate::config::APP_ID;
|
||||||
use crate::fl;
|
use crate::fl;
|
||||||
use crate::toplevel_subscription::toplevel_subscription;
|
use crate::toplevel_subscription::toplevel_subscription;
|
||||||
use crate::toplevel_subscription::ToplevelRequest;
|
use crate::toplevel_subscription::ToplevelRequest;
|
||||||
|
|
@ -13,46 +9,56 @@ use calloop::channel::Sender;
|
||||||
use cctk::toplevel_info::ToplevelInfo;
|
use cctk::toplevel_info::ToplevelInfo;
|
||||||
use cctk::wayland_client::protocol::wl_data_device_manager::DndAction;
|
use cctk::wayland_client::protocol::wl_data_device_manager::DndAction;
|
||||||
use cctk::wayland_client::protocol::wl_seat::WlSeat;
|
use cctk::wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use cosmic::applet::cosmic_panel_config::PanelAnchor;
|
use cosmic::cosmic_config;
|
||||||
use cosmic::applet::CosmicAppletHelper;
|
use cosmic::cosmic_config::Config;
|
||||||
use cosmic::iced;
|
use cosmic::iced;
|
||||||
|
use cosmic::iced::subscription::events_with;
|
||||||
use cosmic::iced::wayland::actions::data_device::DataFromMimeType;
|
use cosmic::iced::wayland::actions::data_device::DataFromMimeType;
|
||||||
use cosmic::iced::wayland::actions::data_device::DndIcon;
|
use cosmic::iced::wayland::actions::data_device::DndIcon;
|
||||||
use cosmic::iced::wayland::actions::window::SctkWindowSettings;
|
use cosmic::iced::wayland::actions::window::SctkWindowSettings;
|
||||||
use cosmic::iced::wayland::popup::destroy_popup;
|
use cosmic::iced::wayland::popup::destroy_popup;
|
||||||
use cosmic::iced::wayland::popup::get_popup;
|
use cosmic::iced::wayland::popup::get_popup;
|
||||||
use cosmic::iced::widget::{column, dnd_source, mouse_listener, row, text, Column, Row};
|
use cosmic::iced::widget::dnd_listener;
|
||||||
|
use cosmic::iced::widget::vertical_rule;
|
||||||
|
use cosmic::iced::widget::vertical_space;
|
||||||
|
use cosmic::iced::widget::{column, dnd_source, mouse_area, row, Column, Row};
|
||||||
|
use cosmic::iced::Color;
|
||||||
|
use cosmic::iced::Limits;
|
||||||
use cosmic::iced::Settings;
|
use cosmic::iced::Settings;
|
||||||
use cosmic::iced::{window, Application, Command, Subscription};
|
use cosmic::iced::{window, Application, Command, Subscription};
|
||||||
use cosmic::iced_native as native;
|
use cosmic::iced_runtime::core::alignment::Horizontal;
|
||||||
use cosmic::iced_native::alignment::Horizontal;
|
use cosmic::iced_runtime::core::event;
|
||||||
use cosmic::iced_native::subscription::events_with;
|
|
||||||
use cosmic::iced_native::widget::vertical_space;
|
|
||||||
use cosmic::iced_sctk::commands::data_device::accept_mime_type;
|
use cosmic::iced_sctk::commands::data_device::accept_mime_type;
|
||||||
use cosmic::iced_sctk::commands::data_device::finish_dnd;
|
use cosmic::iced_sctk::commands::data_device::finish_dnd;
|
||||||
use cosmic::iced_sctk::commands::data_device::request_dnd_data;
|
use cosmic::iced_sctk::commands::data_device::request_dnd_data;
|
||||||
use cosmic::iced_sctk::commands::data_device::set_actions;
|
use cosmic::iced_sctk::commands::data_device::set_actions;
|
||||||
use cosmic::iced_sctk::commands::data_device::start_drag;
|
use cosmic::iced_sctk::commands::data_device::start_drag;
|
||||||
use cosmic::iced_sctk::layout::Limits;
|
|
||||||
use cosmic::iced_sctk::settings::InitialSurface;
|
use cosmic::iced_sctk::settings::InitialSurface;
|
||||||
use cosmic::iced_sctk::widget::dnd_listener;
|
|
||||||
use cosmic::iced_sctk::widget::vertical_rule;
|
|
||||||
use cosmic::iced_style::application::{self, Appearance};
|
use cosmic::iced_style::application::{self, Appearance};
|
||||||
use cosmic::iced_style::Color;
|
|
||||||
use cosmic::theme::Button;
|
use cosmic::theme::Button;
|
||||||
use cosmic::widget::divider;
|
use cosmic::widget::divider;
|
||||||
use cosmic::widget::rectangle_tracker::rectangle_tracker_subscription;
|
use cosmic::widget::rectangle_tracker::rectangle_tracker_subscription;
|
||||||
use cosmic::widget::rectangle_tracker::RectangleTracker;
|
use cosmic::widget::rectangle_tracker::RectangleTracker;
|
||||||
use cosmic::widget::rectangle_tracker::RectangleUpdate;
|
use cosmic::widget::rectangle_tracker::RectangleUpdate;
|
||||||
use cosmic::{Element, Theme};
|
use cosmic::{Element, Theme};
|
||||||
|
use cosmic_applet::cosmic_panel_config::PanelAnchor;
|
||||||
|
use cosmic_applet::CosmicAppletHelper;
|
||||||
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
|
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
|
||||||
use freedesktop_desktop_entry::DesktopEntry;
|
use freedesktop_desktop_entry::DesktopEntry;
|
||||||
|
use futures::future::pending;
|
||||||
use iced::widget::container;
|
use iced::widget::container;
|
||||||
use iced::Alignment;
|
use iced::Alignment;
|
||||||
use iced::Background;
|
use iced::Background;
|
||||||
use iced::Length;
|
use iced::Length;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use native::event;
|
use rand::{thread_rng, Rng};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time::sleep;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
static MIME_TYPE: &str = "text/uri-list";
|
static MIME_TYPE: &str = "text/uri-list";
|
||||||
|
|
@ -71,15 +77,12 @@ pub fn run() -> cosmic::iced::Result {
|
||||||
|
|
||||||
CosmicAppList::run(Settings {
|
CosmicAppList::run(Settings {
|
||||||
initial_surface: InitialSurface::XdgWindow(SctkWindowSettings {
|
initial_surface: InitialSurface::XdgWindow(SctkWindowSettings {
|
||||||
iced_settings: cosmic::iced_native::window::Settings {
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
autosize: true,
|
autosize: true,
|
||||||
size_limits: Limits::NONE
|
size_limits: Limits::NONE
|
||||||
.min_height(1)
|
.min_height(1.0)
|
||||||
.min_width(1)
|
.min_width(1.0)
|
||||||
.max_height(h)
|
.max_height(h as f32)
|
||||||
.max_width(w),
|
.max_width(w as f32),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -144,19 +147,19 @@ impl DockItem {
|
||||||
let dots = (0..toplevels.len())
|
let dots = (0..toplevels.len())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
container(vertical_space(Length::Units(0)))
|
container(vertical_space(Length::Fixed(0.0)))
|
||||||
.padding(dot_radius)
|
.padding(dot_radius)
|
||||||
.style(<<CosmicAppList as cosmic::iced::Application>::Theme as container::StyleSheet>::Style::Custom(
|
.style(<<CosmicAppList as cosmic::iced::Application>::Theme as container::StyleSheet>::Style::Custom(Box::new(
|
||||||
|theme| container::Appearance {
|
|theme| container::Appearance {
|
||||||
text_color: Some(Color::TRANSPARENT),
|
text_color: Some(Color::TRANSPARENT),
|
||||||
background: Some(Background::Color(
|
background: Some(Background::Color(
|
||||||
theme.cosmic().on_bg_color().into(),
|
theme.cosmic().on_bg_color().into(),
|
||||||
)),
|
)),
|
||||||
border_radius: 4.0,
|
border_radius: 4.0.into(),
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
))
|
)))
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
|
@ -179,12 +182,12 @@ impl DockItem {
|
||||||
.into(),
|
.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut icon_button = cosmic::widget::button(Button::Text)
|
let icon_button = cosmic::widget::button(Button::Text)
|
||||||
.custom(vec![icon_wrapper])
|
.custom(vec![icon_wrapper])
|
||||||
.padding(8);
|
.padding(8);
|
||||||
let icon_button = if interaction_enabled {
|
let icon_button = if interaction_enabled {
|
||||||
dnd_source(
|
dnd_source(
|
||||||
mouse_listener(
|
mouse_area(
|
||||||
icon_button
|
icon_button
|
||||||
.on_press(
|
.on_press(
|
||||||
toplevels
|
toplevels
|
||||||
|
|
@ -222,7 +225,7 @@ struct DndOffer {
|
||||||
struct CosmicAppList {
|
struct CosmicAppList {
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
popup: Option<(window::Id, DockItem)>,
|
popup: Option<(window::Id, DockItem)>,
|
||||||
surface_id_ctr: u32,
|
surface_id_ctr: u128,
|
||||||
subscription_ctr: u32,
|
subscription_ctr: u32,
|
||||||
item_ctr: u32,
|
item_ctr: u32,
|
||||||
active_list: Vec<DockItem>,
|
active_list: Vec<DockItem>,
|
||||||
|
|
@ -262,6 +265,8 @@ enum Message {
|
||||||
DndData(PathBuf),
|
DndData(PathBuf),
|
||||||
StartListeningForDnd,
|
StartListeningForDnd,
|
||||||
StopListeningForDnd,
|
StopListeningForDnd,
|
||||||
|
IncrementSubscriptionCtr,
|
||||||
|
ConfigUpdated(AppListConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
@ -363,22 +368,20 @@ impl Application for CosmicAppList {
|
||||||
|
|
||||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||||
let config = config::AppListConfig::load().unwrap_or_default();
|
let config = config::AppListConfig::load().unwrap_or_default();
|
||||||
let mut favorite_ctr = 0;
|
let mut self_ = CosmicAppList {
|
||||||
let self_ = CosmicAppList {
|
|
||||||
favorite_list: desktop_info_for_app_ids(config.favorites.clone())
|
favorite_list: desktop_info_for_app_ids(config.favorites.clone())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|e| {
|
.enumerate()
|
||||||
favorite_ctr += 1;
|
.map(|(favorite_ctr, e)| DockItem {
|
||||||
DockItem {
|
id: favorite_ctr as u32,
|
||||||
id: favorite_ctr,
|
toplevels: Default::default(),
|
||||||
toplevels: Default::default(),
|
desktop_info: e,
|
||||||
desktop_info: e,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
config,
|
config,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
self_.item_ctr = self_.favorite_list.len() as u32;
|
||||||
|
|
||||||
(self_, Command::none())
|
(self_, Command::none())
|
||||||
}
|
}
|
||||||
|
|
@ -405,11 +408,11 @@ impl Application for CosmicAppList {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.surface_id_ctr += 1;
|
self.surface_id_ctr += 1;
|
||||||
let new_id = window::Id::new(self.surface_id_ctr);
|
let new_id = window::Id(self.surface_id_ctr);
|
||||||
self.popup = Some((new_id, toplevel_group.clone()));
|
self.popup = Some((new_id, toplevel_group.clone()));
|
||||||
|
|
||||||
let mut popup_settings = self.applet_helper.get_popup_settings(
|
let mut popup_settings = self.applet_helper.get_popup_settings(
|
||||||
window::Id::new(0),
|
window::Id(0),
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
@ -440,13 +443,16 @@ impl Application for CosmicAppList {
|
||||||
self.favorite_list.push(entry);
|
self.favorite_list.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.config.add_favorite(id);
|
self.config
|
||||||
|
.add_favorite(id, &Config::new(APP_ID, 1).unwrap());
|
||||||
if let Some((popup_id, _toplevel)) = self.popup.take() {
|
if let Some((popup_id, _toplevel)) = self.popup.take() {
|
||||||
return destroy_popup(popup_id);
|
return destroy_popup(popup_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::UnFavorite(id) => {
|
Message::UnFavorite(id) => {
|
||||||
let _ = self.config.remove_favorite(id.clone());
|
let _ = self
|
||||||
|
.config
|
||||||
|
.remove_favorite(id.clone(), &Config::new(APP_ID, 1).unwrap());
|
||||||
if let Some(i) = self
|
if let Some(i) = self
|
||||||
.favorite_list
|
.favorite_list
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -506,7 +512,10 @@ impl Application for CosmicAppList {
|
||||||
.position(|t| t.desktop_info.id == id)
|
.position(|t| t.desktop_info.id == id)
|
||||||
{
|
{
|
||||||
let t = self.favorite_list.remove(pos);
|
let t = self.favorite_list.remove(pos);
|
||||||
let _ = self.config.remove_favorite(t.desktop_info.id.clone());
|
let _ = self.config.remove_favorite(
|
||||||
|
t.desktop_info.id.clone(),
|
||||||
|
&Config::new(APP_ID, 1).unwrap(),
|
||||||
|
);
|
||||||
Some((true, t))
|
Some((true, t))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -514,7 +523,7 @@ impl Application for CosmicAppList {
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
self.surface_id_ctr += 1;
|
self.surface_id_ctr += 1;
|
||||||
let icon_id = window::Id::new(self.surface_id_ctr);
|
let icon_id = window::Id(self.surface_id_ctr);
|
||||||
self.dnd_source = Some((icon_id, toplevel_group.clone(), DndAction::empty()));
|
self.dnd_source = Some((icon_id, toplevel_group.clone(), DndAction::empty()));
|
||||||
return start_drag(
|
return start_drag(
|
||||||
vec![MIME_TYPE.to_string()],
|
vec![MIME_TYPE.to_string()],
|
||||||
|
|
@ -523,7 +532,7 @@ impl Application for CosmicAppList {
|
||||||
} else {
|
} else {
|
||||||
DndAction::Copy
|
DndAction::Copy
|
||||||
},
|
},
|
||||||
window::Id::new(0),
|
window::Id(0),
|
||||||
Some(DndIcon::Custom(icon_id)),
|
Some(DndIcon::Custom(icon_id)),
|
||||||
Box::new(toplevel_group.clone()),
|
Box::new(toplevel_group.clone()),
|
||||||
);
|
);
|
||||||
|
|
@ -629,7 +638,10 @@ impl Application for CosmicAppList {
|
||||||
.and_then(|o| o.dock_item.map(|i| (i, o.preview_index)))
|
.and_then(|o| o.dock_item.map(|i| (i, o.preview_index)))
|
||||||
{
|
{
|
||||||
self.item_ctr += 1;
|
self.item_ctr += 1;
|
||||||
let _ = self.config.add_favorite(dock_item.desktop_info.id.clone());
|
let _ = self.config.add_favorite(
|
||||||
|
dock_item.desktop_info.id.clone(),
|
||||||
|
&Config::new(APP_ID, 1).unwrap(),
|
||||||
|
);
|
||||||
if let Some((pos, is_favorite)) = self
|
if let Some((pos, is_favorite)) = self
|
||||||
.active_list
|
.active_list
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -689,11 +701,26 @@ impl Application for CosmicAppList {
|
||||||
self.toplevel_sender.replace(tx);
|
self.toplevel_sender.replace(tx);
|
||||||
}
|
}
|
||||||
ToplevelUpdate::Finished => {
|
ToplevelUpdate::Finished => {
|
||||||
self.subscription_ctr += 1;
|
|
||||||
for t in &mut self.favorite_list {
|
for t in &mut self.favorite_list {
|
||||||
t.toplevels.clear();
|
t.toplevels.clear();
|
||||||
}
|
}
|
||||||
self.active_list.clear();
|
self.active_list.clear();
|
||||||
|
let subscription_ctr = self.subscription_ctr;
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let rand_d = rng.gen_range(0..100);
|
||||||
|
return Command::perform(
|
||||||
|
async move {
|
||||||
|
if let Some(millis) = 2u64
|
||||||
|
.checked_pow(subscription_ctr)
|
||||||
|
.and_then(|d| d.checked_add(rand_d))
|
||||||
|
{
|
||||||
|
sleep(Duration::from_millis(millis)).await;
|
||||||
|
} else {
|
||||||
|
pending::<()>().await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|_| Message::IncrementSubscriptionCtr,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ToplevelUpdate::RemoveToplevel(handle) => {
|
ToplevelUpdate::RemoveToplevel(handle) => {
|
||||||
for t in self
|
for t in self
|
||||||
|
|
@ -765,6 +792,49 @@ impl Application for CosmicAppList {
|
||||||
Message::StopListeningForDnd => {
|
Message::StopListeningForDnd => {
|
||||||
self.is_listening_for_dnd = false;
|
self.is_listening_for_dnd = false;
|
||||||
}
|
}
|
||||||
|
Message::IncrementSubscriptionCtr => {
|
||||||
|
self.subscription_ctr += 1;
|
||||||
|
}
|
||||||
|
Message::ConfigUpdated(config) => {
|
||||||
|
self.config = config;
|
||||||
|
|
||||||
|
let mut new_list: Vec<_> = desktop_info_for_app_ids(self.config.favorites.clone())
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| {
|
||||||
|
self.item_ctr += 1;
|
||||||
|
|
||||||
|
DockItem {
|
||||||
|
id: self.item_ctr,
|
||||||
|
toplevels: Default::default(),
|
||||||
|
desktop_info: e,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for item in &mut new_list {
|
||||||
|
if let Some(old_item) = self
|
||||||
|
.favorite_list
|
||||||
|
.iter()
|
||||||
|
.position(|i| i.desktop_info.id == item.desktop_info.id)
|
||||||
|
{
|
||||||
|
let old_item = self.favorite_list.swap_remove(old_item);
|
||||||
|
*item = old_item;
|
||||||
|
} else if let Some(old_item) = self
|
||||||
|
.active_list
|
||||||
|
.iter()
|
||||||
|
.position(|i| i.desktop_info.id == item.desktop_info.id)
|
||||||
|
{
|
||||||
|
let old_item = self.active_list.remove(old_item);
|
||||||
|
*item = old_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in self.favorite_list.drain(..) {
|
||||||
|
self.active_list.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.favorite_list = new_list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
@ -970,7 +1040,7 @@ impl Application for CosmicAppList {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
if self.popup.is_some() {
|
if self.popup.is_some() {
|
||||||
mouse_listener(content)
|
mouse_area(content)
|
||||||
.on_right_release(Message::ClosePopup)
|
.on_right_release(Message::ClosePopup)
|
||||||
.on_press(Message::ClosePopup)
|
.on_press(Message::ClosePopup)
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -981,48 +1051,58 @@ impl Application for CosmicAppList {
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
Subscription::batch(vec![
|
Subscription::batch(vec![
|
||||||
toplevel_subscription(self.subscription_ctr).map(|(_, event)| Message::Toplevel(event)),
|
toplevel_subscription(self.subscription_ctr).map(|e| Message::Toplevel(e.1)),
|
||||||
events_with(|e, _| match e {
|
events_with(|e, _| match e {
|
||||||
native::Event::PlatformSpecific(event::PlatformSpecific::Wayland(
|
cosmic::iced_runtime::core::Event::PlatformSpecific(
|
||||||
event::wayland::Event::Seat(e, seat),
|
event::PlatformSpecific::Wayland(event::wayland::Event::Seat(e, seat)),
|
||||||
)) => match e {
|
) => match e {
|
||||||
event::wayland::SeatEvent::Enter => Some(Message::NewSeat(seat)),
|
event::wayland::SeatEvent::Enter => Some(Message::NewSeat(seat)),
|
||||||
event::wayland::SeatEvent::Leave => Some(Message::RemovedSeat(seat)),
|
event::wayland::SeatEvent::Leave => Some(Message::RemovedSeat(seat)),
|
||||||
},
|
},
|
||||||
// XXX Must be done to catch a finished drag after the source is removed
|
// XXX Must be done to catch a finished drag after the source is removed
|
||||||
// (for now, the source is removed when the drag starts)
|
// (for now, the source is removed when the drag starts)
|
||||||
native::Event::PlatformSpecific(event::PlatformSpecific::Wayland(
|
cosmic::iced_runtime::core::Event::PlatformSpecific(
|
||||||
event::wayland::Event::DataSource(
|
event::PlatformSpecific::Wayland(event::wayland::Event::DataSource(
|
||||||
event::wayland::DataSourceEvent::DndFinished
|
event::wayland::DataSourceEvent::DndFinished
|
||||||
| event::wayland::DataSourceEvent::Cancelled,
|
| event::wayland::DataSourceEvent::Cancelled,
|
||||||
),
|
)),
|
||||||
)) => Some(Message::DragFinished),
|
) => Some(Message::DragFinished),
|
||||||
native::Event::PlatformSpecific(event::PlatformSpecific::Wayland(
|
cosmic::iced_runtime::core::Event::PlatformSpecific(
|
||||||
event::wayland::Event::DndOffer(event::wayland::DndOfferEvent::Enter {
|
event::PlatformSpecific::Wayland(event::wayland::Event::DndOffer(
|
||||||
mime_types,
|
event::wayland::DndOfferEvent::Enter { mime_types, .. },
|
||||||
..
|
)),
|
||||||
}),
|
) => {
|
||||||
)) => {
|
|
||||||
if mime_types.iter().any(|m| m == MIME_TYPE) {
|
if mime_types.iter().any(|m| m == MIME_TYPE) {
|
||||||
Some(Message::StartListeningForDnd)
|
Some(Message::StartListeningForDnd)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
native::Event::PlatformSpecific(event::PlatformSpecific::Wayland(
|
cosmic::iced_runtime::core::Event::PlatformSpecific(
|
||||||
event::wayland::Event::DndOffer(
|
event::PlatformSpecific::Wayland(event::wayland::Event::DndOffer(
|
||||||
event::wayland::DndOfferEvent::Leave
|
event::wayland::DndOfferEvent::Leave
|
||||||
| event::wayland::DndOfferEvent::DropPerformed,
|
| event::wayland::DndOfferEvent::DropPerformed,
|
||||||
),
|
)),
|
||||||
)) => Some(Message::StopListeningForDnd),
|
) => Some(Message::StopListeningForDnd),
|
||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
rectangle_tracker_subscription(0).map(|(_, update)| Message::Rectangle(update)),
|
rectangle_tracker_subscription(0).map(|update| Message::Rectangle(update.1)),
|
||||||
|
cosmic_config::config_subscription(0, Cow::from(APP_ID), 1).map(|(_, config)| {
|
||||||
|
match config {
|
||||||
|
Ok(config) => Message::ConfigUpdated(config),
|
||||||
|
Err((errors, config)) => {
|
||||||
|
for error in errors {
|
||||||
|
log::error!("{:?}", error);
|
||||||
|
}
|
||||||
|
Message::ConfigUpdated(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
||||||
|
|
@ -1030,9 +1110,9 @@ impl Application for CosmicAppList {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
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),
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
text_color: theme.cosmic().on_bg_color().into(),
|
text_color: theme.cosmic().on_bg_color().into(),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,23 @@
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
|
use cosmic::cosmic_config::cosmic_config_derive::CosmicConfigEntry;
|
||||||
|
use cosmic::cosmic_config::{self, Config, ConfigGet, ConfigSet, CosmicConfigEntry};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
|
|
||||||
pub const APP_ID: &str = "com.system76.CosmicAppList";
|
pub const APP_ID: &str = "com.system76.CosmicAppList";
|
||||||
pub const VERSION: &str = "0.1.0";
|
pub const VERSION: &str = "0.1.0";
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
|
||||||
pub enum TopLevelFilter {
|
pub enum TopLevelFilter {
|
||||||
#[default]
|
#[default]
|
||||||
ActiveWorkspace,
|
ActiveWorkspace,
|
||||||
ConfiguredOutput,
|
ConfiguredOutput,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq, CosmicConfigEntry)]
|
||||||
pub struct AppListConfig {
|
pub struct AppListConfig {
|
||||||
pub filter_top_levels: Option<TopLevelFilter>,
|
pub filter_top_levels: Option<TopLevelFilter>,
|
||||||
pub favorites: Vec<String>,
|
pub favorites: Vec<String>,
|
||||||
|
|
@ -42,26 +44,17 @@ impl AppListConfig {
|
||||||
.map_err(|err| anyhow!("Failed to parse config file: {}", err))
|
.map_err(|err| anyhow!("Failed to parse config file: {}", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_favorite(&mut self, id: String) -> anyhow::Result<()> {
|
pub fn add_favorite(&mut self, id: String, config: &Config) {
|
||||||
if !self.favorites.contains(&id) {
|
if !self.favorites.contains(&id) {
|
||||||
self.favorites.push(id);
|
self.favorites.push(id);
|
||||||
|
let _ = self.write_entry(&config);
|
||||||
}
|
}
|
||||||
self.save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_favorite(&mut self, id: String) -> anyhow::Result<()> {
|
pub fn remove_favorite(&mut self, id: String, config: &Config) {
|
||||||
self.favorites.retain(|e| e != &id);
|
if let Some(pos) = self.favorites.iter().position(|e| e == &id) {
|
||||||
self.save()
|
self.favorites.remove(pos);
|
||||||
}
|
let _ = self.write_entry(&config);
|
||||||
|
}
|
||||||
// TODO async?
|
|
||||||
pub fn save(&self) -> anyhow::Result<()> {
|
|
||||||
let bd = BaseDirectories::new()?;
|
|
||||||
let mut relative_path = PathBuf::from(APP_ID);
|
|
||||||
relative_path.push("config.ron");
|
|
||||||
let config_path = bd.place_config_file(relative_path)?;
|
|
||||||
let f = File::create(config_path)?;
|
|
||||||
ron::ser::to_writer_pretty(f, self, Default::default())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use futures::{
|
||||||
channel::mpsc::{unbounded, UnboundedReceiver},
|
channel::mpsc::{unbounded, UnboundedReceiver},
|
||||||
StreamExt,
|
StreamExt,
|
||||||
};
|
};
|
||||||
use std::{fmt::Debug, hash::Hash};
|
use std::{fmt::Debug, hash::Hash, thread::JoinHandle};
|
||||||
|
|
||||||
use crate::toplevel_handler::toplevel_handler;
|
use crate::toplevel_handler::toplevel_handler;
|
||||||
|
|
||||||
|
|
@ -26,31 +26,45 @@ pub enum State {
|
||||||
Waiting(
|
Waiting(
|
||||||
UnboundedReceiver<ToplevelUpdate>,
|
UnboundedReceiver<ToplevelUpdate>,
|
||||||
calloop::channel::Sender<ToplevelRequest>,
|
calloop::channel::Sender<ToplevelRequest>,
|
||||||
|
JoinHandle<()>,
|
||||||
),
|
),
|
||||||
Finished,
|
Finished,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_listening<I: Copy>(id: I, state: State) -> (Option<(I, ToplevelUpdate)>, State) {
|
async fn start_listening<I: Copy>(id: I, mut state: State) -> ((I, ToplevelUpdate), State) {
|
||||||
match state {
|
loop {
|
||||||
State::Ready => {
|
let (update, new_state) = match state {
|
||||||
let (calloop_tx, calloop_rx) = calloop::channel::channel();
|
State::Ready => {
|
||||||
let (toplevel_tx, toplevel_rx) = unbounded();
|
let (calloop_tx, calloop_rx) = calloop::channel::channel();
|
||||||
std::thread::spawn(move || {
|
let (toplevel_tx, toplevel_rx) = unbounded();
|
||||||
toplevel_handler(toplevel_tx, calloop_rx);
|
let handle = std::thread::spawn(move || {
|
||||||
});
|
toplevel_handler(toplevel_tx, calloop_rx);
|
||||||
(
|
});
|
||||||
Some((id, ToplevelUpdate::Init(calloop_tx.clone()))),
|
(
|
||||||
State::Waiting(toplevel_rx, calloop_tx),
|
Some((id, ToplevelUpdate::Init(calloop_tx.clone()))),
|
||||||
)
|
State::Waiting(toplevel_rx, calloop_tx, handle),
|
||||||
}
|
)
|
||||||
State::Waiting(mut rx, tx) => match rx.next().await {
|
|
||||||
Some(u) => (Some((id, u)), State::Waiting(rx, tx)),
|
|
||||||
None => {
|
|
||||||
let _ = tx.send(ToplevelRequest::Exit);
|
|
||||||
(Some((id, ToplevelUpdate::Finished)), State::Finished)
|
|
||||||
}
|
}
|
||||||
},
|
State::Waiting(mut rx, tx, handle) => {
|
||||||
State::Finished => iced::futures::future::pending().await,
|
if handle.is_finished() {
|
||||||
|
return ((id, ToplevelUpdate::Finished), State::Finished);
|
||||||
|
}
|
||||||
|
match rx.next().await {
|
||||||
|
Some(u) => (Some((id, u)), State::Waiting(rx, tx, handle)),
|
||||||
|
None => {
|
||||||
|
let _ = tx.send(ToplevelRequest::Exit);
|
||||||
|
(Some((id, ToplevelUpdate::Finished)), State::Finished)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Finished => iced::futures::future::pending().await,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, new_state);
|
||||||
|
} else {
|
||||||
|
state = new_state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,14 @@ icon-loader = { version = "0.3.6", features = ["gtk"] }
|
||||||
libpulse-binding = "2.26.0"
|
libpulse-binding = "2.26.0"
|
||||||
libpulse-glib-binding = "2.25.0"
|
libpulse-glib-binding = "2.25.0"
|
||||||
tokio = { version = "1.20.1", features=["full"] }
|
tokio = { version = "1.20.1", features=["full"] }
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["tokio", "wayland", "applet"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["tokio", "wayland"] }
|
||||||
|
cosmic-applet = { path = "../applet" }
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
|
# Application i18n
|
||||||
|
i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] }
|
||||||
|
i18n-embed-fl = "0.6"
|
||||||
|
rust-embed = "6.6"
|
||||||
|
rust-embed-utils = "7.5.0"
|
||||||
|
once_cell = "1.17.1"
|
||||||
|
|
||||||
|
|
|
||||||
4
cosmic-applet-audio/i18n.toml
Normal file
4
cosmic-applet-audio/i18n.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
fallback_language = "en"
|
||||||
|
|
||||||
|
[fluent]
|
||||||
|
assets_dir = "i18n"
|
||||||
6
cosmic-applet-audio/i18n/en/cosmic_applet_audio.ftl
Normal file
6
cosmic-applet-audio/i18n/en/cosmic_applet_audio.ftl
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
output = Output
|
||||||
|
input = Input
|
||||||
|
show-media-controls = Show Media Controls on Top Panel
|
||||||
|
sound-settings = Sound Settings...
|
||||||
|
disconnected = PulseAudio Disconnected
|
||||||
|
no-device = No device selected
|
||||||
47
cosmic-applet-audio/src/localize.rs
Normal file
47
cosmic-applet-audio/src/localize.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
|
mod localize;
|
||||||
|
|
||||||
use cosmic::iced::widget;
|
use cosmic::iced::widget;
|
||||||
use cosmic::iced_native::alignment::Horizontal;
|
use cosmic::iced::Limits;
|
||||||
use cosmic::iced_native::layout::Limits;
|
use cosmic::iced_runtime::core::alignment::Horizontal;
|
||||||
use cosmic::theme::Svg;
|
use cosmic::theme::Svg;
|
||||||
|
|
||||||
use cosmic::applet::{CosmicAppletHelper, APPLET_BUTTON_THEME};
|
|
||||||
use cosmic::widget::{button, divider, icon};
|
use cosmic::widget::{button, divider, icon};
|
||||||
use cosmic::Renderer;
|
use cosmic::Renderer;
|
||||||
|
use cosmic_applet::{applet_button_theme, CosmicAppletHelper};
|
||||||
|
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{
|
||||||
self,
|
self,
|
||||||
|
|
@ -20,12 +22,16 @@ use iced::widget::container;
|
||||||
use iced::Color;
|
use iced::Color;
|
||||||
|
|
||||||
mod pulse;
|
mod pulse;
|
||||||
|
use crate::localize::localize;
|
||||||
use crate::pulse::DeviceInfo;
|
use crate::pulse::DeviceInfo;
|
||||||
use libpulse_binding::volume::VolumeLinear;
|
use libpulse_binding::volume::VolumeLinear;
|
||||||
|
|
||||||
pub fn main() -> cosmic::iced::Result {
|
pub fn main() -> cosmic::iced::Result {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
|
// Prepare i18n
|
||||||
|
localize();
|
||||||
|
|
||||||
let helper = CosmicAppletHelper::default();
|
let helper = CosmicAppletHelper::default();
|
||||||
Audio::run(helper.window_settings())
|
Audio::run(helper.window_settings())
|
||||||
}
|
}
|
||||||
|
|
@ -43,7 +49,7 @@ struct Audio {
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
show_media_controls_in_top_panel: bool,
|
show_media_controls_in_top_panel: bool,
|
||||||
id_ctr: u32,
|
id_ctr: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
|
@ -93,7 +99,7 @@ impl Application for Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
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 {
|
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),
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
text_color: theme.cosmic().on_bg_color().into(),
|
text_color: theme.cosmic().on_bg_color().into(),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
|
@ -117,21 +123,21 @@ impl Application for Audio {
|
||||||
conn.send(pulse::Message::UpdateConnection);
|
conn.send(pulse::Message::UpdateConnection);
|
||||||
}
|
}
|
||||||
self.id_ctr += 1;
|
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);
|
self.popup.replace(new_id);
|
||||||
|
|
||||||
let mut popup_settings = self.applet_helper.get_popup_settings(
|
let mut popup_settings = self.applet_helper.get_popup_settings(
|
||||||
window::Id::new(0),
|
window::Id(0),
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
popup_settings.positioner.size_limits = Limits::NONE
|
popup_settings.positioner.size_limits = Limits::NONE
|
||||||
.min_height(1)
|
.min_height(1.0)
|
||||||
.min_width(1)
|
.min_width(1.0)
|
||||||
.max_width(400)
|
.max_width(400.0)
|
||||||
.max_height(1080);
|
.max_height(1080.0);
|
||||||
|
|
||||||
if let Some(conn) = self.pulse_state.connection() {
|
if let Some(conn) = self.pulse_state.connection() {
|
||||||
conn.send(pulse::Message::GetDefaultSink);
|
conn.send(pulse::Message::GetDefaultSink);
|
||||||
|
|
@ -268,7 +274,7 @@ impl Application for Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, id: window::Id) -> Element<Message> {
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
if id == window::Id::new(0) {
|
if id == window::Id(0) {
|
||||||
self.applet_helper
|
self.applet_helper
|
||||||
.icon_button(&self.icon_name)
|
.icon_button(&self.icon_name)
|
||||||
.on_press(Message::TogglePopup)
|
.on_press(Message::TogglePopup)
|
||||||
|
|
@ -291,20 +297,18 @@ impl Application for Audio {
|
||||||
.0 * 100.0;
|
.0 * 100.0;
|
||||||
|
|
||||||
let audio_content = if audio_disabled {
|
let audio_content = if audio_disabled {
|
||||||
column![text("PulseAudio Disconnected")
|
column![text(fl!("disconnected"))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.horizontal_alignment(Horizontal::Center)
|
.horizontal_alignment(Horizontal::Center)
|
||||||
.size(24),]
|
.size(24),]
|
||||||
} else {
|
} else {
|
||||||
column![
|
column![
|
||||||
row![
|
row![
|
||||||
icon("audio-volume-high-symbolic", 32)
|
icon("audio-volume-high-symbolic", 24).style(Svg::Symbolic),
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24))
|
|
||||||
.style(Svg::Symbolic),
|
|
||||||
slider(0.0..=100.0, out_f64, Message::SetOutputVolume)
|
slider(0.0..=100.0, out_f64, Message::SetOutputVolume)
|
||||||
.width(Length::FillPortion(5)),
|
.width(Length::FillPortion(5)),
|
||||||
text(format!("{}%", out_f64.round()))
|
text(format!("{}%", out_f64.round()))
|
||||||
|
.size(16)
|
||||||
.width(Length::FillPortion(1))
|
.width(Length::FillPortion(1))
|
||||||
.horizontal_alignment(Horizontal::Right)
|
.horizontal_alignment(Horizontal::Right)
|
||||||
]
|
]
|
||||||
|
|
@ -312,13 +316,11 @@ impl Application for Audio {
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.padding([8, 24]),
|
.padding([8, 24]),
|
||||||
row![
|
row![
|
||||||
icon("audio-input-microphone-symbolic", 32)
|
icon("audio-input-microphone-symbolic", 24).style(Svg::Symbolic),
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24))
|
|
||||||
.style(Svg::Symbolic),
|
|
||||||
slider(0.0..=100.0, in_f64, Message::SetInputVolume)
|
slider(0.0..=100.0, in_f64, Message::SetInputVolume)
|
||||||
.width(Length::FillPortion(5)),
|
.width(Length::FillPortion(5)),
|
||||||
text(format!("{}%", in_f64.round()))
|
text(format!("{}%", in_f64.round()))
|
||||||
|
.size(16)
|
||||||
.width(Length::FillPortion(1))
|
.width(Length::FillPortion(1))
|
||||||
.horizontal_alignment(Horizontal::Right)
|
.horizontal_alignment(Horizontal::Right)
|
||||||
]
|
]
|
||||||
|
|
@ -330,7 +332,7 @@ impl Application for Audio {
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
revealer(
|
revealer(
|
||||||
self.is_open == IsOpen::Output,
|
self.is_open == IsOpen::Output,
|
||||||
"Output",
|
fl!("output"),
|
||||||
match &self.current_output {
|
match &self.current_output {
|
||||||
Some(output) => pretty_name(output.description.clone()),
|
Some(output) => pretty_name(output.description.clone()),
|
||||||
None => String::from("No device selected"),
|
None => String::from("No device selected"),
|
||||||
|
|
@ -348,10 +350,10 @@ impl Application for Audio {
|
||||||
),
|
),
|
||||||
revealer(
|
revealer(
|
||||||
self.is_open == IsOpen::Input,
|
self.is_open == IsOpen::Input,
|
||||||
"Input",
|
fl!("input"),
|
||||||
match &self.current_input {
|
match &self.current_input {
|
||||||
Some(input) => pretty_name(input.description.clone()),
|
Some(input) => pretty_name(input.description.clone()),
|
||||||
None => String::from("No device selected"),
|
None => fl!("no-device"),
|
||||||
},
|
},
|
||||||
self.inputs
|
self.inputs
|
||||||
.clone()
|
.clone()
|
||||||
|
|
@ -372,17 +374,20 @@ impl Application for Audio {
|
||||||
container(divider::horizontal::light())
|
container(divider::horizontal::light())
|
||||||
.padding([12, 24])
|
.padding([12, 24])
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
container(toggler(
|
container(
|
||||||
Some("Show Media Controls on Top Panel".into()),
|
toggler(
|
||||||
self.show_media_controls_in_top_panel,
|
Some(fl!("show-media-controls")),
|
||||||
Message::ToggleMediaControlsInTopPanel,
|
self.show_media_controls_in_top_panel,
|
||||||
))
|
Message::ToggleMediaControlsInTopPanel,
|
||||||
|
)
|
||||||
|
.text_size(14)
|
||||||
|
)
|
||||||
.padding([0, 24]),
|
.padding([0, 24]),
|
||||||
container(divider::horizontal::light())
|
container(divider::horizontal::light())
|
||||||
.padding([12, 24])
|
.padding([12, 24])
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.text("Sound Settings...")
|
.custom(vec![text(fl!("sound-settings")).size(14).into()])
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
]
|
]
|
||||||
|
|
@ -398,19 +403,19 @@ impl Application for Audio {
|
||||||
|
|
||||||
fn revealer(
|
fn revealer(
|
||||||
open: bool,
|
open: bool,
|
||||||
title: &str,
|
title: String,
|
||||||
selected: String,
|
selected: String,
|
||||||
options: Vec<(String, String)>,
|
options: Vec<(String, String)>,
|
||||||
toggle: Message,
|
toggle: Message,
|
||||||
mut change: impl FnMut(String) -> Message + 'static,
|
mut change: impl FnMut(String) -> Message + 'static,
|
||||||
) -> widget::Column<Message, Renderer> {
|
) -> widget::Column<'static, Message, Renderer> {
|
||||||
if open {
|
if open {
|
||||||
options.iter().fold(
|
options.iter().fold(
|
||||||
column![revealer_head(open, title, selected, toggle)].width(Length::Fill),
|
column![revealer_head(open, title, selected, toggle)].width(Length::Fill),
|
||||||
|col, (id, name)| {
|
|col, (id, name)| {
|
||||||
col.push(
|
col.push(
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![text(name).into()])
|
.custom(vec![text(name).size(14).into()])
|
||||||
.on_press(change(id.clone()))
|
.on_press(change(id.clone()))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([8, 48]),
|
.padding([8, 48]),
|
||||||
|
|
@ -424,14 +429,14 @@ fn revealer(
|
||||||
|
|
||||||
fn revealer_head(
|
fn revealer_head(
|
||||||
_open: bool,
|
_open: bool,
|
||||||
title: &str,
|
title: String,
|
||||||
selected: String,
|
selected: String,
|
||||||
toggle: Message,
|
toggle: Message,
|
||||||
) -> widget::Button<Message, Renderer> {
|
) -> widget::Button<'static, Message, Renderer> {
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![
|
.custom(vec![
|
||||||
text(title).width(Length::Fill).into(),
|
text(title).width(Length::Fill).size(14).into(),
|
||||||
text(selected).into(),
|
text(selected).size(10).into(),
|
||||||
])
|
])
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use cosmic::iced_native::subscription::{self, Subscription};
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::{rc::Rc, thread};
|
use std::{rc::Rc, thread};
|
||||||
|
|
||||||
extern crate libpulse_binding as pulse;
|
extern crate libpulse_binding as pulse;
|
||||||
|
use cosmic::iced::{subscription, Subscription};
|
||||||
//use futures::channel::mpsc;
|
//use futures::channel::mpsc;
|
||||||
use libpulse_binding::{
|
use libpulse_binding::{
|
||||||
callbacks::ListResult,
|
callbacks::ListResult,
|
||||||
|
|
@ -15,71 +15,80 @@ use libpulse_binding::{
|
||||||
proplist::Proplist,
|
proplist::Proplist,
|
||||||
volume::ChannelVolumes,
|
volume::ChannelVolumes,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn connect() -> Subscription<Event> {
|
pub fn connect() -> Subscription<Event> {
|
||||||
struct Connect;
|
struct Connect;
|
||||||
|
|
||||||
subscription::unfold(
|
subscription::unfold(
|
||||||
std::any::TypeId::of::<Connect>(),
|
std::any::TypeId::of::<Connect>(),
|
||||||
State::Init,
|
State::Init,
|
||||||
|state| async move {
|
|mut state| async move {
|
||||||
match state {
|
loop {
|
||||||
State::Init => {
|
let (update, new_state) = connection(state).await;
|
||||||
let PulseHandle {
|
state = new_state;
|
||||||
to_pulse,
|
if let Some(update) = update {
|
||||||
from_pulse,
|
return (update, state);
|
||||||
} = 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)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
// #[derive(Debug)]
|
||||||
enum State {
|
enum State {
|
||||||
Init,
|
Init,
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
once_cell = "1.16.0"
|
once_cell = "1.16.0"
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["tokio", "wayland", "applet"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["tokio", "wayland"] }
|
||||||
|
cosmic-applet = { path = "../applet" }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
zbus = { version = "3.5", default-features = false, features = ["tokio"] }
|
zbus = { version = "3.13", default-features = false, features = ["tokio"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.5"
|
||||||
# Application i18n
|
# Application i18n
|
||||||
i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] }
|
i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] }
|
||||||
i18n-embed-fl = "0.6.4"
|
i18n-embed-fl = "0.6.4"
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,19 @@ use crate::upower_device::{device_subscription, DeviceDbusEvent};
|
||||||
use crate::upower_kbdbacklight::{
|
use crate::upower_kbdbacklight::{
|
||||||
kbd_backlight_subscription, KeyboardBacklightRequest, KeyboardBacklightUpdate,
|
kbd_backlight_subscription, KeyboardBacklightRequest, KeyboardBacklightUpdate,
|
||||||
};
|
};
|
||||||
use cosmic::applet::{CosmicAppletHelper, APPLET_BUTTON_THEME};
|
|
||||||
use cosmic::iced::alignment::Horizontal;
|
use cosmic::iced::alignment::Horizontal;
|
||||||
use cosmic::iced::wayland::popup::{destroy_popup, get_popup};
|
use cosmic::iced::wayland::popup::{destroy_popup, get_popup};
|
||||||
|
use cosmic::iced::Color;
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{
|
||||||
widget::{column, container, row, slider, text},
|
widget::{column, container, row, slider, text},
|
||||||
window, Alignment, Application, Command, Length, Subscription,
|
window, Alignment, Application, Command, Length, Subscription,
|
||||||
};
|
};
|
||||||
use cosmic::iced_native::layout::Limits;
|
use cosmic::iced_runtime::core::layout::Limits;
|
||||||
use cosmic::iced_style::application::{self, Appearance};
|
use cosmic::iced_style::application::{self, Appearance};
|
||||||
use cosmic::iced_style::Color;
|
|
||||||
use cosmic::theme::Svg;
|
use cosmic::theme::Svg;
|
||||||
use cosmic::widget::{button, divider, icon, toggler};
|
use cosmic::widget::{button, divider, icon, toggler};
|
||||||
use cosmic::{Element, Theme};
|
use cosmic::{Element, Theme};
|
||||||
|
use cosmic_applet::{applet_button_theme, CosmicAppletHelper};
|
||||||
use log::error;
|
use log::error;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
@ -58,7 +58,7 @@ struct CosmicBatteryApplet {
|
||||||
kbd_brightness: f64,
|
kbd_brightness: f64,
|
||||||
screen_brightness: f64,
|
screen_brightness: f64,
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
id_ctr: u32,
|
id_ctr: u128,
|
||||||
screen_sender: Option<UnboundedSender<ScreenBacklightRequest>>,
|
screen_sender: Option<UnboundedSender<ScreenBacklightRequest>>,
|
||||||
kbd_sender: Option<UnboundedSender<KeyboardBacklightRequest>>,
|
kbd_sender: Option<UnboundedSender<KeyboardBacklightRequest>>,
|
||||||
applet_helper: CosmicAppletHelper,
|
applet_helper: CosmicAppletHelper,
|
||||||
|
|
@ -144,21 +144,21 @@ impl Application for CosmicBatteryApplet {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.id_ctr += 1;
|
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);
|
self.popup.replace(new_id);
|
||||||
|
|
||||||
let mut popup_settings = self.applet_helper.get_popup_settings(
|
let mut popup_settings = self.applet_helper.get_popup_settings(
|
||||||
window::Id::new(0),
|
window::Id(0),
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
popup_settings.positioner.size_limits = Limits::NONE
|
popup_settings.positioner.size_limits = Limits::NONE
|
||||||
.max_width(372)
|
.max_width(372.0)
|
||||||
.min_width(300)
|
.min_width(300.0)
|
||||||
.min_height(200)
|
.min_height(200.0)
|
||||||
.max_height(1080);
|
.max_height(1080.0);
|
||||||
if let Some(tx) = self.power_profile_sender.as_ref() {
|
if let Some(tx) = self.power_profile_sender.as_ref() {
|
||||||
let _ = tx.send(PowerProfileRequest::Get);
|
let _ = tx.send(PowerProfileRequest::Get);
|
||||||
}
|
}
|
||||||
|
|
@ -213,13 +213,13 @@ impl Application for CosmicBatteryApplet {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
fn view(&self, id: window::Id) -> Element<Message> {
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
if id == window::Id::new(0) {
|
if id == window::Id(0) {
|
||||||
self.applet_helper
|
self.applet_helper
|
||||||
.icon_button(&self.icon_name)
|
.icon_button(&self.icon_name)
|
||||||
.on_press(Message::TogglePopup)
|
.on_press(Message::TogglePopup)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
let name = text(fl!("battery")).size(18);
|
let name = text(fl!("battery")).size(14);
|
||||||
let description = text(
|
let description = text(
|
||||||
if "battery-full-charging-symbolic" == self.icon_name
|
if "battery-full-charging-symbolic" == self.icon_name
|
||||||
|| "battery-full-charged-symbolic" == self.icon_name
|
|| "battery-full-charged-symbolic" == self.icon_name
|
||||||
|
|
@ -234,15 +234,12 @@ impl Application for CosmicBatteryApplet {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.size(12);
|
.size(10);
|
||||||
self.applet_helper
|
self.applet_helper
|
||||||
.popup_container(
|
.popup_container(
|
||||||
column![
|
column![
|
||||||
row![
|
row![
|
||||||
icon(&*self.icon_name, 24)
|
icon(&*self.icon_name, 24).style(Svg::Symbolic),
|
||||||
.style(Svg::Symbolic)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24)),
|
|
||||||
column![name, description]
|
column![name, description]
|
||||||
]
|
]
|
||||||
.padding([0, 24])
|
.padding([0, 24])
|
||||||
|
|
@ -251,11 +248,11 @@ impl Application for CosmicBatteryApplet {
|
||||||
container(divider::horizontal::light())
|
container(divider::horizontal::light())
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([0, 12]),
|
.padding([0, 12]),
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![row![
|
.custom(vec![row![
|
||||||
column![
|
column![
|
||||||
text(fl!("battery")).size(14),
|
text(fl!("battery")).size(14),
|
||||||
text(fl!("battery-desc")).size(12)
|
text(fl!("battery-desc")).size(10)
|
||||||
]
|
]
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
icon("emblem-ok-symbolic", 12).size(12).style(
|
icon("emblem-ok-symbolic", 12).size(12).style(
|
||||||
|
|
@ -270,11 +267,11 @@ impl Application for CosmicBatteryApplet {
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
.on_press(Message::SelectProfile(Power::Battery))
|
.on_press(Message::SelectProfile(Power::Battery))
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![row![
|
.custom(vec![row![
|
||||||
column![
|
column![
|
||||||
text(fl!("balanced")).size(14),
|
text(fl!("balanced")).size(14),
|
||||||
text(fl!("balanced-desc")).size(12)
|
text(fl!("balanced-desc")).size(10)
|
||||||
]
|
]
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
icon("emblem-ok-symbolic", 12).size(12).style(
|
icon("emblem-ok-symbolic", 12).size(12).style(
|
||||||
|
|
@ -289,11 +286,11 @@ impl Application for CosmicBatteryApplet {
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
.on_press(Message::SelectProfile(Power::Balanced))
|
.on_press(Message::SelectProfile(Power::Balanced))
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![row![
|
.custom(vec![row![
|
||||||
column![
|
column![
|
||||||
text(fl!("performance")).size(14),
|
text(fl!("performance")).size(14),
|
||||||
text(fl!("performance-desc")).size(12)
|
text(fl!("performance-desc")).size(10)
|
||||||
]
|
]
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
icon("emblem-ok-symbolic", 12).size(12).style(
|
icon("emblem-ok-symbolic", 12).size(12).style(
|
||||||
|
|
@ -311,42 +308,42 @@ impl Application for CosmicBatteryApplet {
|
||||||
container(divider::horizontal::light())
|
container(divider::horizontal::light())
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([0, 12]),
|
.padding([0, 12]),
|
||||||
container(toggler(fl!("max-charge"), self.charging_limit, |_| {
|
container(
|
||||||
Message::SetChargingLimit(!self.charging_limit)
|
toggler(fl!("max-charge"), self.charging_limit, |_| {
|
||||||
}))
|
Message::SetChargingLimit(!self.charging_limit)
|
||||||
|
})
|
||||||
|
.text_size(14)
|
||||||
|
.width(Length::Fill)
|
||||||
|
)
|
||||||
.padding([0, 24])
|
.padding([0, 24])
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
container(divider::horizontal::light())
|
container(divider::horizontal::light())
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([0, 12]),
|
.padding([0, 12]),
|
||||||
row![
|
row![
|
||||||
icon("display-brightness-symbolic", 24)
|
icon("display-brightness-symbolic", 24).style(Svg::Symbolic),
|
||||||
.style(Svg::Symbolic)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24)),
|
|
||||||
slider(
|
slider(
|
||||||
1..=100,
|
1..=100,
|
||||||
(self.screen_brightness * 100.0) as i32,
|
(self.screen_brightness * 100.0) as i32,
|
||||||
Message::SetScreenBrightness
|
Message::SetScreenBrightness
|
||||||
),
|
),
|
||||||
text(format!("{:.0}%", self.screen_brightness * 100.0))
|
text(format!("{:.0}%", self.screen_brightness * 100.0))
|
||||||
.width(Length::Units(40))
|
.size(16)
|
||||||
|
.width(Length::Fixed(40.0))
|
||||||
.horizontal_alignment(Horizontal::Right)
|
.horizontal_alignment(Horizontal::Right)
|
||||||
]
|
]
|
||||||
.padding([0, 24])
|
.padding([0, 24])
|
||||||
.spacing(12),
|
.spacing(12),
|
||||||
row![
|
row![
|
||||||
icon("keyboard-brightness-symbolic", 24)
|
icon("keyboard-brightness-symbolic", 24).style(Svg::Symbolic),
|
||||||
.style(Svg::Symbolic)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24)),
|
|
||||||
slider(
|
slider(
|
||||||
0..=100,
|
0..=100,
|
||||||
(self.kbd_brightness * 100.0) as i32,
|
(self.kbd_brightness * 100.0) as i32,
|
||||||
Message::SetKbdBrightness
|
Message::SetKbdBrightness
|
||||||
),
|
),
|
||||||
text(format!("{:.0}%", self.kbd_brightness * 100.0))
|
text(format!("{:.0}%", self.kbd_brightness * 100.0))
|
||||||
.width(Length::Units(40))
|
.size(16)
|
||||||
|
.width(Length::Fixed(40.0))
|
||||||
.horizontal_alignment(Horizontal::Right)
|
.horizontal_alignment(Horizontal::Right)
|
||||||
]
|
]
|
||||||
.padding([0, 24])
|
.padding([0, 24])
|
||||||
|
|
@ -354,8 +351,11 @@ impl Application for CosmicBatteryApplet {
|
||||||
container(divider::horizontal::light())
|
container(divider::horizontal::light())
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([0, 12]),
|
.padding([0, 12]),
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![text(fl!("power-settings")).width(Length::Fill).into()])
|
.custom(vec![text(fl!("power-settings"))
|
||||||
|
.size(14)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into()])
|
||||||
.on_press(Message::OpenBatterySettings)
|
.on_press(Message::OpenBatterySettings)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
|
|
@ -369,35 +369,38 @@ impl Application for CosmicBatteryApplet {
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
Subscription::batch(vec![
|
Subscription::batch(vec![
|
||||||
device_subscription(0).map(|(_, event)| match event {
|
device_subscription(0).map(
|
||||||
DeviceDbusEvent::Update {
|
|(
|
||||||
icon_name,
|
_,
|
||||||
percent,
|
DeviceDbusEvent::Update {
|
||||||
time_to_empty,
|
icon_name,
|
||||||
} => Message::Update {
|
percent,
|
||||||
|
time_to_empty,
|
||||||
|
},
|
||||||
|
)| Message::Update {
|
||||||
icon_name,
|
icon_name,
|
||||||
percent,
|
percent,
|
||||||
time_to_empty,
|
time_to_empty,
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
kbd_backlight_subscription(0).map(|event| match event {
|
||||||
|
(_, KeyboardBacklightUpdate::Update(b)) => Message::UpdateKbdBrightness(b),
|
||||||
|
(_, KeyboardBacklightUpdate::Init(tx, b)) => Message::InitKbdBacklight(tx, b),
|
||||||
}),
|
}),
|
||||||
kbd_backlight_subscription(0).map(|(_, event)| match event {
|
screen_backlight_subscription(0).map(|e| match e {
|
||||||
KeyboardBacklightUpdate::Update(b) => Message::UpdateKbdBrightness(b),
|
(_, ScreenBacklightUpdate::Update(b)) => Message::UpdateScreenBrightness(b),
|
||||||
KeyboardBacklightUpdate::Init(tx, b) => Message::InitKbdBacklight(tx, b),
|
(_, ScreenBacklightUpdate::Init(tx, b)) => Message::InitScreenBacklight(tx, b),
|
||||||
}),
|
}),
|
||||||
screen_backlight_subscription(0).map(|(_, event)| match event {
|
power_profile_subscription(0).map(|event| match event {
|
||||||
ScreenBacklightUpdate::Update(b) => Message::UpdateScreenBrightness(b),
|
(_, PowerProfileUpdate::Update { profile }) => Message::Profile(profile),
|
||||||
ScreenBacklightUpdate::Init(tx, b) => Message::InitScreenBacklight(tx, b),
|
(_, PowerProfileUpdate::Init(tx, p)) => Message::InitProfile(p, tx),
|
||||||
}),
|
(_, PowerProfileUpdate::Error(e)) => Message::Errored(e), // TODO: handle error
|
||||||
power_profile_subscription(0).map(|(_, event)| match event {
|
|
||||||
PowerProfileUpdate::Update { profile } => Message::Profile(profile),
|
|
||||||
PowerProfileUpdate::Init(tx, p) => Message::InitProfile(p, tx),
|
|
||||||
PowerProfileUpdate::Error(e) => Message::Errored(e), // TODO: handle error
|
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, _id: window::Id) -> Message {
|
fn close_requested(&self, _id: window::Id) -> Message {
|
||||||
|
|
@ -405,9 +408,9 @@ impl Application for CosmicBatteryApplet {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
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),
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
text_color: theme.cosmic().on_bg_color().into(),
|
text_color: theme.cosmic().on_bg_color().into(),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,20 @@ pub async fn backlight() -> io::Result<Option<Backlight>> {
|
||||||
pub fn screen_backlight_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
pub fn screen_backlight_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||||
id: I,
|
id: I,
|
||||||
) -> iced::Subscription<(I, ScreenBacklightUpdate)> {
|
) -> iced::Subscription<(I, ScreenBacklightUpdate)> {
|
||||||
subscription::unfold(id, State::Ready, move |state| start_listening(id, state))
|
subscription::unfold(id, State::Ready, move |state| start_listening_loop(id, state))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_listening_loop<I: Copy + Debug>(
|
||||||
|
id: I,
|
||||||
|
mut state: State,
|
||||||
|
) -> ((I, ScreenBacklightUpdate), State) {
|
||||||
|
loop {
|
||||||
|
let (update, new_state) = start_listening(id, state).await;
|
||||||
|
state = new_state;
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum State {
|
pub enum State {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
||||||
|
|
||||||
use cosmic::iced;
|
use cosmic::iced;
|
||||||
use cosmic::iced_native::subscription;
|
use cosmic::iced::subscription;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
|
|
@ -114,7 +114,9 @@ pub async fn set_power_profile(daemon: PowerDaemonProxy<'_>, power: Power) -> Re
|
||||||
pub fn power_profile_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
pub fn power_profile_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||||
id: I,
|
id: I,
|
||||||
) -> iced::Subscription<(I, PowerProfileUpdate)> {
|
) -> iced::Subscription<(I, PowerProfileUpdate)> {
|
||||||
subscription::unfold(id, State::Ready, move |state| start_listening(id, state))
|
subscription::unfold(id, State::Ready, move |state| {
|
||||||
|
start_listening_loop(id, state)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -124,6 +126,19 @@ pub enum State {
|
||||||
Finished,
|
Finished,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn start_listening_loop<I: Copy + Debug>(
|
||||||
|
id: I,
|
||||||
|
mut state: State,
|
||||||
|
) -> ((I, PowerProfileUpdate), State) {
|
||||||
|
loop {
|
||||||
|
let (update, new_state) = start_listening(id, state).await;
|
||||||
|
state = new_state;
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn start_listening<I: Copy>(id: I, state: State) -> (Option<(I, PowerProfileUpdate)>, State) {
|
async fn start_listening<I: Copy>(id: I, state: State) -> (Option<(I, PowerProfileUpdate)>, State) {
|
||||||
match state {
|
match state {
|
||||||
State::Ready => {
|
State::Ready => {
|
||||||
|
|
@ -189,14 +204,11 @@ async fn start_listening<I: Copy>(id: I, state: State) -> (Option<(I, PowerProfi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(PowerProfileRequest::Set(profile)) => {
|
Some(PowerProfileRequest::Set(profile)) => {
|
||||||
if set_power_profile(power_proxy, profile).await.is_ok() {
|
let _ = set_power_profile(power_proxy, profile).await;
|
||||||
(
|
(
|
||||||
Some((id, PowerProfileUpdate::Update { profile })),
|
Some((id, PowerProfileUpdate::Update { profile })),
|
||||||
State::Waiting(conn, rx),
|
State::Waiting(conn, rx),
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
(None, State::Waiting(conn, rx))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => (None, State::Finished),
|
None => (None, State::Finished),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ trait Device {
|
||||||
pub fn device_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
pub fn device_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||||
id: I,
|
id: I,
|
||||||
) -> iced::Subscription<(I, DeviceDbusEvent)> {
|
) -> iced::Subscription<(I, DeviceDbusEvent)> {
|
||||||
subscription::unfold(id, State::Ready, move |state| start_listening(id, state))
|
subscription::unfold(id, State::Ready, move |state| start_listening_loop(id, state))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -174,6 +174,19 @@ async fn display_device() -> zbus::Result<DeviceProxy<'static>> {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn start_listening_loop<I: Copy + Debug>(
|
||||||
|
id: I,
|
||||||
|
mut state: State,
|
||||||
|
) -> ((I, DeviceDbusEvent), State) {
|
||||||
|
loop {
|
||||||
|
let (update, new_state) = start_listening(id, state).await;
|
||||||
|
state = new_state;
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn start_listening<I: Copy>(id: I, state: State) -> (Option<(I, DeviceDbusEvent)>, State) {
|
async fn start_listening<I: Copy>(id: I, state: State) -> (Option<(I, DeviceDbusEvent)>, State) {
|
||||||
match state {
|
match state {
|
||||||
State::Ready => {
|
State::Ready => {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use iced::subscription;
|
||||||
use std::{fmt::Debug, hash::Hash};
|
use std::{fmt::Debug, hash::Hash};
|
||||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||||
use zbus::dbus_proxy;
|
use zbus::dbus_proxy;
|
||||||
|
|
||||||
#[dbus_proxy(
|
#[dbus_proxy(
|
||||||
default_service = "org.freedesktop.UPower",
|
default_service = "org.freedesktop.UPower",
|
||||||
interface = "org.freedesktop.UPower.KbdBacklight",
|
interface = "org.freedesktop.UPower.KbdBacklight",
|
||||||
|
|
@ -35,7 +36,20 @@ trait KbdBacklight {
|
||||||
pub fn kbd_backlight_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
pub fn kbd_backlight_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||||
id: I,
|
id: I,
|
||||||
) -> iced::Subscription<(I, KeyboardBacklightUpdate)> {
|
) -> iced::Subscription<(I, KeyboardBacklightUpdate)> {
|
||||||
subscription::unfold(id, State::Ready, move |state| start_listening(id, state))
|
subscription::unfold(id, State::Ready, move |state| start_listening_loop(id, state))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_listening_loop<I: Copy + Debug>(
|
||||||
|
id: I,
|
||||||
|
mut state: State,
|
||||||
|
) -> ((I, KeyboardBacklightUpdate), State) {
|
||||||
|
loop {
|
||||||
|
let (update, new_state) = start_listening(id, state).await;
|
||||||
|
state = new_state;
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@ license = "GPL-3.0-or-later"
|
||||||
once_cell = "1.16.0"
|
once_cell = "1.16.0"
|
||||||
bluer = { version = "0.15", features = ["bluetoothd", "id"] }
|
bluer = { version = "0.15", features = ["bluetoothd", "id"] }
|
||||||
futures-util = "0.3.21"
|
futures-util = "0.3.21"
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["wayland", "applet", "tokio"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["wayland", "tokio"] }
|
||||||
|
cosmic-applet = { path = "../applet" }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.5"
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
slotmap = "1.0.6"
|
slotmap = "1.0.6"
|
||||||
tokio = { version = "1.15.0", features = ["full"] }
|
tokio = { version = "1.15.0", features = ["full"] }
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
use crate::bluetooth::{BluerDeviceStatus, BluerRequest, BluerState};
|
use crate::bluetooth::{BluerDeviceStatus, BluerRequest, BluerState};
|
||||||
use cosmic::applet::APPLET_BUTTON_THEME;
|
|
||||||
use cosmic::iced_style;
|
use cosmic::iced_style;
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
applet::CosmicAppletHelper,
|
|
||||||
iced::{
|
iced::{
|
||||||
wayland::{
|
wayland::popup::{destroy_popup, get_popup},
|
||||||
popup::{destroy_popup, get_popup},
|
|
||||||
},
|
|
||||||
widget::{column, container, row, scrollable, text, Column},
|
widget::{column, container, row, scrollable, text, Column},
|
||||||
Alignment, Application, Color, Command, Length, Subscription,
|
Alignment, Application, Color, Command, Length, Subscription,
|
||||||
},
|
},
|
||||||
iced_native::{
|
iced_runtime::core::{
|
||||||
alignment::{Horizontal, Vertical},
|
alignment::{Horizontal, Vertical},
|
||||||
layout::Limits,
|
layout::Limits,
|
||||||
renderer::BorderRadius,
|
|
||||||
window,
|
window,
|
||||||
},
|
},
|
||||||
iced_style::{application, button::StyleSheet},
|
iced_style::{application, button::StyleSheet},
|
||||||
|
|
@ -21,6 +16,7 @@ use cosmic::{
|
||||||
widget::{button, divider, icon, toggler},
|
widget::{button, divider, icon, toggler},
|
||||||
Element, Theme,
|
Element, Theme,
|
||||||
};
|
};
|
||||||
|
use cosmic_applet::{applet_button_theme, CosmicAppletHelper};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
|
|
@ -38,7 +34,7 @@ struct CosmicBluetoothApplet {
|
||||||
icon_name: String,
|
icon_name: String,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
id_ctr: u32,
|
id_ctr: u128,
|
||||||
applet_helper: CosmicAppletHelper,
|
applet_helper: CosmicAppletHelper,
|
||||||
bluer_state: BluerState,
|
bluer_state: BluerState,
|
||||||
bluer_sender: Option<Sender<BluerRequest>>,
|
bluer_sender: Option<Sender<BluerRequest>>,
|
||||||
|
|
@ -87,11 +83,11 @@ impl Application for CosmicBluetoothApplet {
|
||||||
} else {
|
} else {
|
||||||
// TODO request update of state maybe
|
// TODO request update of state maybe
|
||||||
self.id_ctr += 1;
|
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);
|
self.popup.replace(new_id);
|
||||||
|
|
||||||
let mut popup_settings = self.applet_helper.get_popup_settings(
|
let mut popup_settings = self.applet_helper.get_popup_settings(
|
||||||
window::Id::new(0),
|
window::Id(0),
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
@ -99,10 +95,10 @@ impl Application for CosmicBluetoothApplet {
|
||||||
);
|
);
|
||||||
|
|
||||||
popup_settings.positioner.size_limits = Limits::NONE
|
popup_settings.positioner.size_limits = Limits::NONE
|
||||||
.min_height(1)
|
.min_height(1.0)
|
||||||
.min_width(1)
|
.min_width(1.0)
|
||||||
.max_height(800)
|
.max_height(800.0)
|
||||||
.max_width(400);
|
.max_width(400.0);
|
||||||
let tx = self.bluer_sender.as_ref().cloned();
|
let tx = self.bluer_sender.as_ref().cloned();
|
||||||
return Command::batch(vec![
|
return Command::batch(vec![
|
||||||
Command::perform(
|
Command::perform(
|
||||||
|
|
@ -275,17 +271,17 @@ impl Application for CosmicBluetoothApplet {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
fn view(&self, id: window::Id) -> Element<Message> {
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
let button_style = Button::Custom {
|
let button_style = || Button::Custom {
|
||||||
active: |t| iced_style::button::Appearance {
|
active: Box::new(|t| iced_style::button::Appearance {
|
||||||
border_radius: BorderRadius::from(0.0),
|
border_radius: 0.0,
|
||||||
..t.active(&Button::Text)
|
..t.active(&Button::Text)
|
||||||
},
|
}),
|
||||||
hover: |t| iced_style::button::Appearance {
|
hover: Box::new(|t| iced_style::button::Appearance {
|
||||||
border_radius: BorderRadius::from(0.0),
|
border_radius: 0.0,
|
||||||
..t.hovered(&Button::Text)
|
..t.hovered(&Button::Text)
|
||||||
},
|
}),
|
||||||
};
|
};
|
||||||
if id == window::Id::new(0) {
|
if id == window::Id(0) {
|
||||||
self.applet_helper
|
self.applet_helper
|
||||||
.icon_button(&self.icon_name)
|
.icon_button(&self.icon_name)
|
||||||
.on_press(Message::TogglePopup)
|
.on_press(Message::TogglePopup)
|
||||||
|
|
@ -320,20 +316,15 @@ impl Application for CosmicBluetoothApplet {
|
||||||
}
|
}
|
||||||
BluerDeviceStatus::Paired => {}
|
BluerDeviceStatus::Paired => {}
|
||||||
BluerDeviceStatus::Connecting | BluerDeviceStatus::Disconnecting => {
|
BluerDeviceStatus::Connecting | BluerDeviceStatus::Disconnecting => {
|
||||||
row = row.push(
|
row = row.push(icon("process-working-symbolic", 24).style(Svg::Symbolic));
|
||||||
icon("process-working-symbolic", 24)
|
|
||||||
.style(Svg::Symbolic)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
BluerDeviceStatus::Disconnected | BluerDeviceStatus::Pairing => continue,
|
BluerDeviceStatus::Disconnected | BluerDeviceStatus::Pairing => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
known_bluetooth = known_bluetooth.push(
|
known_bluetooth = known_bluetooth.push(
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![row.into()])
|
.custom(vec![row.into()])
|
||||||
.style(APPLET_BUTTON_THEME)
|
.style(applet_button_theme())
|
||||||
.on_press(match dev.status {
|
.on_press(match dev.status {
|
||||||
BluerDeviceStatus::Connected => {
|
BluerDeviceStatus::Connected => {
|
||||||
Message::Request(BluerRequest::DisconnectDevice(dev.address))
|
Message::Request(BluerRequest::DisconnectDevice(dev.address))
|
||||||
|
|
@ -392,25 +383,20 @@ impl Application for CosmicBluetoothApplet {
|
||||||
text(fl!("other-devices"))
|
text(fl!("other-devices"))
|
||||||
.size(14)
|
.size(14)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Units(24))
|
.height(Length::Fixed(24.0))
|
||||||
.vertical_alignment(Vertical::Center)
|
.vertical_alignment(Vertical::Center)
|
||||||
.into(),
|
.into(),
|
||||||
container(
|
container(icon(dropdown_icon, 14).style(Svg::Symbolic))
|
||||||
icon(dropdown_icon, 14)
|
.align_x(Horizontal::Center)
|
||||||
.style(Svg::Symbolic)
|
.align_y(Vertical::Center)
|
||||||
.width(Length::Units(14))
|
.width(Length::Fixed(24.0))
|
||||||
.height(Length::Units(14)),
|
.height(Length::Fixed(24.0))
|
||||||
)
|
.into(),
|
||||||
.align_x(Horizontal::Center)
|
|
||||||
.align_y(Vertical::Center)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24))
|
|
||||||
.into(),
|
|
||||||
]
|
]
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
.style(button_style.clone())
|
.style(button_style())
|
||||||
.on_press(Message::ToggleVisibleDevices(!self.show_visible_devices));
|
.on_press(Message::ToggleVisibleDevices(!self.show_visible_devices));
|
||||||
content = content.push(available_connections_btn);
|
content = content.push(available_connections_btn);
|
||||||
let mut list_column: Vec<Element<'_, Message>> =
|
let mut list_column: Vec<Element<'_, Message>> =
|
||||||
|
|
@ -420,6 +406,7 @@ impl Application for CosmicBluetoothApplet {
|
||||||
let row = column![
|
let row = column![
|
||||||
icon(device.icon.as_str(), 16).style(Svg::Symbolic),
|
icon(device.icon.as_str(), 16).style(Svg::Symbolic),
|
||||||
text(&device.name)
|
text(&device.name)
|
||||||
|
.size(14)
|
||||||
.horizontal_alignment(Horizontal::Left)
|
.horizontal_alignment(Horizontal::Left)
|
||||||
.vertical_alignment(Vertical::Center)
|
.vertical_alignment(Vertical::Center)
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
|
|
@ -435,20 +422,20 @@ impl Application for CosmicBluetoothApplet {
|
||||||
.horizontal_alignment(Horizontal::Center)
|
.horizontal_alignment(Horizontal::Center)
|
||||||
.vertical_alignment(Vertical::Center)
|
.vertical_alignment(Vertical::Center)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.size(32),
|
.size(22),
|
||||||
row![
|
row![
|
||||||
button(Button::Secondary)
|
button(Button::Secondary)
|
||||||
.custom(
|
.custom(
|
||||||
vec![text(fl!("cancel"))
|
vec![text(fl!("cancel"))
|
||||||
.size(14)
|
.size(14)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Units(24))
|
.height(Length::Fixed(24.0))
|
||||||
.vertical_alignment(Vertical::Center)
|
.vertical_alignment(Vertical::Center)
|
||||||
.into(),]
|
.into(),]
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
.style(button_style.clone())
|
.style(button_style())
|
||||||
.on_press(Message::Cancel)
|
.on_press(Message::Cancel)
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
button(Button::Secondary)
|
button(Button::Secondary)
|
||||||
|
|
@ -456,13 +443,13 @@ impl Application for CosmicBluetoothApplet {
|
||||||
vec![text(fl!("confirm"))
|
vec![text(fl!("confirm"))
|
||||||
.size(14)
|
.size(14)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Units(24))
|
.height(Length::Fixed(24.0))
|
||||||
.vertical_alignment(Vertical::Center)
|
.vertical_alignment(Vertical::Center)
|
||||||
.into(),]
|
.into(),]
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
.style(button_style.clone())
|
.style(button_style())
|
||||||
.on_press(Message::Confirm)
|
.on_press(Message::Confirm)
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
]
|
]
|
||||||
|
|
@ -494,7 +481,7 @@ impl Application for CosmicBluetoothApplet {
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.spacing(12);
|
.spacing(12);
|
||||||
visible_devices = visible_devices.push(
|
visible_devices = visible_devices.push(
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![row.width(Length::Fill).into()])
|
.custom(vec![row.width(Length::Fill).into()])
|
||||||
.on_press(Message::Request(BluerRequest::PairDevice(
|
.on_press(Message::Request(BluerRequest::PairDevice(
|
||||||
dev.address.clone(),
|
dev.address.clone(),
|
||||||
|
|
@ -516,7 +503,7 @@ impl Application for CosmicBluetoothApplet {
|
||||||
|
|
||||||
if item_counter > 10 {
|
if item_counter > 10 {
|
||||||
content = content.push(
|
content = content.push(
|
||||||
scrollable(Column::with_children(list_column)).height(Length::Units(300)),
|
scrollable(Column::with_children(list_column)).height(Length::Fixed(300.0)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = content.push(Column::with_children(list_column));
|
content = content.push(Column::with_children(list_column));
|
||||||
|
|
@ -526,11 +513,11 @@ impl Application for CosmicBluetoothApplet {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
bluetooth_subscription(0).map(|e| Message::BluetoothEvent(e.1))
|
bluetooth_subscription(0).map(|(_, e)| Message::BluetoothEvent(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
||||||
|
|
@ -538,9 +525,11 @@ impl Application for CosmicBluetoothApplet {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
||||||
<Self::Theme as application::StyleSheet>::Style::Custom(|theme| application::Appearance {
|
<Self::Theme as application::StyleSheet>::Style::Custom(Box::new(|theme| {
|
||||||
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
application::Appearance {
|
||||||
text_color: theme.cosmic().on_bg_color().into(),
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
})
|
text_color: theme.cosmic().on_bg_color().into(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use tokio::{
|
||||||
pub fn bluetooth_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
pub fn bluetooth_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||||
id: I,
|
id: I,
|
||||||
) -> iced::Subscription<(I, BluerEvent)> {
|
) -> iced::Subscription<(I, BluerEvent)> {
|
||||||
subscription::unfold(id, State::Ready, move |state| start_listening(id, state))
|
subscription::unfold(id, State::Ready, move |state| start_listening_loop(id, state))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum State {
|
pub enum State {
|
||||||
|
|
@ -30,6 +30,19 @@ pub enum State {
|
||||||
Finished,
|
Finished,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn start_listening_loop<I: Copy + Debug>(
|
||||||
|
id: I,
|
||||||
|
mut state: State,
|
||||||
|
) -> ((I, BluerEvent), State) {
|
||||||
|
loop {
|
||||||
|
let (update, new_state) = start_listening(id, state).await;
|
||||||
|
state = new_state;
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn start_listening<I: Copy + Debug>(id: I, state: State) -> (Option<(I, BluerEvent)>, State) {
|
async fn start_listening<I: Copy + Debug>(id: I, state: State) -> (Option<(I, BluerEvent)>, State) {
|
||||||
match state {
|
match state {
|
||||||
State::Ready => {
|
State::Ready => {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zbus = "3.4"
|
zbus = "3.13"
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["tokio", "wayland", "applet"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["tokio", "wayland"] }
|
||||||
|
cosmic-applet = { path = "../applet" }
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
# Application i18n
|
# Application i18n
|
||||||
i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] }
|
i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] }
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ mod localize;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
applet::{cosmic_panel_config::PanelAnchor, CosmicAppletHelper},
|
|
||||||
iced::{wayland::InitialSurface, Application, Settings},
|
iced::{wayland::InitialSurface, Application, Settings},
|
||||||
iced_native::layout::Limits,
|
iced_runtime::core::layout::Limits,
|
||||||
};
|
};
|
||||||
|
use cosmic_applet::{cosmic_panel_config::PanelAnchor, CosmicAppletHelper};
|
||||||
|
|
||||||
use window::*;
|
use window::*;
|
||||||
|
|
||||||
|
|
@ -21,10 +21,10 @@ pub fn main() -> cosmic::iced::Result {
|
||||||
InitialSurface::XdgWindow(w) => {
|
InitialSurface::XdgWindow(w) => {
|
||||||
w.autosize = true;
|
w.autosize = true;
|
||||||
w.size_limits = Limits::NONE
|
w.size_limits = Limits::NONE
|
||||||
.min_height(1)
|
.min_height(1.0)
|
||||||
.max_height(200)
|
.max_height(200.0)
|
||||||
.min_width(1)
|
.min_width(1.0)
|
||||||
.max_width(1000);
|
.max_width(1000.0);
|
||||||
}
|
}
|
||||||
InitialSurface::None => unimplemented!(),
|
InitialSurface::None => unimplemented!(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,22 @@
|
||||||
use crate::dbus::{self, PowerDaemonProxy};
|
use crate::dbus::{self, PowerDaemonProxy};
|
||||||
use crate::fl;
|
use crate::fl;
|
||||||
use crate::graphics::{get_current_graphics, set_graphics, Graphics};
|
use crate::graphics::{get_current_graphics, set_graphics, Graphics};
|
||||||
use cosmic::applet::CosmicAppletHelper;
|
|
||||||
use cosmic::iced::wayland::popup::{destroy_popup, get_popup};
|
use cosmic::iced::wayland::popup::{destroy_popup, get_popup};
|
||||||
use cosmic::iced_native::alignment::Horizontal;
|
use cosmic::iced::Color;
|
||||||
use cosmic::iced_native::Alignment;
|
use cosmic::iced_runtime::core::alignment::Horizontal;
|
||||||
|
use cosmic::iced_runtime::core::Alignment;
|
||||||
use cosmic::iced_style::application::{self, Appearance};
|
use cosmic::iced_style::application::{self, Appearance};
|
||||||
use cosmic::iced_style::Color;
|
|
||||||
use cosmic::theme::Button;
|
use cosmic::theme::Button;
|
||||||
use cosmic::widget::icon;
|
use cosmic::widget::icon;
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
applet::{cosmic_panel_config::PanelAnchor, APPLET_BUTTON_THEME},
|
|
||||||
iced::widget::{column, container, row, text},
|
iced::widget::{column, container, row, text},
|
||||||
iced::{self, Application, Command, Length},
|
iced::{self, Application, Command, Length},
|
||||||
iced_native::window,
|
iced_runtime::core::window,
|
||||||
theme::{Svg, Theme},
|
theme::{Svg, Theme},
|
||||||
widget::{button, divider},
|
widget::{button, divider},
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
|
use cosmic_applet::{applet_button_theme, cosmic_panel_config::PanelAnchor, CosmicAppletHelper};
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
|
@ -41,7 +40,7 @@ impl GraphicsMode {
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
graphics_mode: Option<GraphicsMode>,
|
graphics_mode: Option<GraphicsMode>,
|
||||||
id_ctr: u32,
|
id_ctr: u128,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
dbus: Option<(Connection, PowerDaemonProxy<'static>)>,
|
dbus: Option<(Connection, PowerDaemonProxy<'static>)>,
|
||||||
applet_helper: CosmicAppletHelper,
|
applet_helper: CosmicAppletHelper,
|
||||||
|
|
@ -92,7 +91,7 @@ impl Application for Window {
|
||||||
return destroy_popup(p);
|
return destroy_popup(p);
|
||||||
} else {
|
} else {
|
||||||
self.id_ctr += 1;
|
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);
|
self.popup.replace(new_id);
|
||||||
let mut commands = Vec::new();
|
let mut commands = Vec::new();
|
||||||
if let Some((_, proxy)) = self.dbus.as_ref() {
|
if let Some((_, proxy)) = self.dbus.as_ref() {
|
||||||
|
|
@ -102,7 +101,7 @@ impl Application for Window {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let popup_settings = self.applet_helper.get_popup_settings(
|
let popup_settings = self.applet_helper.get_popup_settings(
|
||||||
window::Id::new(0),
|
window::Id(0),
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
@ -184,7 +183,7 @@ impl Application for Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, id: window::Id) -> Element<Message> {
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
if id == window::Id::new(0) {
|
if id == window::Id(0) {
|
||||||
match self.applet_helper.anchor {
|
match self.applet_helper.anchor {
|
||||||
PanelAnchor::Left | PanelAnchor::Right => self
|
PanelAnchor::Left | PanelAnchor::Right => self
|
||||||
.applet_helper
|
.applet_helper
|
||||||
|
|
@ -206,6 +205,7 @@ impl Application for Window {
|
||||||
Some(Graphics::Hybrid) => fl!("hybrid"),
|
Some(Graphics::Hybrid) => fl!("hybrid"),
|
||||||
None => "".into(),
|
None => "".into(),
|
||||||
})
|
})
|
||||||
|
.size(14)
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.padding([0, self.applet_helper.suggested_size().0 / 2])
|
.padding([0, self.applet_helper.suggested_size().0 / 2])
|
||||||
|
|
@ -220,7 +220,7 @@ impl Application for Window {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let content_list = vec![
|
let content_list = vec![
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![row![
|
.custom(vec![row![
|
||||||
column![
|
column![
|
||||||
text(format!("{} {}", fl!("integrated"), fl!("graphics"))).size(14),
|
text(format!("{} {}", fl!("integrated"), fl!("graphics"))).size(14),
|
||||||
|
|
@ -256,7 +256,7 @@ impl Application for Window {
|
||||||
.on_press(Message::SelectGraphicsMode(Graphics::Integrated))
|
.on_press(Message::SelectGraphicsMode(Graphics::Integrated))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.into(),
|
.into(),
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![row![
|
.custom(vec![row![
|
||||||
column![text(format!("{} {}", fl!("nvidia"), fl!("graphics"))).size(14),]
|
column![text(format!("{} {}", fl!("nvidia"), fl!("graphics"))).size(14),]
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
|
|
@ -289,7 +289,7 @@ impl Application for Window {
|
||||||
.on_press(Message::SelectGraphicsMode(Graphics::Nvidia))
|
.on_press(Message::SelectGraphicsMode(Graphics::Nvidia))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.into(),
|
.into(),
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![row![
|
.custom(vec![row![
|
||||||
column![
|
column![
|
||||||
text(format!("{} {}", fl!("hybrid"), fl!("graphics"))).size(14),
|
text(format!("{} {}", fl!("hybrid"), fl!("graphics"))).size(14),
|
||||||
|
|
@ -325,7 +325,7 @@ impl Application for Window {
|
||||||
.on_press(Message::SelectGraphicsMode(Graphics::Hybrid))
|
.on_press(Message::SelectGraphicsMode(Graphics::Hybrid))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.into(),
|
.into(),
|
||||||
button(APPLET_BUTTON_THEME)
|
button(applet_button_theme())
|
||||||
.custom(vec![row![
|
.custom(vec![row![
|
||||||
column![
|
column![
|
||||||
text(format!("{} {}", fl!("compute"), fl!("graphics"))).size(14),
|
text(format!("{} {}", fl!("compute"), fl!("graphics"))).size(14),
|
||||||
|
|
@ -369,7 +369,7 @@ impl Application for Window {
|
||||||
text(fl!("graphics-mode"))
|
text(fl!("graphics-mode"))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.horizontal_alignment(Horizontal::Center)
|
.horizontal_alignment(Horizontal::Center)
|
||||||
.size(24)
|
.size(14)
|
||||||
.into(),
|
.into(),
|
||||||
container(divider::horizontal::light())
|
container(divider::horizontal::light())
|
||||||
.padding([0, 12])
|
.padding([0, 12])
|
||||||
|
|
@ -385,7 +385,7 @@ impl Application for Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, id: window::Id) -> Self::Message {
|
fn close_requested(&self, id: window::Id) -> Self::Message {
|
||||||
if id != window::Id::new(0) {
|
if id != window::Id(0) {
|
||||||
Message::PopupClosed(id)
|
Message::PopupClosed(id)
|
||||||
} else {
|
} else {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
|
|
@ -393,10 +393,10 @@ impl Application for Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
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),
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
text_color: theme.cosmic().background.on.into(),
|
text_color: theme.cosmic().background.on.into(),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_exit(&self) -> bool {
|
fn should_exit(&self) -> bool {
|
||||||
|
|
@ -404,6 +404,6 @@ impl Application for Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,12 @@ once_cell = "1.16.0"
|
||||||
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings", branch = "main" }
|
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings", branch = "main" }
|
||||||
# cosmic-dbus-networkmanager = { path = "../../../dbus-settings-bindings/networkmanager" }
|
# cosmic-dbus-networkmanager = { path = "../../../dbus-settings-bindings/networkmanager" }
|
||||||
futures-util = "0.3.21"
|
futures-util = "0.3.21"
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["wayland", "applet", "tokio"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["wayland", "tokio"] }
|
||||||
|
cosmic-applet = { path = "../applet" }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
zbus = { version = "3.7", default-features = false }
|
zbus = { version = "3.13", default-features = false }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.5"
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
slotmap = "1.0.6"
|
slotmap = "1.0.6"
|
||||||
tokio = { version = "1.15.0", features = ["full"] }
|
tokio = { version = "1.15.0", features = ["full"] }
|
||||||
|
|
@ -22,3 +23,5 @@ anyhow = "1.0"
|
||||||
i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] }
|
i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] }
|
||||||
i18n-embed-fl = "0.6.4"
|
i18n-embed-fl = "0.6.4"
|
||||||
rust-embed = "6.3.0"
|
rust-embed = "6.3.0"
|
||||||
|
rust-embed-utils = "7.5.0"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
use cosmic::iced_style;
|
use cosmic::iced_style;
|
||||||
|
use cosmic::iced_widget::Row;
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
applet::CosmicAppletHelper,
|
|
||||||
iced::{
|
iced::{
|
||||||
wayland::popup::{destroy_popup, get_popup},
|
wayland::popup::{destroy_popup, get_popup},
|
||||||
widget::{column, container, row, scrollable, text, text_input, Column},
|
widget::{column, container, row, scrollable, text, text_input, Column},
|
||||||
Alignment, Application, Color, Command, Length, Subscription,
|
Alignment, Application, Color, Command, Length, Subscription,
|
||||||
},
|
},
|
||||||
iced_native::{
|
iced_runtime::core::{
|
||||||
alignment::{Horizontal, Vertical},
|
alignment::{Horizontal, Vertical},
|
||||||
layout::Limits,
|
layout::Limits,
|
||||||
renderer::BorderRadius,
|
|
||||||
window,
|
window,
|
||||||
},
|
},
|
||||||
iced_style::{application, button::StyleSheet},
|
iced_style::{application, button::StyleSheet},
|
||||||
|
|
@ -17,9 +16,14 @@ use cosmic::{
|
||||||
widget::{button, divider, icon, toggler},
|
widget::{button, divider, icon, toggler},
|
||||||
Element, Theme,
|
Element, Theme,
|
||||||
};
|
};
|
||||||
|
use cosmic_applet::CosmicAppletHelper;
|
||||||
use cosmic_dbus_networkmanager::interface::enums::{ActiveConnectionState, DeviceState};
|
use cosmic_dbus_networkmanager::interface::enums::{ActiveConnectionState, DeviceState};
|
||||||
use futures::channel::mpsc::UnboundedSender;
|
use futures::channel::mpsc::UnboundedSender;
|
||||||
|
use zbus::Connection;
|
||||||
|
|
||||||
|
use crate::network_manager::active_conns::active_conns_subscription;
|
||||||
|
use crate::network_manager::devices::devices_subscription;
|
||||||
|
use crate::network_manager::wireless_enabled::wireless_enabled_subscription;
|
||||||
use crate::network_manager::NetworkManagerState;
|
use crate::network_manager::NetworkManagerState;
|
||||||
use crate::{
|
use crate::{
|
||||||
config, fl,
|
config, fl,
|
||||||
|
|
@ -77,13 +81,14 @@ struct CosmicNetworkApplet {
|
||||||
icon_name: String,
|
icon_name: String,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
id_ctr: u32,
|
id_ctr: u128,
|
||||||
applet_helper: CosmicAppletHelper,
|
applet_helper: CosmicAppletHelper,
|
||||||
nm_state: NetworkManagerState,
|
nm_state: NetworkManagerState,
|
||||||
// UI state
|
// UI state
|
||||||
nm_sender: Option<UnboundedSender<NetworkManagerRequest>>,
|
nm_sender: Option<UnboundedSender<NetworkManagerRequest>>,
|
||||||
show_visible_networks: bool,
|
show_visible_networks: bool,
|
||||||
new_connection: Option<NewConnectionState>,
|
new_connection: Option<NewConnectionState>,
|
||||||
|
conn: Option<Connection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CosmicNetworkApplet {
|
impl CosmicNetworkApplet {
|
||||||
|
|
@ -110,7 +115,7 @@ impl CosmicNetworkApplet {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
pub(crate) enum Message {
|
||||||
ActivateKnownWifi(String),
|
ActivateKnownWifi(String),
|
||||||
Disconnect(String),
|
Disconnect(String),
|
||||||
TogglePopup,
|
TogglePopup,
|
||||||
|
|
@ -155,11 +160,11 @@ impl Application for CosmicNetworkApplet {
|
||||||
} else {
|
} else {
|
||||||
// TODO request update of state maybe
|
// TODO request update of state maybe
|
||||||
self.id_ctr += 1;
|
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);
|
self.popup.replace(new_id);
|
||||||
|
|
||||||
let mut popup_settings = self.applet_helper.get_popup_settings(
|
let mut popup_settings = self.applet_helper.get_popup_settings(
|
||||||
window::Id::new(0),
|
window::Id(0),
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
@ -167,10 +172,10 @@ impl Application for CosmicNetworkApplet {
|
||||||
);
|
);
|
||||||
|
|
||||||
popup_settings.positioner.size_limits = Limits::NONE
|
popup_settings.positioner.size_limits = Limits::NONE
|
||||||
.min_height(1)
|
.min_height(1.0)
|
||||||
.min_width(1)
|
.min_width(1.0)
|
||||||
.max_height(800)
|
.max_height(800.0)
|
||||||
.max_width(400);
|
.max_width(400.0);
|
||||||
return get_popup(popup_settings);
|
return get_popup(popup_settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -193,10 +198,15 @@ impl Application for CosmicNetworkApplet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::NetworkManagerEvent(event) => match event {
|
Message::NetworkManagerEvent(event) => match event {
|
||||||
NetworkManagerEvent::Init { sender, state } => {
|
NetworkManagerEvent::Init {
|
||||||
|
conn,
|
||||||
|
sender,
|
||||||
|
state,
|
||||||
|
} => {
|
||||||
self.nm_sender.replace(sender);
|
self.nm_sender.replace(sender);
|
||||||
self.nm_state = state;
|
self.nm_state = state;
|
||||||
self.update_icon_name();
|
self.update_icon_name();
|
||||||
|
self.conn = Some(conn);
|
||||||
}
|
}
|
||||||
NetworkManagerEvent::WiFiEnabled(state) => {
|
NetworkManagerEvent::WiFiEnabled(state) => {
|
||||||
self.nm_state = state;
|
self.nm_state = state;
|
||||||
|
|
@ -337,17 +347,17 @@ impl Application for CosmicNetworkApplet {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
fn view(&self, id: window::Id) -> Element<Message> {
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
let button_style = Button::Custom {
|
let button_style = || Button::Custom {
|
||||||
active: |t| iced_style::button::Appearance {
|
active: Box::new(|t| iced_style::button::Appearance {
|
||||||
border_radius: BorderRadius::from(0.0),
|
border_radius: 0.0,
|
||||||
..t.active(&Button::Text)
|
..t.active(&Button::Text)
|
||||||
},
|
}),
|
||||||
hover: |t| iced_style::button::Appearance {
|
hover: Box::new(|t| iced_style::button::Appearance {
|
||||||
border_radius: BorderRadius::from(0.0),
|
border_radius: 0.0,
|
||||||
..t.hovered(&Button::Text)
|
..t.hovered(&Button::Text)
|
||||||
},
|
}),
|
||||||
};
|
};
|
||||||
if id == window::Id::new(0) {
|
if id == window::Id(0) {
|
||||||
self.applet_helper
|
self.applet_helper
|
||||||
.icon_button(&self.icon_name)
|
.icon_button(&self.icon_name)
|
||||||
.on_press(Message::TogglePopup)
|
.on_press(Message::TogglePopup)
|
||||||
|
|
@ -362,7 +372,7 @@ impl Application for CosmicNetworkApplet {
|
||||||
for addr in ip_addresses {
|
for addr in ip_addresses {
|
||||||
ipv4.push(
|
ipv4.push(
|
||||||
text(format!("{}: {}", fl!("ipv4"), addr.to_string()))
|
text(format!("{}: {}", fl!("ipv4"), addr.to_string()))
|
||||||
.size(12)
|
.size(10)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -412,8 +422,6 @@ impl Application for CosmicNetworkApplet {
|
||||||
let mut btn_content = vec![
|
let mut btn_content = vec![
|
||||||
icon("network-wireless-symbolic", 24)
|
icon("network-wireless-symbolic", 24)
|
||||||
.style(Svg::Symbolic)
|
.style(Svg::Symbolic)
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24))
|
|
||||||
.into(),
|
.into(),
|
||||||
column![text(name).size(14), Column::with_children(ipv4)]
|
column![text(name).size(14), Column::with_children(ipv4)]
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
@ -425,8 +433,6 @@ impl Application for CosmicNetworkApplet {
|
||||||
btn_content.push(
|
btn_content.push(
|
||||||
icon("process-working-symbolic", 24)
|
icon("process-working-symbolic", 24)
|
||||||
.style(Svg::Symbolic)
|
.style(Svg::Symbolic)
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24))
|
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -441,9 +447,12 @@ impl Application for CosmicNetworkApplet {
|
||||||
};
|
};
|
||||||
known_wifi = known_wifi.push(
|
known_wifi = known_wifi.push(
|
||||||
column![button(Button::Secondary)
|
column![button(Button::Secondary)
|
||||||
.custom(btn_content)
|
.custom(vec![Row::with_children(btn_content)
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.spacing(8)
|
||||||
|
.into()])
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
.style(button_style.clone())
|
.style(button_style())
|
||||||
.on_press(Message::Disconnect(name.clone()))]
|
.on_press(Message::Disconnect(name.clone()))]
|
||||||
.align_items(Alignment::Center),
|
.align_items(Alignment::Center),
|
||||||
);
|
);
|
||||||
|
|
@ -454,8 +463,6 @@ impl Application for CosmicNetworkApplet {
|
||||||
let mut btn_content = vec![
|
let mut btn_content = vec![
|
||||||
icon("network-wireless-symbolic", 24)
|
icon("network-wireless-symbolic", 24)
|
||||||
.style(Svg::Symbolic)
|
.style(Svg::Symbolic)
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24))
|
|
||||||
.into(),
|
.into(),
|
||||||
text(&known.ssid).size(14).width(Length::Fill).into(),
|
text(&known.ssid).size(14).width(Length::Fill).into(),
|
||||||
];
|
];
|
||||||
|
|
@ -464,17 +471,18 @@ impl Application for CosmicNetworkApplet {
|
||||||
btn_content.push(
|
btn_content.push(
|
||||||
icon("process-working-symbolic", 24)
|
icon("process-working-symbolic", 24)
|
||||||
.style(Svg::Symbolic)
|
.style(Svg::Symbolic)
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24))
|
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut btn = button(Button::Secondary)
|
let mut btn = button(Button::Secondary)
|
||||||
.custom(btn_content)
|
.custom(vec![Row::with_children(btn_content)
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.spacing(8)
|
||||||
|
.into()])
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.style(button_style.clone());
|
.style(button_style());
|
||||||
btn = match known.state {
|
btn = match known.state {
|
||||||
DeviceState::Failed
|
DeviceState::Failed
|
||||||
| DeviceState::Unknown
|
| DeviceState::Unknown
|
||||||
|
|
@ -495,6 +503,7 @@ impl Application for CosmicNetworkApplet {
|
||||||
toggler(fl!("airplane-mode"), self.nm_state.airplane_mode, |m| {
|
toggler(fl!("airplane-mode"), self.nm_state.airplane_mode, |m| {
|
||||||
Message::ToggleAirplaneMode(m)
|
Message::ToggleAirplaneMode(m)
|
||||||
})
|
})
|
||||||
|
.text_size(14)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
)
|
)
|
||||||
.padding([0, 12]),
|
.padding([0, 12]),
|
||||||
|
|
@ -503,6 +512,7 @@ impl Application for CosmicNetworkApplet {
|
||||||
toggler(fl!("wifi"), self.nm_state.wifi_enabled, |m| {
|
toggler(fl!("wifi"), self.nm_state.wifi_enabled, |m| {
|
||||||
Message::ToggleWiFi(m)
|
Message::ToggleWiFi(m)
|
||||||
})
|
})
|
||||||
|
.text_size(14)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
)
|
)
|
||||||
.padding([0, 12]),
|
.padding([0, 12]),
|
||||||
|
|
@ -523,25 +533,20 @@ impl Application for CosmicNetworkApplet {
|
||||||
text(fl!("visible-wireless-networks"))
|
text(fl!("visible-wireless-networks"))
|
||||||
.size(14)
|
.size(14)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Units(24))
|
.height(Length::Fixed(24.0))
|
||||||
.vertical_alignment(Vertical::Center)
|
.vertical_alignment(Vertical::Center)
|
||||||
.into(),
|
.into(),
|
||||||
container(
|
container(icon(dropdown_icon, 14).style(Svg::Symbolic))
|
||||||
icon(dropdown_icon, 14)
|
.align_x(Horizontal::Center)
|
||||||
.style(Svg::Symbolic)
|
.align_y(Vertical::Center)
|
||||||
.width(Length::Units(14))
|
.width(Length::Fixed(24.0))
|
||||||
.height(Length::Units(14)),
|
.height(Length::Fixed(24.0))
|
||||||
)
|
.into(),
|
||||||
.align_x(Horizontal::Center)
|
|
||||||
.align_y(Vertical::Center)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24))
|
|
||||||
.into(),
|
|
||||||
]
|
]
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
.padding([8, 24])
|
.padding([8, 24])
|
||||||
.style(button_style.clone())
|
.style(button_style())
|
||||||
.on_press(Message::ToggleVisibleNetworks);
|
.on_press(Message::ToggleVisibleNetworks);
|
||||||
content = content.push(available_connections_btn);
|
content = content.push(available_connections_btn);
|
||||||
if self.show_visible_networks {
|
if self.show_visible_networks {
|
||||||
|
|
@ -552,10 +557,7 @@ impl Application for CosmicNetworkApplet {
|
||||||
password,
|
password,
|
||||||
} => {
|
} => {
|
||||||
let id = row![
|
let id = row![
|
||||||
icon("network-wireless-symbolic", 24)
|
icon("network-wireless-symbolic", 24).style(Svg::Symbolic),
|
||||||
.style(Svg::Symbolic)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24)),
|
|
||||||
text(&access_point.ssid).size(14),
|
text(&access_point.ssid).size(14),
|
||||||
]
|
]
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
|
|
@ -565,7 +567,9 @@ impl Application for CosmicNetworkApplet {
|
||||||
content = content.push(id);
|
content = content.push(id);
|
||||||
let col = column![
|
let col = column![
|
||||||
text(fl!("enter-password")),
|
text(fl!("enter-password")),
|
||||||
text_input("", password, Message::Password)
|
text_input("", password)
|
||||||
|
.on_input(Message::Password)
|
||||||
|
.on_paste(Message::Password)
|
||||||
.on_submit(Message::SubmitPassword)
|
.on_submit(Message::SubmitPassword)
|
||||||
.password(),
|
.password(),
|
||||||
container(text(fl!("router-wps-button"))).padding(8),
|
container(text(fl!("router-wps-button"))).padding(8),
|
||||||
|
|
@ -590,10 +594,7 @@ impl Application for CosmicNetworkApplet {
|
||||||
}
|
}
|
||||||
NewConnectionState::Waiting(access_point) => {
|
NewConnectionState::Waiting(access_point) => {
|
||||||
let id = row![
|
let id = row![
|
||||||
icon("network-wireless-symbolic", 24)
|
icon("network-wireless-symbolic", 24).style(Svg::Symbolic),
|
||||||
.style(Svg::Symbolic)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24)),
|
|
||||||
text(&access_point.ssid).size(14),
|
text(&access_point.ssid).size(14),
|
||||||
]
|
]
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
|
|
@ -601,10 +602,7 @@ impl Application for CosmicNetworkApplet {
|
||||||
.spacing(12);
|
.spacing(12);
|
||||||
let connecting = row![
|
let connecting = row![
|
||||||
id,
|
id,
|
||||||
icon("process-working-symbolic", 24)
|
icon("process-working-symbolic", 24).style(Svg::Symbolic),
|
||||||
.style(Svg::Symbolic)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24)),
|
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.padding([0, 24]);
|
.padding([0, 24]);
|
||||||
|
|
@ -612,10 +610,7 @@ impl Application for CosmicNetworkApplet {
|
||||||
}
|
}
|
||||||
NewConnectionState::Failure(access_point) => {
|
NewConnectionState::Failure(access_point) => {
|
||||||
let id = row![
|
let id = row![
|
||||||
icon("network-wireless-symbolic", 24)
|
icon("network-wireless-symbolic", 24).style(Svg::Symbolic),
|
||||||
.style(Svg::Symbolic)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Units(24)),
|
|
||||||
text(&access_point.ssid).size(14),
|
text(&access_point.ssid).size(14),
|
||||||
]
|
]
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
|
|
@ -660,15 +655,12 @@ impl Application for CosmicNetworkApplet {
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let button = button(button_style)
|
let button = button(button_style())
|
||||||
.custom(vec![row![
|
.custom(vec![row![
|
||||||
icon("network-wireless-symbolic", 16)
|
icon("network-wireless-symbolic", 16).style(Svg::Symbolic),
|
||||||
.style(Svg::Symbolic)
|
|
||||||
.width(Length::Units(16))
|
|
||||||
.height(Length::Units(16)),
|
|
||||||
text(&ap.ssid)
|
text(&ap.ssid)
|
||||||
.size(14)
|
.size(14)
|
||||||
.height(Length::Units(24))
|
.height(Length::Fixed(24.0))
|
||||||
.vertical_alignment(Vertical::Center)
|
.vertical_alignment(Vertical::Center)
|
||||||
]
|
]
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
|
|
@ -680,7 +672,7 @@ impl Application for CosmicNetworkApplet {
|
||||||
list_col.push(button.into());
|
list_col.push(button.into());
|
||||||
}
|
}
|
||||||
content = content.push(
|
content = content.push(
|
||||||
scrollable(Column::with_children(list_col)).height(Length::Units(300)),
|
scrollable(Column::with_children(list_col)).height(Length::Fixed(300.0)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -689,11 +681,25 @@ impl Application for CosmicNetworkApplet {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
network_manager_subscription(0).map(|(_, event)| Message::NetworkManagerEvent(event))
|
let network_sub =
|
||||||
|
network_manager_subscription(0).map(|e| Message::NetworkManagerEvent(e.1));
|
||||||
|
|
||||||
|
if let Some(conn) = self.conn.as_ref() {
|
||||||
|
Subscription::batch(vec![
|
||||||
|
network_sub,
|
||||||
|
active_conns_subscription(0, conn.clone())
|
||||||
|
.map(|e| Message::NetworkManagerEvent(e.1)),
|
||||||
|
devices_subscription(0, conn.clone()).map(|e| Message::NetworkManagerEvent(e.1)),
|
||||||
|
wireless_enabled_subscription(0, conn.clone())
|
||||||
|
.map(|e| Message::NetworkManagerEvent(e.1)),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
network_sub
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
||||||
|
|
@ -701,9 +707,11 @@ impl Application for CosmicNetworkApplet {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
||||||
<Self::Theme as application::StyleSheet>::Style::Custom(|theme| application::Appearance {
|
<Self::Theme as application::StyleSheet>::Style::Custom(Box::new(|theme| {
|
||||||
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
application::Appearance {
|
||||||
text_color: theme.cosmic().on_bg_color().into(),
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
})
|
text_color: theme.cosmic().on_bg_color().into(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
cosmic-applet-network/src/network_manager/active_conns.rs
Normal file
56
cosmic-applet-network/src/network_manager/active_conns.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
use super::{NetworkManagerEvent, NetworkManagerState};
|
||||||
|
use cosmic::iced::{self, subscription};
|
||||||
|
use cosmic_dbus_networkmanager::nm::NetworkManager;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use log::error;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use zbus::Connection;
|
||||||
|
|
||||||
|
pub fn active_conns_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||||
|
id: I,
|
||||||
|
conn: Connection,
|
||||||
|
) -> iced::Subscription<(I, NetworkManagerEvent)> {
|
||||||
|
subscription::unfold(id, State::Continue(conn), move |mut state| async move {
|
||||||
|
loop {
|
||||||
|
let (update, new_state) = start_listening(id, state).await;
|
||||||
|
state = new_state;
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum State {
|
||||||
|
Continue(Connection),
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_listening<I: Copy + Debug>(
|
||||||
|
id: I,
|
||||||
|
state: State,
|
||||||
|
) -> (Option<(I, NetworkManagerEvent)>, State) {
|
||||||
|
let conn = match state {
|
||||||
|
State::Continue(conn) => conn,
|
||||||
|
State::Error => iced::futures::future::pending().await,
|
||||||
|
};
|
||||||
|
let network_manager = match NetworkManager::new(&conn).await {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to connect to NetworkManager: {}", e);
|
||||||
|
return (None, State::Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut active_conns_changed = network_manager.receive_active_connections_changed().await;
|
||||||
|
active_conns_changed.next().await;
|
||||||
|
|
||||||
|
let new_state = NetworkManagerState::new(&conn).await.unwrap_or_default();
|
||||||
|
|
||||||
|
(
|
||||||
|
Some((id, NetworkManagerEvent::ActiveConns(new_state))),
|
||||||
|
State::Continue(conn),
|
||||||
|
)
|
||||||
|
}
|
||||||
59
cosmic-applet-network/src/network_manager/devices.rs
Normal file
59
cosmic-applet-network/src/network_manager/devices.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use super::{NetworkManagerEvent, NetworkManagerState};
|
||||||
|
use cosmic::iced::{self, subscription};
|
||||||
|
use cosmic_dbus_networkmanager::nm::NetworkManager;
|
||||||
|
use log::error;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use zbus::Connection;
|
||||||
|
use futures::StreamExt;
|
||||||
|
|
||||||
|
pub fn devices_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||||
|
id: I,
|
||||||
|
conn: Connection,
|
||||||
|
) -> iced::Subscription<(I, NetworkManagerEvent)> {
|
||||||
|
subscription::unfold(id, State::Continue(conn), move |mut state| async move {
|
||||||
|
loop {
|
||||||
|
let (update, new_state) = start_listening(id, state).await;
|
||||||
|
state = new_state;
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum State {
|
||||||
|
Continue(Connection),
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_listening<I: Copy + Debug>(
|
||||||
|
id: I,
|
||||||
|
state: State,
|
||||||
|
) -> (Option<(I, NetworkManagerEvent)>, State) {
|
||||||
|
let conn = match state {
|
||||||
|
State::Continue(conn) => conn,
|
||||||
|
State::Error => iced::futures::future::pending().await,
|
||||||
|
};
|
||||||
|
let network_manager = match NetworkManager::new(&conn).await {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to connect to NetworkManager: {}", e);
|
||||||
|
return (None, State::Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut devices_changed = network_manager.receive_devices_changed().await;
|
||||||
|
devices_changed.next().await;
|
||||||
|
|
||||||
|
let new_state = NetworkManagerState::new(&conn).await.unwrap_or_default();
|
||||||
|
|
||||||
|
(
|
||||||
|
Some((
|
||||||
|
id,
|
||||||
|
NetworkManagerEvent::WirelessAccessPoints(new_state),
|
||||||
|
)),
|
||||||
|
State::Continue(conn),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
pub mod active_conns;
|
||||||
pub mod available_wifi;
|
pub mod available_wifi;
|
||||||
pub mod current_networks;
|
pub mod current_networks;
|
||||||
|
pub mod devices;
|
||||||
|
pub mod wireless_enabled;
|
||||||
|
|
||||||
use std::{collections::HashMap, fmt::Debug, hash::Hash, ops::Deref, time::Duration};
|
use std::{collections::HashMap, fmt::Debug, hash::Hash, ops::Deref, time::Duration};
|
||||||
|
|
||||||
|
|
@ -32,7 +35,9 @@ use self::{
|
||||||
pub fn network_manager_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
pub fn network_manager_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||||
id: I,
|
id: I,
|
||||||
) -> iced::Subscription<(I, NetworkManagerEvent)> {
|
) -> iced::Subscription<(I, NetworkManagerEvent)> {
|
||||||
subscription::unfold(id, State::Ready, move |state| start_listening(id, state))
|
subscription::unfold(id, State::Ready, move |state| {
|
||||||
|
start_listening_loop(id, state)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -42,6 +47,19 @@ pub enum State {
|
||||||
Finished,
|
Finished,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn start_listening_loop<I: Copy + Debug>(
|
||||||
|
id: I,
|
||||||
|
mut state: State,
|
||||||
|
) -> ((I, NetworkManagerEvent), State) {
|
||||||
|
loop {
|
||||||
|
let (update, new_state) = start_listening(id, state).await;
|
||||||
|
state = new_state;
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn start_listening<I: Copy + Debug>(
|
async fn start_listening<I: Copy + Debug>(
|
||||||
id: I,
|
id: I,
|
||||||
state: State,
|
state: State,
|
||||||
|
|
@ -59,6 +77,7 @@ async fn start_listening<I: Copy + Debug>(
|
||||||
Some((
|
Some((
|
||||||
id,
|
id,
|
||||||
NetworkManagerEvent::Init {
|
NetworkManagerEvent::Init {
|
||||||
|
conn: conn.clone(),
|
||||||
sender: tx,
|
sender: tx,
|
||||||
state: nm_state,
|
state: nm_state,
|
||||||
},
|
},
|
||||||
|
|
@ -72,289 +91,402 @@ async fn start_listening<I: Copy + Debug>(
|
||||||
Err(_) => return (None, State::Finished),
|
Err(_) => return (None, State::Finished),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut active_conns_changed = tokio::time::sleep(Duration::from_secs(5))
|
let (update, should_exit) = match rx.next().await {
|
||||||
.then(|_| async { network_manager.receive_active_connections_changed().await })
|
Some(NetworkManagerRequest::Disconnect(ssid)) => {
|
||||||
.await;
|
let mut success = false;
|
||||||
let mut devices_changed = network_manager.receive_devices_changed().await;
|
for c in network_manager
|
||||||
let mut wireless_enabled_changed =
|
.active_connections()
|
||||||
network_manager.receive_wireless_enabled_changed().await;
|
.await
|
||||||
let mut req = rx.next().boxed().fuse();
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
let (update, should_exit) = futures::select! {
|
if c.id().await.unwrap_or_default() == ssid {
|
||||||
req = req => {
|
if let Ok(_) = network_manager.deactivate_connection(&c).await {
|
||||||
match req {
|
success = true;
|
||||||
Some(NetworkManagerRequest::Disconnect(ssid)) => {
|
|
||||||
let mut success = false;
|
|
||||||
for c in network_manager.active_connections().await.unwrap_or_default() {
|
|
||||||
if c.id().await.unwrap_or_default() == ssid {
|
|
||||||
if let Ok(_) = network_manager.deactivate_connection(&c).await {
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(Some((id,
|
}
|
||||||
NetworkManagerEvent::RequestResponse {
|
}
|
||||||
|
(
|
||||||
|
Some((
|
||||||
|
id,
|
||||||
|
NetworkManagerEvent::RequestResponse {
|
||||||
req: NetworkManagerRequest::Disconnect(ssid.clone()),
|
req: NetworkManagerRequest::Disconnect(ssid.clone()),
|
||||||
success,
|
success,
|
||||||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
||||||
})), false)
|
},
|
||||||
|
)),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(NetworkManagerRequest::SetAirplaneMode(airplane_mode)) => {
|
||||||
|
// wifi
|
||||||
|
let mut success = network_manager
|
||||||
|
.set_wireless_enabled(!airplane_mode)
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
// bluetooth
|
||||||
|
success = success
|
||||||
|
&& Command::new("rfkill")
|
||||||
|
.arg(if airplane_mode { "block" } else { "unblock" })
|
||||||
|
.arg("bluetooth")
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
let response = NetworkManagerEvent::RequestResponse {
|
||||||
|
req: NetworkManagerRequest::SetAirplaneMode(airplane_mode),
|
||||||
|
success,
|
||||||
|
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
(Some((id, response)), false)
|
||||||
|
}
|
||||||
|
Some(NetworkManagerRequest::SetWiFi(enabled)) => {
|
||||||
|
let success = network_manager.set_wireless_enabled(enabled).await.is_ok();
|
||||||
|
let response = NetworkManagerEvent::RequestResponse {
|
||||||
|
req: NetworkManagerRequest::SetAirplaneMode(enabled),
|
||||||
|
success,
|
||||||
|
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
(Some((id, response)), false)
|
||||||
|
}
|
||||||
|
Some(NetworkManagerRequest::Password(ssid, password)) => {
|
||||||
|
let s = match NetworkManagerSettings::new(&conn).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return (None, State::Finished),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut status = (None, false);
|
||||||
|
|
||||||
|
// First try known connections
|
||||||
|
// TODO more convenient methods of managing settings
|
||||||
|
for c in s.list_connections().await.unwrap_or_default() {
|
||||||
|
let mut settings = match c.get_settings().await.ok() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cur_ssid = settings
|
||||||
|
.get("802-11-wireless")
|
||||||
|
.and_then(|w| w.get("ssid"))
|
||||||
|
.cloned()
|
||||||
|
.and_then(|ssid| ssid.try_into().ok())
|
||||||
|
.and_then(|ssid| String::from_utf8(ssid).ok());
|
||||||
|
if cur_ssid.as_ref() != Some(&ssid) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
Some(NetworkManagerRequest::SetAirplaneMode(airplane_mode)) => {
|
|
||||||
// wifi
|
|
||||||
let mut success = network_manager.set_wireless_enabled(!airplane_mode).await.is_ok();
|
|
||||||
// bluetooth
|
|
||||||
success = success && Command::new("rfkill")
|
|
||||||
.arg(if airplane_mode { "block" } else { "unblock" })
|
|
||||||
.arg("bluetooth")
|
|
||||||
.output()
|
|
||||||
.await
|
|
||||||
.is_ok();
|
|
||||||
let response = NetworkManagerEvent::RequestResponse {
|
|
||||||
req: NetworkManagerRequest::SetAirplaneMode(airplane_mode),
|
|
||||||
success,
|
|
||||||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
|
||||||
};
|
|
||||||
(Some((id, response)), false)
|
|
||||||
}
|
|
||||||
Some(NetworkManagerRequest::SetWiFi(enabled)) => {
|
|
||||||
let success = network_manager.set_wireless_enabled(enabled).await.is_ok();
|
|
||||||
let response = NetworkManagerEvent::RequestResponse {
|
|
||||||
req: NetworkManagerRequest::SetAirplaneMode(enabled),
|
|
||||||
success,
|
|
||||||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
|
||||||
};
|
|
||||||
(Some((id, response)), false)
|
|
||||||
}
|
|
||||||
Some(NetworkManagerRequest::Password(ssid, password)) => {
|
|
||||||
let s = match NetworkManagerSettings::new(&conn).await {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return (None, State::Finished),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut status = (None, false);
|
let mut secrets = match c.get_secrets("802-11-wireless-security").await {
|
||||||
|
Ok(s) => s,
|
||||||
// First try known connections
|
_ => HashMap::from([(
|
||||||
// TODO more convenient methods of managing settings
|
"802-11-wireless-security".into(),
|
||||||
for c in s.list_connections().await.unwrap_or_default() {
|
HashMap::from([
|
||||||
let mut settings = match c.get_settings().await.ok() {
|
(
|
||||||
Some(s) => s,
|
"psk".into(),
|
||||||
None => continue,
|
Value::Str(password.as_str().into()).to_owned(),
|
||||||
};
|
),
|
||||||
|
("key-mgmt".into(), Value::Str("wpa-psk".into()).to_owned()),
|
||||||
let cur_ssid = settings
|
]),
|
||||||
.get("802-11-wireless")
|
)]),
|
||||||
.and_then(|w| w.get("ssid"))
|
};
|
||||||
.cloned()
|
if let Some(s) = secrets.get_mut("802-11-wireless-security") {
|
||||||
.and_then(|ssid| ssid.try_into().ok())
|
s.insert("psk".into(), Value::Str(password.clone().into()).to_owned());
|
||||||
.and_then(|ssid| String::from_utf8(ssid).ok());
|
drop(s);
|
||||||
if cur_ssid.as_ref() != Some(&ssid) {
|
settings.extend(secrets.into_iter());
|
||||||
continue;
|
let settings: HashMap<_, _> = settings
|
||||||
}
|
.iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
let mut secrets = match
|
let map = (
|
||||||
c.get_secrets("802-11-wireless-security")
|
k.as_str(),
|
||||||
.await {
|
v.iter()
|
||||||
Ok(s) => s,
|
|
||||||
_ => HashMap::from([("802-11-wireless-security".into(), HashMap::from([
|
|
||||||
("psk".into(), Value::Str(password.as_str().into()).to_owned()),
|
|
||||||
("key-mgmt".into(), Value::Str("wpa-psk".into()).to_owned())
|
|
||||||
]))]),
|
|
||||||
};
|
|
||||||
if let Some(s) = secrets.get_mut("802-11-wireless-security") {
|
|
||||||
s.insert("psk".into(), Value::Str(password.clone().into()).to_owned());
|
|
||||||
drop(s);
|
|
||||||
settings.extend(secrets.into_iter());
|
|
||||||
let settings: HashMap<_, _> = settings.iter().map(|(k, v)| {
|
|
||||||
let map = (k.as_str(), v.iter()
|
|
||||||
.map(|(k, v)| (k.as_str(), v.into()))
|
.map(|(k, v)| (k.as_str(), v.into()))
|
||||||
.collect::<HashMap<_, _>>());
|
.collect::<HashMap<_, _>>(),
|
||||||
map
|
);
|
||||||
}).collect();
|
map
|
||||||
let updated = c.update(settings).await;
|
})
|
||||||
if updated.is_ok() {
|
.collect();
|
||||||
let success = if let Ok(path) = network_manager.deref().activate_connection(c.deref().path(), &ObjectPath::try_from("/").unwrap(), &ObjectPath::try_from("/").unwrap()).await {
|
let updated = c.update(settings).await;
|
||||||
// let active_conn = ActiveConnection::from(ActiveConnectionProxy::from(conn.1));
|
if updated.is_ok() {
|
||||||
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
|
let success = if let Ok(path) = network_manager
|
||||||
let active = ActiveConnectionProxy::builder(&conn).path(path).unwrap().destination(dummy.destination()).unwrap().interface(dummy.interface()).unwrap().build().await.unwrap();
|
.deref()
|
||||||
let state = enums::ActiveConnectionState::from(active.state().await.unwrap_or_default());
|
.activate_connection(
|
||||||
let s = if let enums::ActiveConnectionState::Activating = state {
|
c.deref().path(),
|
||||||
if let Ok(Some(s)) = timeout(Duration::from_secs(10), active.receive_state_changed().await.next()).await {
|
&ObjectPath::try_from("/").unwrap(),
|
||||||
s.get().await.unwrap_or_default().into()
|
&ObjectPath::try_from("/").unwrap(),
|
||||||
} else {
|
)
|
||||||
state
|
.await
|
||||||
}
|
{
|
||||||
} else {
|
// let active_conn = ActiveConnection::from(ActiveConnectionProxy::from(conn.1));
|
||||||
state
|
|
||||||
};
|
|
||||||
matches!(s, enums::ActiveConnectionState::Activated)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
status = (Some((id, NetworkManagerEvent::RequestResponse {
|
|
||||||
req: NetworkManagerRequest::Password(ssid.clone(), password.clone()),
|
|
||||||
success,
|
|
||||||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
|
||||||
})), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a connection
|
|
||||||
if status.0.is_none() {
|
|
||||||
for device in network_manager.devices().await.ok().unwrap_or_default() {
|
|
||||||
if matches!(device.device_type().await.unwrap_or(DeviceType::Other), DeviceType::Wifi) {
|
|
||||||
let conn_settings: HashMap<&str, HashMap<&str, zvariant::Value>> = HashMap::from([
|
|
||||||
("802-11-wireless".into(), HashMap::from([
|
|
||||||
("ssid".into(), Value::Array(ssid.as_bytes().into())),
|
|
||||||
])),
|
|
||||||
("connection".into(), HashMap::from([
|
|
||||||
("id".into(), Value::Str(ssid.as_str().into())),
|
|
||||||
("type".into(), Value::Str("802-11-wireless".into())),
|
|
||||||
])),
|
|
||||||
("802-11-wireless-security".into(), HashMap::from([
|
|
||||||
("psk".into(), Value::Str(password.as_str().into())),
|
|
||||||
("key-mgmt".into(), Value::Str("wpa-psk".into()))
|
|
||||||
]))
|
|
||||||
]);
|
|
||||||
let success = if let Ok((_, path)) = network_manager.add_and_activate_connection(conn_settings, device.path(), &ObjectPath::try_from("/").unwrap()).await {
|
|
||||||
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
|
|
||||||
let active = ActiveConnectionProxy::builder(&conn).path(path).unwrap().destination(dummy.destination()).unwrap().interface(dummy.interface()).unwrap().build().await.unwrap();
|
|
||||||
let state = enums::ActiveConnectionState::from(active.state().await.unwrap_or_default());
|
|
||||||
let s = if let enums::ActiveConnectionState::Activating = state {
|
|
||||||
if let Ok(Some(s)) = timeout(Duration::from_secs(10), active.receive_state_changed().await.next()).await {
|
|
||||||
s.get().await.unwrap_or_default().into()
|
|
||||||
} else {
|
|
||||||
state
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state
|
|
||||||
};
|
|
||||||
matches!(s, enums::ActiveConnectionState::Activated)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
status = (Some((id, NetworkManagerEvent::RequestResponse {
|
|
||||||
req: NetworkManagerRequest::Password(ssid.clone(), password.clone()),
|
|
||||||
success,
|
|
||||||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
|
||||||
})), false);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if status.0.is_none() {
|
|
||||||
status = (Some((id, NetworkManagerEvent::RequestResponse {
|
|
||||||
req: NetworkManagerRequest::Password(ssid, password),
|
|
||||||
success: false,
|
|
||||||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
|
||||||
})), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
status
|
|
||||||
}
|
|
||||||
Some(NetworkManagerRequest::SelectAccessPoint(ssid)) => {
|
|
||||||
let s = match NetworkManagerSettings::new(&conn).await {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return (None, State::Finished),
|
|
||||||
};
|
|
||||||
// find known connection with matching ssid and activate
|
|
||||||
let mut status = (None, false);
|
|
||||||
|
|
||||||
for c in s.list_connections().await.unwrap_or_default() {
|
|
||||||
let settings = match c.get_settings().await.ok() {
|
|
||||||
Some(s) => s,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
let cur_ssid = settings
|
|
||||||
.get("802-11-wireless")
|
|
||||||
.and_then(|w| w.get("ssid"))
|
|
||||||
.cloned()
|
|
||||||
.and_then(|ssid| ssid.try_into().ok())
|
|
||||||
.and_then(|ssid| String::from_utf8(ssid).ok());
|
|
||||||
|
|
||||||
if cur_ssid.as_ref() != Some(&ssid) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let success = if let Ok(path) = network_manager.deref().activate_connection(c.deref().path(), &ObjectPath::try_from("/").unwrap(), &ObjectPath::try_from("/").unwrap()).await {
|
|
||||||
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
|
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
|
||||||
let active = ActiveConnectionProxy::builder(&conn).path(path).unwrap().destination(dummy.destination()).unwrap().interface(dummy.interface()).unwrap().build().await.unwrap();
|
let active = ActiveConnectionProxy::builder(&conn)
|
||||||
let mut state = enums::ActiveConnectionState::from(active.state().await.unwrap_or_default());
|
.path(path)
|
||||||
while let enums::ActiveConnectionState::Activating = state {
|
.unwrap()
|
||||||
if let Ok(Some(s)) = timeout(Duration::from_secs(20), active.receive_state_changed().await.next()).await {
|
.destination(dummy.destination())
|
||||||
state = s.get().await.unwrap_or_default().into();
|
.unwrap()
|
||||||
|
.interface(dummy.interface())
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let state = enums::ActiveConnectionState::from(
|
||||||
|
active.state().await.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
let s = if let enums::ActiveConnectionState::Activating = state
|
||||||
|
{
|
||||||
|
if let Ok(Some(s)) = timeout(
|
||||||
|
Duration::from_secs(10),
|
||||||
|
active.receive_state_changed().await.next(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
s.get().await.unwrap_or_default().into()
|
||||||
} else {
|
} else {
|
||||||
break;
|
state
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
state
|
||||||
};
|
};
|
||||||
matches!(state, enums::ActiveConnectionState::Activated)
|
matches!(s, enums::ActiveConnectionState::Activated)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
status = (Some((id, NetworkManagerEvent::RequestResponse {
|
status = (
|
||||||
req: NetworkManagerRequest::SelectAccessPoint(ssid.clone()),
|
Some((
|
||||||
success,
|
id,
|
||||||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
NetworkManagerEvent::RequestResponse {
|
||||||
})), false);
|
req: NetworkManagerRequest::Password(
|
||||||
|
ssid.clone(),
|
||||||
|
password.clone(),
|
||||||
|
),
|
||||||
|
success,
|
||||||
|
state: NetworkManagerState::new(&conn)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a connection
|
||||||
|
if status.0.is_none() {
|
||||||
|
for device in network_manager.devices().await.ok().unwrap_or_default() {
|
||||||
|
if matches!(
|
||||||
|
device.device_type().await.unwrap_or(DeviceType::Other),
|
||||||
|
DeviceType::Wifi
|
||||||
|
) {
|
||||||
|
let conn_settings: HashMap<&str, HashMap<&str, zvariant::Value>> =
|
||||||
|
HashMap::from([
|
||||||
|
(
|
||||||
|
"802-11-wireless".into(),
|
||||||
|
HashMap::from([(
|
||||||
|
"ssid".into(),
|
||||||
|
Value::Array(ssid.as_bytes().into()),
|
||||||
|
)]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"connection".into(),
|
||||||
|
HashMap::from([
|
||||||
|
("id".into(), Value::Str(ssid.as_str().into())),
|
||||||
|
(
|
||||||
|
"type".into(),
|
||||||
|
Value::Str("802-11-wireless".into()),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"802-11-wireless-security".into(),
|
||||||
|
HashMap::from([
|
||||||
|
(
|
||||||
|
"psk".into(),
|
||||||
|
Value::Str(password.as_str().into()),
|
||||||
|
),
|
||||||
|
("key-mgmt".into(), Value::Str("wpa-psk".into())),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
let success = if let Ok((_, path)) = network_manager
|
||||||
|
.add_and_activate_connection(
|
||||||
|
conn_settings,
|
||||||
|
device.path(),
|
||||||
|
&ObjectPath::try_from("/").unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
|
||||||
|
let active = ActiveConnectionProxy::builder(&conn)
|
||||||
|
.path(path)
|
||||||
|
.unwrap()
|
||||||
|
.destination(dummy.destination())
|
||||||
|
.unwrap()
|
||||||
|
.interface(dummy.interface())
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let state = enums::ActiveConnectionState::from(
|
||||||
|
active.state().await.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
let s = if let enums::ActiveConnectionState::Activating = state
|
||||||
|
{
|
||||||
|
if let Ok(Some(s)) = timeout(
|
||||||
|
Duration::from_secs(10),
|
||||||
|
active.receive_state_changed().await.next(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
s.get().await.unwrap_or_default().into()
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
};
|
||||||
|
matches!(s, enums::ActiveConnectionState::Activated)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
status = (
|
||||||
|
Some((
|
||||||
|
id,
|
||||||
|
NetworkManagerEvent::RequestResponse {
|
||||||
|
req: NetworkManagerRequest::Password(
|
||||||
|
ssid.clone(),
|
||||||
|
password.clone(),
|
||||||
|
),
|
||||||
|
success,
|
||||||
|
state: NetworkManagerState::new(&conn)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if status.0.is_none() {
|
if status.0.is_none() {
|
||||||
status = (Some((id, NetworkManagerEvent::RequestResponse {
|
status = (
|
||||||
|
Some((
|
||||||
|
id,
|
||||||
|
NetworkManagerEvent::RequestResponse {
|
||||||
|
req: NetworkManagerRequest::Password(ssid, password),
|
||||||
|
success: false,
|
||||||
|
state: NetworkManagerState::new(&conn)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
status
|
||||||
|
}
|
||||||
|
Some(NetworkManagerRequest::SelectAccessPoint(ssid)) => {
|
||||||
|
let s = match NetworkManagerSettings::new(&conn).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return (None, State::Finished),
|
||||||
|
};
|
||||||
|
// find known connection with matching ssid and activate
|
||||||
|
let mut status = (None, false);
|
||||||
|
|
||||||
|
for c in s.list_connections().await.unwrap_or_default() {
|
||||||
|
let settings = match c.get_settings().await.ok() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cur_ssid = settings
|
||||||
|
.get("802-11-wireless")
|
||||||
|
.and_then(|w| w.get("ssid"))
|
||||||
|
.cloned()
|
||||||
|
.and_then(|ssid| ssid.try_into().ok())
|
||||||
|
.and_then(|ssid| String::from_utf8(ssid).ok());
|
||||||
|
|
||||||
|
if cur_ssid.as_ref() != Some(&ssid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let success = if let Ok(path) = network_manager
|
||||||
|
.deref()
|
||||||
|
.activate_connection(
|
||||||
|
c.deref().path(),
|
||||||
|
&ObjectPath::try_from("/").unwrap(),
|
||||||
|
&ObjectPath::try_from("/").unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
|
||||||
|
let active = ActiveConnectionProxy::builder(&conn)
|
||||||
|
.path(path)
|
||||||
|
.unwrap()
|
||||||
|
.destination(dummy.destination())
|
||||||
|
.unwrap()
|
||||||
|
.interface(dummy.interface())
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut state = enums::ActiveConnectionState::from(
|
||||||
|
active.state().await.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
while let enums::ActiveConnectionState::Activating = state {
|
||||||
|
if let Ok(Some(s)) = timeout(
|
||||||
|
Duration::from_secs(20),
|
||||||
|
active.receive_state_changed().await.next(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
state = s.get().await.unwrap_or_default().into();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matches!(state, enums::ActiveConnectionState::Activated)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
status = (
|
||||||
|
Some((
|
||||||
|
id,
|
||||||
|
NetworkManagerEvent::RequestResponse {
|
||||||
|
req: NetworkManagerRequest::SelectAccessPoint(ssid.clone()),
|
||||||
|
success,
|
||||||
|
state: NetworkManagerState::new(&conn)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.0.is_none() {
|
||||||
|
status = (
|
||||||
|
Some((
|
||||||
|
id,
|
||||||
|
NetworkManagerEvent::RequestResponse {
|
||||||
req: NetworkManagerRequest::SelectAccessPoint(ssid.clone()),
|
req: NetworkManagerRequest::SelectAccessPoint(ssid.clone()),
|
||||||
success: false,
|
success: false,
|
||||||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
state: NetworkManagerState::new(&conn)
|
||||||
})), false);
|
.await
|
||||||
}
|
.unwrap_or_default(),
|
||||||
status
|
},
|
||||||
}
|
)),
|
||||||
None => {
|
false,
|
||||||
(None, true)
|
);
|
||||||
}
|
|
||||||
}}
|
|
||||||
_ = active_conns_changed.next().boxed().fuse() => {
|
|
||||||
(Some((id, NetworkManagerEvent::ActiveConns(NetworkManagerState::new(&conn).await.unwrap_or_default()))), false)
|
|
||||||
}
|
|
||||||
_ = devices_changed.next().boxed().fuse() => {
|
|
||||||
let devices = network_manager.devices().await.ok().unwrap_or_default();
|
|
||||||
let wireless_access_point_futures: Vec<_> = devices.into_iter().map(|device| async move {
|
|
||||||
if let Ok(Some(SpecificDevice::Wireless(wireless_device))) =
|
|
||||||
device.downcast_to_device().await
|
|
||||||
{
|
|
||||||
handle_wireless_device(wireless_device).await.unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
}).collect();
|
|
||||||
let mut wireless_access_points = Vec::with_capacity(wireless_access_point_futures.len());
|
|
||||||
for f in wireless_access_point_futures {
|
|
||||||
wireless_access_points.append(&mut f.await);
|
|
||||||
}
|
}
|
||||||
(Some((id, NetworkManagerEvent::WirelessAccessPoints(NetworkManagerState::new(&conn).await.unwrap_or_default()))), false)
|
status
|
||||||
}
|
|
||||||
enabled = wireless_enabled_changed.next().boxed().fuse() => {
|
|
||||||
let update = if let Some(update) = enabled {
|
|
||||||
if let Ok(_) = update.get().await {
|
|
||||||
Some((id, NetworkManagerEvent::WiFiEnabled(NetworkManagerState::new(&conn).await.unwrap_or_default())))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
(update, false)
|
|
||||||
}
|
}
|
||||||
|
None => (None, true),
|
||||||
};
|
};
|
||||||
drop(active_conns_changed);
|
|
||||||
drop(wireless_enabled_changed);
|
|
||||||
drop(req);
|
|
||||||
(
|
(
|
||||||
update,
|
update,
|
||||||
if should_exit {
|
if should_exit {
|
||||||
|
|
@ -385,6 +517,7 @@ pub enum NetworkManagerEvent {
|
||||||
success: bool,
|
success: bool,
|
||||||
},
|
},
|
||||||
Init {
|
Init {
|
||||||
|
conn: Connection,
|
||||||
sender: UnboundedSender<NetworkManagerRequest>,
|
sender: UnboundedSender<NetworkManagerRequest>,
|
||||||
state: NetworkManagerState,
|
state: NetworkManagerState,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
use super::{NetworkManagerEvent, NetworkManagerState};
|
||||||
|
use cosmic::iced::{self, subscription};
|
||||||
|
use cosmic_dbus_networkmanager::nm::NetworkManager;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use log::error;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use zbus::Connection;
|
||||||
|
|
||||||
|
pub fn wireless_enabled_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||||
|
id: I,
|
||||||
|
conn: Connection,
|
||||||
|
) -> iced::Subscription<(I, NetworkManagerEvent)> {
|
||||||
|
subscription::unfold(id, State::Continue(conn), move |mut state| async move {
|
||||||
|
loop {
|
||||||
|
let (update, new_state) = start_listening(id, state).await;
|
||||||
|
state = new_state;
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum State {
|
||||||
|
Continue(Connection),
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_listening<I: Copy + Debug>(
|
||||||
|
id: I,
|
||||||
|
state: State,
|
||||||
|
) -> (Option<(I, NetworkManagerEvent)>, State) {
|
||||||
|
let conn = match state {
|
||||||
|
State::Continue(conn) => conn,
|
||||||
|
State::Error => iced::futures::future::pending().await,
|
||||||
|
};
|
||||||
|
let network_manager = match NetworkManager::new(&conn).await {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to connect to NetworkManager: {}", e);
|
||||||
|
return (None, State::Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut wireless_enabled_changed = network_manager.receive_wireless_enabled_changed().await;
|
||||||
|
wireless_enabled_changed.next().await;
|
||||||
|
|
||||||
|
let new_state = NetworkManagerState::new(&conn).await.unwrap_or_default();
|
||||||
|
|
||||||
|
(
|
||||||
|
Some((id, NetworkManagerEvent::WiFiEnabled(new_state))),
|
||||||
|
State::Continue(conn),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -6,5 +6,6 @@ license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
icon-loader = { version = "0.3.6", features = ["gtk"] }
|
icon-loader = { version = "0.3.6", features = ["gtk"] }
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["tokio", "wayland", "applet"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["tokio", "wayland"] }
|
||||||
|
cosmic-applet = { path = "../applet" }
|
||||||
nix = "0.24.1"
|
nix = "0.24.1"
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
use cosmic::applet::{CosmicAppletHelper, APPLET_BUTTON_THEME};
|
use cosmic::iced::wayland::popup::{destroy_popup, get_popup};
|
||||||
use cosmic::iced::wayland::{
|
|
||||||
popup::{destroy_popup, get_popup},
|
|
||||||
};
|
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{
|
||||||
widget::{button, column, row, text, Row, Space},
|
widget::{button, column, row, text, Row, Space},
|
||||||
window, Alignment, Application, Color, Command, Length, Subscription,
|
window, Alignment, Application, Color, Command, Length, Subscription,
|
||||||
};
|
};
|
||||||
|
use cosmic_applet::{applet_button_theme, CosmicAppletHelper};
|
||||||
|
|
||||||
use cosmic::iced_style::application::{self, Appearance};
|
use cosmic::iced_style::application::{self, Appearance};
|
||||||
|
|
||||||
|
use cosmic::iced_widget::Button;
|
||||||
use cosmic::theme::Svg;
|
use cosmic::theme::Svg;
|
||||||
use cosmic::widget::{divider, icon, toggler};
|
use cosmic::widget::{divider, icon, toggler};
|
||||||
use cosmic::Renderer;
|
use cosmic::Renderer;
|
||||||
|
|
@ -27,7 +26,7 @@ struct Notifications {
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
icon_name: String,
|
icon_name: String,
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
id_ctr: u32,
|
id_ctr: u128,
|
||||||
do_not_disturb: bool,
|
do_not_disturb: bool,
|
||||||
notifications: Vec<Vec<String>>,
|
notifications: Vec<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +60,7 @@ impl Application for Notifications {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
||||||
|
|
@ -69,10 +68,10 @@ impl Application for Notifications {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
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),
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
text_color: theme.cosmic().on_bg_color().into(),
|
text_color: theme.cosmic().on_bg_color().into(),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
|
|
@ -86,11 +85,11 @@ impl Application for Notifications {
|
||||||
destroy_popup(p)
|
destroy_popup(p)
|
||||||
} else {
|
} else {
|
||||||
self.id_ctr += 1;
|
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);
|
self.popup.replace(new_id);
|
||||||
|
|
||||||
let popup_settings = self.applet_helper.get_popup_settings(
|
let popup_settings = self.applet_helper.get_popup_settings(
|
||||||
window::Id::new(0),
|
window::Id(0),
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
@ -112,7 +111,7 @@ impl Application for Notifications {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, id: window::Id) -> Element<Message> {
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
if id == window::Id::new(0) {
|
if id == window::Id(0) {
|
||||||
self.applet_helper
|
self.applet_helper
|
||||||
.icon_button(&self.icon_name)
|
.icon_button(&self.icon_name)
|
||||||
.on_press(Message::TogglePopup)
|
.on_press(Message::TogglePopup)
|
||||||
|
|
@ -164,11 +163,9 @@ impl Application for Notifications {
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo put into libcosmic doing so will fix the row_button's boarder radius
|
// todo put into libcosmic doing so will fix the row_button's boarder radius
|
||||||
fn row_button(
|
fn row_button(mut content: Vec<Element<Message>>) -> Button<Message, Renderer> {
|
||||||
mut content: Vec<Element<Message>>,
|
content.insert(0, Space::with_width(Length::Fixed(24.0)).into());
|
||||||
) -> cosmic::iced_native::widget::Button<Message, Renderer> {
|
content.push(Space::with_width(Length::Fixed(24.0)).into());
|
||||||
content.insert(0, Space::with_width(Length::Units(24)).into());
|
|
||||||
content.push(Space::with_width(Length::Units(24)).into());
|
|
||||||
|
|
||||||
button(
|
button(
|
||||||
Row::with_children(content)
|
Row::with_children(content)
|
||||||
|
|
@ -176,8 +173,8 @@ fn row_button(
|
||||||
.align_items(Alignment::Center),
|
.align_items(Alignment::Center),
|
||||||
)
|
)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Units(36))
|
.height(Length::Fixed(36.0))
|
||||||
.style(APPLET_BUTTON_THEME)
|
.style(applet_button_theme())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_icon(name: &str, size: u16) -> cosmic::widget::Icon {
|
fn text_icon(name: &str, size: u16) -> cosmic::widget::Icon {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,15 @@ icon-loader = { version = "0.3.6", features = ["gtk"] }
|
||||||
libpulse-binding = "2.26.0"
|
libpulse-binding = "2.26.0"
|
||||||
libpulse-glib-binding = "2.25.0"
|
libpulse-glib-binding = "2.25.0"
|
||||||
tokio = { version = "1.20.1", features=["full"] }
|
tokio = { version = "1.20.1", features=["full"] }
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["tokio", "wayland", "applet"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["tokio", "wayland"] }
|
||||||
nix = "0.26.1"
|
cosmic-applet = { path = "../applet" }
|
||||||
zbus = "3.7"
|
nix = "0.26.2"
|
||||||
|
zbus = "3.13"
|
||||||
logind-zbus = "3.1"
|
logind-zbus = "3.1"
|
||||||
|
# Application i18n
|
||||||
|
i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] }
|
||||||
|
i18n-embed-fl = "0.6"
|
||||||
|
rust-embed = "6.6"
|
||||||
|
rust-embed-utils = "7.5.0"
|
||||||
|
once_cell = "1.17.1"
|
||||||
|
|
||||||
|
|
|
||||||
4
cosmic-applet-power/i18n.toml
Normal file
4
cosmic-applet-power/i18n.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
fallback_language = "en"
|
||||||
|
|
||||||
|
[fluent]
|
||||||
|
assets_dir = "i18n"
|
||||||
21
cosmic-applet-power/i18n/en/cosmic_applet_power.ftl
Normal file
21
cosmic-applet-power/i18n/en/cosmic_applet_power.ftl
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
power = Power
|
||||||
|
settings = Settings...
|
||||||
|
lock-screen = Lock Screen
|
||||||
|
lock-screen-shortcut = Super + Escape
|
||||||
|
log-out = Log Out
|
||||||
|
log-out-shortcut = Ctrl + Alt + Delete
|
||||||
|
suspend = Suspend
|
||||||
|
restart = Restart
|
||||||
|
shutdown = Shutdown
|
||||||
|
confirm = Confirm
|
||||||
|
cancel = Cancel
|
||||||
|
confirm-question =
|
||||||
|
Are you sure? { $action ->
|
||||||
|
[restart] { restart }
|
||||||
|
[suspend] { suspend }
|
||||||
|
[shutdown] { shutdown }
|
||||||
|
[lock-screen] Locking the screen
|
||||||
|
[log-out] Logging out
|
||||||
|
*[other] The selected action
|
||||||
|
} will continue in { $countdown } seconds.
|
||||||
|
|
||||||
47
cosmic-applet-power/src/localize.rs
Normal file
47
cosmic-applet-power/src/localize.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,28 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::process;
|
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::wayland::popup::{destroy_popup, get_popup};
|
||||||
use cosmic::iced_native::layout::Limits;
|
use cosmic::iced_runtime::core::layout::Limits;
|
||||||
use cosmic::iced_native::widget::Space;
|
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::widget::{button, divider, icon};
|
||||||
use cosmic::Renderer;
|
use cosmic::Renderer;
|
||||||
|
use cosmic_applet::{applet_button_theme, CosmicAppletHelper};
|
||||||
|
|
||||||
|
use cosmic::iced::Color;
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{
|
||||||
widget::{self, column, container, row, Row},
|
widget::{self, column, container, row, space::Space, text, Row},
|
||||||
window, Alignment, Application, Command, Length, Subscription,
|
window, Alignment, Application, Command, Length, Subscription,
|
||||||
};
|
};
|
||||||
use cosmic::iced_style::application::{self, Appearance};
|
use cosmic::iced_style::application::{self, Appearance};
|
||||||
use cosmic::iced_style::Color;
|
|
||||||
use cosmic::theme::{self, Svg};
|
use cosmic::theme::{self, Svg};
|
||||||
use cosmic::{Element, Theme};
|
use cosmic::{Element, Theme};
|
||||||
|
|
||||||
|
|
@ -20,9 +30,11 @@ use logind_zbus::manager::ManagerProxy;
|
||||||
use logind_zbus::session::{SessionProxy, SessionType};
|
use logind_zbus::session::{SessionProxy, SessionType};
|
||||||
use logind_zbus::user::UserProxy;
|
use logind_zbus::user::UserProxy;
|
||||||
use nix::unistd::getuid;
|
use nix::unistd::getuid;
|
||||||
|
use tokio::time::sleep;
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
pub mod cosmic_session;
|
pub mod cosmic_session;
|
||||||
|
mod localize;
|
||||||
pub mod session_manager;
|
pub mod session_manager;
|
||||||
|
|
||||||
use crate::cosmic_session::CosmicSessionProxy;
|
use crate::cosmic_session::CosmicSessionProxy;
|
||||||
|
|
@ -39,19 +51,28 @@ struct Power {
|
||||||
icon_name: String,
|
icon_name: String,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
id_ctr: u32,
|
id_ctr: u128,
|
||||||
|
action_to_confirm: Option<(window::Id, PowerAction)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum Message {
|
enum PowerAction {
|
||||||
Lock,
|
Lock,
|
||||||
LogOut,
|
LogOut,
|
||||||
Suspend,
|
Suspend,
|
||||||
Restart,
|
Restart,
|
||||||
Shutdown,
|
Shutdown,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
Timeout(window::Id),
|
||||||
|
Action(PowerAction),
|
||||||
TogglePopup,
|
TogglePopup,
|
||||||
Settings,
|
Settings,
|
||||||
Ignore,
|
Confirm,
|
||||||
|
Cancel,
|
||||||
|
Closed(window::Id),
|
||||||
Zbus(Result<(), zbus::Error>),
|
Zbus(Result<(), zbus::Error>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,26 +93,34 @@ impl Application for Power {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
String::from("Power")
|
fl!("power")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
fn close_requested(&self, id: window::Id) -> Self::Message {
|
||||||
Message::Ignore
|
Message::Closed(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
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),
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
text_color: theme.cosmic().on_bg_color().into(),
|
text_color: theme.cosmic().on_bg_color().into(),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
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> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
|
@ -101,21 +130,21 @@ impl Application for Power {
|
||||||
destroy_popup(p)
|
destroy_popup(p)
|
||||||
} else {
|
} else {
|
||||||
self.id_ctr += 1;
|
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);
|
self.popup.replace(new_id);
|
||||||
|
|
||||||
let mut popup_settings = self.applet_helper.get_popup_settings(
|
let mut popup_settings = self.applet_helper.get_popup_settings(
|
||||||
window::Id::new(0),
|
window::Id(0),
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
popup_settings.positioner.size_limits = Limits::NONE
|
popup_settings.positioner.size_limits = Limits::NONE
|
||||||
.min_width(100)
|
.min_width(100.0)
|
||||||
.min_height(100)
|
.min_height(100.0)
|
||||||
.max_height(400)
|
.max_height(400.0)
|
||||||
.max_width(500);
|
.max_width(500.0);
|
||||||
get_popup(popup_settings)
|
get_popup(popup_settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -123,51 +152,117 @@ impl Application for Power {
|
||||||
let _ = process::Command::new("cosmic-settings").spawn();
|
let _ = process::Command::new("cosmic-settings").spawn();
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::Lock => Command::perform(lock(), Message::Zbus),
|
Message::Action(action) => {
|
||||||
Message::LogOut => Command::perform(log_out(), Message::Zbus),
|
self.id_ctr += 1;
|
||||||
Message::Suspend => Command::perform(suspend(), Message::Zbus),
|
let id = window::Id(self.id_ctr);
|
||||||
Message::Restart => Command::perform(restart(), Message::Zbus),
|
self.action_to_confirm = Some((id, action));
|
||||||
Message::Shutdown => Command::perform(shutdown(), Message::Zbus),
|
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) => {
|
Message::Zbus(result) => {
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
eprintln!("cosmic-applet-power ERROR: '{}'", e);
|
eprintln!("cosmic-applet-power ERROR: '{}'", e);
|
||||||
}
|
}
|
||||||
Command::none()
|
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> {
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
if id == window::Id::new(0) {
|
if matches!(self.popup, Some(p) if p == id) {
|
||||||
self.applet_helper
|
let settings =
|
||||||
.icon_button(&self.icon_name)
|
row_button(vec![text(fl!("settings")).size(14).into()]).on_press(Message::Settings);
|
||||||
.on_press(Message::TogglePopup)
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
let settings = row_button(vec!["Settings...".into()]).on_press(Message::Settings);
|
|
||||||
|
|
||||||
let session = column![
|
let session = column![
|
||||||
row_button(vec![
|
row_button(vec![
|
||||||
text_icon("system-lock-screen-symbolic", 24).into(),
|
text_icon("system-lock-screen-symbolic", 24).into(),
|
||||||
"Lock Screen".into(),
|
text(fl!("lock-screen")).size(14).into(),
|
||||||
Space::with_width(Length::Fill).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![
|
row_button(vec![
|
||||||
text_icon("system-log-out-symbolic", 24).into(),
|
text_icon("system-log-out-symbolic", 24).into(),
|
||||||
"Log Out".into(),
|
text(fl!("log-out")).size(14).into(),
|
||||||
Space::with_width(Length::Fill).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![
|
let power = row![
|
||||||
power_buttons("system-lock-screen-symbolic", "Suspend").on_press(Message::Suspend),
|
power_buttons("system-lock-screen-symbolic", fl!("suspend"))
|
||||||
power_buttons("system-restart-symbolic", "Restart").on_press(Message::Restart),
|
.on_press(Message::Action(PowerAction::Suspend)),
|
||||||
power_buttons("system-shutdown-symbolic", "Shutdown").on_press(Message::Shutdown),
|
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)
|
.spacing(24)
|
||||||
.padding([0, 24]);
|
.padding([0, 24]);
|
||||||
|
|
@ -188,6 +283,65 @@ impl Application for Power {
|
||||||
.padding([8, 0]);
|
.padding([8, 0]);
|
||||||
|
|
||||||
self.applet_helper.popup_container(content).into()
|
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
|
// ### UI Helplers
|
||||||
|
|
||||||
fn row_button(content: Vec<Element<Message>>) -> widget::Button<Message, Renderer> {
|
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)
|
.custom(vec![Row::with_children(content)
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
|
|
@ -204,14 +358,14 @@ fn row_button(content: Vec<Element<Message>>) -> widget::Button<Message, Rendere
|
||||||
.padding([8, 24])
|
.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(
|
widget::button(
|
||||||
column![text_icon(name, 40), text]
|
column![text_icon(name, 40), text(msg).size(14)]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_items(Alignment::Center),
|
.align_items(Alignment::Center),
|
||||||
)
|
)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Units(76))
|
.height(Length::Fixed(76.0))
|
||||||
.style(theme::Button::Text)
|
.style(theme::Button::Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
icon-loader = { version = "0.3.6", features = ["gtk"] }
|
icon-loader = { version = "0.3.6", features = ["gtk"] }
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["tokio", "wayland", "applet"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["tokio", "wayland"] }
|
||||||
|
cosmic-applet = { path = "../applet" }
|
||||||
nix = "0.24.1"
|
nix = "0.24.1"
|
||||||
chrono = { version = "0.4.23", features = ["clock"] }
|
chrono = { version = "0.4.23", features = ["clock"] }
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
use cosmic::applet::{cosmic_panel_config::PanelAnchor, CosmicAppletHelper};
|
|
||||||
use cosmic::iced::wayland::popup::{destroy_popup, get_popup};
|
use cosmic::iced::wayland::popup::{destroy_popup, get_popup};
|
||||||
|
use cosmic::iced::Limits;
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{
|
||||||
time,
|
time,
|
||||||
wayland::InitialSurface,
|
wayland::InitialSurface,
|
||||||
widget::{button, column, text, vertical_space},
|
widget::{button, column, text, vertical_space},
|
||||||
window, Alignment, Application, Color, Command, Length, Rectangle, Subscription,
|
window, Alignment, Application, Color, Command, Length, Rectangle, Subscription,
|
||||||
};
|
};
|
||||||
use cosmic::iced_sctk::layout::Limits;
|
|
||||||
use cosmic::iced_style::application::{self, Appearance};
|
use cosmic::iced_style::application::{self, Appearance};
|
||||||
use cosmic::theme;
|
use cosmic::theme;
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
widget::{icon, rectangle_tracker::*},
|
widget::{icon, rectangle_tracker::*},
|
||||||
Element, Theme,
|
Element, Theme,
|
||||||
};
|
};
|
||||||
|
use cosmic_applet::{cosmic_panel_config::PanelAnchor, CosmicAppletHelper};
|
||||||
|
|
||||||
use chrono::{DateTime, Local, Timelike};
|
use chrono::{DateTime, Local, Timelike};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
@ -22,10 +22,8 @@ pub fn main() -> cosmic::iced::Result {
|
||||||
let mut settings = helper.window_settings();
|
let mut settings = helper.window_settings();
|
||||||
match &mut settings.initial_surface {
|
match &mut settings.initial_surface {
|
||||||
InitialSurface::XdgWindow(s) => {
|
InitialSurface::XdgWindow(s) => {
|
||||||
s.iced_settings.min_size = Some((1, 1));
|
|
||||||
s.iced_settings.max_size = None;
|
|
||||||
s.autosize = true;
|
s.autosize = true;
|
||||||
s.size_limits = Limits::NONE.min_height(1).min_width(1);
|
s.size_limits = Limits::NONE.min_height(1.0).min_width(1.0);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
@ -36,7 +34,7 @@ struct Time {
|
||||||
applet_helper: CosmicAppletHelper,
|
applet_helper: CosmicAppletHelper,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
id_ctr: u32,
|
id_ctr: u128,
|
||||||
update_at: Every,
|
update_at: Every,
|
||||||
now: DateTime<Local>,
|
now: DateTime<Local>,
|
||||||
msg: String,
|
msg: String,
|
||||||
|
|
@ -90,7 +88,7 @@ impl Application for Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
||||||
|
|
@ -98,10 +96,10 @@ impl Application for Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
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),
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
text_color: theme.cosmic().on_bg_color().into(),
|
text_color: theme.cosmic().on_bg_color().into(),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
|
|
@ -120,7 +118,7 @@ impl Application for Time {
|
||||||
.expect("Setting nanoseconds to 0 should always be possible.");
|
.expect("Setting nanoseconds to 0 should always be possible.");
|
||||||
let wait = 1.max((next - now).num_milliseconds());
|
let wait = 1.max((next - now).num_milliseconds());
|
||||||
Subscription::batch(vec![
|
Subscription::batch(vec![
|
||||||
rectangle_tracker_subscription(0).map(|(_, update)| Message::Rectangle(update)),
|
rectangle_tracker_subscription(0).map(|e| Message::Rectangle(e.1)),
|
||||||
time::every(Duration::from_millis(
|
time::every(Duration::from_millis(
|
||||||
wait.try_into().unwrap_or(FALLBACK_DELAY),
|
wait.try_into().unwrap_or(FALLBACK_DELAY),
|
||||||
))
|
))
|
||||||
|
|
@ -149,11 +147,11 @@ impl Application for Time {
|
||||||
.to_string();
|
.to_string();
|
||||||
self.msg = calendar;
|
self.msg = calendar;
|
||||||
self.id_ctr += 1;
|
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);
|
self.popup.replace(new_id);
|
||||||
|
|
||||||
let mut popup_settings = self.applet_helper.get_popup_settings(
|
let mut popup_settings = self.applet_helper.get_popup_settings(
|
||||||
window::Id::new(0),
|
window::Id(0),
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
@ -194,13 +192,13 @@ impl Application for Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, id: window::Id) -> Element<Message> {
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
if id == window::Id::new(0) {
|
if id == window::Id(0) {
|
||||||
let button = button(
|
let button = button(
|
||||||
if matches!(
|
if matches!(
|
||||||
self.applet_helper.anchor,
|
self.applet_helper.anchor,
|
||||||
PanelAnchor::Top | PanelAnchor::Bottom
|
PanelAnchor::Top | PanelAnchor::Bottom
|
||||||
) {
|
) {
|
||||||
column![text(self.now.format("%b %-d %-I:%M %p").to_string())]
|
column![text(self.now.format("%b %-d %-I:%M %p").to_string()).size(14)]
|
||||||
} else {
|
} else {
|
||||||
let mut date_time_col = column![
|
let mut date_time_col = column![
|
||||||
icon(
|
icon(
|
||||||
|
|
@ -208,10 +206,10 @@ impl Application for Time {
|
||||||
self.applet_helper.suggested_size().0
|
self.applet_helper.suggested_size().0
|
||||||
)
|
)
|
||||||
.style(theme::Svg::Symbolic),
|
.style(theme::Svg::Symbolic),
|
||||||
text(self.now.format("%I").to_string()),
|
text(self.now.format("%I").to_string()).size(14),
|
||||||
text(self.now.format("%M").to_string()),
|
text(self.now.format("%M").to_string()).size(14),
|
||||||
text(self.now.format("%p").to_string()),
|
text(self.now.format("%p").to_string()).size(14),
|
||||||
vertical_space(Length::Units(4)),
|
vertical_space(Length::Fixed(4.0)),
|
||||||
// TODO better calendar icon?
|
// TODO better calendar icon?
|
||||||
icon(
|
icon(
|
||||||
"calendar-go-today-symbolic",
|
"calendar-go-today-symbolic",
|
||||||
|
|
@ -222,7 +220,7 @@ impl Application for Time {
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.spacing(4);
|
.spacing(4);
|
||||||
for d in self.now.format("%x").to_string().split("/") {
|
for d in self.now.format("%x").to_string().split("/") {
|
||||||
date_time_col = date_time_col.push(text(d.to_string()));
|
date_time_col = date_time_col.push(text(d.to_string()).size(14));
|
||||||
}
|
}
|
||||||
date_time_col
|
date_time_col
|
||||||
},
|
},
|
||||||
|
|
@ -240,7 +238,7 @@ impl Application for Time {
|
||||||
.align_items(Alignment::Start)
|
.align_items(Alignment::Start)
|
||||||
.spacing(12)
|
.spacing(12)
|
||||||
.padding([24, 0])
|
.padding([24, 0])
|
||||||
.push(text(&self.msg))
|
.push(text(&self.msg).size(14))
|
||||||
.padding(8);
|
.padding(8);
|
||||||
|
|
||||||
self.applet_helper.popup_container(content).into()
|
self.applet_helper.popup_container(content).into()
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,16 @@ authors = ["Ashley Wulber <ashley@system76.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["tokio", "wayland", "applet"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["tokio", "wayland"] }
|
||||||
cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false }
|
cosmic-applet = { path = "../applet" }
|
||||||
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = ["client"] }
|
cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, rev = "f0cfe09" }
|
||||||
|
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = ["client"], rev = "f0cfe09" }
|
||||||
wayland-backend = {version = "0.1.0", features = ["client_system"]}
|
wayland-backend = {version = "0.1.0", features = ["client_system"]}
|
||||||
wayland-client = {version = "0.30.0"}
|
wayland-client = {version = "0.30.0"}
|
||||||
calloop = "0.10.1"
|
calloop = "0.10.1"
|
||||||
nix = "0.26.1"
|
nix = "0.26.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.5"
|
||||||
once_cell = "1.9"
|
once_cell = "1.9"
|
||||||
futures = "0.3.21"
|
futures = "0.3.21"
|
||||||
xdg = "2.4.0"
|
xdg = "2.4.0"
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
use calloop::channel::SyncSender;
|
use calloop::channel::SyncSender;
|
||||||
use cosmic::applet::cosmic_panel_config::PanelAnchor;
|
|
||||||
use cosmic::applet::CosmicAppletHelper;
|
|
||||||
use cosmic::iced::alignment::{Horizontal, Vertical};
|
use cosmic::iced::alignment::{Horizontal, Vertical};
|
||||||
use cosmic::iced::mouse::{self, ScrollDelta};
|
use cosmic::iced::mouse::{self, ScrollDelta};
|
||||||
use cosmic::iced::wayland::actions::window::SctkWindowSettings;
|
use cosmic::iced::wayland::actions::window::SctkWindowSettings;
|
||||||
use cosmic::iced::wayland::{window::resize_window, InitialSurface};
|
use cosmic::iced::wayland::{window::resize_window, InitialSurface};
|
||||||
use cosmic::iced::widget::{column, container, row, text};
|
use cosmic::iced::widget::{column, container, row, text};
|
||||||
|
use cosmic::iced::Color;
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{
|
||||||
subscription, widget::button, window, Application, Command, Event::Mouse, Length, Settings,
|
subscription, widget::button, window, Application, Command, Event::Mouse, Length, Settings,
|
||||||
Subscription,
|
Subscription,
|
||||||
};
|
};
|
||||||
use cosmic::iced_style::application::{self, Appearance};
|
use cosmic::iced_style::application::{self, Appearance};
|
||||||
use cosmic::iced_style::Color;
|
|
||||||
use cosmic::theme::Button;
|
use cosmic::theme::Button;
|
||||||
use cosmic::{Element, Theme};
|
use cosmic::{Element, Theme};
|
||||||
|
use cosmic_applet::cosmic_panel_config::PanelAnchor;
|
||||||
|
use cosmic_applet::CosmicAppletHelper;
|
||||||
use cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1;
|
use cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use wayland_backend::client::ObjectId;
|
use wayland_backend::client::ObjectId;
|
||||||
|
|
@ -25,10 +25,7 @@ use crate::wayland_subscription::{workspaces, WorkspacesUpdate};
|
||||||
pub fn run() -> cosmic::iced::Result {
|
pub fn run() -> cosmic::iced::Result {
|
||||||
let settings = Settings {
|
let settings = Settings {
|
||||||
initial_surface: InitialSurface::XdgWindow(SctkWindowSettings {
|
initial_surface: InitialSurface::XdgWindow(SctkWindowSettings {
|
||||||
iced_settings: cosmic::iced_native::window::Settings {
|
size: (32, 32),
|
||||||
size: (32, 32),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -103,7 +100,7 @@ impl Application for IcedWorkspacesApplet {
|
||||||
Layout::Row => (unit * self.workspaces.len().max(1) as u32, unit),
|
Layout::Row => (unit * self.workspaces.len().max(1) as u32, unit),
|
||||||
Layout::Column => (unit, unit * self.workspaces.len().max(1) as u32),
|
Layout::Column => (unit, unit * self.workspaces.len().max(1) as u32),
|
||||||
};
|
};
|
||||||
return resize_window(window::Id::new(0), w, h);
|
return resize_window(window::Id(0), w, h);
|
||||||
}
|
}
|
||||||
WorkspacesUpdate::Started(tx) => {
|
WorkspacesUpdate::Started(tx) => {
|
||||||
self.workspace_tx.replace(tx);
|
self.workspace_tx.replace(tx);
|
||||||
|
|
@ -141,6 +138,7 @@ impl Application for IcedWorkspacesApplet {
|
||||||
.filter_map(|w| {
|
.filter_map(|w| {
|
||||||
let btn = button(
|
let btn = button(
|
||||||
text(w.0.clone())
|
text(w.0.clone())
|
||||||
|
.size(14)
|
||||||
.horizontal_alignment(Horizontal::Center)
|
.horizontal_alignment(Horizontal::Center)
|
||||||
.vertical_alignment(Vertical::Center)
|
.vertical_alignment(Vertical::Center)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
@ -184,7 +182,7 @@ impl Application for IcedWorkspacesApplet {
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
Subscription::batch(
|
Subscription::batch(
|
||||||
vec![
|
vec![
|
||||||
workspaces(0).map(|(_, msg)| Message::WorkspaceUpdate(msg)),
|
workspaces(0).map(|e| Message::WorkspaceUpdate(e.1)),
|
||||||
subscription::events_with(|e, _| match e {
|
subscription::events_with(|e, _| match e {
|
||||||
Mouse(mouse::Event::WheelScrolled { delta }) => {
|
Mouse(mouse::Event::WheelScrolled { delta }) => {
|
||||||
Some(Message::WheelScrolled(delta))
|
Some(Message::WheelScrolled(delta))
|
||||||
|
|
@ -197,7 +195,7 @@ impl Application for IcedWorkspacesApplet {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
||||||
|
|
@ -205,9 +203,9 @@ impl Application for IcedWorkspacesApplet {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
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),
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
text_color: theme.cosmic().on_bg_color().into(),
|
text_color: theme.cosmic().on_bg_color().into(),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
global_conf = configuration_data()
|
|
||||||
global_conf.set_quoted('APP_ID', application_id)
|
|
||||||
global_conf.set_quoted('PROFILE', profile)
|
|
||||||
global_conf.set_quoted('VERSION', version + version_suffix)
|
|
||||||
config = configure_file(
|
|
||||||
input: 'config.rs.in',
|
|
||||||
output: 'config.rs',
|
|
||||||
configuration: global_conf
|
|
||||||
)
|
|
||||||
# Copy the config.rs output to the source directory.
|
|
||||||
run_command(
|
|
||||||
'cp',
|
|
||||||
meson.project_build_root() / 'src' / 'config.rs',
|
|
||||||
meson.project_source_root() / 'src' / 'config.rs',
|
|
||||||
check: true
|
|
||||||
)
|
|
||||||
|
|
||||||
cargo_options = [ '--manifest-path', meson.project_source_root() / 'Cargo.toml' ]
|
|
||||||
cargo_options += [ '--target-dir', meson.project_build_root() / 'src' ]
|
|
||||||
|
|
||||||
if get_option('profile') == 'default'
|
|
||||||
cargo_options += [ '--release' ]
|
|
||||||
rust_target = 'release'
|
|
||||||
message('Building in release mode')
|
|
||||||
else
|
|
||||||
rust_target = 'debug'
|
|
||||||
message('Building in debug mode')
|
|
||||||
endif
|
|
||||||
|
|
||||||
if get_option('vendor') == true
|
|
||||||
cargo_options += [ '--locked' ]
|
|
||||||
message('Building with vendoring')
|
|
||||||
endif
|
|
||||||
|
|
||||||
cargo_env = [ 'CARGO_HOME=' + meson.project_build_root() / 'cargo-home' ]
|
|
||||||
|
|
||||||
cargo_build = custom_target(
|
|
||||||
'cargo-build',
|
|
||||||
build_by_default: true,
|
|
||||||
build_always_stale: true,
|
|
||||||
output: meson.project_name(),
|
|
||||||
console: true,
|
|
||||||
install: true,
|
|
||||||
install_dir: bindir,
|
|
||||||
command: [
|
|
||||||
'env',
|
|
||||||
cargo_env,
|
|
||||||
cargo, 'build',
|
|
||||||
cargo_options,
|
|
||||||
'&&',
|
|
||||||
'cp', 'src' / rust_target / meson.project_name(), '@OUTPUT@',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use calloop::channel::*;
|
use calloop::channel::*;
|
||||||
use cosmic::applet::cosmic_panel_config::CosmicPanelOuput;
|
|
||||||
use cosmic_client_toolkit::{
|
use cosmic_client_toolkit::{
|
||||||
sctk::{
|
sctk::{
|
||||||
self,
|
self,
|
||||||
|
|
@ -11,7 +10,7 @@ use cosmic_client_toolkit::{
|
||||||
};
|
};
|
||||||
use cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1;
|
use cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1;
|
||||||
use futures::{channel::mpsc, executor::block_on, SinkExt};
|
use futures::{channel::mpsc, executor::block_on, SinkExt};
|
||||||
use std::{env, os::unix::net::UnixStream, path::PathBuf, str::FromStr, time::Duration};
|
use std::{env, os::unix::net::UnixStream, path::PathBuf, time::Duration};
|
||||||
use wayland_backend::client::ObjectId;
|
use wayland_backend::client::ObjectId;
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
globals::registry_queue_init,
|
globals::registry_queue_init,
|
||||||
|
|
@ -45,10 +44,6 @@ pub fn spawn_workspaces(tx: mpsc::Sender<WorkspaceList>) -> SyncSender<Workspace
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let configured_output = std::env::var("COSMIC_PANEL_OUTPUT")
|
let configured_output = std::env::var("COSMIC_PANEL_OUTPUT")
|
||||||
.ok()
|
.ok()
|
||||||
.map(|output_str| match CosmicPanelOuput::from_str(&output_str) {
|
|
||||||
Ok(CosmicPanelOuput::Name(name)) => name,
|
|
||||||
_ => "".to_string(),
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let mut event_loop = calloop::EventLoop::<State>::try_new().unwrap();
|
let mut event_loop = calloop::EventLoop::<State>::try_new().unwrap();
|
||||||
let loop_handle = event_loop.handle();
|
let loop_handle = event_loop.handle();
|
||||||
|
|
@ -97,7 +92,11 @@ pub fn spawn_workspaces(tx: mpsc::Sender<WorkspaceList>) -> SyncSender<Workspace
|
||||||
.workspace_groups()
|
.workspace_groups()
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|g| {
|
.find_map(|g| {
|
||||||
if !g.outputs.iter().any(|o| Some(o) == state.expected_output.as_ref()) {
|
if !g
|
||||||
|
.outputs
|
||||||
|
.iter()
|
||||||
|
.any(|o| Some(o) == state.expected_output.as_ref())
|
||||||
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
g.workspaces
|
g.workspaces
|
||||||
|
|
@ -179,7 +178,10 @@ impl State {
|
||||||
.workspace_groups()
|
.workspace_groups()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|g| {
|
.filter_map(|g| {
|
||||||
if g.outputs.iter().any(|o| Some(o) == self.expected_output.as_ref()) {
|
if g.outputs
|
||||||
|
.iter()
|
||||||
|
.any(|o| Some(o) == self.expected_output.as_ref())
|
||||||
|
{
|
||||||
Some(g.workspaces.iter().map(|w| {
|
Some(g.workspaces.iter().map(|w| {
|
||||||
(
|
(
|
||||||
w.name.clone(),
|
w.name.clone(),
|
||||||
|
|
|
||||||
|
|
@ -18,29 +18,36 @@ pub fn workspaces<I: 'static + Hash + Copy + Send + Sync>(
|
||||||
subscription::unfold(id, State::Ready, move |state| _workspaces(id, state))
|
subscription::unfold(id, State::Ready, move |state| _workspaces(id, state))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn _workspaces<I: Copy>(id: I, state: State) -> (Option<(I, WorkspacesUpdate)>, State) {
|
async fn _workspaces<I: Copy>(id: I, mut state: State) -> ((I, WorkspacesUpdate), State) {
|
||||||
match state {
|
loop {
|
||||||
State::Ready => {
|
let (update, new_state) = match state {
|
||||||
if let Ok(watcher) = WorkspacesWatcher::new() {
|
State::Ready => {
|
||||||
(
|
if let Ok(watcher) = WorkspacesWatcher::new() {
|
||||||
Some((id, WorkspacesUpdate::Started(watcher.get_sender()))),
|
(
|
||||||
State::Waiting(watcher),
|
Some((id, WorkspacesUpdate::Started(watcher.get_sender()))),
|
||||||
)
|
State::Waiting(watcher),
|
||||||
} else {
|
)
|
||||||
(Some((id, WorkspacesUpdate::Errored)), State::Error)
|
} else {
|
||||||
|
(Some((id, WorkspacesUpdate::Errored)), State::Error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
State::Waiting(mut t) => {
|
||||||
State::Waiting(mut t) => {
|
if let Some(w) = t.workspaces().await {
|
||||||
if let Some(w) = t.workspaces().await {
|
(
|
||||||
(
|
Some((id, WorkspacesUpdate::Workspaces(w))),
|
||||||
Some((id, WorkspacesUpdate::Workspaces(w))),
|
State::Waiting(t),
|
||||||
State::Waiting(t),
|
)
|
||||||
)
|
} else {
|
||||||
} else {
|
(Some((id, WorkspacesUpdate::Errored)), State::Error)
|
||||||
(Some((id, WorkspacesUpdate::Errored)), State::Error)
|
}
|
||||||
}
|
}
|
||||||
|
State::Error => cosmic::iced::futures::future::pending().await,
|
||||||
|
};
|
||||||
|
state = new_state;
|
||||||
|
|
||||||
|
if let Some(update) = update {
|
||||||
|
return (update, state);
|
||||||
}
|
}
|
||||||
State::Error => cosmic::iced::futures::future::pending().await,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
freedesktop-desktop-entry = "0.5.0"
|
freedesktop-desktop-entry = "0.5.0"
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["tokio", "wayland", "applet"] }
|
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "31f7e97", default-features = false, features = ["tokio", "wayland"] }
|
||||||
|
cosmic-applet = { path = "../applet" }
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
applet::CosmicAppletHelper,
|
iced::Limits,
|
||||||
iced::{
|
iced::{self, wayland::InitialSurface, Application},
|
||||||
self,
|
iced_runtime::core::window,
|
||||||
wayland::InitialSurface,
|
|
||||||
Application,
|
|
||||||
},
|
|
||||||
iced_sctk::layout::Limits,
|
|
||||||
iced_style::application,
|
iced_style::application,
|
||||||
iced_native::window,
|
|
||||||
};
|
};
|
||||||
|
use cosmic_applet::CosmicAppletHelper;
|
||||||
use freedesktop_desktop_entry::DesktopEntry;
|
use freedesktop_desktop_entry::DesktopEntry;
|
||||||
use std::{env, fs, process::Command};
|
use std::{env, fs, process::Command};
|
||||||
|
|
||||||
|
|
@ -47,10 +43,12 @@ impl iced::Application for Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
||||||
<Self::Theme as application::StyleSheet>::Style::Custom(|theme| application::Appearance {
|
<Self::Theme as application::StyleSheet>::Style::Custom(Box::new(|theme| {
|
||||||
background_color: iced::Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
application::Appearance {
|
||||||
text_color: theme.cosmic().on_bg_color().into(),
|
background_color: iced::Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
})
|
text_color: theme.cosmic().on_bg_color().into(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> iced::Subscription<Msg> {
|
fn subscription(&self) -> iced::Subscription<Msg> {
|
||||||
|
|
@ -112,10 +110,8 @@ pub fn main() -> iced::Result {
|
||||||
};
|
};
|
||||||
match &mut settings.initial_surface {
|
match &mut settings.initial_surface {
|
||||||
InitialSurface::XdgWindow(s) => {
|
InitialSurface::XdgWindow(s) => {
|
||||||
s.iced_settings.min_size = Some((1, 1));
|
|
||||||
s.iced_settings.max_size = None;
|
|
||||||
s.autosize = true;
|
s.autosize = true;
|
||||||
s.size_limits = Limits::NONE.min_height(1).min_width(1);
|
s.size_limits = Limits::NONE.min_height(1.0).min_width(1.0);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue