chore: updates after iced-rebase

This commit is contained in:
Ashley Wulber 2026-03-31 16:34:59 -04:00 committed by GitHub
parent bd0d180482
commit 71d9d6d5bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 1786 additions and 2396 deletions

2736
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -30,8 +30,6 @@ cosmic-applets-config = { path = "cosmic-applets-config" }
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = [ cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = [
"client", "client",
], rev = "d0e95be" } ], rev = "d0e95be" }
cosmic-time = { git = "https://github.com/pop-os/cosmic-time", default-features = false }
# cosmic-time = { path = "../cosmic-time", default-features = false ] }
futures = "0.3" futures = "0.3"
futures-util = "0.3" futures-util = "0.3"
@ -59,24 +57,26 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
tracing-log = "0.2.0" tracing-log = "0.2.0"
tokio = { version = "1.49.0", features = ["full"] } tokio = { version = "1.49.0", features = ["full"] }
# cosmic-config = { path = "../libcosmic/cosmic-config" }
cosmic-config = { git = "https://github.com/pop-os/libcosmic" } cosmic-config = { git = "https://github.com/pop-os/libcosmic" }
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
[profile.release] [profile.release]
opt-level = 3 # opt-level = 3
panic = "abort" # panic = "abort"
lto = "thin" # lto = "thin"
opt-level = 1
[workspace.metadata.cargo-machete] [workspace.metadata.cargo-machete]
ignored = ["libcosmic"] ignored = ["libcosmic"]
# [patch."https://github.com/pop-os/libcosmic"] # [patch."https://github.com/pop-os/libcosmic"]
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "" }
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "" }
# iced_futures = { git = "https://github.com/pop-os/libcosmic//", branch = "" }
# cosmic-config = { path = "../libcosmic/cosmic-config" } # cosmic-config = { path = "../libcosmic/cosmic-config" }
# libcosmic = { path = "../libcosmic" } # libcosmic = { path = "../libcosmic" }
# iced_futures = { path = "../libcosmic/iced/futures" } # iced_futures = { path = "../libcosmic/iced/futures" }
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//" }
# libcosmic = { git = "https://github.com/pop-os/libcosmic//" }
# iced_futures = { git = "https://github.com/pop-os/libcosmic//" }
# [patch."https://github.com/pop-os/winit.git"] # [patch."https://github.com/pop-os/winit.git"]
# winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" } # winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" }

View file

@ -19,7 +19,6 @@ use cctk::{
workspace::v1::client::ext_workspace_handle_v1::ExtWorkspaceHandleV1, workspace::v1::client::ext_workspace_handle_v1::ExtWorkspaceHandleV1,
}, },
}; };
use cosmic::desktop::fde::{self, DesktopEntry, get_languages_from_env, unicase::Ascii};
use cosmic::{ use cosmic::{
Apply, Element, Task, app, Apply, Element, Task, app,
applet::{ applet::{
@ -34,20 +33,27 @@ use cosmic::{
clipboard::mime::{AllowedMimeTypes, AsMimeTypes}, clipboard::mime::{AllowedMimeTypes, AsMimeTypes},
event::listen_with, event::listen_with,
platform_specific::shell::commands::popup::{destroy_popup, get_popup}, platform_specific::shell::commands::popup::{destroy_popup, get_popup},
widget::{Column, Row, column, mouse_area, row, stack, vertical_rule, vertical_space}, widget::{
Column, Row, column, mouse_area, row, rule::vertical as vertical_rule,
space::horizontal as horizontal_space, space::vertical as vertical_space, stack,
},
window, window,
}, },
iced_runtime::{core::event, dnd::peek_dnd}, iced_runtime::{core::event, dnd::peek_dnd},
surface, surface,
theme::{self, Button, Container}, theme::{self, Button, Container},
widget::{ widget::{
DndDestination, Image, button, container, divider, dnd_source, horizontal_space, DndDestination, Image, button, container, divider, dnd_source,
icon::{self, from_name}, icon::{self, from_name},
image::Handle, image::Handle,
rectangle_tracker::{RectangleTracker, RectangleUpdate, rectangle_tracker_subscription}, rectangle_tracker::{RectangleTracker, RectangleUpdate, rectangle_tracker_subscription},
svg, text, svg, text,
}, },
}; };
use cosmic::{
desktop::fde::{self, DesktopEntry, get_languages_from_env, unicase::Ascii},
widget::DndSource,
};
use cosmic_app_list_config::{APP_ID, AppListConfig}; use cosmic_app_list_config::{APP_ID, AppListConfig};
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::State; use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::State;
use futures::future::pending; use futures::future::pending;
@ -295,7 +301,7 @@ impl DockItem {
let path = desktop_info.path.clone(); let path = desktop_info.path.clone();
let icon_button = if dnd_source_enabled && interaction_enabled { let icon_button = if dnd_source_enabled && interaction_enabled {
dnd_source(icon_button) DndSource::with_id(icon_button, cosmic::widget::Id::new("asdfasdfadfs"))
.window(window_id) .window(window_id)
.drag_icon(move |_| { .drag_icon(move |_| {
( (
@ -2410,7 +2416,7 @@ impl cosmic::Application for CosmicAppList {
]) ])
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }

View file

@ -13,7 +13,7 @@ use cctk::{
}; };
use cosmic::{ use cosmic::{
iced::{self, Subscription, stream}, iced::{self, Subscription, stream},
iced_core::image::Bytes, iced_core::Bytes,
}; };
use image::EncodableLayout; use image::EncodableLayout;
@ -31,16 +31,15 @@ pub static WAYLAND_RX: LazyLock<Mutex<Option<UnboundedReceiver<WaylandUpdate>>>>
LazyLock::new(|| Mutex::new(None)); LazyLock::new(|| Mutex::new(None));
pub fn wayland_subscription() -> iced::Subscription<WaylandUpdate> { pub fn wayland_subscription() -> iced::Subscription<WaylandUpdate> {
Subscription::run_with_id( Subscription::run_with(std::any::TypeId::of::<WaylandUpdate>(), |_| {
std::any::TypeId::of::<WaylandUpdate>(),
stream::channel(50, move |mut output| async move { stream::channel(50, move |mut output| async move {
let mut state = State::Waiting; let mut state = State::Waiting;
loop { loop {
state = start_listening(state, &mut output).await; state = start_listening(state, &mut output).await;
} }
}), })
) })
} }
pub enum State { pub enum State {

View file

@ -7,7 +7,6 @@ edition = "2024"
anyhow.workspace = true anyhow.workspace = true
cctk.workspace = true cctk.workspace = true
cosmic-protocols.workspace = true cosmic-protocols.workspace = true
cosmic-time.workspace = true
i18n-embed-fl.workspace = true i18n-embed-fl.workspace = true
i18n-embed.workspace = true i18n-embed.workspace = true
libcosmic.workspace = true libcosmic.workspace = true
@ -19,6 +18,8 @@ tracing.workspace = true
[dependencies.cosmic-settings-a11y-manager-subscription] [dependencies.cosmic-settings-a11y-manager-subscription]
git = "https://github.com/pop-os/cosmic-settings" git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/a11y-manager"
[dependencies.cosmic-settings-accessibility-subscription] [dependencies.cosmic-settings-accessibility-subscription]
git = "https://github.com/pop-os/cosmic-settings" git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/accessibility"

View file

@ -22,23 +22,16 @@ use cosmic::{
}, },
surface, surface,
theme::{self, CosmicTheme}, theme::{self, CosmicTheme},
widget::{Column, divider, text}, widget::{Column, divider, text, toggler},
}; };
use cosmic_settings_a11y_manager_subscription::{ use cosmic_settings_a11y_manager_subscription::{
self as cosmic_a11y_manager, AccessibilityEvent, AccessibilityRequest, ColorFilter, self as cosmic_a11y_manager, AccessibilityEvent, AccessibilityRequest, ColorFilter,
}; };
use cosmic_settings_accessibility_subscription::{self as accessibility}; use cosmic_settings_accessibility_subscription::{self as accessibility};
use cosmic_time::{Instant, Timeline, anim, chain, id};
use std::sync::LazyLock; use std::sync::LazyLock;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
static READER_TOGGLE: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
static FILTER_TOGGLE: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
static HC_TOGGLE: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
static MAGNIFIER_TOGGLE: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
static INVERT_COLORS_TOGGLE: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
pub fn run() -> cosmic::iced::Result { pub fn run() -> cosmic::iced::Result {
cosmic::applet::run::<CosmicA11yApplet>(()) cosmic::applet::run::<CosmicA11yApplet>(())
} }
@ -54,7 +47,6 @@ struct CosmicA11yApplet {
dbus_sender: Option<UnboundedSender<accessibility::Request>>, dbus_sender: Option<UnboundedSender<accessibility::Request>>,
wayland_sender: Option<calloop::channel::Sender<AccessibilityRequest>>, wayland_sender: Option<calloop::channel::Sender<AccessibilityRequest>>,
wayland_protocol_version: Option<u32>, wayland_protocol_version: Option<u32>,
timeline: Timeline,
token_tx: Option<channel::Sender<TokenRequest>>, token_tx: Option<channel::Sender<TokenRequest>>,
screen_filter_active: bool, screen_filter_active: bool,
} }
@ -63,12 +55,11 @@ struct CosmicA11yApplet {
enum Message { enum Message {
TogglePopup, TogglePopup,
CloseRequested(window::Id), CloseRequested(window::Id),
HighContrastEnabled(chain::Toggler, bool), HighContrastEnabled(bool),
ScreenReaderEnabled(chain::Toggler, bool), ScreenReaderEnabled(bool),
MagnifierEnabled(chain::Toggler, bool), MagnifierEnabled(bool),
InvertedColorsEnabled(chain::Toggler, bool), InvertedColorsEnabled(bool),
FilterColorsEnabled(chain::Toggler, bool), FilterColorsEnabled(bool),
Frame(Instant),
Token(TokenUpdate), Token(TokenUpdate),
OpenSettings, OpenSettings,
DBusUpdate(accessibility::Response), DBusUpdate(accessibility::Response),
@ -104,28 +95,24 @@ impl cosmic::Application for CosmicA11yApplet {
fn update(&mut self, message: Self::Message) -> app::Task<Self::Message> { fn update(&mut self, message: Self::Message) -> app::Task<Self::Message> {
match message { match message {
Message::Frame(now) => self.timeline.now(now), Message::ScreenReaderEnabled(enabled) => {
Message::ScreenReaderEnabled(chain, enabled) => {
if let Some(tx) = &self.dbus_sender { if let Some(tx) = &self.dbus_sender {
self.timeline.set_chain(chain).start();
self.reader_enabled = enabled; self.reader_enabled = enabled;
let _ = tx.send(accessibility::Request::ScreenReader(enabled)); let _ = tx.send(accessibility::Request::ScreenReader(enabled));
} else { } else {
self.reader_enabled = false; self.reader_enabled = false;
} }
} }
Message::MagnifierEnabled(chain, enabled) => { Message::MagnifierEnabled(enabled) => {
if let Some(tx) = &self.wayland_sender { if let Some(tx) = &self.wayland_sender {
self.timeline.set_chain(chain).start();
self.magnifier_enabled = enabled; self.magnifier_enabled = enabled;
let _ = tx.send(AccessibilityRequest::Magnifier(enabled)); let _ = tx.send(AccessibilityRequest::Magnifier(enabled));
} else { } else {
self.magnifier_enabled = false; self.magnifier_enabled = false;
} }
} }
Message::InvertedColorsEnabled(chain, enabled) => { Message::InvertedColorsEnabled(enabled) => {
if let Some(tx) = &self.wayland_sender { if let Some(tx) = &self.wayland_sender {
self.timeline.set_chain(chain).start();
self.inverted_colors_enabled = enabled; self.inverted_colors_enabled = enabled;
let _ = tx.send(AccessibilityRequest::ScreenFilter { let _ = tx.send(AccessibilityRequest::ScreenFilter {
inverted: enabled, inverted: enabled,
@ -135,9 +122,8 @@ impl cosmic::Application for CosmicA11yApplet {
self.inverted_colors_enabled = false; self.inverted_colors_enabled = false;
} }
} }
Message::FilterColorsEnabled(chain, enabled) => { Message::FilterColorsEnabled(enabled) => {
if let Some(sender) = self.wayland_sender.as_ref() { if let Some(sender) = self.wayland_sender.as_ref() {
self.timeline.set_chain(chain).start();
self.screen_filter_active = enabled; self.screen_filter_active = enabled;
let _ = sender.send(AccessibilityRequest::ScreenFilter { let _ = sender.send(AccessibilityRequest::ScreenFilter {
inverted: self.inverted_colors_enabled, inverted: self.inverted_colors_enabled,
@ -149,8 +135,6 @@ impl cosmic::Application for CosmicA11yApplet {
if let Some(p) = self.popup.take() { if let Some(p) = self.popup.take() {
return destroy_popup(p); return destroy_popup(p);
} else { } else {
self.timeline = Timeline::new();
let new_id = window::Id::unique(); let new_id = window::Id::unique();
self.popup.replace(new_id); self.popup.replace(new_id);
@ -170,13 +154,13 @@ impl cosmic::Application for CosmicA11yApplet {
self.popup = None; self.popup = None;
} }
} }
Message::HighContrastEnabled(chain, enabled) => { Message::HighContrastEnabled(enabled) => {
if self.core.system_theme().cosmic().is_high_contrast == enabled if self.core.system_theme().cosmic().is_high_contrast == enabled
|| self.high_contrast.is_some_and(|hc| hc == enabled) || self.high_contrast.is_some_and(|hc| hc == enabled)
{ {
return Task::none(); return Task::none();
} }
self.timeline.set_chain(chain).start();
self.high_contrast = Some(enabled); self.high_contrast = Some(enabled);
_ = std::thread::spawn(move || { _ = std::thread::spawn(move || {
@ -319,62 +303,44 @@ impl cosmic::Application for CosmicA11yApplet {
} = theme::active().cosmic().spacing; } = theme::active().cosmic().spacing;
let reader_toggle = padded_control( let reader_toggle = padded_control(
anim!( toggler(self.reader_enabled)
READER_TOGGLE, .on_toggle(Message::ScreenReaderEnabled)
&self.timeline, .text_size(14)
fl!("screen-reader"), .width(Length::Fill)
self.reader_enabled, .label(fl!("screen-reader")),
Message::ScreenReaderEnabled,
)
.text_size(14)
.width(Length::Fill),
); );
let magnifier_toggle = padded_control( let magnifier_toggle = padded_control(
anim!( toggler(self.magnifier_enabled)
MAGNIFIER_TOGGLE, .on_toggle(Message::MagnifierEnabled)
&self.timeline, .text_size(14)
fl!("magnifier"), .width(Length::Fill)
self.magnifier_enabled, .label(fl!("magnifier")),
Message::MagnifierEnabled,
)
.text_size(14)
.width(Length::Fill),
); );
let invert_colors_toggle = padded_control( let invert_colors_toggle = padded_control(
anim!( toggler(self.inverted_colors_enabled)
INVERT_COLORS_TOGGLE, .on_toggle(Message::InvertedColorsEnabled)
&self.timeline, .text_size(14)
fl!("invert-colors"), .width(Length::Fill)
self.inverted_colors_enabled, .label(fl!("invert-colors")),
Message::InvertedColorsEnabled,
)
.text_size(14)
.width(Length::Fill),
); );
let hc_colors_toggle = padded_control( let hc_colors_toggle = padded_control(
anim!( toggler(
HC_TOGGLE,
&self.timeline,
fl!("high-contrast"),
self.high_contrast self.high_contrast
.unwrap_or(self.core.system_theme().cosmic().is_high_contrast), .unwrap_or(self.core.system_theme().cosmic().is_high_contrast),
Message::HighContrastEnabled,
) )
.on_toggle(Message::HighContrastEnabled)
.label(fl!("high-contrast"))
.text_size(14) .text_size(14)
.width(Length::Fill), .width(Length::Fill),
); );
let filter_colors_toggle = padded_control( let filter_colors_toggle = padded_control(
anim!( toggler(self.screen_filter_active)
FILTER_TOGGLE, .on_toggle(Message::FilterColorsEnabled)
&self.timeline, .label(fl!("filter-colors"))
fl!("filter-colors"), .width(Length::Fill)
self.screen_filter_active, .text_size(14),
Message::FilterColorsEnabled,
)
.text_size(14)
.width(Length::Fill),
); );
let content_list = Column::with_capacity(5) let content_list = Column::with_capacity(5)
@ -406,9 +372,6 @@ impl cosmic::Application for CosmicA11yApplet {
Subscription::batch([ Subscription::batch([
accessibility::subscription().map(Message::DBusUpdate), accessibility::subscription().map(Message::DBusUpdate),
backend::wayland::a11y_subscription().map(Message::WaylandUpdate), backend::wayland::a11y_subscription().map(Message::WaylandUpdate),
self.timeline
.as_subscription()
.map(|(_, now)| Message::Frame(now)),
activation_token_subscription(0).map(Message::Token), activation_token_subscription(0).map(Message::Token),
]) ])
} }
@ -417,7 +380,7 @@ impl cosmic::Application for CosmicA11yApplet {
Some(Message::CloseRequested(id)) Some(Message::CloseRequested(id))
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<cosmic::iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
} }

View file

@ -26,16 +26,15 @@ pub enum WaylandUpdate {
} }
pub fn a11y_subscription() -> iced::Subscription<WaylandUpdate> { pub fn a11y_subscription() -> iced::Subscription<WaylandUpdate> {
Subscription::run_with_id( Subscription::run_with(std::any::TypeId::of::<WaylandUpdate>(), |_| {
std::any::TypeId::of::<WaylandUpdate>(),
stream::channel(50, move |mut output| async move { stream::channel(50, move |mut output| async move {
let mut state = State::Waiting; let mut state = State::Waiting;
loop { loop {
state = start_listening(state, &mut output).await; state = start_listening(state, &mut output).await;
} }
}), })
) })
} }
async fn start_listening( async fn start_listening(

View file

@ -5,7 +5,6 @@ edition = "2024"
license = "GPL-3.0-only" license = "GPL-3.0-only"
[dependencies] [dependencies]
cosmic-time.workspace = true
i18n-embed-fl.workspace = true i18n-embed-fl.workspace = true
i18n-embed.workspace = true i18n-embed.workspace = true
libcosmic.workspace = true libcosmic.workspace = true
@ -23,3 +22,4 @@ zbus.workspace = true
[dependencies.cosmic-settings-sound-subscription] [dependencies.cosmic-settings-sound-subscription]
git = "https://github.com/pop-os/cosmic-settings" git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/sound"

View file

@ -24,20 +24,16 @@ use cosmic::{
window, window,
}, },
surface, theme, surface, theme,
widget::{Row, button, container, divider, horizontal_space, icon, text}, widget::{Row, button, container, divider, icon, space, text, toggler},
}; };
use cosmic_settings_sound_subscription as css; use cosmic_settings_sound_subscription as css;
use cosmic_time::{Instant, Timeline, anim, chain, id};
use iced::platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup}; use iced::platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup};
use mpris_subscription::{MprisRequest, MprisUpdate}; use mpris_subscription::{MprisRequest, MprisUpdate};
use mpris2_zbus::player::PlaybackStatus; use mpris2_zbus::player::PlaybackStatus;
use std::sync::LazyLock;
mod config; mod config;
mod mpris_subscription; mod mpris_subscription;
static SHOW_MEDIA_CONTROLS: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
const GO_BACK: &str = "media-skip-backward-symbolic"; const GO_BACK: &str = "media-skip-backward-symbolic";
const GO_NEXT: &str = "media-skip-forward-symbolic"; const GO_NEXT: &str = "media-skip-forward-symbolic";
const PAUSE: &str = "media-playback-pause-symbolic"; const PAUSE: &str = "media-playback-pause-symbolic";
@ -66,8 +62,6 @@ pub struct Audio {
sink_breakpoints: &'static [u32], sink_breakpoints: &'static [u32],
/// Breakpoitns for the source volume slider. /// Breakpoitns for the source volume slider.
source_breakpoints: &'static [u32], source_breakpoints: &'static [u32],
/// Track animations used by the revealers.
timeline: Timeline,
/// Config file specific to this applet. /// Config file specific to this applet.
config: AudioAppletConfig, config: AudioAppletConfig,
/// mpris player status /// mpris player status
@ -129,8 +123,7 @@ pub enum Message {
InputToggle, InputToggle,
TogglePopup, TogglePopup,
CloseRequested(window::Id), CloseRequested(window::Id),
ToggleMediaControlsInTopPanel(chain::Toggler, bool), ToggleMediaControlsInTopPanel(bool),
Frame(Instant),
ConfigChanged(AudioAppletConfig), ConfigChanged(AudioAppletConfig),
Mpris(mpris_subscription::MprisUpdate), Mpris(mpris_subscription::MprisUpdate),
MprisRequest(MprisRequest), MprisRequest(MprisRequest),
@ -272,13 +265,12 @@ impl cosmic::Application for Audio {
&mut self.core &mut self.core
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
fn update(&mut self, message: Message) -> app::Task<Message> { fn update(&mut self, message: Message) -> app::Task<Message> {
match message { match message {
Message::Frame(now) => self.timeline.now(now),
Message::Ignore => {} Message::Ignore => {}
Message::TogglePopup => { Message::TogglePopup => {
if let Some(p) = self.popup.take() { if let Some(p) = self.popup.take() {
@ -286,7 +278,6 @@ impl cosmic::Application for Audio {
} else { } else {
let new_id = window::Id::unique(); let new_id = window::Id::unique();
self.popup.replace(new_id); self.popup.replace(new_id);
self.timeline = Timeline::new();
(self.max_sink_volume, self.sink_breakpoints) = if amplification_sink() { (self.max_sink_volume, self.sink_breakpoints) = if amplification_sink() {
(150, &[100][..]) (150, &[100][..])
@ -365,8 +356,7 @@ impl cosmic::Application for Audio {
.map(|message| Message::Subscription(message).into()); .map(|message| Message::Subscription(message).into());
} }
Message::ToggleMediaControlsInTopPanel(chain, enabled) => { Message::ToggleMediaControlsInTopPanel(enabled) => {
self.timeline.set_chain(chain).start();
self.config.show_media_controls_in_top_panel = enabled; self.config.show_media_controls_in_top_panel = enabled;
if let Ok(helper) = if let Ok(helper) =
cosmic::cosmic_config::Config::new(Self::APP_ID, AudioAppletConfig::VERSION) cosmic::cosmic_config::Config::new(Self::APP_ID, AudioAppletConfig::VERSION)
@ -478,9 +468,6 @@ impl cosmic::Application for Audio {
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
Subscription::batch([ Subscription::batch([
self.timeline
.as_subscription()
.map(|(_, now)| Message::Frame(now)),
self.core.watch_config(Self::APP_ID).map(|u| { self.core.watch_config(Self::APP_ID).map(|u| {
for err in u.errors { for err in u.errors {
tracing::error!(?err, "Error watching config"); tracing::error!(?err, "Error watching config");
@ -532,6 +519,7 @@ impl cosmic::Application for Audio {
applet_column::Column::with_children(playback_buttons) applet_column::Column::with_children(playback_buttons)
.push(btn) .push(btn)
.align_x(Alignment::Center) .align_x(Alignment::Center)
.height(Length::Shrink)
// TODO configurable variable from the panel? // TODO configurable variable from the panel?
.spacing( .spacing(
-(self.core.applet.suggested_padding(true).0 as f32) -(self.core.applet.suggested_padding(true).0 as f32)
@ -542,6 +530,7 @@ impl cosmic::Application for Audio {
applet_row::Row::with_children(playback_buttons) applet_row::Row::with_children(playback_buttons)
.push(btn) .push(btn)
.align_y(Alignment::Center) .align_y(Alignment::Center)
.width(Length::Shrink)
// TODO configurable variable from the panel? // TODO configurable variable from the panel?
.spacing( .spacing(
-(self.core.applet.suggested_padding(true).0 as f32) -(self.core.applet.suggested_padding(true).0 as f32)
@ -696,7 +685,7 @@ impl cosmic::Application for Audio {
); );
let mut control_elements = Vec::with_capacity(4); let mut control_elements = Vec::with_capacity(4);
control_elements.push(horizontal_space().width(Length::Fill).into()); control_elements.push(space::horizontal().width(Length::Fill).into());
if let Some(go_prev) = self.go_previous(32) { if let Some(go_prev) = self.go_previous(32) {
control_elements.push(go_prev); control_elements.push(go_prev);
} }
@ -745,16 +734,11 @@ impl cosmic::Application for Audio {
audio_content, audio_content,
padded_control(divider::horizontal::default()).padding([space_xxs, space_s]), padded_control(divider::horizontal::default()).padding([space_xxs, space_s]),
padded_control( padded_control(
anim!( toggler(self.config.show_media_controls_in_top_panel)
// toggler .on_toggle(Message::ToggleMediaControlsInTopPanel)
SHOW_MEDIA_CONTROLS, .label(fl!("show-media-controls"))
&self.timeline, .text_size(14)
Some(fl!("show-media-controls")), .width(Length::Fill)
self.config.show_media_controls_in_top_panel,
Message::ToggleMediaControlsInTopPanel,
)
.text_size(14)
.width(Length::Fill)
), ),
padded_control(divider::horizontal::default()).padding([space_xxs, space_s]), padded_control(divider::horizontal::default()).padding([space_xxs, space_s]),
menu_button(text::body(fl!("sound-settings"))).on_press(Message::OpenSettings) menu_button(text::body(fl!("sound-settings"))).on_press(Message::OpenSettings)

View file

@ -156,50 +156,51 @@ where
} }
fn layout( fn layout(
&self, &mut self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
self.content self.content
.as_widget() .as_widget_mut()
.layout(&mut tree.children[0], renderer, limits) .layout(&mut tree.children[0], renderer, limits)
} }
fn operate( fn operate(
&self, &mut self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation<()>, operation: &mut dyn Operation<()>,
) { ) {
self.content self.content
.as_widget() .as_widget_mut()
.operate(&mut tree.children[0], layout, renderer, operation); .operate(&mut tree.children[0], layout, renderer, operation);
} }
fn on_event( fn update(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: Event, event: &Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) -> event::Status { ) {
if let event::Status::Captured = self.content.as_widget_mut().on_event( self.content.as_widget_mut().update(
&mut tree.children[0], &mut tree.children[0],
event.clone(), &event,
layout, layout,
cursor, cursor,
renderer, renderer,
clipboard, clipboard,
shell, shell,
viewport, viewport,
) { );
return event::Status::Captured; if shell.is_event_captured() {
return;
} }
update( update(
@ -209,7 +210,7 @@ where
cursor, cursor,
shell, shell,
tree.state.downcast_mut::<State>(), tree.state.downcast_mut::<State>(),
) );
} }
fn mouse_interaction( fn mouse_interaction(
@ -249,17 +250,24 @@ where
viewport, viewport,
); );
} }
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'b>,
renderer: &Renderer, renderer: &Renderer,
viewport: &Rectangle,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content self.content.as_widget_mut().overlay(
.as_widget_mut() &mut tree.children[0],
.overlay(&mut tree.children[0], layout, renderer, translation) layout,
renderer,
viewport,
translation,
)
} }
fn drag_destinations( fn drag_destinations(
&self, &self,
state: &Tree, state: &Tree,
@ -298,7 +306,7 @@ fn update<Message: Clone, Theme, Renderer>(
cursor: mouse::Cursor, cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
state: &mut State, state: &mut State,
) -> event::Status { ) {
if !cursor.is_over(layout.bounds()) { if !cursor.is_over(layout.bounds()) {
if !state.is_out_of_bounds { if !state.is_out_of_bounds {
if widget if widget
@ -312,12 +320,13 @@ fn update<Message: Clone, Theme, Renderer>(
if let Some(message) = widget.on_mouse_exit.as_ref() { if let Some(message) = widget.on_mouse_exit.as_ref() {
shell.publish(message.clone()); shell.publish(message.clone());
} }
return event::Status::Captured; shell.capture_event();
return;
} }
} }
} }
return event::Status::Ignored; return;
} }
if let Some(message) = widget.on_press.as_ref() { if let Some(message) = widget.on_press.as_ref() {
@ -326,8 +335,8 @@ fn update<Message: Clone, Theme, Renderer>(
{ {
state.drag_initiated = cursor.position(); state.drag_initiated = cursor.position();
shell.publish(message.clone()); shell.publish(message.clone());
shell.capture_event();
return event::Status::Captured; return;
} }
} }
@ -337,32 +346,32 @@ fn update<Message: Clone, Theme, Renderer>(
{ {
state.drag_initiated = None; state.drag_initiated = None;
shell.publish(message.clone()); shell.publish(message.clone());
shell.capture_event();
return event::Status::Captured; return;
} }
} }
if let Some(message) = widget.on_right_press.as_ref() { if let Some(message) = widget.on_right_press.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = event { if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = event {
shell.publish(message.clone()); shell.publish(message.clone());
shell.capture_event();
return event::Status::Captured; return;
} }
} }
if let Some(message) = widget.on_right_release.as_ref() { if let Some(message) = widget.on_right_release.as_ref() {
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) = event { if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) = event {
shell.publish(message.clone()); shell.publish(message.clone());
shell.capture_event();
return event::Status::Captured; return;
} }
} }
if let Some(message) = widget.on_middle_press.as_ref() { if let Some(message) = widget.on_middle_press.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) = event { if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) = event {
shell.publish(message.clone()); shell.publish(message.clone());
shell.capture_event();
return event::Status::Captured; return;
} }
} }
@ -370,7 +379,8 @@ fn update<Message: Clone, Theme, Renderer>(
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) = event { if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) = event {
shell.publish(message.clone()); shell.publish(message.clone());
return event::Status::Captured; shell.capture_event();
return;
} }
} }
if let Some(message) = widget if let Some(message) = widget
@ -384,7 +394,8 @@ fn update<Message: Clone, Theme, Renderer>(
if widget.on_mouse_enter.is_some() { if widget.on_mouse_enter.is_some() {
shell.publish(message.clone()); shell.publish(message.clone());
} }
return event::Status::Captured; shell.capture_event();
return;
} }
} }
} }
@ -400,8 +411,8 @@ fn update<Message: Clone, Theme, Renderer>(
if position.distance(drag_source) > 1.0 { if position.distance(drag_source) > 1.0 {
state.drag_initiated = None; state.drag_initiated = None;
shell.publish(message.clone()); shell.publish(message.clone());
shell.capture_event();
return event::Status::Captured; return;
} }
} }
} }
@ -409,9 +420,8 @@ fn update<Message: Clone, Theme, Renderer>(
if let Some(message) = widget.on_mouse_wheel.as_ref() { if let Some(message) = widget.on_mouse_wheel.as_ref() {
if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event { if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
shell.publish((message)(*delta)); shell.publish((message)(*delta));
return event::Status::Captured; shell.capture_event();
return;
} }
} }
event::Status::Ignored
} }

View file

@ -83,14 +83,13 @@ impl PlayerStatus {
pub fn mpris_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>( pub fn mpris_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
id: I, id: I,
) -> iced::Subscription<MprisUpdate> { ) -> iced::Subscription<MprisUpdate> {
Subscription::run_with_id( Subscription::run_with(id, |_| {
id,
stream::channel(50, move |mut output| async move { stream::channel(50, move |mut output| async move {
run(&mut output).await; run(&mut output).await;
let _ = output.send(MprisUpdate::Finished).await; let _ = output.send(MprisUpdate::Finished).await;
futures::future::pending().await futures::future::pending().await
}), })
) })
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -6,7 +6,6 @@ license = "GPL-3.0-only"
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
cosmic-time.workspace = true
cosmic-config.workspace = true cosmic-config.workspace = true
cosmic-applets-config.workspace = true cosmic-applets-config.workspace = true
drm = "0.14.1" drm = "0.14.1"
@ -26,6 +25,8 @@ serde.workspace = true
[dependencies.cosmic-settings-upower-subscription] [dependencies.cosmic-settings-upower-subscription]
git = "https://github.com/pop-os/cosmic-settings" git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/upower"
[dependencies.cosmic-settings-daemon-subscription] [dependencies.cosmic-settings-daemon-subscription]
git = "https://github.com/pop-os/cosmic-settings" git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/settings-daemon"

View file

@ -29,7 +29,7 @@ use cosmic::{
iced_core::{Alignment, Background, Border, Color, Shadow}, iced_core::{Alignment, Background, Border, Color, Shadow},
surface, surface,
theme::{self, Button}, theme::{self, Button},
widget::{button, divider, horizontal_space, icon, scrollable, slider, text, vertical_space}, widget::{button, divider, icon, scrollable, slider, space, text, toggler},
}; };
use cosmic_applets_config::battery::BatteryAppletConfig; use cosmic_applets_config::battery::BatteryAppletConfig;
use cosmic_config::{Config, CosmicConfigEntry}; use cosmic_config::{Config, CosmicConfigEntry};
@ -39,8 +39,6 @@ use cosmic_settings_upower_subscription::{
kbdbacklight::{KeyboardBacklightRequest, KeyboardBacklightUpdate, kbd_backlight_subscription}, kbdbacklight::{KeyboardBacklightRequest, KeyboardBacklightUpdate, kbd_backlight_subscription},
}; };
use cosmic_time::{Instant, Timeline, anim, chain, id};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::{path::PathBuf, sync::LazyLock, time::Duration}; use std::{path::PathBuf, sync::LazyLock, time::Duration};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
@ -65,8 +63,6 @@ pub fn run() -> cosmic::iced::Result {
cosmic::applet::run::<CosmicBatteryApplet>(()) cosmic::applet::run::<CosmicBatteryApplet>(())
} }
static MAX_CHARGE: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
#[derive(Clone, Default)] #[derive(Clone, Default)]
struct GPUData { struct GPUData {
name: String, name: String,
@ -95,7 +91,6 @@ struct CosmicBatteryApplet {
kbd_sender: Option<UnboundedSender<KeyboardBacklightRequest>>, kbd_sender: Option<UnboundedSender<KeyboardBacklightRequest>>,
power_profile: Power, power_profile: Power,
power_profile_sender: Option<UnboundedSender<PowerProfileRequest>>, power_profile_sender: Option<UnboundedSender<PowerProfileRequest>>,
timeline: Timeline,
token_tx: Option<calloop::channel::Sender<TokenRequest>>, token_tx: Option<calloop::channel::Sender<TokenRequest>>,
zbus_connection: Option<zbus::Connection>, zbus_connection: Option<zbus::Connection>,
dragging_screen_brightness: bool, dragging_screen_brightness: bool,
@ -186,7 +181,7 @@ enum Message {
SetScreenBrightnessDebounced, SetScreenBrightnessDebounced,
ReleaseScreenBrightness, ReleaseScreenBrightness,
InitChargingLimit(Option<bool>), InitChargingLimit(Option<bool>),
SetChargingLimit(chain::Toggler, bool), SetChargingLimit(bool),
KeyboardBacklight(KeyboardBacklightUpdate), KeyboardBacklight(KeyboardBacklightUpdate),
UpowerDevice(DeviceDbusEvent), UpowerDevice(DeviceDbusEvent),
GpuInit(UnboundedSender<()>), GpuInit(UnboundedSender<()>),
@ -197,7 +192,6 @@ enum Message {
InitProfile(UnboundedSender<PowerProfileRequest>, Power), InitProfile(UnboundedSender<PowerProfileRequest>, Power),
Profile(Power), Profile(Power),
SelectProfile(Power), SelectProfile(Power),
Frame(Instant),
ConfigChanged(BatteryAppletConfig), ConfigChanged(BatteryAppletConfig),
Token(TokenUpdate), Token(TokenUpdate),
OpenSettings, OpenSettings,
@ -248,7 +242,6 @@ impl cosmic::Application for CosmicBatteryApplet {
fn update(&mut self, message: Self::Message) -> app::Task<Self::Message> { fn update(&mut self, message: Self::Message) -> app::Task<Self::Message> {
match message { match message {
Message::Frame(now) => self.timeline.now(now),
Message::SetKbdBrightness(brightness) => { Message::SetKbdBrightness(brightness) => {
self.kbd_brightness = Some(brightness); self.kbd_brightness = Some(brightness);
@ -330,8 +323,7 @@ impl cosmic::Application for CosmicBatteryApplet {
self.set_charging_limit(enable); self.set_charging_limit(enable);
} }
} }
Message::SetChargingLimit(chain, enable) => { Message::SetChargingLimit(enable) => {
self.timeline.set_chain(chain).start();
self.set_charging_limit(enable); self.set_charging_limit(enable);
if enable { if enable {
@ -357,7 +349,6 @@ impl cosmic::Application for CosmicBatteryApplet {
if let Some(tx) = &self.kbd_sender { if let Some(tx) = &self.kbd_sender {
let _ = tx.send(KeyboardBacklightRequest::Get); let _ = tx.send(KeyboardBacklightRequest::Get);
} }
self.timeline = Timeline::new();
let new_id = window::Id::unique(); let new_id = window::Id::unique();
self.popup.replace(new_id); self.popup.replace(new_id);
@ -582,7 +573,7 @@ impl cosmic::Application for CosmicBatteryApplet {
let content = if self.gpus.is_empty() { let content = if self.gpus.is_empty() {
btn btn
} else { } else {
let dot = container(vertical_space().height(Length::Fixed(0.0))) let dot = container(space::vertical().height(Length::Fixed(0.0)))
.padding(2.0) .padding(2.0)
.class(cosmic::style::Container::Custom(Box::new(|theme| { .class(cosmic::style::Container::Custom(Box::new(|theme| {
container::Style { container::Style {
@ -595,6 +586,7 @@ impl cosmic::Application for CosmicBatteryApplet {
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
icon_color: Some(Color::TRANSPARENT), icon_color: Some(Color::TRANSPARENT),
snap: true,
} }
}))); })));
let (dot_align_x, dot_align_y) = match self.core.applet.anchor { let (dot_align_x, dot_align_y) = match self.core.applet.anchor {
@ -638,7 +630,7 @@ impl cosmic::Application for CosmicBatteryApplet {
}, },
); );
let mut content = vec![ let mut content: Vec<Element<'_, Message>> = vec![
padded_control( padded_control(
row![ row![
icon::from_name(&*self.icon_name).size(24).symbolic(true), icon::from_name(&*self.icon_name).size(24).symbolic(true),
@ -665,7 +657,7 @@ impl cosmic::Application for CosmicBatteryApplet {
.symbolic(true), .symbolic(true),
) )
} else { } else {
container(horizontal_space().width(1.0)) container(space::horizontal().width(1.0))
} }
] ]
.align_y(Alignment::Center), .align_y(Alignment::Center),
@ -686,7 +678,7 @@ impl cosmic::Application for CosmicBatteryApplet {
.symbolic(true), .symbolic(true),
) )
} else { } else {
container(horizontal_space().width(1.0)) container(space::horizontal().width(1.0))
} }
] ]
.align_y(Alignment::Center), .align_y(Alignment::Center),
@ -707,7 +699,7 @@ impl cosmic::Application for CosmicBatteryApplet {
.symbolic(true), .symbolic(true),
) )
} else { } else {
container(horizontal_space().width(1.0)) container(space::horizontal().width(1.0))
} }
] ]
.align_y(Alignment::Center), .align_y(Alignment::Center),
@ -722,16 +714,11 @@ impl cosmic::Application for CosmicBatteryApplet {
if let Some(charging_limit) = self.charging_limit { if let Some(charging_limit) = self.charging_limit {
content.push( content.push(
padded_control( padded_control(
anim!( toggler(charging_limit)
//toggler .on_toggle(Message::SetChargingLimit)
MAX_CHARGE, .label(fl!("max-charge"))
&self.timeline, .text_size(14)
fl!("max-charge"), .width(Length::Fill),
charging_limit,
Message::SetChargingLimit,
)
.text_size(14)
.width(Length::Fill),
) )
.into(), .into(),
); );
@ -819,7 +806,7 @@ impl cosmic::Application for CosmicBatteryApplet {
.width(Length::Fill) .width(Length::Fill)
.align_x(Alignment::Start), .align_x(Alignment::Start),
container( container(
vertical_space() space::vertical()
.width(Length::Fixed(0.0)) .width(Length::Fixed(0.0))
.height(Length::Fixed(0.0)) .height(Length::Fixed(0.0))
) )
@ -837,6 +824,7 @@ impl cosmic::Application for CosmicBatteryApplet {
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
icon_color: Some(Color::TRANSPARENT), icon_color: Some(Color::TRANSPARENT),
snap: true,
} }
},))), },))),
] ]
@ -902,7 +890,7 @@ impl cosmic::Application for CosmicBatteryApplet {
if let Some(icon) = &app.icon { if let Some(icon) = &app.icon {
container(icon::from_name(&**icon).size(12).symbolic(true)) container(icon::from_name(&**icon).size(12).symbolic(true))
} else { } else {
container(horizontal_space().width(12.0)) container(space::horizontal().width(12.0))
}, },
column![text::body(&app.name), text::caption(&app.secondary)] column![text::body(&app.name), text::caption(&app.secondary)]
.width(Length::Fill), .width(Length::Fill),
@ -952,9 +940,6 @@ impl cosmic::Application for CosmicBatteryApplet {
GpuUpdate::On(path, name, list) => Message::GpuOn(path, name, list), GpuUpdate::On(path, name, list) => Message::GpuOn(path, name, list),
GpuUpdate::Off(path) => Message::GpuOff(path), GpuUpdate::Off(path) => Message::GpuOff(path),
}), }),
self.timeline
.as_subscription()
.map(|(_, now)| Message::Frame(now)),
activation_token_subscription(0).map(Message::Token), activation_token_subscription(0).map(Message::Token),
self.core.watch_config(Self::APP_ID).map(|u| { self.core.watch_config(Self::APP_ID).map(|u| {
for err in u.errors { for err in u.errors {
@ -973,7 +958,7 @@ impl cosmic::Application for CosmicBatteryApplet {
Some(Message::CloseRequested(id)) Some(Message::CloseRequested(id))
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<cosmic::iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
} }

View file

@ -98,16 +98,15 @@ pub async fn set_power_profile(daemon: Backend<'_>, power: Power) -> Result<()>
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<PowerProfileUpdate> { ) -> iced::Subscription<PowerProfileUpdate> {
Subscription::run_with_id( Subscription::run_with(id, |_| {
id,
stream::channel(50, move |mut output| async move { stream::channel(50, move |mut output| async move {
let mut state = State::Ready; let mut state = State::Ready;
loop { loop {
state = start_listening(state, &mut output).await; state = start_listening(state, &mut output).await;
} }
}), })
) })
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -418,16 +418,15 @@ fn all_gpus<S: AsRef<str>>(seat: S) -> io::Result<Vec<Gpu>> {
pub fn dgpu_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>( pub fn dgpu_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
id: I, id: I,
) -> iced::Subscription<GpuUpdate> { ) -> iced::Subscription<GpuUpdate> {
Subscription::run_with_id( Subscription::run_with(id, |_| {
id,
stream::channel(50, move |mut output| async move { stream::channel(50, move |mut output| async move {
let mut state = State::Ready; let mut state = State::Ready;
loop { loop {
state = start_listening(state, &mut output).await; state = start_listening(state, &mut output).await;
} }
}), })
) })
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -7,7 +7,6 @@ license = "GPL-3.0-only"
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
bluer = { version = "0.17", features = ["bluetoothd", "id"] } bluer = { version = "0.17", features = ["bluetoothd", "id"] }
cosmic-time.workspace = true
futures.workspace = true futures.workspace = true
i18n-embed-fl.workspace = true i18n-embed-fl.workspace = true
i18n-embed.workspace = true i18n-embed.workspace = true

View file

@ -7,6 +7,7 @@ use cosmic::{
applet::token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription}, applet::token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription},
cctk::sctk::reexports::calloop, cctk::sctk::reexports::calloop,
surface, surface,
widget::toggler,
}; };
use cosmic::{ use cosmic::{
@ -22,7 +23,6 @@ use cosmic::{
theme, theme,
widget::{button, divider, icon, scrollable, text}, widget::{button, divider, icon, scrollable, text},
}; };
use cosmic_time::{Instant, Timeline, anim, chain, id};
use futures::FutureExt; use futures::FutureExt;
use std::{collections::HashMap, sync::LazyLock, time::Duration}; use std::{collections::HashMap, sync::LazyLock, time::Duration};
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
@ -32,8 +32,6 @@ use crate::{
config, fl, config, fl,
}; };
static BLUETOOTH_ENABLED: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
#[inline] #[inline]
pub fn run() -> cosmic::iced::Result { pub fn run() -> cosmic::iced::Result {
cosmic::applet::run::<CosmicBluetoothApplet>(()) cosmic::applet::run::<CosmicBluetoothApplet>(())
@ -50,7 +48,6 @@ struct CosmicBluetoothApplet {
show_visible_devices: bool, show_visible_devices: bool,
request_confirmation: Option<(BluerDevice, String, Sender<bool>)>, request_confirmation: Option<(BluerDevice, String, Sender<bool>)>,
token_tx: Option<calloop::channel::Sender<TokenRequest>>, token_tx: Option<calloop::channel::Sender<TokenRequest>>,
timeline: Timeline,
} }
impl CosmicBluetoothApplet { impl CosmicBluetoothApplet {
@ -77,8 +74,7 @@ enum Message {
Confirm, Confirm,
Token(TokenUpdate), Token(TokenUpdate),
OpenSettings, OpenSettings,
Frame(Instant), ToggleBluetooth(bool),
ToggleBluetooth(chain::Toggler, bool),
Surface(surface::Action), Surface(surface::Action),
} }
@ -127,7 +123,6 @@ impl cosmic::Application for CosmicBluetoothApplet {
// TODO request update of state maybe // TODO request update of state maybe
let new_id = window::Id::unique(); let new_id = window::Id::unique();
self.popup.replace(new_id); self.popup.replace(new_id);
self.timeline = Timeline::new();
let popup_settings = self.core.applet.get_popup_settings( let popup_settings = self.core.applet.get_popup_settings(
self.core.main_window_id().unwrap(), self.core.main_window_id().unwrap(),
@ -157,15 +152,6 @@ impl cosmic::Application for CosmicBluetoothApplet {
if let Some(err_msg) = err_msg { if let Some(err_msg) = err_msg {
eprintln!("bluetooth request error: {err_msg}"); eprintln!("bluetooth request error: {err_msg}");
} }
if self.bluer_state.bluetooth_enabled != state.bluetooth_enabled {
self.timeline
.set_chain(if state.bluetooth_enabled {
chain::Toggler::on(BLUETOOTH_ENABLED.clone(), 1.0)
} else {
chain::Toggler::off(BLUETOOTH_ENABLED.clone(), 1.0)
})
.start();
}
self.bluer_state = state; self.bluer_state = state;
} }
@ -300,12 +286,10 @@ impl cosmic::Application for CosmicBluetoothApplet {
tokio::spawn(cosmic::process::spawn(cmd)); tokio::spawn(cosmic::process::spawn(cmd));
} }
}, },
Message::Frame(instant) => self.timeline.now(instant), Message::ToggleBluetooth(enabled) => {
Message::ToggleBluetooth(chain, enabled) => {
if self.bluer_state.bluetooth_enabled == enabled { if self.bluer_state.bluetooth_enabled == enabled {
return Task::none(); return Task::none();
} }
self.timeline.set_chain(chain).start();
self.bluer_state.bluetooth_enabled = enabled; self.bluer_state.bluetooth_enabled = enabled;
if let Some(tx) = self.bluer_sender.clone() { if let Some(tx) = self.bluer_sender.clone() {
tokio::spawn(async move { tokio::spawn(async move {
@ -416,16 +400,11 @@ impl cosmic::Application for CosmicBluetoothApplet {
} }
let mut content = column![column![padded_control( let mut content = column![column![padded_control(
anim!( toggler(self.bluer_state.bluetooth_enabled)
//toggler .label(fl!("bluetooth"))
BLUETOOTH_ENABLED, .on_toggle(Message::ToggleBluetooth)
&self.timeline, .width(Length::Fill)
fl!("bluetooth"), .text_size(14)
self.bluer_state.bluetooth_enabled,
Message::ToggleBluetooth,
)
.text_size(14)
.width(Length::Fill)
),],] ),],]
.align_x(Alignment::Center) .align_x(Alignment::Center)
.padding([8, 0]); .padding([8, 0]);
@ -549,13 +528,10 @@ impl cosmic::Application for CosmicBluetoothApplet {
Subscription::batch([ Subscription::batch([
activation_token_subscription(0).map(Message::Token), activation_token_subscription(0).map(Message::Token),
bluetooth_subscription(0).map(Message::BluetoothEvent), bluetooth_subscription(0).map(Message::BluetoothEvent),
self.timeline
.as_subscription()
.map(|(_, now)| Message::Frame(now)),
]) ])
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }

View file

@ -6,7 +6,6 @@ use rustc_hash::FxHashMap;
use std::{ use std::{
fmt::Debug, fmt::Debug,
hash::Hash, hash::Hash,
mem,
sync::{ sync::{
Arc, LazyLock, Arc, LazyLock,
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
@ -90,100 +89,104 @@ fn rfkill_path_var() -> std::ffi::OsString {
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<BluerEvent> { ) -> iced::Subscription<BluerEvent> {
Subscription::run_with_id( Subscription::run_with(id, |_| {
id, stream::channel(
stream::channel(50, move |mut output| async move { 50,
let mut retry_count = 0u32; move |mut output: futures::channel::mpsc::Sender<BluerEvent>| async move {
let mut retry_count = 0u32;
// Initialize connection. // Initialize connection.
let mut session_state = loop { let mut session_state = loop {
if let Ok(session) = Session::new().await { if let Ok(session) = Session::new().await {
if let Ok(state) = BluerSessionState::new(session).await { if let Ok(state) = BluerSessionState::new(session).await {
break state; break state;
}
}
retry_count = retry_count.saturating_add(1);
() = tokio::time::sleep(Duration::from_millis(
2_u64.saturating_pow(retry_count).min(68719476734),
))
.await;
};
let state = bluer_state(&session_state.adapter).await;
// reconnect to paired and trusted devices
if state.bluetooth_enabled {
for d in &state.devices {
if d.paired_and_trusted() && !matches!(d.status, BluerDeviceStatus::Connected) {
_ = session_state
.req_tx
.send(BluerRequest::ConnectDevice(d.address))
.await;
}
}
}
_ = output
.send(BluerEvent::Init {
sender: session_state.req_tx.clone(),
state: state.clone(),
})
.await;
let mut event_handler = async |event| {
let message = match event {
BluerSessionEvent::ChangesProcessed(state) => {
BluerEvent::DevicesChanged { state }
}
BluerSessionEvent::RequestResponse {
req,
state,
err_msg,
} => BluerEvent::RequestResponse {
req,
state,
err_msg,
},
BluerSessionEvent::AgentEvent(e) => BluerEvent::AgentEvent(e),
_ => return,
};
_ = output.send(message).await;
};
let mut interval = tokio::time::interval(Duration::from_secs(10));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
let Some(mut session_rx) = session_state.rx.take() else {
break;
};
if let Some(event) = session_rx.recv().await {
event_handler(event).await;
// Consume any additional available events.
let mut count = 0;
while let Ok(event) = session_rx.try_recv() {
event_handler(event).await;
count += 1;
if count == 100 {
break;
} }
} }
} else {
break; retry_count = retry_count.saturating_add(1);
() = tokio::time::sleep(Duration::from_millis(
2_u64.saturating_pow(retry_count).min(68719476734),
))
.await;
};
let state = bluer_state(&session_state.adapter).await;
// reconnect to paired and trusted devices
if state.bluetooth_enabled {
for d in &state.devices {
if d.paired_and_trusted()
&& !matches!(d.status, BluerDeviceStatus::Connected)
{
_ = session_state
.req_tx
.send(BluerRequest::ConnectDevice(d.address))
.await;
}
}
}
_ = output
.send(BluerEvent::Init {
sender: session_state.req_tx.clone(),
state: state.clone(),
})
.await;
let mut event_handler = async |event| {
let message = match event {
BluerSessionEvent::ChangesProcessed(state) => {
BluerEvent::DevicesChanged { state }
}
BluerSessionEvent::RequestResponse {
req,
state,
err_msg,
} => BluerEvent::RequestResponse {
req,
state,
err_msg,
},
BluerSessionEvent::AgentEvent(e) => BluerEvent::AgentEvent(e),
_ => return,
};
_ = output.send(message).await;
};
let mut interval = tokio::time::interval(Duration::from_secs(10));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
let Some(mut session_rx) = session_state.rx.take() else {
break;
};
if let Some(event) = session_rx.recv().await {
event_handler(event).await;
// Consume any additional available events.
let mut count = 0;
while let Ok(event) = session_rx.try_recv() {
event_handler(event).await;
count += 1;
if count == 100 {
break;
}
}
} else {
break;
}
session_state.rx = Some(session_rx);
interval.tick().await;
} }
session_state.rx = Some(session_rx); _ = output.send(BluerEvent::Finished).await;
interval.tick().await; futures::future::pending().await
} },
)
_ = output.send(BluerEvent::Finished).await; })
futures::future::pending().await
}),
)
} }
#[derive(Debug, Clone, Hash, Eq, PartialEq)] #[derive(Debug, Clone, Hash, Eq, PartialEq)]

View file

@ -5,7 +5,6 @@ edition = "2024"
license = "GPL-3.0-only" license = "GPL-3.0-only"
[dependencies] [dependencies]
# cosmic-time.workspace = true
cosmic-comp-config = { git = "https://github.com/pop-os/cosmic-comp.git", rev = "5eb5af4" } cosmic-comp-config = { git = "https://github.com/pop-os/cosmic-comp.git", rev = "5eb5af4" }
i18n-embed-fl.workspace = true i18n-embed-fl.workspace = true
i18n-embed.workspace = true i18n-embed.workspace = true

View file

@ -21,9 +21,9 @@ use cosmic::{
prelude::*, prelude::*,
surface, theme, surface, theme,
widget::{ widget::{
self, autosize, horizontal_space, self, autosize,
rectangle_tracker::{RectangleTracker, RectangleUpdate, rectangle_tracker_subscription}, rectangle_tracker::{RectangleTracker, RectangleUpdate, rectangle_tracker_subscription},
vertical_space, space,
}, },
}; };
use cosmic_comp_config::CosmicCompConfig; use cosmic_comp_config::CosmicCompConfig;
@ -291,7 +291,7 @@ impl cosmic::Application for Window {
]) ])
} }
fn style(&self) -> Option<Appearance> { fn style(&self) -> Option<cosmic::iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
} }

View file

@ -158,7 +158,7 @@ impl cosmic::Application for Minimize {
&mut self.core &mut self.core
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }

View file

@ -12,7 +12,7 @@ use cosmic::{
wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
}, },
iced::{self, Subscription}, iced::{self, Subscription},
iced_core::image::Bytes, iced_core::Bytes,
iced_futures::{futures, stream}, iced_futures::{futures, stream},
}; };
use futures::SinkExt; use futures::SinkExt;
@ -22,24 +22,26 @@ use std::fmt::Debug;
use crate::wayland_handler::wayland_handler; use crate::wayland_handler::wayland_handler;
pub fn wayland_subscription() -> iced::Subscription<WaylandUpdate> { pub fn wayland_subscription() -> iced::Subscription<WaylandUpdate> {
Subscription::run_with_id( Subscription::run_with(std::any::TypeId::of::<WaylandUpdate>(), |_| {
std::any::TypeId::of::<WaylandUpdate>(), stream::channel(
stream::channel(1, move |mut output| async move { 1,
let (calloop_tx, calloop_rx) = calloop::channel::channel(); move |mut output: futures::channel::mpsc::Sender<WaylandUpdate>| async move {
let runtime = tokio::runtime::Handle::current(); let (calloop_tx, calloop_rx) = calloop::channel::channel();
let runtime = tokio::runtime::Handle::current();
let _ = std::thread::spawn(move || { let _ = std::thread::spawn(move || {
runtime.block_on(async move { runtime.block_on(async move {
_ = output.send(WaylandUpdate::Init(calloop_tx)).await; _ = output.send(WaylandUpdate::Init(calloop_tx)).await;
wayland_handler(output.clone(), calloop_rx); wayland_handler(output.clone(), calloop_rx);
tracing::error!("Wayland handler thread died"); tracing::error!("Wayland handler thread died");
_ = output.send(WaylandUpdate::Finished).await; _ = output.send(WaylandUpdate::Finished).await;
});
}); });
});
futures::future::pending().await futures::future::pending().await
}), },
) )
})
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -93,8 +93,9 @@ impl<Msg> Widget<Msg, cosmic::Theme, cosmic::Renderer> for WindowImage<'_, Msg>
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
state: &'b mut Tree, state: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'b>,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
viewport: &cosmic::iced_core::Rectangle,
translation: Vector, translation: Vector,
) -> Option<cosmic::iced_core::overlay::Element<'b, Msg, cosmic::Theme, cosmic::Renderer>> { ) -> Option<cosmic::iced_core::overlay::Element<'b, Msg, cosmic::Theme, cosmic::Renderer>> {
let children = [&mut self.image_button, &mut self.icon] let children = [&mut self.image_button, &mut self.icon]
@ -104,7 +105,7 @@ impl<Msg> Widget<Msg, cosmic::Theme, cosmic::Renderer> for WindowImage<'_, Msg>
.filter_map(|((child, state), layout)| { .filter_map(|((child, state), layout)| {
child child
.as_widget_mut() .as_widget_mut()
.overlay(state, layout, renderer, translation) .overlay(state, layout, renderer, viewport, translation)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -116,7 +117,7 @@ impl<Msg> Widget<Msg, cosmic::Theme, cosmic::Renderer> for WindowImage<'_, Msg>
} }
fn layout( fn layout(
&self, &mut self,
tree: &mut cosmic::iced_core::widget::Tree, tree: &mut cosmic::iced_core::widget::Tree,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
limits: &cosmic::iced_core::layout::Limits, limits: &cosmic::iced_core::layout::Limits,
@ -125,7 +126,7 @@ impl<Msg> Widget<Msg, cosmic::Theme, cosmic::Renderer> for WindowImage<'_, Msg>
let button = &mut children[0]; let button = &mut children[0];
let button_node = self let button_node = self
.image_button .image_button
.as_widget() .as_widget_mut()
.layout(button, renderer, limits); .layout(button, renderer, limits);
let img_node = &button_node.children()[0].children()[0]; let img_node = &button_node.children()[0].children()[0];
@ -135,7 +136,7 @@ impl<Msg> Widget<Msg, cosmic::Theme, cosmic::Renderer> for WindowImage<'_, Msg>
let icon = &mut children[1]; let icon = &mut children[1];
let icon_node = self let icon_node = self
.icon .icon
.as_widget() .as_widget_mut()
.layout( .layout(
icon, icon,
renderer, renderer,
@ -185,14 +186,14 @@ impl<Msg> Widget<Msg, cosmic::Theme, cosmic::Renderer> for WindowImage<'_, Msg>
} }
fn operate( fn operate(
&self, &mut self,
tree: &mut cosmic::iced_core::widget::Tree, tree: &mut cosmic::iced_core::widget::Tree,
layout: cosmic::iced_core::Layout<'_>, layout: cosmic::iced_core::Layout<'_>,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
operation: &mut dyn cosmic::widget::Operation<()>, operation: &mut dyn cosmic::widget::Operation<()>,
) { ) {
let layout = layout.children().collect::<Vec<_>>(); let layout = layout.children().collect::<Vec<_>>();
let children = [&self.image_button, &self.icon]; let children = [&mut self.image_button, &mut self.icon];
for (i, (layout, child)) in layout for (i, (layout, child)) in layout
.into_iter() .into_iter()
.zip(children.into_iter()) .zip(children.into_iter())
@ -200,26 +201,27 @@ impl<Msg> Widget<Msg, cosmic::Theme, cosmic::Renderer> for WindowImage<'_, Msg>
.rev() .rev()
{ {
let tree = &mut tree.children[i]; let tree = &mut tree.children[i];
child.as_widget().operate(tree, layout, renderer, operation); child
.as_widget_mut()
.operate(tree, layout, renderer, operation);
} }
} }
fn on_event( fn update(
&mut self, &mut self,
state: &mut cosmic::iced_core::widget::Tree, state: &mut cosmic::iced_core::widget::Tree,
event: cosmic::iced_core::Event, event: &cosmic::iced_core::Event,
layout: cosmic::iced_core::Layout<'_>, layout: cosmic::iced_core::Layout<'_>,
cursor: cosmic::iced_core::mouse::Cursor, cursor: cosmic::iced_core::mouse::Cursor,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
clipboard: &mut dyn cosmic::iced_core::Clipboard, clipboard: &mut dyn cosmic::iced_core::Clipboard,
shell: &mut cosmic::iced_core::Shell<'_, Msg>, shell: &mut cosmic::iced_core::Shell<'_, Msg>,
viewport: &cosmic::iced_core::Rectangle, viewport: &cosmic::iced_core::Rectangle,
) -> cosmic::iced_core::event::Status { ) {
let children = [&mut self.image_button, &mut self.icon]; let children = [&mut self.image_button, &mut self.icon];
let layout = layout.children().collect::<Vec<_>>(); let layout = layout.children().collect::<Vec<_>>();
// draw children in order // draw children in order
let mut status = cosmic::iced_core::event::Status::Ignored;
for (i, (layout, child)) in layout for (i, (layout, child)) in layout
.into_iter() .into_iter()
.zip(children.into_iter()) .zip(children.into_iter())
@ -228,21 +230,13 @@ impl<Msg> Widget<Msg, cosmic::Theme, cosmic::Renderer> for WindowImage<'_, Msg>
{ {
let tree = &mut state.children[i]; let tree = &mut state.children[i];
status = child.as_widget_mut().on_event( child.as_widget_mut().update(
tree, tree, event, layout, cursor, renderer, clipboard, shell, viewport,
event.clone(),
layout,
cursor,
renderer,
clipboard,
shell,
viewport,
); );
if matches!(status, cosmic::iced_core::event::Status::Captured) { if shell.is_event_captured() {
return status; return;
} }
} }
status
} }
fn mouse_interaction( fn mouse_interaction(

View file

@ -8,7 +8,6 @@ license = "GPL-3.0-or-later"
anyhow.workspace = true anyhow.workspace = true
async-fn-stream = "0.3" async-fn-stream = "0.3"
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings" } cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings" }
cosmic-time.workspace = true
futures.workspace = true futures.workspace = true
futures-util.workspace = true futures-util.workspace = true
i18n-embed-fl.workspace = true i18n-embed-fl.workspace = true
@ -35,7 +34,8 @@ uuid = { version = "1.21.0", features = ["v4"] }
[dependencies.cosmic-settings-network-manager-subscription] [dependencies.cosmic-settings-network-manager-subscription]
git = "https://github.com/pop-os/cosmic-settings/" git = "https://github.com/pop-os/cosmic-settings/"
# path = "../../cosmic-settings/subscriptions/network-manager"
[dependencies.cosmic-settings-airplane-mode-subscription] [dependencies.cosmic-settings-airplane-mode-subscription]
git = "https://github.com/pop-os/cosmic-settings/" git = "https://github.com/pop-os/cosmic-settings/"
# path = "../../cosmic-settings/subscriptions/airplane-mode"

View file

@ -36,14 +36,13 @@ use cosmic::{
widget::{ widget::{
Column, Id, Row, button, container, divider, Column, Id, Row, button, container, divider,
icon::{self, from_name}, icon::{self, from_name},
scrollable, secure_input, text, text_input, scrollable, secure_input, text, text_input, toggler,
}, },
}; };
use cosmic_dbus_networkmanager::interface::{ use cosmic_dbus_networkmanager::interface::{
access_point, access_point,
enums::{ActiveConnectionState, DeviceState, NmConnectivityState, NmState}, enums::{ActiveConnectionState, DeviceState, NmConnectivityState, NmState},
}; };
use cosmic_time::{Instant, Timeline, anim, chain, id};
use futures::{StreamExt, channel::mpsc::TrySendError}; use futures::{StreamExt, channel::mpsc::TrySendError};
use zbus::{Connection, zvariant::ObjectPath}; use zbus::{Connection, zvariant::ObjectPath};
@ -96,9 +95,6 @@ impl From<NewConnectionState> for AccessPoint {
} }
} }
static WIFI: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
static AIRPLANE_MODE: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
pub static SECURE_INPUT_WIFI: LazyLock<Id> = LazyLock::new(Id::unique); pub static SECURE_INPUT_WIFI: LazyLock<Id> = LazyLock::new(Id::unique);
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
@ -170,7 +166,6 @@ struct CosmicNetworkApplet {
show_available_vpns: bool, show_available_vpns: bool,
new_connection: Option<NewConnectionState>, new_connection: Option<NewConnectionState>,
conn: Option<Connection>, conn: Option<Connection>,
timeline: Timeline,
toggle_wifi_ctr: u128, toggle_wifi_ctr: u128,
token_tx: Option<calloop::channel::Sender<TokenRequest>>, token_tx: Option<calloop::channel::Sender<TokenRequest>>,
failed_known_ssids: FxHashSet<Arc<str>>, failed_known_ssids: FxHashSet<Arc<str>>,
@ -363,31 +358,15 @@ impl CosmicNetworkApplet {
} }
fn update_togglers(&mut self, state: &NetworkManagerState) { fn update_togglers(&mut self, state: &NetworkManagerState) {
let timeline = &mut self.timeline;
let mut changed = false; let mut changed = false;
if self.nm_state.nm_state.wifi_enabled != state.wifi_enabled { if self.nm_state.nm_state.wifi_enabled != state.wifi_enabled {
self.nm_state.nm_state.wifi_enabled = state.wifi_enabled; self.nm_state.nm_state.wifi_enabled = state.wifi_enabled;
changed = true; changed = true;
let chain = if state.wifi_enabled {
chain::Toggler::on(WIFI.clone(), 1.)
} else {
chain::Toggler::off(WIFI.clone(), 1.)
};
timeline.set_chain(chain);
} }
if self.nm_state.nm_state.airplane_mode != state.airplane_mode { if self.nm_state.nm_state.airplane_mode != state.airplane_mode {
self.nm_state.nm_state.airplane_mode = state.airplane_mode; self.nm_state.nm_state.airplane_mode = state.airplane_mode;
changed = true; changed = true;
let chain = if state.airplane_mode {
chain::Toggler::on(AIRPLANE_MODE.clone(), 1.)
} else {
chain::Toggler::off(AIRPLANE_MODE.clone(), 1.)
};
timeline.set_chain(chain);
}
if changed {
timeline.start();
} }
} }
fn view_window_return<'a>(&self, mut content: Column<'a, Message>) -> Element<'a, Message> { fn view_window_return<'a>(&self, mut content: Column<'a, Message>) -> Element<'a, Message> {
@ -463,7 +442,6 @@ pub(crate) enum Message {
ToggleVisibleNetworks, ToggleVisibleNetworks,
SelectWirelessAccessPoint(AccessPoint), SelectWirelessAccessPoint(AccessPoint),
CancelNewConnection, CancelNewConnection,
Frame(Instant),
Token(TokenUpdate), Token(TokenUpdate),
OpenSettings, OpenSettings,
ResetFailedKnownSsid(String, HwAddress), ResetFailedKnownSsid(String, HwAddress),
@ -789,7 +767,6 @@ impl cosmic::Application for CosmicNetworkApplet {
fn update(&mut self, message: Message) -> app::Task<Message> { fn update(&mut self, message: Message) -> app::Task<Message> {
match message { match message {
Message::Frame(now) => self.timeline.now(now),
Message::TogglePopup => { Message::TogglePopup => {
if let Some(p) = self.popup.take() { if let Some(p) = self.popup.take() {
self.show_visible_networks = false; self.show_visible_networks = false;
@ -817,7 +794,6 @@ impl cosmic::Application for CosmicNetworkApplet {
// TODO request update of state maybe // TODO request update of state maybe
let new_id = window::Id::unique(); let new_id = window::Id::unique();
self.popup.replace(new_id); self.popup.replace(new_id);
self.timeline = Timeline::new();
let popup_settings = self.core.applet.get_popup_settings( let popup_settings = self.core.applet.get_popup_settings(
self.core.main_window_id().unwrap(), self.core.main_window_id().unwrap(),
@ -1604,16 +1580,11 @@ impl cosmic::Application for CosmicNetworkApplet {
column![ column![
vpn_ethernet_col, vpn_ethernet_col,
padded_control( padded_control(
anim!( toggler(self.nm_state.nm_state.airplane_mode,)
//toggler .label(fl!("airplane-mode"))
AIRPLANE_MODE, .on_toggle(Message::ToggleAirplaneMode)
&self.timeline, .text_size(14)
fl!("airplane-mode"), .width(Length::Fill)
self.nm_state.nm_state.airplane_mode,
|_chain, enable| { Message::ToggleAirplaneMode(enable) },
)
.text_size(14)
.width(Length::Fill)
), ),
padded_control(divider::horizontal::default()) padded_control(divider::horizontal::default())
.padding([space_xxs, space_s]), .padding([space_xxs, space_s]),
@ -1621,16 +1592,11 @@ impl cosmic::Application for CosmicNetworkApplet {
.align_x(Alignment::Center) .align_x(Alignment::Center)
), ),
padded_control( padded_control(
anim!( toggler(self.nm_state.nm_state.wifi_enabled,)
//toggler .label(fl!("wifi"))
WIFI, .on_toggle(Message::WiFiEnable)
&self.timeline, .text_size(14)
fl!("wifi"), .width(Length::Fill)
self.nm_state.nm_state.wifi_enabled,
|_chain, enable| { Message::WiFiEnable(enable) },
)
.text_size(14)
.width(Length::Fill)
), ),
] ]
.align_x(Alignment::Center) .align_x(Alignment::Center)
@ -1993,16 +1959,10 @@ impl cosmic::Application for CosmicNetworkApplet {
} }
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
let timeline = self activation_token_subscription(0).map(Message::Token)
.timeline
.as_subscription()
.map(|(_, now)| Message::Frame(now));
let token_sub = activation_token_subscription(0).map(Message::Token);
Subscription::batch([timeline, token_sub])
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<cosmic::iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }

View file

@ -6,7 +6,6 @@ license = "GPL-3.0-only"
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
cosmic-time.workspace = true
libcosmic.workspace = true libcosmic.workspace = true
tokio.workspace = true tokio.workspace = true
cosmic-notifications-util = { git = "https://github.com/pop-os/cosmic-notifications" } cosmic-notifications-util = { git = "https://github.com/pop-os/cosmic-notifications" }

View file

@ -16,18 +16,17 @@ use cosmic::{
Alignment, Length, Subscription, Alignment, Length, Subscription,
advanced::text::{Ellipsize, EllipsizeHeightLimit}, advanced::text::{Ellipsize, EllipsizeHeightLimit},
platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup}, platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup},
widget::{column, row}, widget::{self, column, row},
window, window,
}, },
surface, theme, surface, theme,
widget::{Column, button, container, divider, icon, scrollable, text}, widget::{Column, button, cards, container, divider, icon, scrollable, space, text, toggler},
}; };
use cosmic::iced_futures::futures::executor::block_on; use cosmic::iced_futures::futures::executor::block_on;
use cosmic_notifications_config::NotificationsConfig; use cosmic_notifications_config::NotificationsConfig;
use cosmic_notifications_util::{ActionId, Image, Notification}; use cosmic_notifications_util::{ActionId, Image, Notification};
use cosmic_time::{Instant, Timeline, anim, chain, id};
use std::{borrow::Cow, collections::HashMap, path::PathBuf, sync::LazyLock}; use std::{borrow::Cow, collections::HashMap, path::PathBuf, sync::LazyLock};
use subscriptions::notifications::{self, NotificationsAppletProxy}; use subscriptions::notifications::{self, NotificationsAppletProxy};
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
@ -38,8 +37,6 @@ pub fn run() -> cosmic::iced::Result {
cosmic::applet::run::<Notifications>(()) cosmic::applet::run::<Notifications>(())
} }
static DO_NOT_DISTURB: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
struct Notifications { struct Notifications {
core: cosmic::app::Core, core: cosmic::app::Core,
config: NotificationsConfig, config: NotificationsConfig,
@ -47,27 +44,14 @@ struct Notifications {
icon_name: String, icon_name: String,
popup: Option<window::Id>, popup: Option<window::Id>,
// notifications: Vec<Notification>, // notifications: Vec<Notification>,
timeline: Timeline,
dbus_sender: Option<Sender<subscriptions::dbus::Input>>, dbus_sender: Option<Sender<subscriptions::dbus::Input>>,
cards: Vec<(id::Cards, Vec<Notification>, bool, String, String, String)>, cards: Vec<(widget::Id, Vec<Notification>, bool, String, String, String)>,
token_tx: Option<calloop::channel::Sender<TokenRequest>>, token_tx: Option<calloop::channel::Sender<TokenRequest>>,
proxy: NotificationsAppletProxy<'static>, proxy: NotificationsAppletProxy<'static>,
notifications_tx: Option<Sender<notifications::Input>>, notifications_tx: Option<Sender<notifications::Input>>,
} }
impl Notifications { impl Notifications {
fn update_cards(&mut self, id: id::Cards) {
if let Some((id, _, card_value, ..)) = self.cards.iter().find(|c| c.0 == id) {
let chain = if *card_value {
chain::Cards::on(id.clone(), 1.)
} else {
chain::Cards::off(id.clone(), 1.)
};
self.timeline.set_chain(chain);
self.timeline.start();
}
}
fn update_icon(&mut self) { fn update_icon(&mut self) {
self.icon_name = if self.config.do_not_disturb { self.icon_name = if self.config.do_not_disturb {
"cosmic-applet-notification-disabled-symbolic" "cosmic-applet-notification-disabled-symbolic"
@ -84,8 +68,7 @@ impl Notifications {
enum Message { enum Message {
TogglePopup, TogglePopup,
CloseRequested(window::Id), CloseRequested(window::Id),
DoNotDisturb(chain::Toggler, bool), DoNotDisturb(bool),
Frame(Instant),
NotificationEvent(notifications::Output), NotificationEvent(notifications::Output),
Config(NotificationsConfig), Config(NotificationsConfig),
DbusEvent(subscriptions::dbus::Output), DbusEvent(subscriptions::dbus::Output),
@ -128,7 +111,6 @@ impl cosmic::Application for Notifications {
config, config,
icon_name: String::default(), icon_name: String::default(),
popup: None, popup: None,
timeline: Timeline::default(),
dbus_sender: Option::default(), dbus_sender: Option::default(),
cards: Vec::new(), cards: Vec::new(),
token_tx: Option::default(), token_tx: Option::default(),
@ -148,7 +130,7 @@ impl cosmic::Application for Notifications {
&mut self.core &mut self.core
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<cosmic::iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
@ -162,9 +144,6 @@ impl cosmic::Application for Notifications {
} }
Message::Config(res.config) Message::Config(res.config)
}), }),
self.timeline
.as_subscription()
.map(|(_, now)| Message::Frame(now)),
subscriptions::dbus::proxy().map(Message::DbusEvent), subscriptions::dbus::proxy().map(Message::DbusEvent),
subscriptions::notifications::notifications(self.proxy.clone()) subscriptions::notifications::notifications(self.proxy.clone())
.map(Message::NotificationEvent), .map(Message::NotificationEvent),
@ -174,16 +153,12 @@ impl cosmic::Application for Notifications {
fn update(&mut self, message: Self::Message) -> app::Task<Self::Message> { fn update(&mut self, message: Self::Message) -> app::Task<Self::Message> {
match message { match message {
Message::Frame(now) => {
self.timeline.now(now);
}
Message::TogglePopup => { Message::TogglePopup => {
if let Some(p) = self.popup.take() { if let Some(p) = self.popup.take() {
return destroy_popup(p); return destroy_popup(p);
} else { } else {
let new_id = window::Id::unique(); let new_id = window::Id::unique();
self.popup.replace(new_id); self.popup.replace(new_id);
self.timeline = Timeline::new();
let popup_settings = self.core.applet.get_popup_settings( let popup_settings = self.core.applet.get_popup_settings(
self.core.main_window_id().unwrap(), self.core.main_window_id().unwrap(),
@ -196,8 +171,7 @@ impl cosmic::Application for Notifications {
return get_popup(popup_settings); return get_popup(popup_settings);
} }
} }
Message::DoNotDisturb(chain, b) => { Message::DoNotDisturb(b) => {
self.timeline.set_chain(chain).start();
self.config.do_not_disturb = b; self.config.do_not_disturb = b;
if let Some(helper) = &self.config_helper { if let Some(helper) = &self.config_helper {
if let Err(err) = self.config.write_entry(helper) { if let Err(err) = self.config.write_entry(helper) {
@ -223,7 +197,7 @@ impl cosmic::Application for Notifications {
} }
} else { } else {
self.cards.push(( self.cards.push((
id::Cards::new(n.app_name.clone()), widget::Id::new(n.app_name.clone()),
vec![n], vec![n],
false, false,
fl!("show-more", HashMap::from([("more", "1")])), fl!("show-more", HashMap::from([("more", "1")])),
@ -315,7 +289,6 @@ impl cosmic::Application for Notifications {
} else { } else {
return Task::none(); return Task::none();
}; };
self.update_cards(id);
} }
Message::CloseRequested(id) => { Message::CloseRequested(id) => {
if Some(id) == self.popup { if Some(id) == self.popup {
@ -412,15 +385,11 @@ impl cosmic::Application for Notifications {
} = theme::active().cosmic().spacing; } = theme::active().cosmic().spacing;
let do_not_disturb = padded_control(row![ let do_not_disturb = padded_control(row![
anim!( toggler(self.config.do_not_disturb)
DO_NOT_DISTURB, .on_toggle(Message::DoNotDisturb)
&self.timeline, .text_size(14)
fl!("do-not-disturb"), .width(Length::Fill)
self.config.do_not_disturb, .label(fl!("do-not-disturb"))
Message::DoNotDisturb
)
.text_size(14)
.width(Length::Fill)
]); ]);
let notifications = if self.cards.is_empty() { let notifications = if self.cards.is_empty() {
@ -522,13 +491,12 @@ impl cosmic::Application for Notifications {
Some(cosmic::widget::icon::from_name(n.app_icon.as_str()).handle()) Some(cosmic::widget::icon::from_name(n.app_icon.as_str()).handle())
} }
}); });
let card_list = anim!( let card_list = cards(
//cards //cards
c.0.clone(), c.0.clone(),
&self.timeline,
notif_elems, notif_elems,
Message::ClearAll(Some(name.clone())), Message::ClearAll(Some(name.clone())),
Some(move |_, e| Message::CardsToggled(name.clone(), e)), Some(move |e| Message::CardsToggled(name.clone(), e)),
Some(move |id| Message::ActivateNotification(ids[id])), Some(move |id| Message::ActivateNotification(ids[id])),
&c.3, &c.3,
&c.4, &c.4,
@ -536,6 +504,7 @@ impl cosmic::Application for Notifications {
show_more_icon, show_more_icon,
c.2, c.2,
); );
// let card_list = space::horizontal().width(Length::Fixed(10.));
notifs.push(card_list.into()); notifs.push(card_list.into());
} }

View file

@ -33,73 +33,75 @@ pub enum Output {
pub fn proxy() -> Subscription<Output> { pub fn proxy() -> Subscription<Output> {
struct SomeWorker; struct SomeWorker;
Subscription::run_with_id( Subscription::run_with(std::any::TypeId::of::<SomeWorker>(), |_| {
std::any::TypeId::of::<SomeWorker>(), stream::channel(
stream::channel(50, |mut output| async move { 50,
let mut state = State::Ready; |mut output: futures::channel::mpsc::Sender<Output>| async move {
let mut state = State::Ready;
loop { loop {
match &mut state { match &mut state {
State::Ready => { State::Ready => {
let (sender, receiver) = channel(10); let (sender, receiver) = channel(10);
let Ok(conn) = Connection::session().await else { let Ok(conn) = Connection::session().await else {
error!("Failed to connect to session bus"); error!("Failed to connect to session bus");
state = State::Finished; state = State::Finished;
continue; continue;
}; };
let Ok(proxy) = NotificationsProxy::new(&conn).await else { let Ok(proxy) = NotificationsProxy::new(&conn).await else {
error!("Failed to create proxy from session connection"); error!("Failed to create proxy from session connection");
state = State::Finished; state = State::Finished;
continue; continue;
}; };
let tx = sender.clone(); let tx = sender.clone();
if let Err(err) = output.send(Output::Ready(sender)).await { if let Err(err) = output.send(Output::Ready(sender)).await {
error!("Failed to send sender: {}", err); error!("Failed to send sender: {}", err);
state = State::Finished; state = State::Finished;
continue; continue;
}
state = match proxy.receive_notification_closed().await {
Ok(mut s) => {
tokio::spawn(async move {
while let Some(msg) = s.next().await {
let Ok(id) = msg.args().map(|args| args.id) else {
continue;
};
_ = tx.send(Input::CloseEvent(id)).await;
}
});
State::WaitingForNotificationEvent(proxy, receiver)
} }
Err(err) => { state = match proxy.receive_notification_closed().await {
error!( Ok(mut s) => {
"failed to get a stream of signals for notifications. {}", tokio::spawn(async move {
err while let Some(msg) = s.next().await {
); let Ok(id) = msg.args().map(|args| args.id) else {
State::Finished continue;
};
_ = tx.send(Input::CloseEvent(id)).await;
}
});
State::WaitingForNotificationEvent(proxy, receiver)
}
Err(err) => {
error!(
"failed to get a stream of signals for notifications. {}",
err
);
State::Finished
}
};
}
State::WaitingForNotificationEvent(proxy, rx) => match rx.recv().await {
Some(Input::Dismiss(id)) => {
if let Err(err) = proxy.close_notification(id).await {
error!("Failed to close notification: {}", err);
}
} }
}; Some(Input::CloseEvent(id)) => {
} _ = output.send(Output::CloseEvent(id)).await;
State::WaitingForNotificationEvent(proxy, rx) => match rx.recv().await {
Some(Input::Dismiss(id)) => {
if let Err(err) = proxy.close_notification(id).await {
error!("Failed to close notification: {}", err);
} }
None => {
warn!("Notification event channel closed");
state = State::Finished;
continue;
}
},
State::Finished => {
let () = futures::future::pending().await;
} }
Some(Input::CloseEvent(id)) => {
_ = output.send(Output::CloseEvent(id)).await;
}
None => {
warn!("Notification event channel closed");
state = State::Finished;
continue;
}
},
State::Finished => {
let () = futures::future::pending().await;
} }
} }
} },
}), )
) })
} }

View file

@ -12,6 +12,7 @@ use cosmic_notifications_util::Notification;
use futures_util::{SinkExt, StreamExt}; use futures_util::{SinkExt, StreamExt};
use std::{ use std::{
collections::HashMap, collections::HashMap,
hash::Hash,
os::unix::io::{FromRawFd, RawFd}, os::unix::io::{FromRawFd, RawFd},
pin::pin, pin::pin,
}; };
@ -37,89 +38,97 @@ pub enum Output {
} }
pub fn notifications(proxy: NotificationsAppletProxy<'static>) -> Subscription<Output> { pub fn notifications(proxy: NotificationsAppletProxy<'static>) -> Subscription<Output> {
struct SomeWorker; struct Wrapper(NotificationsAppletProxy<'static>);
Subscription::run_with_id( impl Hash for Wrapper {
std::any::TypeId::of::<SomeWorker>(), fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
stream::channel(50, |mut output| async move { std::any::TypeId::of::<NotificationsAppletProxy<'static>>().hash(state);
let mut state = State::WaitingForNotificationEvent; }
let (sender, mut receiver) = mpsc::channel(10); }
_ = output.send(Output::Ready(sender)).await; Subscription::run_with(Wrapper(proxy), |Wrapper(proxy)| {
let proxy = proxy.clone();
stream::channel(
50,
move |mut output: futures::channel::mpsc::Sender<Output>| async move {
let mut state = State::WaitingForNotificationEvent;
let (sender, mut receiver) = mpsc::channel(10);
_ = output.send(Output::Ready(sender)).await;
let mut signal; let mut signal;
let mut fail_count: u8 = 0; let mut fail_count: u8 = 0;
loop { loop {
match proxy.receive_notify().await { match proxy.receive_notify().await {
Ok(s) => { Ok(s) => {
signal = s; signal = s;
break; break;
} }
Err(err) => { Err(err) => {
error!( error!(
"failed to get a stream of signals for notifications. {}", "failed to get a stream of signals for notifications. {}",
err err
); );
fail_count = fail_count.saturating_add(1); fail_count = fail_count.saturating_add(1);
if fail_count > 5 { if fail_count > 5 {
error!("Failed to receive notification events"); error!("Failed to receive notification events");
// exit because the applet needs the notifications daemon in order to work properly // exit because the applet needs the notifications daemon in order to work properly
std::process::exit(0); std::process::exit(0);
} else { } else {
tokio::time::sleep(std::time::Duration::from_secs(1)).await; tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
continue;
} }
continue;
} }
} }
} loop {
loop { match &mut state {
match &mut state { State::WaitingForNotificationEvent => {
State::WaitingForNotificationEvent => { trace!("Waiting for notification events...");
trace!("Waiting for notification events..."); let mut next_signal = signal.next();
let mut next_signal = signal.next(); let mut next_input = pin!(receiver.recv().fuse());
let mut next_input = pin!(receiver.recv().fuse()); cosmic::iced::futures::select! {
cosmic::iced::futures::select! { v = next_signal => {
v = next_signal => { if let Some(msg) = v {
if let Some(msg) = v { let Ok(args) = msg.args() else {
let Ok(args) = msg.args() else { break;
break; };
}; let notification = Notification::new(
let notification = Notification::new( args.app_name,
args.app_name, args.id,
args.id, args.app_icon,
args.app_icon, args.summary,
args.summary, args.body,
args.body, args.actions,
args.actions, args.hints,
args.hints, args.expire_timeout,
args.expire_timeout, );
); _ = output.send(Output::Notification(notification)).await;
_ = output.send(Output::Notification(notification)).await;
} else {
tracing::error!("Signal stream closed, ending notifications subscription");
state = State::Finished;
}
}
v = next_input => {
if let Some(Input::Activated(id, action)) = v {
if proxy.invoke_action(id, action.clone()).await.is_err() {
tracing::error!("Failed to invoke action {id} {action}");
} else { } else {
tracing::error!("Invoked {action} for {id}"); tracing::error!("Signal stream closed, ending notifications subscription");
state = State::Finished;
}
}
v = next_input => {
if let Some(Input::Activated(id, action)) = v {
if proxy.invoke_action(id, action.clone()).await.is_err() {
tracing::error!("Failed to invoke action {id} {action}");
} else {
tracing::error!("Invoked {action} for {id}");
}
} else {
tracing::error!("Channel closed, ending notifications subscription");
state = State::Finished;
} }
} else {
tracing::error!("Channel closed, ending notifications subscription");
state = State::Finished;
} }
} }
} }
} State::Finished => {
State::Finished => { let () = futures::future::pending().await;
let () = futures::future::pending().await; }
} }
} }
} },
}), )
) })
} }
#[proxy( #[proxy(

View file

@ -16,7 +16,7 @@ use cosmic::{
window, window,
}, },
surface, theme, surface, theme,
widget::{Space, button, divider, icon, text}, widget::{Space, button, divider, icon, space, text},
}; };
use std::sync::LazyLock; use std::sync::LazyLock;
@ -227,7 +227,7 @@ impl cosmic::Application for Power {
row![ row![
text_icon("system-lock-screen-symbolic", 24), text_icon("system-lock-screen-symbolic", 24),
text::body(fl!("lock-screen")), text::body(fl!("lock-screen")),
Space::with_width(Length::Fill), space::horizontal().width(Length::Fill),
text::body(fl!("lock-screen-shortcut")), text::body(fl!("lock-screen-shortcut")),
] ]
.align_y(Alignment::Center) .align_y(Alignment::Center)
@ -238,7 +238,7 @@ impl cosmic::Application for Power {
row![ row![
text_icon("system-log-out-symbolic", 24), text_icon("system-log-out-symbolic", 24),
text::body(fl!("log-out")), text::body(fl!("log-out")),
Space::with_width(Length::Fill), space::horizontal().width(Length::Fill),
text::body(fl!("log-out-shortcut")), text::body(fl!("log-out-shortcut")),
] ]
.align_y(Alignment::Center) .align_y(Alignment::Center)
@ -285,7 +285,7 @@ impl cosmic::Application for Power {
activation_token_subscription(0).map(Message::Token) activation_token_subscription(0).map(Message::Token)
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
} }

View file

@ -3,12 +3,15 @@
use cosmic::{ use cosmic::{
Element, Task, app, Element, Task, app,
applet::cosmic_panel_config::PanelAnchor, applet::{
applet::token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription}, cosmic_panel_config::PanelAnchor,
token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription},
},
cctk::sctk::reexports::calloop, cctk::sctk::reexports::calloop,
iced::{ iced::{
self, Length, Subscription, self, Length, Subscription,
platform_specific::shell::commands::popup::{destroy_popup, get_popup}, platform_specific::shell::commands::popup::{destroy_popup, get_popup},
theme::Style,
window, window,
}, },
surface, surface,
@ -146,7 +149,7 @@ impl cosmic::Application for App {
&mut self.core &mut self.core
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }

View file

@ -192,7 +192,7 @@ fn layout_view(layout: &Layout, expanded: Option<i32>) -> cosmic::Element<'_, Ms
if !i.visible() { if !i.visible() {
None None
} else if i.type_() == Some("separator") { } else if i.type_() == Some("separator") {
Some(iced::widget::horizontal_rule(2).into()) Some(iced::widget::rule::horizontal(2).into())
} else if let Some(label) = i.label() { } else if let Some(label) = i.label() {
// Strip _ when not doubled // Strip _ when not doubled
// TODO: interpret as "access key"? And label with underline. // TODO: interpret as "access key"? And label with underline.

View file

@ -1,10 +1,11 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use std::hash::Hash;
use cosmic::iced::{self, Subscription}; use cosmic::iced::{self, Subscription};
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::path::PathBuf;
use zbus::zvariant::{self, OwnedValue}; use zbus::zvariant::{self, OwnedValue};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -76,22 +77,40 @@ impl StatusNotifierItem {
let Some(menu_proxy) = self.menu_proxy.clone() else { let Some(menu_proxy) = self.menu_proxy.clone() else {
return Subscription::none(); return Subscription::none();
}; };
Subscription::run_with_id( struct Wrapper {
format!("status-notifier-item-layout-{}", &self.name), menu_proxy: DBusMenuProxy<'static>,
async move { name: String,
let initial = futures::stream::once(get_layout(menu_proxy.clone())); }
impl Hash for Wrapper {
let layout_updated = menu_proxy.receive_layout_updated().await.unwrap(); fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let props_updated = menu_proxy.receive_items_properties_updated().await.unwrap(); self.name.hash(state);
// Merge both streams - any update triggers a layout refetch
let updates =
futures::stream_select!(layout_updated.map(|_| ()), props_updated.map(|_| ()))
.then(move |()| get_layout(menu_proxy.clone()));
initial.chain(updates)
} }
.flatten_stream(), }
Subscription::run_with(
Wrapper {
menu_proxy,
name: format!("status-notifier-item-layout-{}", &self.name),
},
|Wrapper { menu_proxy, .. }| {
let menu_proxy = menu_proxy.clone();
async move {
let initial = futures::stream::once(get_layout(menu_proxy.clone()));
let layout_updated = menu_proxy.receive_layout_updated().await.unwrap();
let props_updated =
menu_proxy.receive_items_properties_updated().await.unwrap();
// Merge both streams - any update triggers a layout refetch
let updates = futures::stream_select!(
layout_updated.map(|_| ()),
props_updated.map(|_| ())
)
.then(move |()| get_layout(menu_proxy.clone()));
initial.chain(updates)
}
.flatten_stream()
},
) )
} }
@ -108,15 +127,30 @@ impl StatusNotifierItem {
} }
let item_proxy = self.item_proxy.clone(); let item_proxy = self.item_proxy.clone();
Subscription::run_with_id( struct Wrapper {
format!("status-notifier-item-icon-{}", &self.name), item_proxy: StatusNotifierItemProxy<'static>,
async move { name: String,
let new_icon_stream = item_proxy.receive_new_icon().await.unwrap(); }
futures::stream::once(async {}) impl Hash for Wrapper {
.chain(new_icon_stream.map(|_| ())) fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
.then(move |()| icon_events(item_proxy.clone())) self.name.hash(state);
} }
.flatten_stream(), }
Subscription::run_with(
Wrapper {
item_proxy,
name: format!("status-notifier-item-icon-{}", &self.name),
},
|Wrapper { item_proxy, .. }| {
let item_proxy = item_proxy.clone();
async move {
let new_icon_stream = item_proxy.receive_new_icon().await.unwrap();
futures::stream::once(async {})
.chain(new_icon_stream.map(|_| ()))
.then(move |()| icon_events(item_proxy.clone()))
}
.flatten_stream()
},
) )
} }

View file

@ -3,6 +3,8 @@
// TODO: Both this and server proxy could emit same events, have way to generate stream from either? // TODO: Both this and server proxy could emit same events, have way to generate stream from either?
use std::any::TypeId;
use cosmic::iced::{self, Subscription}; use cosmic::iced::{self, Subscription};
use futures::{StreamExt, stream}; use futures::{StreamExt, stream};
@ -26,8 +28,8 @@ enum State {
} }
pub fn subscription() -> iced::Subscription<Event> { pub fn subscription() -> iced::Subscription<Event> {
Subscription::run_with_id( pub struct MyID;
"status-notifier-watcher", Subscription::run_with(TypeId::of::<MyID>(), |_| {
stream::unfold(State::NotConnected, |state| async move { stream::unfold(State::NotConnected, |state| async move {
match state { match state {
State::NotConnected => match connect().await { State::NotConnected => match connect().await {
@ -42,8 +44,8 @@ pub fn subscription() -> iced::Subscription<Event> {
.map(|event| (event, State::Connected(stream))), .map(|event| (event, State::Connected(stream))),
State::Failed => None, State::Failed => None,
} }
}), })
) })
} }
async fn connect() -> zbus::Result<(zbus::Connection, client::EventStream)> { async fn connect() -> zbus::Result<(zbus::Connection, client::EventStream)> {

View file

@ -10,7 +10,6 @@ anyhow.workspace = true
cctk.workspace = true cctk.workspace = true
cosmic-comp-config = { git = "https://github.com/pop-os/cosmic-comp.git", rev = "5eb5af4" } cosmic-comp-config = { git = "https://github.com/pop-os/cosmic-comp.git", rev = "5eb5af4" }
cosmic-protocols.workspace = true cosmic-protocols.workspace = true
cosmic-time.workspace = true
i18n-embed-fl.workspace = true i18n-embed-fl.workspace = true
i18n-embed.workspace = true i18n-embed.workspace = true
rust-embed.workspace = true rust-embed.workspace = true

View file

@ -23,16 +23,15 @@ pub enum WorkspacesUpdate {
} }
pub fn workspaces() -> iced::Subscription<WorkspacesUpdate> { pub fn workspaces() -> iced::Subscription<WorkspacesUpdate> {
Subscription::run_with_id( Subscription::run_with(std::any::TypeId::of::<WorkspacesUpdate>(), |_| {
std::any::TypeId::of::<WorkspacesUpdate>(),
stream::channel(50, move |mut output| async move { stream::channel(50, move |mut output| async move {
let mut state = State::Waiting; let mut state = State::Waiting;
loop { loop {
state = start_listening(state, &mut output).await; state = start_listening(state, &mut output).await;
} }
}), })
) })
} }
async fn start_listening( async fn start_listening(

View file

@ -6,8 +6,8 @@ use crate::{
}; };
use cctk::sctk::reexports::calloop::channel::SyncSender; use cctk::sctk::reexports::calloop::channel::SyncSender;
use cosmic::{ use cosmic::{
Element, Task, app, Element, Task,
app::Core, app::{self, Core},
applet::{menu_button, padded_control}, applet::{menu_button, padded_control},
cosmic_config::{Config, ConfigSet, CosmicConfigEntry}, cosmic_config::{Config, ConfigSet, CosmicConfigEntry},
cosmic_theme::Spacing, cosmic_theme::Spacing,
@ -21,12 +21,11 @@ use cosmic::{
widget::{ widget::{
container, divider, container, divider,
segmented_button::{self, Entity, SingleSelectModel}, segmented_button::{self, Entity, SingleSelectModel},
segmented_control, text, segmented_control, text, toggler,
}, },
}; };
use cosmic_comp_config::{CosmicCompConfig, TileBehavior}; use cosmic_comp_config::{CosmicCompConfig, TileBehavior};
use cosmic_protocols::workspace::v2::client::zcosmic_workspace_handle_v2::TilingState; use cosmic_protocols::workspace::v2::client::zcosmic_workspace_handle_v2::TilingState;
use cosmic_time::{Timeline, anim, chain, id};
use std::{thread, time::Instant}; use std::{thread, time::Instant};
use tracing::error; use tracing::error;
@ -37,7 +36,6 @@ const OFF: &str = "com.system76.CosmicAppletTiling.Off";
pub struct Window { pub struct Window {
core: Core, core: Core,
popup: Option<Id>, popup: Option<Id>,
timeline: Timeline,
config: CosmicCompConfig, config: CosmicCompConfig,
config_helper: Config, config_helper: Config,
new_workspace_behavior_model: segmented_button::SingleSelectModel, new_workspace_behavior_model: segmented_button::SingleSelectModel,
@ -45,17 +43,14 @@ pub struct Window {
/// may not match the config value if behavior is per-workspace /// may not match the config value if behavior is per-workspace
autotiled: bool, autotiled: bool,
workspace_tx: Option<SyncSender<AppRequest>>, workspace_tx: Option<SyncSender<AppRequest>>,
tile_windows: id::Toggler,
active_hint: id::Toggler,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Message { pub enum Message {
TogglePopup, TogglePopup,
PopupClosed(Id), PopupClosed(Id),
Frame(Instant), ToggleTileWindows(bool),
ToggleTileWindows(chain::Toggler, bool), ToggleActiveHint(bool),
ToggleActiveHint(chain::Toggler, bool),
MyConfigUpdate(Box<CosmicCompConfig>), MyConfigUpdate(Box<CosmicCompConfig>),
WorkspaceUpdate(WorkspacesUpdate), WorkspaceUpdate(WorkspacesUpdate),
NewWorkspace(Entity), NewWorkspace(Entity),
@ -110,15 +105,12 @@ impl cosmic::Application for Window {
let window = Self { let window = Self {
core, core,
popup: None, popup: None,
timeline: Timeline::default(),
autotiled: config.autotile, autotiled: config.autotile,
config, config,
config_helper, config_helper,
new_workspace_behavior_model, new_workspace_behavior_model,
new_workspace_entity, new_workspace_entity,
workspace_tx: None, workspace_tx: None,
tile_windows: id::Toggler::unique(),
active_hint: id::Toggler::unique(),
}; };
(window, Task::none()) (window, Task::none())
} }
@ -128,12 +120,7 @@ impl cosmic::Application for Window {
} }
fn subscription(&self) -> Subscription<Self::Message> { fn subscription(&self) -> Subscription<Self::Message> {
let timeline = self
.timeline
.as_subscription()
.map(|(_, now)| Message::Frame(now));
Subscription::batch([ Subscription::batch([
timeline,
self.core self.core
.watch_config::<CosmicCompConfig>("com.system76.CosmicComp") .watch_config::<CosmicCompConfig>("com.system76.CosmicComp")
.map(|u| Message::MyConfigUpdate(Box::new(u.config))), .map(|u| Message::MyConfigUpdate(Box::new(u.config))),
@ -146,15 +133,7 @@ impl cosmic::Application for Window {
Message::WorkspaceUpdate(msg) => match msg { Message::WorkspaceUpdate(msg) => match msg {
WorkspacesUpdate::State(state) => { WorkspacesUpdate::State(state) => {
self.autotiled = matches!(state, TilingState::TilingEnabled); self.autotiled = matches!(state, TilingState::TilingEnabled);
if self.popup.is_some() { if self.popup.is_some() {}
self.timeline
.set_chain(if self.autotiled {
cosmic_time::chain::Toggler::on(self.tile_windows.clone(), 1.0)
} else {
cosmic_time::chain::Toggler::off(self.tile_windows.clone(), 1.0)
})
.start();
}
} }
WorkspacesUpdate::Started(tx) => { WorkspacesUpdate::Started(tx) => {
self.workspace_tx = Some(tx); self.workspace_tx = Some(tx);
@ -167,9 +146,6 @@ impl cosmic::Application for Window {
return if let Some(p) = self.popup.take() { return if let Some(p) = self.popup.take() {
destroy_popup(p) destroy_popup(p)
} else { } else {
self.timeline = Timeline::default();
self.tile_windows = id::Toggler::unique();
self.active_hint = id::Toggler::unique();
let new_id = Id::unique(); let new_id = Id::unique();
self.popup = Some(new_id); self.popup = Some(new_id);
let popup_settings = self.core.applet.get_popup_settings( let popup_settings = self.core.applet.get_popup_settings(
@ -188,9 +164,7 @@ impl cosmic::Application for Window {
self.popup = None; self.popup = None;
} }
} }
Message::Frame(now) => self.timeline.now(now), Message::ToggleTileWindows(toggled) => {
Message::ToggleTileWindows(chain, toggled) => {
self.timeline.set_chain(chain).start();
self.autotiled = toggled; self.autotiled = toggled;
// set via protocol // set via protocol
@ -206,8 +180,7 @@ impl cosmic::Application for Window {
} }
} }
} }
Message::ToggleActiveHint(chain, toggled) => { Message::ToggleActiveHint(toggled) => {
self.timeline.set_chain(chain).start();
self.config.active_hint = toggled; self.config.active_hint = toggled;
let helper = self.config_helper.clone(); let helper = self.config_helper.clone();
@ -223,16 +196,6 @@ impl cosmic::Application for Window {
.activate_position(if c.autotile { 0 } else { 1 }); .activate_position(if c.autotile { 0 } else { 1 });
} }
if c.active_hint != self.config.active_hint && self.popup.is_some() {
self.timeline
.set_chain(if c.active_hint {
cosmic_time::chain::Toggler::on(self.active_hint.clone(), 1.0)
} else {
cosmic_time::chain::Toggler::off(self.active_hint.clone(), 1.0)
})
.start();
}
self.config = *c; self.config = *c;
} }
Message::NewWorkspace(e) => { Message::NewWorkspace(e) => {
@ -295,17 +258,12 @@ impl cosmic::Application for Window {
.on_activate(Message::NewWorkspace); .on_activate(Message::NewWorkspace);
let content_list = column![ let content_list = column![
padded_control(container( padded_control(container(
anim!( toggler(self.autotiled)
self.tile_windows, .on_toggle(Message::ToggleTileWindows)
&self.timeline, .text_size(14)
fl!("tile-current"), .width(Length::Fill)
self.autotiled, .label(fl!("tile-current"))
|chain, enable| { Message::ToggleTileWindows(chain, enable) }, )),
)
.text_size(14)
.width(Length::Fill),
))
.width(Length::Fill),
padded_control(divider::horizontal::default()).padding([space_xxs, space_s]), padded_control(divider::horizontal::default()).padding([space_xxs, space_s]),
padded_control( padded_control(
column![ column![
@ -334,15 +292,11 @@ impl cosmic::Application for Window {
)), )),
padded_control(divider::horizontal::default()).padding([space_xxs, space_s]), padded_control(divider::horizontal::default()).padding([space_xxs, space_s]),
padded_control( padded_control(
anim!( toggler(self.config.active_hint)
self.active_hint, .on_toggle(Message::ToggleActiveHint)
&self.timeline, .label(fl!("active-hint"))
fl!("active-hint"), .text_size(14)
self.config.active_hint, .width(Length::Fill),
|chain, enable| { Message::ToggleActiveHint(chain, enable) },
)
.text_size(14)
.width(Length::Fill),
), ),
padded_control(divider::horizontal::default()).padding([space_xxs, space_s]), padded_control(divider::horizontal::default()).padding([space_xxs, space_s]),
menu_button(text::body(fl!("window-management-settings"))) menu_button(text::body(fl!("window-management-settings")))
@ -353,7 +307,7 @@ impl cosmic::Application for Window {
self.core.applet.popup_container(content_list).into() self.core.applet.popup_container(content_list).into()
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<cosmic::iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
} }

View file

@ -10,15 +10,15 @@ use cosmic::{
Alignment, Length, Rectangle, Subscription, Alignment, Length, Rectangle, Subscription,
futures::{SinkExt, StreamExt, channel::mpsc}, futures::{SinkExt, StreamExt, channel::mpsc},
platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup}, platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup},
widget::{column, row, vertical_space}, widget::{column, row, rule},
window, window,
}, },
iced_futures::stream, iced_futures::stream,
iced_widget::{Column, horizontal_rule}, iced_widget::Column,
surface, theme, surface, theme,
widget::{ widget::{
Button, Grid, Id, Space, autosize, button, container, divider, grid, horizontal_space, Button, Grid, Id, autosize, button, container, divider, grid, icon, rectangle_tracker::*,
icon, rectangle_tracker::*, text, space, text,
}, },
}; };
use jiff::{ use jiff::{
@ -28,6 +28,7 @@ use jiff::{
tz::TimeZone, tz::TimeZone,
}; };
use logind_zbus::manager::ManagerProxy; use logind_zbus::manager::ManagerProxy;
use std::hash::Hash;
use std::sync::LazyLock; use std::sync::LazyLock;
use timedate_zbus::TimeDateProxy; use timedate_zbus::TimeDateProxy;
use tokio::{sync::watch, time}; use tokio::{sync::watch, time};
@ -215,7 +216,7 @@ impl Window {
elements.push(self.core.applet.text(p.to_owned()).into()); elements.push(self.core.applet.text(p.to_owned()).into());
} }
elements.push( elements.push(
horizontal_rule(2) rule::horizontal(2)
.width(self.core.applet.suggested_size(true).0) .width(self.core.applet.suggested_size(true).0)
.into(), .into(),
); );
@ -245,7 +246,7 @@ impl Window {
Element::from( Element::from(
column!( column!(
date_time_col, date_time_col,
horizontal_space().width(Length::Fixed( space::horizontal().width(Length::Fixed(
(self.core.applet.suggested_size(true).0 (self.core.applet.suggested_size(true).0
+ 2 * self.core.applet.suggested_padding(true).1) + 2 * self.core.applet.suggested_padding(true).1)
as f32 as f32
@ -302,7 +303,7 @@ impl Window {
Element::from( Element::from(
row!( row!(
self.core.applet.text(formatted_date), self.core.applet.text(formatted_date),
container(vertical_space().height(Length::Fixed( container(space::vertical().height(Length::Fixed(
(self.core.applet.suggested_size(true).1 (self.core.applet.suggested_size(true).1
+ 2 * self.core.applet.suggested_padding(true).1) + 2 * self.core.applet.suggested_padding(true).1)
as f32 as f32
@ -355,64 +356,79 @@ impl cosmic::Application for Window {
&mut self.core &mut self.core
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<cosmic::iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
fn time_subscription(mut show_seconds: watch::Receiver<bool>) -> Subscription<Message> { fn time_subscription(mut show_seconds: watch::Receiver<bool>) -> Subscription<Message> {
Subscription::run_with_id( struct Wrapper {
"time-sub", inner: watch::Receiver<bool>,
stream::channel(1, |mut output| async move { id: &'static str,
// Mark this receiver's state as changed so that it always receives an initial }
// update during the loop below impl Hash for Wrapper {
// This allows us to avoid duplicating code from the loop fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
show_seconds.mark_changed(); self.id.hash(state);
let mut period = 1; }
let mut timer = time::interval(time::Duration::from_secs(period)); }
timer.set_missed_tick_behavior(time::MissedTickBehavior::Skip); Subscription::run_with(
Wrapper {
inner: show_seconds,
id: "time-sub",
},
|Wrapper { inner, id }| {
let mut show_seconds = inner.clone();
stream::channel(1, move |mut output: mpsc::Sender<Message>| async move {
// Mark this receiver's state as changed so that it always receives an initial
// update during the loop below
// This allows us to avoid duplicating code from the loop
show_seconds.mark_changed();
let mut period = 1;
let mut timer = time::interval(time::Duration::from_secs(period));
timer.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
loop { loop {
tokio::select! { tokio::select! {
_ = timer.tick() => { _ = timer.tick() => {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
if let Err(err) = output.send(Message::Tick).await { if let Err(err) = output.send(Message::Tick).await {
tracing::error!(?err, "Failed sending tick request to applet"); tracing::error!(?err, "Failed sending tick request to applet");
} }
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
let _ = output.send(Message::Tick).await; let _ = output.send(Message::Tick).await;
// Calculate a delta if we're ticking per minute to keep ticks stable // Calculate a delta if we're ticking per minute to keep ticks stable
// Based on i3status-rust // Based on i3status-rust
let current = Timestamp::now().as_second() as u64 % period; let current = Timestamp::now().as_second() as u64 % period;
if current != 0 { if current != 0 {
timer.reset_after(time::Duration::from_secs(period - current)); timer.reset_after(time::Duration::from_secs(period - current));
} }
}, },
// Update timer if the user toggles show_seconds // Update timer if the user toggles show_seconds
Ok(()) = show_seconds.changed() => { Ok(()) = show_seconds.changed() => {
let seconds = *show_seconds.borrow_and_update(); let seconds = *show_seconds.borrow_and_update();
if seconds { if seconds {
period = 1; period = 1;
// Subsecond precision isn't needed; skip calculating offset // Subsecond precision isn't needed; skip calculating offset
let period = time::Duration::from_secs(period); let period = time::Duration::from_secs(period);
let start = time::Instant::now() + period; let start = time::Instant::now() + period;
timer = time::interval_at(start, period); timer = time::interval_at(start, period);
} else { } else {
period = 60; period = 60;
let delta = time::Duration::from_secs(period - Timestamp::now().as_second() as u64 % period); let delta = time::Duration::from_secs(period - Timestamp::now().as_second() as u64 % period);
let now = time::Instant::now(); let now = time::Instant::now();
// Start ticking from the next minute to update the time properly // Start ticking from the next minute to update the time properly
let start = now + delta; let start = now + delta;
let period = time::Duration::from_secs(period); let period = time::Duration::from_secs(period);
timer = time::interval_at(start, period); timer = time::interval_at(start, period);
}
timer.set_missed_tick_behavior(time::MissedTickBehavior::Skip); timer.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
}
}
} }
} }
} })
}), },
) )
} }
@ -438,8 +454,7 @@ impl cosmic::Application for Window {
} }
fn timezone_subscription() -> Subscription<Message> { fn timezone_subscription() -> Subscription<Message> {
Subscription::run_with_id( Subscription::run_with("timezone-sub", |_| {
"timezone-sub",
stream::channel(1, |mut output| async move { stream::channel(1, |mut output| async move {
'retry: loop { 'retry: loop {
match timezone_update(&mut output).await { match timezone_update(&mut output).await {
@ -455,8 +470,8 @@ impl cosmic::Application for Window {
} }
std::future::pending().await std::future::pending().await
}), })
) })
} }
// Update the time when waking from sleep // Update the time when waking from sleep
@ -474,14 +489,13 @@ impl cosmic::Application for Window {
} }
fn wake_from_sleep_subscription() -> Subscription<Message> { fn wake_from_sleep_subscription() -> Subscription<Message> {
Subscription::run_with_id( Subscription::run_with("wake-from-suspend-sub", |_| {
"wake-from-suspend-sub",
stream::channel(1, |mut output| async move { stream::channel(1, |mut output| async move {
if let Err(err) = wake_from_sleep(&mut output).await { if let Err(err) = wake_from_sleep(&mut output).await {
tracing::error!(?err, "Failed to subscribe to wake-from-sleep signal"); tracing::error!(?err, "Failed to subscribe to wake-from-sleep signal");
} }
}), })
) })
} }
let show_seconds_rx = self.show_seconds_tx.subscribe(); let show_seconds_rx = self.show_seconds_tx.subscribe();
@ -728,7 +742,7 @@ impl cosmic::Application for Window {
let content_list = column![ let content_list = column![
row![ row![
column![date, day_of_week], column![date, day_of_week],
Space::with_width(Length::Fill), space::horizontal().width(Length::Fill),
month_controls, month_controls,
] ]
.align_y(Alignment::Center) .align_y(Alignment::Center)

View file

@ -23,7 +23,7 @@ use cosmic::{
iced_core::{Background, Border}, iced_core::{Background, Border},
scroll::DiscreteScrollState, scroll::DiscreteScrollState,
surface, surface,
widget::{Id, autosize, container, horizontal_space, vertical_space}, widget::{Id, autosize, container, space},
}; };
use crate::{ use crate::{
@ -196,10 +196,10 @@ impl cosmic::Application for IcedWorkspacesApplet {
(suggested_window_size.0.get() as f32, suggested_total as f32) (suggested_window_size.0.get() as f32, suggested_total as f32)
}; };
let content = row!(content, vertical_space().height(Length::Fixed(height))) let content = row!(content, space::vertical().height(Length::Fixed(height)))
.align_y(Alignment::Center); .align_y(Alignment::Center);
let content = column!(content, horizontal_space().width(Length::Fixed(width))) let content = column!(content, space::horizontal().width(Length::Fixed(width)))
.align_x(Alignment::Center); .align_x(Alignment::Center);
let btn = button( let btn = button(
@ -323,7 +323,7 @@ impl cosmic::Application for IcedWorkspacesApplet {
]) ])
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<cosmic::iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
} }

View file

@ -22,16 +22,15 @@ pub enum WorkspacesUpdate {
} }
pub fn workspaces() -> iced::Subscription<WorkspacesUpdate> { pub fn workspaces() -> iced::Subscription<WorkspacesUpdate> {
Subscription::run_with_id( Subscription::run_with(std::any::TypeId::of::<WorkspacesUpdate>(), |_| {
std::any::TypeId::of::<WorkspacesUpdate>(),
stream::channel(50, move |mut output| async move { stream::channel(50, move |mut output| async move {
let mut state = State::Waiting; let mut state = State::Waiting;
loop { loop {
state = start_listening(state, &mut output).await; state = start_listening(state, &mut output).await;
} }
}), })
) })
} }
async fn start_listening( async fn start_listening(

View file

@ -3,6 +3,7 @@
use config::{CosmicPanelButtonConfig, IndividualConfig, Override}; use config::{CosmicPanelButtonConfig, IndividualConfig, Override};
use cosmic::desktop::fde::{self, DesktopEntry, get_languages_from_env}; use cosmic::desktop::fde::{self, DesktopEntry, get_languages_from_env};
use cosmic::widget::space;
use cosmic::{ use cosmic::{
Task, app, Task, app,
applet::{ applet::{
@ -12,7 +13,7 @@ use cosmic::{
iced::{self, Length}, iced::{self, Length},
iced_widget::row, iced_widget::row,
surface, surface,
widget::{Id, autosize, vertical_space}, widget::{Id, autosize},
}; };
use cosmic_config::{Config, CosmicConfigEntry}; use cosmic_config::{Config, CosmicConfigEntry};
use std::{env, fs, process::Command, sync::LazyLock}; use std::{env, fs, process::Command, sync::LazyLock};
@ -115,7 +116,7 @@ impl cosmic::Application for Button {
&mut self.core &mut self.core
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
@ -179,7 +180,7 @@ impl cosmic::Application for Button {
} else { } else {
let content = row!( let content = row!(
self.core.applet.text(&self.desktop.name), self.core.applet.text(&self.desktop.name),
vertical_space().height(Length::Fixed( space::vertical().height(Length::Fixed(
(self.core.applet.suggested_size(true).1 (self.core.applet.suggested_size(true).1
+ 2 * self.core.applet.suggested_padding(true).1) + 2 * self.core.applet.suggested_padding(true).1)
as f32 as f32