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 = [
"client",
], 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-util = "0.3"
@ -59,24 +57,26 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
tracing-log = "0.2.0"
tokio = { version = "1.49.0", features = ["full"] }
# cosmic-config = { path = "../libcosmic/cosmic-config" }
cosmic-config = { git = "https://github.com/pop-os/libcosmic" }
serde = { version = "1.0.228", features = ["derive"] }
[profile.release]
opt-level = 3
panic = "abort"
lto = "thin"
# opt-level = 3
# panic = "abort"
# lto = "thin"
opt-level = 1
[workspace.metadata.cargo-machete]
ignored = ["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" }
# libcosmic = { path = "../libcosmic" }
# 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"]
# 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,
},
};
use cosmic::desktop::fde::{self, DesktopEntry, get_languages_from_env, unicase::Ascii};
use cosmic::{
Apply, Element, Task, app,
applet::{
@ -34,20 +33,27 @@ use cosmic::{
clipboard::mime::{AllowedMimeTypes, AsMimeTypes},
event::listen_with,
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,
},
iced_runtime::{core::event, dnd::peek_dnd},
surface,
theme::{self, Button, Container},
widget::{
DndDestination, Image, button, container, divider, dnd_source, horizontal_space,
DndDestination, Image, button, container, divider, dnd_source,
icon::{self, from_name},
image::Handle,
rectangle_tracker::{RectangleTracker, RectangleUpdate, rectangle_tracker_subscription},
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_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::State;
use futures::future::pending;
@ -295,7 +301,7 @@ impl DockItem {
let path = desktop_info.path.clone();
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)
.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())
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -156,50 +156,51 @@ where
}
fn layout(
&self,
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.content
.as_widget()
.as_widget_mut()
.layout(&mut tree.children[0], renderer, limits)
}
fn operate(
&self,
&mut self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<()>,
) {
self.content
.as_widget()
.as_widget_mut()
.operate(&mut tree.children[0], layout, renderer, operation);
}
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: Event,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
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],
event.clone(),
&event,
layout,
cursor,
renderer,
clipboard,
shell,
viewport,
) {
return event::Status::Captured;
);
if shell.is_event_captured() {
return;
}
update(
@ -209,7 +210,7 @@ where
cursor,
shell,
tree.state.downcast_mut::<State>(),
)
);
}
fn mouse_interaction(
@ -249,17 +250,24 @@ where
viewport,
);
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
layout: Layout<'b>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content
.as_widget_mut()
.overlay(&mut tree.children[0], layout, renderer, translation)
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout,
renderer,
viewport,
translation,
)
}
fn drag_destinations(
&self,
state: &Tree,
@ -298,7 +306,7 @@ fn update<Message: Clone, Theme, Renderer>(
cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
state: &mut State,
) -> event::Status {
) {
if !cursor.is_over(layout.bounds()) {
if !state.is_out_of_bounds {
if widget
@ -312,12 +320,13 @@ fn update<Message: Clone, Theme, Renderer>(
if let Some(message) = widget.on_mouse_exit.as_ref() {
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() {
@ -326,8 +335,8 @@ fn update<Message: Clone, Theme, Renderer>(
{
state.drag_initiated = cursor.position();
shell.publish(message.clone());
return event::Status::Captured;
shell.capture_event();
return;
}
}
@ -337,32 +346,32 @@ fn update<Message: Clone, Theme, Renderer>(
{
state.drag_initiated = None;
shell.publish(message.clone());
return event::Status::Captured;
shell.capture_event();
return;
}
}
if let Some(message) = widget.on_right_press.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = event {
shell.publish(message.clone());
return event::Status::Captured;
shell.capture_event();
return;
}
}
if let Some(message) = widget.on_right_release.as_ref() {
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) = event {
shell.publish(message.clone());
return event::Status::Captured;
shell.capture_event();
return;
}
}
if let Some(message) = widget.on_middle_press.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) = event {
shell.publish(message.clone());
return event::Status::Captured;
shell.capture_event();
return;
}
}
@ -370,7 +379,8 @@ fn update<Message: Clone, Theme, Renderer>(
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) = event {
shell.publish(message.clone());
return event::Status::Captured;
shell.capture_event();
return;
}
}
if let Some(message) = widget
@ -384,7 +394,8 @@ fn update<Message: Clone, Theme, Renderer>(
if widget.on_mouse_enter.is_some() {
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 {
state.drag_initiated = None;
shell.publish(message.clone());
return event::Status::Captured;
shell.capture_event();
return;
}
}
}
@ -409,9 +420,8 @@ fn update<Message: Clone, Theme, Renderer>(
if let Some(message) = widget.on_mouse_wheel.as_ref() {
if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
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>(
id: I,
) -> iced::Subscription<MprisUpdate> {
Subscription::run_with_id(
id,
Subscription::run_with(id, |_| {
stream::channel(50, move |mut output| async move {
run(&mut output).await;
let _ = output.send(MprisUpdate::Finished).await;
futures::future::pending().await
}),
)
})
})
}
#[derive(Clone, Debug)]

View file

@ -6,7 +6,6 @@ license = "GPL-3.0-only"
[dependencies]
anyhow.workspace = true
cosmic-time.workspace = true
cosmic-config.workspace = true
cosmic-applets-config.workspace = true
drm = "0.14.1"
@ -26,6 +25,8 @@ serde.workspace = true
[dependencies.cosmic-settings-upower-subscription]
git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/upower"
[dependencies.cosmic-settings-daemon-subscription]
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},
surface,
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_config::{Config, CosmicConfigEntry};
@ -39,8 +39,6 @@ use cosmic_settings_upower_subscription::{
kbdbacklight::{KeyboardBacklightRequest, KeyboardBacklightUpdate, kbd_backlight_subscription},
};
use cosmic_time::{Instant, Timeline, anim, chain, id};
use rustc_hash::FxHashMap;
use std::{path::PathBuf, sync::LazyLock, time::Duration};
use tokio::sync::mpsc::UnboundedSender;
@ -65,8 +63,6 @@ pub fn run() -> cosmic::iced::Result {
cosmic::applet::run::<CosmicBatteryApplet>(())
}
static MAX_CHARGE: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
#[derive(Clone, Default)]
struct GPUData {
name: String,
@ -95,7 +91,6 @@ struct CosmicBatteryApplet {
kbd_sender: Option<UnboundedSender<KeyboardBacklightRequest>>,
power_profile: Power,
power_profile_sender: Option<UnboundedSender<PowerProfileRequest>>,
timeline: Timeline,
token_tx: Option<calloop::channel::Sender<TokenRequest>>,
zbus_connection: Option<zbus::Connection>,
dragging_screen_brightness: bool,
@ -186,7 +181,7 @@ enum Message {
SetScreenBrightnessDebounced,
ReleaseScreenBrightness,
InitChargingLimit(Option<bool>),
SetChargingLimit(chain::Toggler, bool),
SetChargingLimit(bool),
KeyboardBacklight(KeyboardBacklightUpdate),
UpowerDevice(DeviceDbusEvent),
GpuInit(UnboundedSender<()>),
@ -197,7 +192,6 @@ enum Message {
InitProfile(UnboundedSender<PowerProfileRequest>, Power),
Profile(Power),
SelectProfile(Power),
Frame(Instant),
ConfigChanged(BatteryAppletConfig),
Token(TokenUpdate),
OpenSettings,
@ -248,7 +242,6 @@ impl cosmic::Application for CosmicBatteryApplet {
fn update(&mut self, message: Self::Message) -> app::Task<Self::Message> {
match message {
Message::Frame(now) => self.timeline.now(now),
Message::SetKbdBrightness(brightness) => {
self.kbd_brightness = Some(brightness);
@ -330,8 +323,7 @@ impl cosmic::Application for CosmicBatteryApplet {
self.set_charging_limit(enable);
}
}
Message::SetChargingLimit(chain, enable) => {
self.timeline.set_chain(chain).start();
Message::SetChargingLimit(enable) => {
self.set_charging_limit(enable);
if enable {
@ -357,7 +349,6 @@ impl cosmic::Application for CosmicBatteryApplet {
if let Some(tx) = &self.kbd_sender {
let _ = tx.send(KeyboardBacklightRequest::Get);
}
self.timeline = Timeline::new();
let new_id = window::Id::unique();
self.popup.replace(new_id);
@ -582,7 +573,7 @@ impl cosmic::Application for CosmicBatteryApplet {
let content = if self.gpus.is_empty() {
btn
} else {
let dot = container(vertical_space().height(Length::Fixed(0.0)))
let dot = container(space::vertical().height(Length::Fixed(0.0)))
.padding(2.0)
.class(cosmic::style::Container::Custom(Box::new(|theme| {
container::Style {
@ -595,6 +586,7 @@ impl cosmic::Application for CosmicBatteryApplet {
},
shadow: Shadow::default(),
icon_color: Some(Color::TRANSPARENT),
snap: true,
}
})));
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(
row![
icon::from_name(&*self.icon_name).size(24).symbolic(true),
@ -665,7 +657,7 @@ impl cosmic::Application for CosmicBatteryApplet {
.symbolic(true),
)
} else {
container(horizontal_space().width(1.0))
container(space::horizontal().width(1.0))
}
]
.align_y(Alignment::Center),
@ -686,7 +678,7 @@ impl cosmic::Application for CosmicBatteryApplet {
.symbolic(true),
)
} else {
container(horizontal_space().width(1.0))
container(space::horizontal().width(1.0))
}
]
.align_y(Alignment::Center),
@ -707,7 +699,7 @@ impl cosmic::Application for CosmicBatteryApplet {
.symbolic(true),
)
} else {
container(horizontal_space().width(1.0))
container(space::horizontal().width(1.0))
}
]
.align_y(Alignment::Center),
@ -722,16 +714,11 @@ impl cosmic::Application for CosmicBatteryApplet {
if let Some(charging_limit) = self.charging_limit {
content.push(
padded_control(
anim!(
//toggler
MAX_CHARGE,
&self.timeline,
fl!("max-charge"),
charging_limit,
Message::SetChargingLimit,
)
.text_size(14)
.width(Length::Fill),
toggler(charging_limit)
.on_toggle(Message::SetChargingLimit)
.label(fl!("max-charge"))
.text_size(14)
.width(Length::Fill),
)
.into(),
);
@ -819,7 +806,7 @@ impl cosmic::Application for CosmicBatteryApplet {
.width(Length::Fill)
.align_x(Alignment::Start),
container(
vertical_space()
space::vertical()
.width(Length::Fixed(0.0))
.height(Length::Fixed(0.0))
)
@ -837,6 +824,7 @@ impl cosmic::Application for CosmicBatteryApplet {
},
shadow: Shadow::default(),
icon_color: Some(Color::TRANSPARENT),
snap: true,
}
},))),
]
@ -902,7 +890,7 @@ impl cosmic::Application for CosmicBatteryApplet {
if let Some(icon) = &app.icon {
container(icon::from_name(&**icon).size(12).symbolic(true))
} else {
container(horizontal_space().width(12.0))
container(space::horizontal().width(12.0))
},
column![text::body(&app.name), text::caption(&app.secondary)]
.width(Length::Fill),
@ -952,9 +940,6 @@ impl cosmic::Application for CosmicBatteryApplet {
GpuUpdate::On(path, name, list) => Message::GpuOn(path, name, list),
GpuUpdate::Off(path) => Message::GpuOff(path),
}),
self.timeline
.as_subscription()
.map(|(_, now)| Message::Frame(now)),
activation_token_subscription(0).map(Message::Token),
self.core.watch_config(Self::APP_ID).map(|u| {
for err in u.errors {
@ -973,7 +958,7 @@ impl cosmic::Application for CosmicBatteryApplet {
Some(Message::CloseRequested(id))
}
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
fn style(&self) -> Option<cosmic::iced::theme::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>(
id: I,
) -> iced::Subscription<PowerProfileUpdate> {
Subscription::run_with_id(
id,
Subscription::run_with(id, |_| {
stream::channel(50, move |mut output| async move {
let mut state = State::Ready;
loop {
state = start_listening(state, &mut output).await;
}
}),
)
})
})
}
#[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>(
id: I,
) -> iced::Subscription<GpuUpdate> {
Subscription::run_with_id(
id,
Subscription::run_with(id, |_| {
stream::channel(50, move |mut output| async move {
let mut state = State::Ready;
loop {
state = start_listening(state, &mut output).await;
}
}),
)
})
})
}
#[derive(Debug)]

View file

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

View file

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

View file

@ -6,7 +6,6 @@ use rustc_hash::FxHashMap;
use std::{
fmt::Debug,
hash::Hash,
mem,
sync::{
Arc, LazyLock,
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>(
id: I,
) -> iced::Subscription<BluerEvent> {
Subscription::run_with_id(
id,
stream::channel(50, move |mut output| async move {
let mut retry_count = 0u32;
Subscription::run_with(id, |_| {
stream::channel(
50,
move |mut output: futures::channel::mpsc::Sender<BluerEvent>| async move {
let mut retry_count = 0u32;
// Initialize connection.
let mut session_state = loop {
if let Ok(session) = Session::new().await {
if let Ok(state) = BluerSessionState::new(session).await {
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;
// Initialize connection.
let mut session_state = loop {
if let Ok(session) = Session::new().await {
if let Ok(state) = BluerSessionState::new(session).await {
break state;
}
}
} 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);
interval.tick().await;
}
_ = output.send(BluerEvent::Finished).await;
futures::future::pending().await
}),
)
_ = output.send(BluerEvent::Finished).await;
futures::future::pending().await
},
)
})
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]

View file

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

View file

@ -21,9 +21,9 @@ use cosmic::{
prelude::*,
surface, theme,
widget::{
self, autosize, horizontal_space,
self, autosize,
rectangle_tracker::{RectangleTracker, RectangleUpdate, rectangle_tracker_subscription},
vertical_space,
space,
},
};
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())
}
}

View file

@ -158,7 +158,7 @@ impl cosmic::Application for Minimize {
&mut self.core
}
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
fn style(&self) -> Option<iced::theme::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,
},
iced::{self, Subscription},
iced_core::image::Bytes,
iced_core::Bytes,
iced_futures::{futures, stream},
};
use futures::SinkExt;
@ -22,24 +22,26 @@ use std::fmt::Debug;
use crate::wayland_handler::wayland_handler;
pub fn wayland_subscription() -> iced::Subscription<WaylandUpdate> {
Subscription::run_with_id(
std::any::TypeId::of::<WaylandUpdate>(),
stream::channel(1, move |mut output| async move {
let (calloop_tx, calloop_rx) = calloop::channel::channel();
let runtime = tokio::runtime::Handle::current();
Subscription::run_with(std::any::TypeId::of::<WaylandUpdate>(), |_| {
stream::channel(
1,
move |mut output: futures::channel::mpsc::Sender<WaylandUpdate>| async move {
let (calloop_tx, calloop_rx) = calloop::channel::channel();
let runtime = tokio::runtime::Handle::current();
let _ = std::thread::spawn(move || {
runtime.block_on(async move {
_ = output.send(WaylandUpdate::Init(calloop_tx)).await;
wayland_handler(output.clone(), calloop_rx);
tracing::error!("Wayland handler thread died");
_ = output.send(WaylandUpdate::Finished).await;
let _ = std::thread::spawn(move || {
runtime.block_on(async move {
_ = output.send(WaylandUpdate::Init(calloop_tx)).await;
wayland_handler(output.clone(), calloop_rx);
tracing::error!("Wayland handler thread died");
_ = output.send(WaylandUpdate::Finished).await;
});
});
});
futures::future::pending().await
}),
)
futures::future::pending().await
},
)
})
}
#[derive(Clone, Debug)]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ use cosmic::{
window,
},
surface, theme,
widget::{Space, button, divider, icon, text},
widget::{Space, button, divider, icon, space, text},
};
use std::sync::LazyLock;
@ -227,7 +227,7 @@ impl cosmic::Application for Power {
row![
text_icon("system-lock-screen-symbolic", 24),
text::body(fl!("lock-screen")),
Space::with_width(Length::Fill),
space::horizontal().width(Length::Fill),
text::body(fl!("lock-screen-shortcut")),
]
.align_y(Alignment::Center)
@ -238,7 +238,7 @@ impl cosmic::Application for Power {
row![
text_icon("system-log-out-symbolic", 24),
text::body(fl!("log-out")),
Space::with_width(Length::Fill),
space::horizontal().width(Length::Fill),
text::body(fl!("log-out-shortcut")),
]
.align_y(Alignment::Center)
@ -285,7 +285,7 @@ impl cosmic::Application for Power {
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())
}
}

View file

@ -3,12 +3,15 @@
use cosmic::{
Element, Task, app,
applet::cosmic_panel_config::PanelAnchor,
applet::token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription},
applet::{
cosmic_panel_config::PanelAnchor,
token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription},
},
cctk::sctk::reexports::calloop,
iced::{
self, Length, Subscription,
platform_specific::shell::commands::popup::{destroy_popup, get_popup},
theme::Style,
window,
},
surface,
@ -146,7 +149,7 @@ impl cosmic::Application for App {
&mut self.core
}
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
fn style(&self) -> Option<iced::theme::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() {
None
} 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() {
// Strip _ when not doubled
// TODO: interpret as "access key"? And label with underline.

View file

@ -1,10 +1,11 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use std::hash::Hash;
use cosmic::iced::{self, Subscription};
use futures::{FutureExt, StreamExt};
use rustc_hash::FxHashMap;
use std::path::PathBuf;
use zbus::zvariant::{self, OwnedValue};
#[derive(Clone, Debug)]
@ -76,22 +77,40 @@ impl StatusNotifierItem {
let Some(menu_proxy) = self.menu_proxy.clone() else {
return Subscription::none();
};
Subscription::run_with_id(
format!("status-notifier-item-layout-{}", &self.name),
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)
struct Wrapper {
menu_proxy: DBusMenuProxy<'static>,
name: String,
}
impl Hash for Wrapper {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
.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();
Subscription::run_with_id(
format!("status-notifier-item-icon-{}", &self.name),
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()))
struct Wrapper {
item_proxy: StatusNotifierItemProxy<'static>,
name: String,
}
impl Hash for Wrapper {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
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?
use std::any::TypeId;
use cosmic::iced::{self, Subscription};
use futures::{StreamExt, stream};
@ -26,8 +28,8 @@ enum State {
}
pub fn subscription() -> iced::Subscription<Event> {
Subscription::run_with_id(
"status-notifier-watcher",
pub struct MyID;
Subscription::run_with(TypeId::of::<MyID>(), |_| {
stream::unfold(State::NotConnected, |state| async move {
match state {
State::NotConnected => match connect().await {
@ -42,8 +44,8 @@ pub fn subscription() -> iced::Subscription<Event> {
.map(|event| (event, State::Connected(stream))),
State::Failed => None,
}
}),
)
})
})
}
async fn connect() -> zbus::Result<(zbus::Connection, client::EventStream)> {

View file

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

View file

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

View file

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

View file

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

View file

@ -23,7 +23,7 @@ use cosmic::{
iced_core::{Background, Border},
scroll::DiscreteScrollState,
surface,
widget::{Id, autosize, container, horizontal_space, vertical_space},
widget::{Id, autosize, container, space},
};
use crate::{
@ -196,10 +196,10 @@ impl cosmic::Application for IcedWorkspacesApplet {
(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);
let content = column!(content, horizontal_space().width(Length::Fixed(width)))
let content = column!(content, space::horizontal().width(Length::Fixed(width)))
.align_x(Alignment::Center);
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())
}
}

View file

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

View file

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