feat: libcosmic iced 0.14 rebase
This commit is contained in:
parent
0020132e63
commit
cf7fc32adf
47 changed files with 2731 additions and 1869 deletions
2636
Cargo.lock
generated
2636
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
24
Cargo.toml
24
Cargo.toml
|
|
@ -11,7 +11,14 @@ cosmic-randr = { git = "https://github.com/pop-os/cosmic-randr" }
|
|||
tokio = { version = "1.49.0", features = ["macros"] }
|
||||
|
||||
[workspace.dependencies.libcosmic]
|
||||
features = ["dbus-config", "desktop", "multi-window", "winit", "tokio", "qr_code"]
|
||||
features = [
|
||||
"dbus-config",
|
||||
"desktop",
|
||||
"multi-window",
|
||||
"winit",
|
||||
"tokio",
|
||||
"qr_code",
|
||||
]
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
|
||||
[workspace.dependencies.cosmic-config]
|
||||
|
|
@ -54,9 +61,9 @@ debug = true
|
|||
# [patch.'https://github.com/pop-os/cosmic-text']
|
||||
# cosmic-text = { git = "https://github.com/pop-os/cosmic-text//", rev = "b017d7c" }
|
||||
|
||||
# [patch.'https://github.com/pop-os/cosmic-protocols']
|
||||
# cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols//", rev = "d0e95be" }
|
||||
# cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols//", rev = "d0e95be" }
|
||||
[patch.'https://github.com/pop-os/cosmic-protocols']
|
||||
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols//", rev = "d0e95be" }
|
||||
cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols//", rev = "d0e95be" }
|
||||
|
||||
# [patch.'https://github.com/pop-os/cosmic-settings-daemon']
|
||||
# cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon//", branch = "input_nobuild" }
|
||||
|
|
@ -67,8 +74,17 @@ debug = true
|
|||
# cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||
# iced_futures = { path = "../libcosmic/iced/futures" }
|
||||
#
|
||||
#iced_futures = { git = "https://github.com/pop-os/libcosmic//" }
|
||||
#libcosmic = { git = "https://github.com/pop-os/libcosmic//" }
|
||||
#cosmic-config = { git = "https://github.com/pop-os/libcosmic//" }
|
||||
#cosmic-theme = { git = "https://github.com/pop-os/libcosmic//" }
|
||||
|
||||
# [patch.'https://github.com/pop-os/dbus-settings-bindings']
|
||||
# cosmic-dbus-networkmanager = { path = "../dbus-settings-bindings/networkmanager" }
|
||||
# upower_dbus = { path = "../dbus-settings-bindings/upower" }
|
||||
# nm-secret-agent-manager = { git = "https://github.com/pop-os/dbus-settings-bindings//", branch = "nm-secret-agent" }
|
||||
|
||||
[patch.crates-io]
|
||||
atspi = { git = "https://github.com/wash2/atspi" }
|
||||
atspi-common = { git = "https://github.com/wash2/atspi" }
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ itertools = "0.14.0"
|
|||
itoa = "1.0.17"
|
||||
libcosmic.workspace = true
|
||||
locale1 = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
sysinfo = { version = "0.38.2", optional = true }
|
||||
sysinfo = { version = "=0.38.0", optional = true }
|
||||
mime-apps = { package = "cosmic-mime-apps", git = "https://github.com/pop-os/cosmic-mime-apps", optional = true }
|
||||
notify = "8.2.0"
|
||||
regex = "1.12.3"
|
||||
|
|
@ -173,7 +173,13 @@ page-networking = [
|
|||
"dep:zbus",
|
||||
]
|
||||
page-power = ["dep:upower_dbus", "dep:zbus"]
|
||||
page-region = ["gettext", "dep:locales-rs", "dep:locale1", "dep:zbus", "dep:accounts-zbus"]
|
||||
page-region = [
|
||||
"gettext",
|
||||
"dep:locales-rs",
|
||||
"dep:locale1",
|
||||
"dep:zbus",
|
||||
"dep:accounts-zbus",
|
||||
]
|
||||
page-sound = ["dep:cosmic-settings-sound-subscription"]
|
||||
page-users = ["xdg-portal", "dep:accounts-zbus", "dep:zbus", "dep:zbus_polkit"]
|
||||
page-window-management = ["cosmic-comp-config", "dep:cosmic-settings-config"]
|
||||
|
|
@ -186,10 +192,6 @@ cosmic-comp-config = ["dep:cosmic-comp-config"]
|
|||
dbus-config = ["libcosmic/dbus-config", "cosmic-config/dbus"]
|
||||
single-instance = ["libcosmic/single-instance"]
|
||||
test = []
|
||||
wayland = [
|
||||
"libcosmic/wayland",
|
||||
"dep:cosmic-panel-config",
|
||||
"dep:cosmic-randr"
|
||||
]
|
||||
wayland = ["libcosmic/wayland", "dep:cosmic-panel-config", "dep:cosmic-randr"]
|
||||
wgpu = ["libcosmic/wgpu"]
|
||||
xdg-portal = ["ashpd", "libcosmic/xdg-portal"]
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
|
||||
return cosmic::Task::stream(cosmic::iced_futures::stream::channel(
|
||||
1,
|
||||
|mut sender| async move {
|
||||
|mut sender: futures::channel::mpsc::Sender<crate::pages::Message>| async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
let _ = sender
|
||||
.send(crate::pages::Message::AccessibilityMagnifier(
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
|
||||
return cosmic::Task::stream(cosmic::iced_futures::stream::channel(
|
||||
1,
|
||||
|mut sender| async move {
|
||||
|mut sender: futures::channel::mpsc::Sender<super::Message>| async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
let _ = sender
|
||||
.send(crate::pages::Message::Accessibility(Message::Event(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ use std::{
|
|||
};
|
||||
|
||||
use cosmic::{
|
||||
Apply, Element, Task, surface,
|
||||
Apply, Element, Task,
|
||||
iced::Alignment,
|
||||
surface,
|
||||
widget::{self, dropdown, icon, settings},
|
||||
};
|
||||
use cosmic_config::{ConfigGet, ConfigSet};
|
||||
|
|
@ -296,6 +298,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
fl!("default-apps", "web-browser"),
|
||||
widget::text(fl!("default-apps", "not-installed")),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
} else {
|
||||
settings::flex_item(
|
||||
fl!("default-apps", "web-browser"),
|
||||
|
|
@ -313,6 +316,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.icons(Cow::Borrowed(&meta.icons)),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
.min_item_width(300.0)
|
||||
}
|
||||
})
|
||||
|
|
@ -323,6 +327,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
fl!("default-apps", "file-manager"),
|
||||
widget::text(fl!("default-apps", "not-installed")),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
} else {
|
||||
settings::flex_item(
|
||||
fl!("default-apps", "file-manager"),
|
||||
|
|
@ -340,6 +345,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.icons(Cow::Borrowed(&meta.icons)),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
}
|
||||
})
|
||||
.add({
|
||||
|
|
@ -349,6 +355,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
fl!("default-apps", "mail-client"),
|
||||
widget::text(fl!("default-apps", "not-installed")),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
} else {
|
||||
settings::flex_item(
|
||||
fl!("default-apps", "mail-client"),
|
||||
|
|
@ -366,6 +373,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.icons(Cow::Borrowed(&meta.icons)),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
}
|
||||
})
|
||||
.add({
|
||||
|
|
@ -375,6 +383,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
fl!("default-apps", "music"),
|
||||
widget::text(fl!("default-apps", "not-installed")),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
} else {
|
||||
settings::flex_item(
|
||||
fl!("default-apps", "music"),
|
||||
|
|
@ -392,6 +401,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.icons(Cow::Borrowed(&meta.icons)),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
}
|
||||
})
|
||||
.add({
|
||||
|
|
@ -401,6 +411,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
fl!("default-apps", "video"),
|
||||
widget::text(fl!("default-apps", "not-installed")),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
} else {
|
||||
settings::flex_item(
|
||||
fl!("default-apps", "video"),
|
||||
|
|
@ -418,6 +429,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.icons(Cow::Borrowed(&meta.icons)),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
}
|
||||
})
|
||||
.add({
|
||||
|
|
@ -427,6 +439,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
fl!("default-apps", "photos"),
|
||||
widget::text(fl!("default-apps", "not-installed")),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
} else {
|
||||
settings::flex_item(
|
||||
fl!("default-apps", "photos"),
|
||||
|
|
@ -444,6 +457,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.icons(Cow::Borrowed(&meta.icons)),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
}
|
||||
})
|
||||
.add({
|
||||
|
|
@ -453,6 +467,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
fl!("default-apps", "calendar"),
|
||||
widget::text(fl!("default-apps", "not-installed")),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
} else {
|
||||
settings::flex_item(
|
||||
fl!("default-apps", "calendar"),
|
||||
|
|
@ -470,6 +485,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.icons(Cow::Borrowed(&meta.icons)),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
}
|
||||
})
|
||||
.add({
|
||||
|
|
@ -479,6 +495,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
fl!("default-apps", "terminal"),
|
||||
widget::text(fl!("default-apps", "not-installed")),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
} else {
|
||||
settings::flex_item(
|
||||
fl!("default-apps", "terminal"),
|
||||
|
|
@ -496,6 +513,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.icons(Cow::Borrowed(&meta.icons)),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
}
|
||||
})
|
||||
.add({
|
||||
|
|
@ -505,6 +523,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
fl!("default-apps", "text-editor"),
|
||||
widget::text(fl!("default-apps", "not-installed")),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
} else {
|
||||
settings::flex_item(
|
||||
fl!("default-apps", "text-editor"),
|
||||
|
|
@ -522,6 +541,7 @@ fn apps() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.icons(Cow::Borrowed(&meta.icons)),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
}
|
||||
})
|
||||
.apply(Element::from)
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
// Forward messages from another thread to prevent the monitoring thread from blocking.
|
||||
let (randr_task, randr_handle) = Task::stream(cosmic::iced_futures::stream::channel(
|
||||
1,
|
||||
|mut sender| async move {
|
||||
|mut sender: futures::channel::mpsc::Sender<_>| async move {
|
||||
while let Some(message) = rx.recv().await {
|
||||
if let cosmic_randr::Message::ManagerDone = message
|
||||
&& !refresh_pending.swap(true, Ordering::SeqCst)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use cosmic::iced::{Alignment, Length, color};
|
||||
use cosmic::iced_core::text::Wrapping;
|
||||
use cosmic::widget::{self, settings, text};
|
||||
use cosmic::widget::{self, settings, space::horizontal as horizontal_space, text};
|
||||
use cosmic::{Apply, Element, Task, theme};
|
||||
use cosmic_settings_bluetooth_subscription::*;
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
|
|
@ -878,7 +878,7 @@ fn connected_devices() -> Section<crate::pages::Message> {
|
|||
.wrapping(Wrapping::Word)
|
||||
.into()
|
||||
},
|
||||
widget::horizontal_space().into(),
|
||||
horizontal_space().into(),
|
||||
match device.enabled {
|
||||
Active::Enabled => widget::text(&descriptions[device_connected]).into(),
|
||||
Active::Enabling => widget::text(&descriptions[device_connecting])
|
||||
|
|
@ -936,18 +936,26 @@ fn available_devices() -> Section<crate::pages::Message> {
|
|||
let mut items = vec![
|
||||
widget::icon::from_name(device.icon).size(16).into(),
|
||||
text(device.alias_or_addr()).wrapping(Wrapping::Word).into(),
|
||||
widget::horizontal_space().into(),
|
||||
horizontal_space().into(),
|
||||
];
|
||||
|
||||
if device.enabled == Active::Disabled {
|
||||
items.push(widget::button::text(&descriptions[device_connect]).on_press(Message::ConnectDevice(path.clone())).into(), )
|
||||
items.push(
|
||||
widget::button::text(&descriptions[device_connect])
|
||||
.on_press(Message::ConnectDevice(path.clone()))
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
if device.enabled == Active::Enabling || device.enabled == Active::Enabled {
|
||||
items.push(text(&descriptions[device_connecting]).class(theme::Text::Color(color!(128, 128, 128))).into(), );
|
||||
items.push(
|
||||
text(&descriptions[device_connecting])
|
||||
.class(theme::Text::Color(color!(128, 128, 128)))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
Some(widget::mouse_area(settings::item_row(items)).into(), )
|
||||
Some(widget::mouse_area(settings::item_row(items)).into())
|
||||
})
|
||||
.fold(section, settings::Section::add)
|
||||
.apply(Element::from)
|
||||
|
|
@ -978,11 +986,9 @@ fn multiple_adapter() -> Section<crate::pages::Message> {
|
|||
widget::icon::from_name("bluetooth-symbolic")
|
||||
.size(20)
|
||||
.into(),
|
||||
widget::horizontal_space()
|
||||
.width(theme::spacing().space_xxs)
|
||||
.into(),
|
||||
horizontal_space().width(theme::spacing().space_xxs).into(),
|
||||
text(&adapter.alias).wrapping(Wrapping::Word).into(),
|
||||
widget::horizontal_space().into(),
|
||||
horizontal_space().into(),
|
||||
widget::icon::from_name("go-next-symbolic").into(),
|
||||
];
|
||||
if page.model.adapter_connected(path) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use cosmic::{
|
|||
Apply, Element, Task,
|
||||
config::{CosmicTk, FontConfig},
|
||||
iced_core::text::Wrapping,
|
||||
widget::{self, settings, svg},
|
||||
widget::{self, settings, space::horizontal as horizontal_space, svg},
|
||||
};
|
||||
use cosmic_config::ConfigSet;
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ impl Model {
|
|||
.class(cosmic::theme::Svg::Custom(svg_accent.clone()))
|
||||
.into()
|
||||
} else {
|
||||
widget::horizontal_space().width(16).into()
|
||||
horizontal_space().width(16.).into()
|
||||
},
|
||||
])
|
||||
.apply(widget::container)
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ use cosmic::dialog::file_chooser::{self, FileFilter};
|
|||
use cosmic::iced::Subscription;
|
||||
use cosmic::iced_core::{Alignment, Length};
|
||||
use cosmic::widget::{
|
||||
button, color_picker::ColorPickerUpdate, container, horizontal_space, radio, row, settings,
|
||||
text,
|
||||
button, color_picker::ColorPickerUpdate, container, radio, row, settings,
|
||||
space::horizontal as horizontal_space, text,
|
||||
};
|
||||
use cosmic::{Apply, Element, Task, widget};
|
||||
#[cfg(feature = "wayland")]
|
||||
|
|
@ -926,7 +926,7 @@ pub fn reset_button() -> Section<crate::pages::Message> {
|
|||
.on_press(Message::Reset)
|
||||
.into()
|
||||
} else {
|
||||
horizontal_space().width(1).apply(Element::from)
|
||||
horizontal_space().width(1.).apply(Element::from)
|
||||
}
|
||||
.map(crate::pages::Message::Appearance)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -899,30 +899,28 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &cosmic::Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let inner_layout = self
|
||||
.inner
|
||||
.as_widget()
|
||||
let inner_layout =
|
||||
self.inner
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits);
|
||||
layout::Node::with_children(inner_layout.size(), vec![inner_layout])
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
renderer: &cosmic::Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<ReorderWidgetState>();
|
||||
operation.container(Some(&self.id), layout.bounds());
|
||||
|
||||
operation.custom(state, Some(&self.id));
|
||||
|
||||
self.inner.as_widget().operate(
|
||||
self.inner.as_widget_mut().operate(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
|
|
@ -931,31 +929,31 @@ where
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_lines, clippy::needless_match)]
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: event::Event,
|
||||
event: &event::Event,
|
||||
layout: layout::Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &cosmic::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let space_xxs = theme::spacing().space_xxs;
|
||||
let mut ret = match self.inner.as_widget_mut().on_event(
|
||||
self.inner.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
event,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
) {
|
||||
event::Status::Captured => return event::Status::Captured,
|
||||
event::Status::Ignored => event::Status::Ignored,
|
||||
};
|
||||
);
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
|
||||
let height = (layout.bounds().height
|
||||
- space_xxs as f32 * (self.info.len().saturating_sub(1)) as f32)
|
||||
|
|
@ -967,15 +965,14 @@ where
|
|||
DraggingState::Dragging(applet) => match &event {
|
||||
event::Event::Dnd(DndEvent::Source(source_event)) => match source_event {
|
||||
SourceEvent::Cancelled => {
|
||||
ret = event::Status::Captured;
|
||||
shell.capture_event();
|
||||
if let Some(on_cancel) = self.on_cancel.clone() {
|
||||
shell.publish(on_cancel);
|
||||
}
|
||||
DraggingState::None
|
||||
}
|
||||
SourceEvent::Finished => {
|
||||
ret = event::Status::Captured;
|
||||
|
||||
shell.capture_event();
|
||||
DraggingState::None
|
||||
}
|
||||
_ => DraggingState::Dragging(applet),
|
||||
|
|
@ -989,7 +986,7 @@ where
|
|||
| event::Event::Touch(touch::Event::FingerPressed { .. })
|
||||
if cursor_position.is_over(layout.bounds()) =>
|
||||
{
|
||||
ret = event::Status::Captured;
|
||||
shell.capture_event();
|
||||
|
||||
DraggingState::Pressed(cursor_position.position().unwrap_or_default())
|
||||
}
|
||||
|
|
@ -1040,7 +1037,8 @@ where
|
|||
Box::new(AppletString(p.clone())),
|
||||
DndAction::Move,
|
||||
);
|
||||
ret = event::Status::Captured;
|
||||
shell.capture_event();
|
||||
|
||||
let reordered = self
|
||||
.info
|
||||
.iter()
|
||||
|
|
@ -1063,7 +1061,7 @@ where
|
|||
| event::Event::Touch(
|
||||
touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. },
|
||||
) => {
|
||||
ret = event::Status::Captured;
|
||||
shell.capture_event();
|
||||
DraggingState::None
|
||||
}
|
||||
_ => DraggingState::Pressed(start),
|
||||
|
|
@ -1161,8 +1159,6 @@ where
|
|||
_ => DndOfferState::HandlingOffer,
|
||||
},
|
||||
};
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
@ -1189,14 +1185,16 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
layout: layout::Layout<'b>,
|
||||
renderer: &cosmic::Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, cosmic::Theme, cosmic::Renderer>> {
|
||||
self.inner.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use cosmic::{
|
|||
iced::{Alignment, Length},
|
||||
surface, theme,
|
||||
widget::{
|
||||
button, container, dropdown, horizontal_space, icon, row, settings, slider, text, toggler,
|
||||
button, container, dropdown, icon, row, settings, slider,
|
||||
space::horizontal as horizontal_space, text, toggler,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -203,8 +204,7 @@ pub(crate) fn style<
|
|||
move |a| crate::app::Message::PageMessage(msg_map(a)),
|
||||
),
|
||||
))
|
||||
.add(settings::flex_item(
|
||||
&descriptions[size],
|
||||
.add(settings::item::builder(&descriptions[size]).flex_control({
|
||||
// TODO custom discrete slider variant
|
||||
row::with_children(vec![
|
||||
text::body(fl!("small")).into(),
|
||||
|
|
@ -232,17 +232,22 @@ pub(crate) fn style<
|
|||
}
|
||||
},
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.apply(cosmic::widget::container)
|
||||
.max_width(250)
|
||||
.into(),
|
||||
text::body(fl!("large")).into(),
|
||||
])
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(8),
|
||||
))
|
||||
.add(settings::flex_item(
|
||||
&descriptions[background_opacity],
|
||||
.spacing(8)
|
||||
.width(Length::Fill)
|
||||
}))
|
||||
.add(
|
||||
settings::item::builder(&descriptions[background_opacity]).flex_control({
|
||||
row::with_capacity(2)
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(8)
|
||||
.width(Length::Fill)
|
||||
.push(
|
||||
text::body(fl!(
|
||||
"number",
|
||||
|
|
@ -258,9 +263,12 @@ pub(crate) fn style<
|
|||
slider(0..=100, (panel_config.opacity * 100.0) as i32, |v| {
|
||||
Message::OpacityRequest(v as f32 / 100.0)
|
||||
})
|
||||
.breakpoints(&[50]),
|
||||
),
|
||||
))
|
||||
.width(Length::Fill)
|
||||
.apply(container)
|
||||
.max_width(250),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.apply(Element::from)
|
||||
.map(msg_map)
|
||||
})
|
||||
|
|
@ -293,10 +301,13 @@ pub(crate) fn configuration<P: page::Page<crate::pages::Message> + PanelPage>(
|
|||
settings::item::builder(&*descriptions[applets_label])
|
||||
.control(control)
|
||||
.spacing(16)
|
||||
.width(Length::Fill)
|
||||
.apply(container)
|
||||
.class(theme::Container::List)
|
||||
.apply(button::custom)
|
||||
.width(Length::Fill)
|
||||
.class(theme::Button::Transparent)
|
||||
.width(Length::Fill)
|
||||
.on_press(crate::pages::Message::Page(panel_applets_entity)),
|
||||
)
|
||||
} else {
|
||||
|
|
@ -347,7 +358,7 @@ pub fn reset_button<
|
|||
let descriptions = §ion.descriptions;
|
||||
let inner = page.inner();
|
||||
if inner.system_default == inner.panel_config {
|
||||
Element::from(horizontal_space().width(1))
|
||||
Element::from(horizontal_space().width(1.))
|
||||
} else {
|
||||
button::standard(&descriptions[reset_to_default])
|
||||
.on_press(Message::ResetPanel)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ use cosmic::{
|
|||
widget::{
|
||||
button, dropdown, list_column, row,
|
||||
segmented_button::{self, SingleSelectModel},
|
||||
settings, tab_bar, text, toggler,
|
||||
settings,
|
||||
space::horizontal as horizontal_space,
|
||||
tab_bar, text, toggler,
|
||||
},
|
||||
};
|
||||
use cosmic::{
|
||||
|
|
@ -691,7 +693,10 @@ impl Page {
|
|||
.width(Length::Fixed(SIMULATED_WIDTH as f32))
|
||||
.into(),
|
||||
|
||||
None => cosmic::widget::Space::new(SIMULATED_WIDTH, SIMULATED_HEIGHT).into(),
|
||||
None => cosmic::widget::Space::new()
|
||||
.width(Length::Fixed(SIMULATED_WIDTH as f32))
|
||||
.height(Length::Fixed(SIMULATED_HEIGHT as f32))
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1342,7 +1347,7 @@ pub fn settings() -> Section<crate::pages::Message> {
|
|||
},
|
||||
)
|
||||
.push(category_selection)
|
||||
.push(cosmic::widget::horizontal_space())
|
||||
.push(horizontal_space())
|
||||
.push_maybe(add_button)
|
||||
.into(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -47,11 +47,12 @@ pub fn color_image<'a, M: 'a>(
|
|||
height: u16,
|
||||
border_radius: Option<f32>,
|
||||
) -> Element<'a, M> {
|
||||
container(Space::new(width, height))
|
||||
container(Space::new().width(width).height(height))
|
||||
.class(cosmic::theme::Container::custom(move |theme| {
|
||||
container::Style {
|
||||
icon_color: None,
|
||||
text_color: None,
|
||||
snap: true,
|
||||
background: Some(match &color {
|
||||
wallpaper::Color::Single([r, g, b]) => {
|
||||
Background::Color(Color::from_rgb(*r, *g, *b))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use cosmic::{
|
||||
Apply, Element,
|
||||
iced::Length,
|
||||
iced::{Alignment, Length},
|
||||
surface,
|
||||
widget::{self, settings, toggler},
|
||||
};
|
||||
|
|
@ -258,12 +258,15 @@ pub fn window_management() -> Section<crate::pages::Message> {
|
|||
},
|
||||
),
|
||||
))
|
||||
.add(settings::flex_item(
|
||||
.add(
|
||||
settings::flex_item(
|
||||
&descriptions[edge_gravity],
|
||||
toggler(page.edge_snap_threshold != 0).on_toggle(|is_enabled| {
|
||||
Message::SetEdgeSnapThreshold(if is_enabled { 10 } else { 0 })
|
||||
}),
|
||||
))
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::WindowManagement)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use cosmic::iced_core::{
|
|||
Shell, Size, Widget,
|
||||
};
|
||||
use cosmic::iced_core::{Point, layout, mouse, renderer, touch};
|
||||
use cosmic::iced_core::{alignment, event, text};
|
||||
use cosmic::iced_core::{alignment, text};
|
||||
use cosmic::widget::segmented_button::{self, SingleSelectModel};
|
||||
use cosmic_randr_shell::{self as randr, OutputKey};
|
||||
use randr::Transform;
|
||||
|
|
@ -96,7 +96,7 @@ impl<Message: Clone> Widget<Message, cosmic::Theme, Renderer> for Arrangement<'_
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
|
|
@ -157,17 +157,17 @@ impl<Message: Clone> Widget<Message, cosmic::Theme, Renderer> for Arrangement<'_
|
|||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: cosmic::iced_core::Event,
|
||||
event: &cosmic::iced_core::Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
match event {
|
||||
|
|
@ -198,7 +198,7 @@ impl<Message: Clone> Widget<Message, cosmic::Theme, Renderer> for Arrangement<'_
|
|||
),
|
||||
);
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -217,7 +217,7 @@ impl<Message: Clone> Widget<Message, cosmic::Theme, Renderer> for Arrangement<'_
|
|||
state.drag_from = position;
|
||||
state.offset = (position.x - output_region.x, position.y - output_region.y);
|
||||
state.dragging = Some((output_key, output_region));
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -239,7 +239,8 @@ impl<Message: Clone> Widget<Message, cosmic::Theme, Renderer> for Arrangement<'_
|
|||
}
|
||||
}
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ref on_placement) = self.on_placement {
|
||||
|
|
@ -253,14 +254,12 @@ impl<Message: Clone> Widget<Message, cosmic::Theme, Renderer> for Arrangement<'_
|
|||
));
|
||||
}
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -333,6 +332,7 @@ impl<Message: Clone> Widget<Message, cosmic::Theme, Renderer> for Arrangement<'_
|
|||
width: 3.0,
|
||||
},
|
||||
shadow: Default::default(),
|
||||
snap: true,
|
||||
},
|
||||
core::Background::Color(background.into()),
|
||||
);
|
||||
|
|
@ -352,6 +352,7 @@ impl<Message: Clone> Widget<Message, cosmic::Theme, Renderer> for Arrangement<'_
|
|||
..Default::default()
|
||||
},
|
||||
shadow: Default::default(),
|
||||
snap: true,
|
||||
},
|
||||
core::Background::Color(cosmic_theme.palette.neutral_1.into()),
|
||||
);
|
||||
|
|
@ -364,8 +365,8 @@ impl<Message: Clone> Widget<Message, cosmic::Theme, Renderer> for Arrangement<'_
|
|||
line_height: core::text::LineHeight::Relative(1.2),
|
||||
font: cosmic::font::bold(),
|
||||
bounds: id_bounds.size(),
|
||||
horizontal_alignment: alignment::Horizontal::Center,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
align_x: text::Alignment::Center,
|
||||
align_y: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Basic,
|
||||
wrapping: text::Wrapping::Word,
|
||||
ellipsize: text::Ellipsize::None,
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
// Forward messages from another thread to prevent the monitoring thread from blocking.
|
||||
let (randr_task, randr_handle) = Task::stream(cosmic::iced_futures::stream::channel(
|
||||
1,
|
||||
|mut emitter| async move {
|
||||
|mut emitter: futures::channel::mpsc::Sender<_>| async move {
|
||||
while let Some(message) = rx.recv().await {
|
||||
if let cosmic_randr::Message::ManagerDone = message
|
||||
&& !refreshing_page.swap(true, Ordering::SeqCst)
|
||||
|
|
@ -359,7 +359,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
// Forward messages from the DRM hotplug thread.
|
||||
let (hotplug_task, hotplug_handle) = Task::stream(cosmic::iced_futures::stream::channel(
|
||||
1,
|
||||
|mut emitter| async move {
|
||||
|mut emitter: futures::channel::mpsc::Sender<pages::Message>| async move {
|
||||
while let Some(message) = rx.recv().await {
|
||||
_ = emitter.send(message).await;
|
||||
}
|
||||
|
|
@ -609,8 +609,8 @@ impl Page {
|
|||
return cosmic::iced::widget::scrollable::snap_to(
|
||||
self.display_arrangement_scrollable.clone(),
|
||||
RelativeOffset {
|
||||
x: self.last_pan,
|
||||
y: 0.0,
|
||||
x: Some(self.last_pan),
|
||||
y: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -662,7 +662,10 @@ impl Page {
|
|||
self.last_pan = 0.5;
|
||||
cosmic::iced::widget::scrollable::snap_to(
|
||||
self.display_arrangement_scrollable.clone(),
|
||||
RelativeOffset { x: 0.5, y: 0.5 },
|
||||
RelativeOffset {
|
||||
x: Some(0.5),
|
||||
y: Some(0.5),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -801,7 +801,8 @@ fn keyboard_typing_assist() -> Section<crate::pages::Message> {
|
|||
|
||||
settings::section()
|
||||
.title(§ion.title)
|
||||
.add(settings::flex_item(&descriptions[repeat_delay], {
|
||||
.add(
|
||||
settings::flex_item(&descriptions[repeat_delay], {
|
||||
// Delay
|
||||
let delay_slider = cosmic::widget::slider(
|
||||
KB_REPEAT_DELAY_MIN..=KB_REPEAT_DELAY_MAX,
|
||||
|
|
@ -820,8 +821,11 @@ fn keyboard_typing_assist() -> Section<crate::pages::Message> {
|
|||
.push(widget::text::body(&descriptions[short]))
|
||||
.push(delay_slider)
|
||||
.push(widget::text::body(&descriptions[long]))
|
||||
}))
|
||||
.add(settings::flex_item(&descriptions[repeat_rate], {
|
||||
})
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(
|
||||
settings::flex_item(&descriptions[repeat_rate], {
|
||||
// Repeat rate
|
||||
let rate_slider = cosmic::widget::slider(
|
||||
KB_REPEAT_RATE_MIN..=KB_REPEAT_RATE_MAX,
|
||||
|
|
@ -840,7 +844,9 @@ fn keyboard_typing_assist() -> Section<crate::pages::Message> {
|
|||
.push(widget::text::body(&descriptions[slow]))
|
||||
.push(rate_slider)
|
||||
.push(widget::text::body(&descriptions[fast]))
|
||||
}))
|
||||
})
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.apply(cosmic::Element::from)
|
||||
.map(crate::pages::Message::Keyboard)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -882,6 +882,7 @@ fn shortcut_item(custom: bool, id: usize, data: &ShortcutModel) -> Element<'_, S
|
|||
|
||||
settings::item::builder(&data.description)
|
||||
.flex_control(control)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(16)
|
||||
.apply(widget::container)
|
||||
.class(theme::Container::List)
|
||||
|
|
|
|||
|
|
@ -437,7 +437,7 @@ fn shortcuts() -> Section<crate::pages::Message> {
|
|||
let descriptions = §ion.descriptions;
|
||||
|
||||
let search = widget::search_input("", &page.search.input)
|
||||
.width(314)
|
||||
.width(314.)
|
||||
.on_clear(Message::Search(String::new()))
|
||||
.on_input(Message::Search)
|
||||
.apply(widget::container)
|
||||
|
|
@ -516,6 +516,7 @@ fn category_item(category: Category, name: &str, modified: u16) -> Element<'_, M
|
|||
.class(theme::Container::List)
|
||||
.apply(widget::button::custom)
|
||||
.class(theme::Button::Transparent)
|
||||
.width(Length::Fill)
|
||||
.on_press(Message::Category(category))
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,12 +62,15 @@ fn mouse() -> Section<crate::pages::Message> {
|
|||
|
||||
settings::section()
|
||||
.title(§ion.title)
|
||||
.add(settings::flex_item(
|
||||
.add(
|
||||
settings::flex_item(
|
||||
&descriptions[primary_button],
|
||||
cosmic::widget::segmented_control::horizontal(&input.primary_button)
|
||||
.minimum_button_width(0)
|
||||
.on_activate(|x| Message::PrimaryButtonSelected(x, false)),
|
||||
))
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(
|
||||
settings::item::builder(&descriptions[mouse_speed]).flex_control({
|
||||
let value = (input
|
||||
|
|
@ -130,7 +133,8 @@ fn scrolling() -> Section<crate::pages::Message> {
|
|||
|
||||
settings::section()
|
||||
.title(§ion.title)
|
||||
.add(settings::flex_item(&descriptions[scroll_speed], {
|
||||
.add(
|
||||
settings::flex_item(&descriptions[scroll_speed], {
|
||||
let value = input
|
||||
.input_default
|
||||
.scroll_config
|
||||
|
|
@ -158,7 +162,9 @@ fn scrolling() -> Section<crate::pages::Message> {
|
|||
.align_x(Alignment::Center),
|
||||
)
|
||||
.push(slider)
|
||||
}))
|
||||
})
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(
|
||||
settings::item::builder(&descriptions[natural])
|
||||
.description(&descriptions[natural_desc])
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@ use std::sync::{Arc, LazyLock};
|
|||
use anyhow::Context;
|
||||
use cosmic::dialog::file_chooser::FileFilter;
|
||||
use cosmic::task;
|
||||
use cosmic::widget::text_input::focus;
|
||||
use cosmic::{
|
||||
Apply, Element, Task,
|
||||
iced::{Alignment, Length},
|
||||
iced_core::text::Wrapping,
|
||||
widget::{self, icon},
|
||||
widget::{self, icon, space::horizontal as horizontal_space, text_input::focus},
|
||||
};
|
||||
use cosmic_settings_network_manager_subscription::nm_secret_agent::{self, PasswordFlag};
|
||||
use cosmic_settings_network_manager_subscription::{
|
||||
|
|
@ -1086,7 +1085,7 @@ fn devices_view() -> Section<crate::pages::Message> {
|
|||
|
||||
let widget = widget::settings::item_row(vec![
|
||||
identifier.into(),
|
||||
widget::horizontal_space().into(),
|
||||
horizontal_space().into(),
|
||||
controls.into(),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ use anyhow::Context;
|
|||
use cosmic::{
|
||||
Apply, Element, Task,
|
||||
app::ContextDrawer,
|
||||
iced::{Alignment, Length},
|
||||
iced::{Alignment, Length, widget::operation::focus_next},
|
||||
iced_core::text::Wrapping,
|
||||
iced_widget::focus_next,
|
||||
task,
|
||||
widget::{self, column, icon, text_input::focus},
|
||||
widget::{self, column, icon, space::horizontal as horizontal_space, text_input::focus},
|
||||
};
|
||||
use cosmic_settings_network_manager_subscription::{
|
||||
self as network_manager, NetworkManagerState,
|
||||
|
|
@ -1020,7 +1019,7 @@ fn devices_view() -> Section<crate::pages::Message> {
|
|||
|
||||
let item = widget::settings::item_row(vec![
|
||||
identifier.into(),
|
||||
widget::horizontal_space().into(),
|
||||
horizontal_space().into(),
|
||||
controls.into(),
|
||||
]);
|
||||
|
||||
|
|
@ -1125,7 +1124,7 @@ fn devices_view() -> Section<crate::pages::Message> {
|
|||
|
||||
let item = widget::settings::item_row(vec![
|
||||
identifier.into(),
|
||||
widget::horizontal_space().into(),
|
||||
horizontal_space().into(),
|
||||
controls.into(),
|
||||
]);
|
||||
|
||||
|
|
@ -1235,7 +1234,7 @@ fn devices_view() -> Section<crate::pages::Message> {
|
|||
|
||||
let item = widget::settings::item_row(vec![
|
||||
identifier.into(),
|
||||
widget::horizontal_space().into(),
|
||||
horizontal_space().into(),
|
||||
connect,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use cosmic::{
|
|||
Apply, Element, Task,
|
||||
iced::{Alignment, Length},
|
||||
iced_core::text::Wrapping,
|
||||
widget::{self, icon},
|
||||
widget::{self, icon, space::horizontal as horizontal_space},
|
||||
};
|
||||
use cosmic_dbus_networkmanager::interface::enums::DeviceState;
|
||||
use cosmic_settings_network_manager_subscription::{
|
||||
|
|
@ -549,7 +549,7 @@ impl Page {
|
|||
|
||||
let widget = widget::settings::item_row(vec![
|
||||
identifier.into(),
|
||||
widget::horizontal_space().into(),
|
||||
horizontal_space().into(),
|
||||
controls.into(),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use backend::{Battery, ConnectedDevice, PowerProfile};
|
|||
|
||||
use cosmic::iced::{self, Alignment, Length};
|
||||
use cosmic::iced_widget::{column, row};
|
||||
use cosmic::widget::{self, radio, settings, text};
|
||||
use cosmic::widget::{self, radio, settings, space::horizontal as horizontal_space, text};
|
||||
use cosmic::{Apply, surface};
|
||||
use cosmic::{Task, iced_futures};
|
||||
use cosmic_config::{Config, CosmicConfigEntry};
|
||||
|
|
@ -15,6 +15,7 @@ use futures::{SinkExt, StreamExt};
|
|||
use itertools::Itertools;
|
||||
use slab::Slab;
|
||||
use slotmap::SlotMap;
|
||||
use std::hash::Hash;
|
||||
use std::iter;
|
||||
use std::time::Duration;
|
||||
use upower_dbus::DeviceProxy;
|
||||
|
|
@ -153,8 +154,8 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
});
|
||||
|
||||
// Subscriptions for all connected device batteries.
|
||||
let device_batteries = self
|
||||
.connected_devices
|
||||
let device_batteries =
|
||||
self.connected_devices
|
||||
.iter()
|
||||
.filter_map(|device| {
|
||||
device
|
||||
|
|
@ -163,12 +164,38 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
.map(|p| (device.device_path.clone(), p))
|
||||
})
|
||||
.map(|(path, proxy)| {
|
||||
iced::Subscription::run_with_id(
|
||||
path.clone(),
|
||||
iced_futures::stream::channel(1, |sender| async move {
|
||||
receive_battery_changes(proxy, path, sender, Message::UpdateDeviceBattery)
|
||||
#[derive(Clone)]
|
||||
struct DeviceBatterySubscriptionData {
|
||||
proxy: DeviceProxy<'static>,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl Hash for DeviceBatterySubscriptionData {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.path.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
iced::Subscription::run_with(
|
||||
DeviceBatterySubscriptionData { proxy, path },
|
||||
|DeviceBatterySubscriptionData { proxy, path }| {
|
||||
let path = path.clone();
|
||||
let proxy = proxy.clone();
|
||||
iced_futures::stream::channel(
|
||||
1,
|
||||
move |sender: futures::channel::mpsc::Sender<
|
||||
crate::pages::Message,
|
||||
>| async move {
|
||||
receive_battery_changes(
|
||||
proxy,
|
||||
path,
|
||||
sender,
|
||||
Message::UpdateDeviceBattery,
|
||||
)
|
||||
.await
|
||||
}),
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
|
|
@ -199,8 +226,11 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
}
|
||||
}),
|
||||
cosmic::Task::run(
|
||||
iced_futures::stream::channel(1, |mut emitter| async move {
|
||||
let span = tracing::span!(tracing::Level::INFO, "power::device_stream task");
|
||||
iced_futures::stream::channel(
|
||||
1,
|
||||
|mut emitter: futures::channel::mpsc::Sender<Message>| async move {
|
||||
let span =
|
||||
tracing::span!(tracing::Level::INFO, "power::device_stream task");
|
||||
let _span_handle = span.enter();
|
||||
|
||||
let Ok(connection) = zbus::Connection::system().await else {
|
||||
|
|
@ -209,7 +239,8 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
};
|
||||
|
||||
let added_stream = ConnectedDevice::device_added_stream(&connection).await;
|
||||
let removed_stream = ConnectedDevice::device_removed_stream(&connection).await;
|
||||
let removed_stream =
|
||||
ConnectedDevice::device_removed_stream(&connection).await;
|
||||
|
||||
let mut sender = emitter.clone();
|
||||
let added_future = std::pin::pin!(async {
|
||||
|
|
@ -231,15 +262,20 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
let mut stream = std::pin::pin!(stream);
|
||||
while let Some(device_path) = stream.next().await {
|
||||
tracing::debug!(device_path, "device removed");
|
||||
_ = emitter.send(Message::DeviceDisconnect(device_path)).await;
|
||||
_ = emitter
|
||||
.send(Message::DeviceDisconnect(device_path))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
Err(err) => tracing::error!(?err, "cannot establish removed stream"),
|
||||
Err(err) => {
|
||||
tracing::error!(?err, "cannot establish removed stream")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
futures::future::select(added_future, removed_future).await;
|
||||
}),
|
||||
},
|
||||
),
|
||||
|msg| msg,
|
||||
),
|
||||
];
|
||||
|
|
@ -449,7 +485,7 @@ fn connected_devices() -> Section<crate::pages::Message> {
|
|||
.width(Length::Fill)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.height(64)
|
||||
.height(64.)
|
||||
.class(cosmic::theme::Container::List)
|
||||
.into()
|
||||
})
|
||||
|
|
@ -469,14 +505,10 @@ fn connected_devices() -> Section<crate::pages::Message> {
|
|||
cosmic::Element::from(
|
||||
row!(
|
||||
device_row.next().unwrap_or(
|
||||
widget::horizontal_space()
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
horizontal_space().width(Length::Fill).into()
|
||||
),
|
||||
device_row.next().unwrap_or(
|
||||
widget::horizontal_space()
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
horizontal_space().width(Length::Fill).into()
|
||||
),
|
||||
)
|
||||
.spacing(8),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use cosmic::{
|
|||
Apply, Element, Task,
|
||||
iced::{Alignment, Length, window},
|
||||
surface,
|
||||
widget::{self, settings},
|
||||
widget::{self, settings, space::horizontal as horizontal_space},
|
||||
};
|
||||
use cosmic_config::{Config, ConfigGet, ConfigSet};
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
|
|
@ -275,7 +275,10 @@ fn input() -> Section<crate::pages::Message> {
|
|||
widget::slider(0..=100, page.model.source_volume, |change| {
|
||||
Message::SetSourceVolume(change).into()
|
||||
})
|
||||
};
|
||||
}
|
||||
.width(Length::Fill)
|
||||
.apply(widget::container)
|
||||
.max_width(250.);
|
||||
|
||||
let volume_control = widget::row::with_capacity(4)
|
||||
.align_y(Alignment::Center)
|
||||
|
|
@ -292,7 +295,7 @@ fn input() -> Section<crate::pages::Message> {
|
|||
.width(Length::Fixed(22.0))
|
||||
.align_x(Alignment::Center),
|
||||
)
|
||||
.push(widget::horizontal_space().width(8))
|
||||
.push(horizontal_space().width(8.))
|
||||
.push(slider);
|
||||
let devices = widget::dropdown::popup_dropdown(
|
||||
page.model.sources(),
|
||||
|
|
@ -307,10 +310,11 @@ fn input() -> Section<crate::pages::Message> {
|
|||
|
||||
let mut controls = settings::section()
|
||||
.title(§ion.title)
|
||||
.add(settings::flex_item(
|
||||
&*section.descriptions[volume],
|
||||
volume_control,
|
||||
))
|
||||
.add(
|
||||
settings::item::builder(&*section.descriptions[volume])
|
||||
.flex_control(volume_control)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(settings::item(&*section.descriptions[device], devices));
|
||||
|
||||
controls = controls.add(
|
||||
|
|
@ -351,7 +355,10 @@ fn output() -> Section<crate::pages::Message> {
|
|||
widget::slider(0..=100, page.model.sink_volume, |change| {
|
||||
Message::SetSinkVolume(change).into()
|
||||
})
|
||||
};
|
||||
}
|
||||
.width(Length::Fill)
|
||||
.apply(widget::container)
|
||||
.max_width(250.);
|
||||
|
||||
let volume_control = widget::row::with_capacity(4)
|
||||
.align_y(Alignment::Center)
|
||||
|
|
@ -368,7 +375,7 @@ fn output() -> Section<crate::pages::Message> {
|
|||
.width(Length::Fixed(22.0))
|
||||
.align_x(Alignment::Center),
|
||||
)
|
||||
.push(widget::horizontal_space().width(8))
|
||||
.push(horizontal_space().width(8.))
|
||||
.push(slider);
|
||||
|
||||
let devices = widget::dropdown::popup_dropdown(
|
||||
|
|
@ -384,10 +391,11 @@ fn output() -> Section<crate::pages::Message> {
|
|||
|
||||
let mut controls = settings::section()
|
||||
.title(§ion.title)
|
||||
.add(settings::flex_item(
|
||||
&*section.descriptions[volume],
|
||||
volume_control,
|
||||
))
|
||||
.add(
|
||||
settings::item::builder(&*section.descriptions[volume])
|
||||
.flex_control(volume_control)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(settings::item(&*section.descriptions[device], devices))
|
||||
.add(settings::item(
|
||||
&*section.descriptions[balance],
|
||||
|
|
@ -398,7 +406,7 @@ fn output() -> Section<crate::pages::Message> {
|
|||
.width(Length::Fixed(22.0))
|
||||
.align_x(Alignment::Center),
|
||||
)
|
||||
.push(widget::horizontal_space().width(8))
|
||||
.push(horizontal_space().width(8.))
|
||||
.push(
|
||||
widget::slider(
|
||||
0..=200,
|
||||
|
|
@ -408,7 +416,7 @@ fn output() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.breakpoints(&[100]),
|
||||
)
|
||||
.push(widget::horizontal_space().width(8))
|
||||
.push(horizontal_space().width(8.))
|
||||
.push(
|
||||
widget::text::body(&*section.descriptions[right])
|
||||
.width(Length::Fixed(22.0))
|
||||
|
|
@ -440,7 +448,7 @@ fn device_profiles() -> Section<crate::pages::Message> {
|
|||
.view::<Page>(move |_binder, page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
let button = widget::row::with_children(vec![
|
||||
widget::horizontal_space().into(),
|
||||
horizontal_space().into(),
|
||||
widget::icon::from_name("go-next-symbolic").size(16).into(),
|
||||
]);
|
||||
|
||||
|
|
@ -448,10 +456,13 @@ fn device_profiles() -> Section<crate::pages::Message> {
|
|||
.control(button)
|
||||
.spacing(16)
|
||||
.apply(widget::container)
|
||||
.width(Length::Fill)
|
||||
.class(cosmic::theme::Container::List)
|
||||
.apply(widget::button::custom)
|
||||
.width(Length::Fill)
|
||||
.class(cosmic::theme::Button::Transparent)
|
||||
.on_press(crate::pages::Message::Page(page.device_profiles));
|
||||
.on_press(crate::pages::Message::Page(page.device_profiles))
|
||||
.width(Length::Fill);
|
||||
|
||||
settings::section().add(device_profiles).into()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::iced::Alignment;
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
|
||||
use super::info::Info;
|
||||
|
|
@ -177,7 +178,7 @@ fn device() -> Section<crate::pages::Message> {
|
|||
page.editing_device_name,
|
||||
Message::HostnameEdit,
|
||||
)
|
||||
.width(250)
|
||||
.width(250.)
|
||||
.on_input(Message::HostnameInput)
|
||||
.on_unfocus(Message::HostnameSubmit)
|
||||
.on_submit(|_| Message::HostnameSubmit);
|
||||
|
|
@ -210,31 +211,34 @@ fn hardware() -> Section<crate::pages::Message> {
|
|||
|
||||
let mut section_builder = settings::section()
|
||||
.title(§ion.title)
|
||||
.add(settings::flex_item(
|
||||
&*desc[model],
|
||||
text::body(&page.info.hardware_model),
|
||||
))
|
||||
.add(settings::flex_item(
|
||||
&*desc[memory],
|
||||
text::body(&page.info.memory),
|
||||
))
|
||||
.add(settings::flex_item(
|
||||
&*desc[processor],
|
||||
text::body(&page.info.processor),
|
||||
));
|
||||
.add(
|
||||
settings::flex_item(&*desc[model], text::body(&page.info.hardware_model))
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(
|
||||
settings::flex_item(&*desc[memory], text::body(&page.info.memory))
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(
|
||||
settings::flex_item(&*desc[processor], text::body(&page.info.processor))
|
||||
.align_items(Alignment::Center),
|
||||
);
|
||||
|
||||
for card in &page.info.graphics {
|
||||
section_builder = section_builder.add(settings::flex_item(
|
||||
&*desc[graphics],
|
||||
text::body(card.as_str()),
|
||||
));
|
||||
section_builder = section_builder.add(
|
||||
settings::flex_item(&*desc[graphics], text::body(card.as_str()))
|
||||
.align_items(Alignment::Center),
|
||||
);
|
||||
}
|
||||
|
||||
section_builder
|
||||
.add(settings::flex_item(
|
||||
.add(
|
||||
settings::flex_item(
|
||||
&*desc[disk_capacity],
|
||||
text::body(&page.info.disk_capacity),
|
||||
))
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
|
@ -255,26 +259,32 @@ fn os() -> Section<crate::pages::Message> {
|
|||
let desc = §ion.descriptions;
|
||||
settings::section()
|
||||
.title(§ion.title)
|
||||
.add(settings::flex_item(
|
||||
&*desc[os],
|
||||
text::body(&page.info.operating_system),
|
||||
))
|
||||
.add(settings::flex_item(
|
||||
&*desc[os_arch],
|
||||
text::body(&page.info.os_architecture),
|
||||
))
|
||||
.add(settings::flex_item(
|
||||
&*desc[kernel],
|
||||
text::body(&page.info.kernel_version),
|
||||
))
|
||||
.add(settings::flex_item(
|
||||
.add(
|
||||
settings::flex_item(&*desc[os], text::body(&page.info.operating_system))
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(
|
||||
settings::flex_item(&*desc[os_arch], text::body(&page.info.os_architecture))
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(
|
||||
settings::flex_item(&*desc[kernel], text::body(&page.info.kernel_version))
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(
|
||||
settings::flex_item(
|
||||
&*desc[desktop],
|
||||
text::body(&page.info.desktop_environment),
|
||||
))
|
||||
.add(settings::flex_item(
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.add(
|
||||
settings::flex_item(
|
||||
&*desc[windowing_system],
|
||||
text::body(&page.info.windowing_system),
|
||||
))
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use cosmic::{
|
|||
Apply, Element,
|
||||
dialog::file_chooser,
|
||||
iced::{Alignment, Length},
|
||||
widget::{self, Space, column, icon, row, settings, text},
|
||||
widget::{self, column, icon, row, settings, space::horizontal as horizontal_space, text},
|
||||
};
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
use image::GenericImageView;
|
||||
|
|
@ -322,7 +322,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
)))
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(Space::new(5, 0))
|
||||
.push(horizontal_space().width(5.))
|
||||
.push(admin_toggler)
|
||||
.align_y(Alignment::Center),
|
||||
),
|
||||
|
|
@ -843,7 +843,7 @@ fn user_list() -> Section<crate::pages::Message> {
|
|||
.push(text::caption(crate::fl!("administrator", "desc")))
|
||||
.width(Length::Fill)
|
||||
.into(),
|
||||
Space::new(5, 0).into(),
|
||||
horizontal_space().width(5.).into(),
|
||||
widget::toggler(user.is_admin)
|
||||
.on_toggle(|enabled| {
|
||||
Message::SelectedUserSetAdmin(user.id, enabled)
|
||||
|
|
@ -853,7 +853,7 @@ fn user_list() -> Section<crate::pages::Message> {
|
|||
|
||||
if page.users.len() > 1 {
|
||||
details_list = details_list.add(settings::item_row(vec![
|
||||
widget::horizontal_space().width(Length::Fill).into(),
|
||||
horizontal_space().width(Length::Fill).into(),
|
||||
widget::button::destructive(crate::fl!("remove-user"))
|
||||
.on_press(Message::SelectedUserDelete(user.id))
|
||||
.into(),
|
||||
|
|
@ -885,7 +885,7 @@ fn user_list() -> Section<crate::pages::Message> {
|
|||
.align_y(Alignment::Center)
|
||||
.spacing(space_xxs)
|
||||
.into(),
|
||||
widget::horizontal_space().width(Length::Fill).into(),
|
||||
horizontal_space().width(Length::Fill).into(),
|
||||
icon::from_name(if expanded {
|
||||
"go-up-symbolic"
|
||||
} else {
|
||||
|
|
@ -901,6 +901,7 @@ fn user_list() -> Section<crate::pages::Message> {
|
|||
.padding([space_xxs, space_m])
|
||||
.on_press(Message::SelectUser(idx))
|
||||
.class(cosmic::theme::Button::ListItem)
|
||||
.width(Length::Fill)
|
||||
.selected(expanded)
|
||||
.apply(Element::from),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use cosmic::{
|
|||
cosmic_config::{self, ConfigGet, ConfigSet},
|
||||
iced_core::text::Wrapping,
|
||||
surface,
|
||||
widget::{self, dropdown, settings},
|
||||
widget::{self, dropdown, settings, space::horizontal as horizontal_space},
|
||||
};
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
use icu::{
|
||||
|
|
@ -366,7 +366,7 @@ impl Page {
|
|||
.class(cosmic::theme::Svg::Custom(svg_accent.clone()))
|
||||
.into()
|
||||
} else {
|
||||
widget::horizontal_space().width(16).into()
|
||||
horizontal_space().width(16.).into()
|
||||
},
|
||||
])
|
||||
.apply(widget::container)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::sync::Arc;
|
|||
use cosmic::app::{ContextDrawer, context_drawer};
|
||||
use cosmic::iced::{Alignment, Length};
|
||||
use cosmic::iced_core::text::Wrapping;
|
||||
use cosmic::widget::{self, button};
|
||||
use cosmic::widget::{self, button, space::horizontal as horizontal_space};
|
||||
use cosmic::{Apply, Element};
|
||||
use cosmic_config::{ConfigGet, ConfigSet};
|
||||
use cosmic_settings_page::Section;
|
||||
|
|
@ -398,7 +398,7 @@ impl Page {
|
|||
.class(cosmic::theme::Svg::Custom(svg_accent.clone()))
|
||||
.into()
|
||||
} else {
|
||||
widget::horizontal_space().width(16).into()
|
||||
horizontal_space().width(16.).into()
|
||||
},
|
||||
])
|
||||
.apply(widget::container)
|
||||
|
|
@ -526,7 +526,7 @@ impl Page {
|
|||
.class(cosmic::theme::Svg::Custom(svg_accent.clone()))
|
||||
.into()
|
||||
} else {
|
||||
widget::horizontal_space().width(16).into()
|
||||
horizontal_space().width(16.).into()
|
||||
},
|
||||
])
|
||||
.apply(widget::container)
|
||||
|
|
|
|||
|
|
@ -12,15 +12,14 @@ use tokio::select;
|
|||
|
||||
pub fn daytime() -> cosmic::iced::Subscription<bool> {
|
||||
struct Sunset;
|
||||
Subscription::run_with_id(
|
||||
TypeId::of::<Sunset>(),
|
||||
stream::channel(2, |tx| async {
|
||||
Subscription::run_with(TypeId::of::<Sunset>(), |_| {
|
||||
stream::channel(2, |tx: Sender<bool>| async {
|
||||
if let Err(err) = inner(tx).await {
|
||||
tracing::error!("Sunset subscription error: {:?}", err);
|
||||
}
|
||||
future::pending().await
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
enum Event {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ pub enum Event {
|
|||
pub fn desktop_files<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||
id: I,
|
||||
) -> cosmic::iced::Subscription<Event> {
|
||||
Subscription::run_with_id(
|
||||
id,
|
||||
stream::channel(1, move |mut output| async move {
|
||||
Subscription::run_with(id, |_| {
|
||||
stream::channel(
|
||||
1,
|
||||
move |mut output: futures::channel::mpsc::Sender<Event>| async move {
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
let (tx, mut rx) = mpsc::channel(4);
|
||||
let mut last_update = std::time::Instant::now();
|
||||
|
|
@ -28,7 +29,9 @@ pub fn desktop_files<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
|||
move |res: Result<notify::Event, notify::Error>| {
|
||||
if let Ok(event) = res {
|
||||
match event.kind {
|
||||
EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) => {
|
||||
EventKind::Create(_)
|
||||
| EventKind::Modify(_)
|
||||
| EventKind::Remove(_) => {
|
||||
let now = std::time::Instant::now();
|
||||
if now.duration_since(last_update).as_secs() > 3 {
|
||||
_ = handle.block_on(tx.send(()));
|
||||
|
|
@ -54,6 +57,7 @@ pub fn desktop_files<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
|||
}
|
||||
|
||||
futures::future::pending().await
|
||||
}),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,15 +25,16 @@ pub enum WallpaperEvent {
|
|||
}
|
||||
|
||||
pub fn wallpapers(current_dir: PathBuf) -> cosmic::iced::Subscription<WallpaperEvent> {
|
||||
Subscription::run_with_id(
|
||||
current_dir.clone(),
|
||||
stream::channel(2, |tx| async {
|
||||
Subscription::run_with(current_dir, |current_dir: &PathBuf| {
|
||||
let current_dir = current_dir.clone();
|
||||
stream::channel(2, move |tx: Sender<WallpaperEvent>| async move {
|
||||
let current_dir = current_dir.clone();
|
||||
if let Err(err) = inner(tx, current_dir).await {
|
||||
tracing::error!("Wallpapers subscription error: {:?}", err);
|
||||
}
|
||||
future::pending().await
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async fn inner(tx: Sender<WallpaperEvent>, current_dir: PathBuf) -> anyhow::Result<()> {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ pub fn display_container_frame() -> cosmic::theme::Container<'static> {
|
|||
width: 3.0,
|
||||
},
|
||||
shadow: Default::default(),
|
||||
snap: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -35,6 +36,7 @@ pub fn display_container_screen() -> cosmic::theme::Container<'static> {
|
|||
width: 0.0,
|
||||
},
|
||||
shadow: Default::default(),
|
||||
snap: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ use cosmic::iced::{Alignment, Length};
|
|||
use cosmic::iced_core::text::Wrapping;
|
||||
use cosmic::widget::color_picker::ColorPickerUpdate;
|
||||
use cosmic::widget::{
|
||||
self, ColorPickerModel, button, column, container, divider, horizontal_space, icon, row,
|
||||
settings, text, vertical_space,
|
||||
self, ColorPickerModel, button, column, container, divider, icon, row, settings,
|
||||
space::{horizontal as horizontal_space, vertical as vertical_space},
|
||||
text,
|
||||
};
|
||||
use cosmic::{Apply, Element, theme};
|
||||
use cosmic_settings_page as page;
|
||||
|
|
@ -150,10 +151,12 @@ pub fn page_list_item<'a, Message: 'static + Clone>(
|
|||
.padding([space_s, space_m])
|
||||
.align_x(Alignment::Center)
|
||||
.class(theme::Container::List)
|
||||
.width(Length::Fill)
|
||||
.apply(button::custom)
|
||||
.padding(0)
|
||||
.class(theme::Button::Transparent)
|
||||
.on_press(message)
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
|
|
@ -190,9 +193,12 @@ pub fn go_next_item<Msg: Clone + 'static>(
|
|||
horizontal_space().into(),
|
||||
icon::from_name("go-next-symbolic").size(16).icon().into(),
|
||||
])
|
||||
.width(Length::Fill)
|
||||
.apply(widget::container)
|
||||
.class(cosmic::theme::Container::List)
|
||||
.width(Length::Fill)
|
||||
.apply(button::custom)
|
||||
.width(Length::Fill)
|
||||
.padding(0)
|
||||
.class(theme::Button::Transparent)
|
||||
.on_press_maybe(msg_opt.into())
|
||||
|
|
@ -214,10 +220,13 @@ pub fn go_next_with_item<'a, Msg: Clone + 'static>(
|
|||
.spacing(cosmic::theme::spacing().space_s)
|
||||
.into(),
|
||||
])
|
||||
.width(Length::Fill)
|
||||
.apply(widget::container)
|
||||
.class(cosmic::theme::Container::List)
|
||||
.width(Length::Fill)
|
||||
.apply(button::custom)
|
||||
.padding(0)
|
||||
.width(Length::Fill)
|
||||
.class(theme::Button::Transparent)
|
||||
.on_press_maybe(msg_opt.into())
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -34,16 +34,18 @@ pub struct State {
|
|||
pub fn subscription() -> Subscription<Response> {
|
||||
struct MyId;
|
||||
|
||||
Subscription::run_with_id(
|
||||
std::any::TypeId::of::<MyId>(),
|
||||
stream::channel(1, move |mut output| async move {
|
||||
Subscription::run_with(std::any::TypeId::of::<MyId>(), |_| {
|
||||
stream::channel(
|
||||
1,
|
||||
move |mut output: futures::channel::mpsc::Sender<Response>| async move {
|
||||
if let Some(state) = State::new(&mut output).await {
|
||||
state.listen(&mut output).await;
|
||||
}
|
||||
|
||||
futures::future::pending::<()>().await;
|
||||
}),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ use iced_futures::Subscription;
|
|||
use std::collections::HashMap;
|
||||
|
||||
pub fn subscription() -> iced_futures::Subscription<bool> {
|
||||
Subscription::run_with_id(
|
||||
"airplane-mode",
|
||||
struct MyId;
|
||||
|
||||
Subscription::run_with(std::any::TypeId::of::<MyId>(), |_| {
|
||||
async {
|
||||
match rfkill::rfkill_updates() {
|
||||
Ok(updates) => updates.filter_map(|state| async {
|
||||
|
|
@ -25,8 +26,8 @@ pub fn subscription() -> iced_futures::Subscription<bool> {
|
|||
}
|
||||
}
|
||||
}
|
||||
.flatten_stream(),
|
||||
)
|
||||
.flatten_stream()
|
||||
})
|
||||
}
|
||||
|
||||
// Test that:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright 2024 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::Wrapper;
|
||||
|
||||
use super::Event;
|
||||
use cosmic_dbus_networkmanager::nm::NetworkManager;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
|
|
@ -18,13 +20,13 @@ pub fn active_conns_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>
|
|||
id: I,
|
||||
conn: Connection,
|
||||
) -> iced_futures::Subscription<Event> {
|
||||
Subscription::run_with_id(
|
||||
id,
|
||||
Subscription::run_with(Wrapper { id, conn: conn }, |Wrapper { id: _id, conn }| {
|
||||
let conn = conn.clone();
|
||||
stream::channel(50, move |output| async move {
|
||||
watch(conn, output).await;
|
||||
futures::future::pending().await
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn watch(conn: zbus::Connection, mut output: futures::channel::mpsc::Sender<Event>) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ pub use cosmic_dbus_networkmanager::interface::enums::{
|
|||
ActiveConnectionState, DeviceState, DeviceType,
|
||||
};
|
||||
|
||||
use core::hash;
|
||||
use cosmic_dbus_networkmanager::nm::NetworkManager;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use iced_futures::{self, Subscription, stream};
|
||||
|
|
@ -166,12 +167,35 @@ pub fn subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
|||
has_popup: bool,
|
||||
conn: Connection,
|
||||
) -> iced_futures::Subscription<Event> {
|
||||
Subscription::run_with_id(
|
||||
(id, has_popup),
|
||||
struct Wrapper<I> {
|
||||
id: I,
|
||||
has_popup: bool,
|
||||
conn: Connection,
|
||||
}
|
||||
impl<I: Hash> Hash for Wrapper<I> {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
self.has_popup.hash(state);
|
||||
}
|
||||
}
|
||||
Subscription::run_with(
|
||||
Wrapper {
|
||||
id,
|
||||
has_popup,
|
||||
conn,
|
||||
},
|
||||
|Wrapper {
|
||||
id,
|
||||
has_popup,
|
||||
conn,
|
||||
}| {
|
||||
let conn = conn.clone();
|
||||
let has_popup = *has_popup;
|
||||
stream::channel(50, move |output| async move {
|
||||
watch(conn, has_popup, output).await;
|
||||
futures::future::pending().await
|
||||
}),
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub mod hw_address;
|
|||
pub mod nm_secret_agent;
|
||||
pub mod wireless_enabled;
|
||||
|
||||
use std::{collections::HashMap, fmt::Debug, sync::Arc, time::Duration};
|
||||
use std::{collections::HashMap, fmt::Debug, hash::Hash, sync::Arc, time::Duration};
|
||||
|
||||
use available_wifi::NetworkType;
|
||||
pub use cosmic_dbus_networkmanager as dbus;
|
||||
|
|
@ -41,6 +41,17 @@ use self::{
|
|||
pub type SSID = Arc<str>;
|
||||
pub type UUID = Arc<str>;
|
||||
|
||||
pub(crate) struct Wrapper<I> {
|
||||
id: I,
|
||||
conn: zbus::Connection,
|
||||
}
|
||||
|
||||
impl<I: Hash> Hash for Wrapper<I> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("failed to list bluetooth devices with rfkill")]
|
||||
|
|
@ -121,13 +132,25 @@ pub fn subscription<I: Copy + Debug + std::hash::Hash + 'static>(
|
|||
id: I,
|
||||
conn: zbus::Connection,
|
||||
) -> iced_futures::Subscription<Event> {
|
||||
Subscription::run_with_id(
|
||||
id,
|
||||
stream::channel(50, |output| async move {
|
||||
struct Wrapper<I> {
|
||||
id: I,
|
||||
conn: zbus::Connection,
|
||||
}
|
||||
impl<I: Hash> Hash for Wrapper<I> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
Subscription::run_with(Wrapper { id, conn }, |Wrapper { id, conn }| {
|
||||
let conn = conn.clone();
|
||||
stream::channel(
|
||||
50,
|
||||
|output: futures::channel::mpsc::Sender<Event>| async move {
|
||||
watch(conn, output).await;
|
||||
futures::future::pending().await
|
||||
}),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn watch(conn: zbus::Connection, mut output: futures::channel::mpsc::Sender<Event>) {
|
||||
|
|
|
|||
|
|
@ -135,11 +135,15 @@ pub fn secret_agent_stream(
|
|||
identifier: impl AsRef<str>,
|
||||
rx: tokio::sync::mpsc::Receiver<Request>,
|
||||
) -> impl Stream<Item = Event> {
|
||||
iced_futures::stream::channel(4, move |mut msg_tx| async move {
|
||||
if let Err(e) = secret_agent_stream_impl(identifier.as_ref(), msg_tx.clone(), rx).await {
|
||||
iced_futures::stream::channel(
|
||||
4,
|
||||
move |mut msg_tx: futures::channel::mpsc::Sender<Event>| async move {
|
||||
if let Err(e) = secret_agent_stream_impl(identifier.as_ref(), msg_tx.clone(), rx).await
|
||||
{
|
||||
let _ = msg_tx.send(Event::Failed(e)).await;
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
async fn secret_agent_stream_impl(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright 2024 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::Wrapper;
|
||||
|
||||
use super::Event;
|
||||
use cosmic_dbus_networkmanager::nm::NetworkManager;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
|
|
@ -18,13 +20,13 @@ pub fn wireless_enabled_subscription<I: 'static + Hash + Copy + Send + Sync + De
|
|||
id: I,
|
||||
conn: Connection,
|
||||
) -> iced_futures::Subscription<Event> {
|
||||
Subscription::run_with_id(
|
||||
id,
|
||||
Subscription::run_with(Wrapper { id, conn: conn }, |Wrapper { id: _id, conn }| {
|
||||
let conn = conn.clone();
|
||||
stream::channel(50, move |output| async move {
|
||||
watch(conn, output).await;
|
||||
futures::future::pending().await
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn watch(conn: zbus::Connection, mut output: futures::channel::mpsc::Sender<Event>) {
|
||||
|
|
|
|||
12
subscriptions/pulse/Cargo.toml
Normal file
12
subscriptions/pulse/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "cosmic-settings-pulse-subscription"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libpulse-binding = { version = "2.30.1" }
|
||||
rustix = { version = "1.1.3", features = ["pipe"] }
|
||||
iced_futures = { git = "https://github.com/pop-os/libcosmic" }
|
||||
futures = "0.3.32"
|
||||
log = "0.4.27"
|
||||
751
subscriptions/pulse/src/lib.rs
Normal file
751
subscriptions/pulse/src/lib.rs
Normal file
|
|
@ -0,0 +1,751 @@
|
|||
// Copyright 2024 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Make sure not to fail if pulse not found, and reconnect?
|
||||
// change to device shouldn't send osd?
|
||||
|
||||
use futures::{SinkExt, executor::block_on};
|
||||
use iced_futures::{Subscription, stream};
|
||||
use libpulse_binding::{
|
||||
callbacks::ListResult,
|
||||
channelmap::Map,
|
||||
context::{
|
||||
Context, FlagSet, State,
|
||||
introspect::{CardInfo, CardProfileInfo, Introspector, ServerInfo, SinkInfo, SourceInfo},
|
||||
subscribe::{Facility, InterestMaskSet, Operation},
|
||||
},
|
||||
def::{PortAvailable, Retval},
|
||||
mainloop::{
|
||||
api::MainloopApi,
|
||||
events::io::IoEventInternal,
|
||||
standard::{IterateResult, Mainloop},
|
||||
},
|
||||
volume::{ChannelVolumes, Volume},
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
convert::Infallible,
|
||||
io::{Read, Write},
|
||||
os::{
|
||||
fd::{FromRawFd, IntoRawFd, RawFd},
|
||||
raw::c_void,
|
||||
},
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
sync::mpsc,
|
||||
};
|
||||
|
||||
pub fn subscription() -> iced_futures::Subscription<Event> {
|
||||
Subscription::run_with("pulse", |_| {
|
||||
stream::channel(20, |sender| async {
|
||||
std::thread::spawn(move || thread(sender));
|
||||
futures::future::pending().await
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn thread(sender: futures::channel::mpsc::Sender<Event>) {
|
||||
let Some(mut main_loop) = Mainloop::new() else {
|
||||
log::error!("Failed to create PA main loop");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(mut context) = Context::new(&main_loop, "cosmic-osd") else {
|
||||
log::error!("Failed to create PA context");
|
||||
return;
|
||||
};
|
||||
|
||||
let data = Rc::new(Data {
|
||||
main_loop: RefCell::new(Mainloop {
|
||||
_inner: Rc::clone(&main_loop._inner),
|
||||
}),
|
||||
introspector: context.introspect(),
|
||||
sink_volume: Cell::new(None),
|
||||
sink_mute: Cell::new(None),
|
||||
source_volume: Cell::new(None),
|
||||
source_mute: Cell::new(None),
|
||||
default_sink_name: RefCell::new(None),
|
||||
default_source_name: RefCell::new(None),
|
||||
sender: RefCell::new(sender.clone()),
|
||||
});
|
||||
|
||||
let data_clone = data.clone();
|
||||
context.set_subscribe_callback(Some(Box::new(move |facility, operation, index| {
|
||||
data_clone.subscribe_cb(facility.unwrap(), operation, index);
|
||||
})));
|
||||
|
||||
let _ = context.connect(None, FlagSet::NOFAIL, None);
|
||||
|
||||
loop {
|
||||
if sender.is_closed() {
|
||||
return;
|
||||
}
|
||||
|
||||
match main_loop.iterate(false) {
|
||||
IterateResult::Success(_) => {}
|
||||
IterateResult::Err(_e) => {
|
||||
return;
|
||||
}
|
||||
IterateResult::Quit(_e) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if context.get_state() == State::Ready {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect all available cards on startup
|
||||
data.introspector.get_card_info_list({
|
||||
let data_weak = Rc::downgrade(&data);
|
||||
move |card_info_res| {
|
||||
if let Some(data) = data_weak.upgrade() {
|
||||
data.card_info_cb(card_info_res)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
data.get_server_info();
|
||||
context.subscribe(
|
||||
InterestMaskSet::SERVER | InterestMaskSet::SINK | InterestMaskSet::SOURCE,
|
||||
|_| {},
|
||||
);
|
||||
|
||||
if let Err((err, retval)) = main_loop.run() {
|
||||
log::error!("PA main loop returned {:?}, error {}", retval, err);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Event {
|
||||
Balance(Option<f32>),
|
||||
CardInfo(Card),
|
||||
DefaultSink(String),
|
||||
DefaultSource(String),
|
||||
SinkVolume(u32),
|
||||
Channels(PulseChannels),
|
||||
SinkMute(bool),
|
||||
SourceVolume(u32),
|
||||
SourceMute(bool),
|
||||
}
|
||||
|
||||
enum Request {
|
||||
Volume(u32, f32),
|
||||
Balance(u32, f32),
|
||||
Quit,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PulseChannels {
|
||||
tx: mpsc::Sender<Request>,
|
||||
pipe_tx: std::fs::File,
|
||||
index: u32,
|
||||
}
|
||||
|
||||
impl Clone for PulseChannels {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
tx: self.tx.clone(),
|
||||
pipe_tx: self
|
||||
.pipe_tx
|
||||
.try_clone()
|
||||
.expect("failed to clone PulseChannels pipe writer"),
|
||||
index: self.index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data used by the [`handle_balance_io_new`] callback.
|
||||
struct HandleBalanceData(
|
||||
Context,
|
||||
ChannelVolumes,
|
||||
Map,
|
||||
std::sync::mpsc::Receiver<Request>,
|
||||
);
|
||||
|
||||
/// Callback for creating an IO event source [`MainloopApi::io_new`].
|
||||
extern "C" fn handle_balance_io_new(
|
||||
api: *const MainloopApi,
|
||||
event: *mut IoEventInternal,
|
||||
reader_fd: RawFd,
|
||||
_flags: libpulse_binding::mainloop::events::io::FlagSet,
|
||||
data: *mut c_void,
|
||||
) {
|
||||
// Take ownership of the data and borrow its contents.
|
||||
let mut data = unsafe { Box::<HandleBalanceData>::from_raw(data as _) };
|
||||
let HandleBalanceData(ctx, volumes, map, rx) = data.as_mut();
|
||||
|
||||
// Return early if the context is not ready, and give the data back.
|
||||
if ctx.get_state() != State::Ready {
|
||||
let _ = Box::leak(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the first byte cannot be read, destroy this event source with its reader and data.
|
||||
let mut buf = [0u8; 1];
|
||||
let mut reader = unsafe { std::fs::File::from_raw_fd(reader_fd) };
|
||||
if reader.read_exact(&mut buf).is_err() {
|
||||
(unsafe { &*api })
|
||||
.io_free
|
||||
.as_ref()
|
||||
.expect("io_free function is missing")(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Give ownership of the reader back.
|
||||
_ = reader.into_raw_fd();
|
||||
|
||||
while let Ok(req) = rx.try_recv() {
|
||||
match req {
|
||||
Request::Volume(index, volume_scale) => {
|
||||
let mut intro = ctx.introspect();
|
||||
|
||||
let new_scale = Volume((volume_scale * Volume::NORMAL.0 as f32).round() as u32);
|
||||
|
||||
if let Some(v) = volumes.scale(new_scale) {
|
||||
_ = intro.set_sink_volume_by_index(
|
||||
index,
|
||||
v,
|
||||
Some(Box::new(|success| {
|
||||
if !success {
|
||||
log::error!("Failed to set sink balance");
|
||||
}
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
Request::Balance(index, new_balance) => {
|
||||
if map.can_balance() {
|
||||
if let Some(v) = volumes.set_balance(&map, new_balance) {
|
||||
let mut intro = ctx.introspect();
|
||||
|
||||
_ = intro.set_sink_volume_by_index(
|
||||
index,
|
||||
v,
|
||||
Some(Box::new(|success| {
|
||||
if !success {
|
||||
log::error!("Failed to set sink balance");
|
||||
}
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Request::Quit => unsafe { &*api }
|
||||
.quit
|
||||
.as_ref()
|
||||
.expect("quit function missing")(api, 0),
|
||||
}
|
||||
}
|
||||
|
||||
let _ = Box::leak(data);
|
||||
}
|
||||
|
||||
impl PulseChannels {
|
||||
fn new(
|
||||
volumes: ChannelVolumes,
|
||||
map: Map,
|
||||
api: &MainloopApi,
|
||||
index: u32,
|
||||
ctx: Context,
|
||||
) -> PulseChannels {
|
||||
let (reader, writer) = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::CLOEXEC)
|
||||
.expect("failed to crate pipe");
|
||||
|
||||
let (tx, rx) = mpsc::channel::<Request>();
|
||||
|
||||
// Create IO event source object for handling speaker balance.
|
||||
let event_source = api.io_new.as_ref().unwrap()(
|
||||
api as *const _,
|
||||
reader.into_raw_fd(),
|
||||
libpulse_binding::mainloop::events::io::FlagSet::INPUT,
|
||||
Some(handle_balance_io_new),
|
||||
Box::into_raw(Box::new(HandleBalanceData(ctx, volumes, map, rx))) as *mut c_void,
|
||||
);
|
||||
|
||||
if let Some(enable) = api.io_enable.as_ref() {
|
||||
enable(
|
||||
event_source,
|
||||
libpulse_binding::mainloop::events::io::FlagSet::INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
Self {
|
||||
tx,
|
||||
pipe_tx: std::fs::File::from(writer),
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the active index.
|
||||
#[inline]
|
||||
pub fn set_index(&mut self, index: u32) {
|
||||
self.index = index;
|
||||
}
|
||||
|
||||
/// Set the speaker balance of the active sink.
|
||||
pub fn set_balance(&mut self, balance: f32) {
|
||||
if let Err(err) = self.tx.send(Request::Balance(self.index, balance)) {
|
||||
log::error!("Failed to send new balance to channel");
|
||||
} else {
|
||||
self.pipe_tx
|
||||
.write_all(&[1])
|
||||
.expect("PulseChannels pipe write failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the volume of the active sink.
|
||||
pub fn set_volume(&mut self, volume: f32) {
|
||||
if let Err(err) = self.tx.send(Request::Volume(self.index, volume)) {
|
||||
log::error!("Failed to send new volume to channel");
|
||||
} else {
|
||||
self.pipe_tx
|
||||
.write_all(&[1])
|
||||
.expect("PulseChannels pipe write failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// Request the pulse thread to quit.
|
||||
pub fn quit(mut self) {
|
||||
_ = self.tx.send(Request::Quit);
|
||||
_ = self.pipe_tx.write_all(&[1]);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct Card {
|
||||
pub object_id: u32,
|
||||
pub name: String,
|
||||
pub product_name: String,
|
||||
pub variant: DeviceVariant,
|
||||
pub ports: Vec<CardPort>,
|
||||
pub profiles: Vec<CardProfile>,
|
||||
pub active_profile: Option<CardProfile>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct CardPort {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub direction: Direction,
|
||||
pub port_type: PortType,
|
||||
pub profile_port: u32,
|
||||
pub priority: u32,
|
||||
pub profiles: Vec<CardProfile>,
|
||||
pub availability: Availability,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum Availability {
|
||||
Unknown,
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
impl From<PortAvailable> for Availability {
|
||||
fn from(pa: PortAvailable) -> Self {
|
||||
match pa {
|
||||
PortAvailable::Unknown => Availability::Unknown,
|
||||
PortAvailable::No => Availability::No,
|
||||
PortAvailable::Yes => Availability::Yes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct CardProfile {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub available: bool,
|
||||
pub n_sinks: u32,
|
||||
pub n_sources: u32,
|
||||
pub priority: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum DeviceVariant {
|
||||
Alsa { alsa_card: u32 },
|
||||
Bluez5 { address: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum Direction {
|
||||
Input,
|
||||
Output,
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum PortType {
|
||||
Mic,
|
||||
Speaker,
|
||||
Headphones,
|
||||
Headset,
|
||||
Digital,
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl FromStr for PortType {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"mic" => Ok(PortType::Mic),
|
||||
"speaker" => Ok(PortType::Speaker),
|
||||
"headphones" => Ok(PortType::Headphones),
|
||||
"headset" => Ok(PortType::Headset),
|
||||
"digital" => Ok(PortType::Digital),
|
||||
_ => Ok(PortType::Unknown),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Data {
|
||||
main_loop: RefCell<Mainloop>,
|
||||
default_sink_name: RefCell<Option<String>>,
|
||||
default_source_name: RefCell<Option<String>>,
|
||||
sink_volume: Cell<Option<u32>>,
|
||||
sink_mute: Cell<Option<bool>>,
|
||||
source_volume: Cell<Option<u32>>,
|
||||
source_mute: Cell<Option<bool>>,
|
||||
introspector: Introspector,
|
||||
sender: RefCell<futures::channel::mpsc::Sender<Event>>,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
fn card_info_cb(self: &Rc<Self>, card_info: ListResult<&CardInfo>) {
|
||||
if let ListResult::Item(card_info) = card_info {
|
||||
let Some(object_id) = card_info
|
||||
.proplist
|
||||
.get_str("object.id")
|
||||
.and_then(|v| v.parse::<u32>().ok())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let variant = if let Some(alsa_card) = card_info
|
||||
.proplist
|
||||
.get_str("alsa.card")
|
||||
.and_then(|v| v.parse::<u32>().ok())
|
||||
{
|
||||
DeviceVariant::Alsa { alsa_card }
|
||||
} else if let Some(address) = card_info.proplist.get_str("api.bluez5.address") {
|
||||
DeviceVariant::Bluez5 { address }
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let card = Card {
|
||||
name: card_info
|
||||
.name
|
||||
.as_ref()
|
||||
.map(Cow::to_string)
|
||||
.unwrap_or_default(),
|
||||
product_name: card_info
|
||||
.proplist
|
||||
.get_str("device.product.name")
|
||||
.unwrap_or_default(),
|
||||
object_id,
|
||||
variant,
|
||||
ports: card_info
|
||||
.ports
|
||||
.iter()
|
||||
.map(|port| CardPort {
|
||||
name: port.name.as_ref().map(Cow::to_string).unwrap_or_default(),
|
||||
description: port
|
||||
.description
|
||||
.as_ref()
|
||||
.map(Cow::to_string)
|
||||
.unwrap_or_default(),
|
||||
direction: match port.direction.bits() {
|
||||
x if x == libpulse_binding::direction::FlagSet::INPUT.bits() => {
|
||||
Direction::Input
|
||||
}
|
||||
x if x == libpulse_binding::direction::FlagSet::OUTPUT.bits() => {
|
||||
Direction::Output
|
||||
}
|
||||
_ => Direction::Both,
|
||||
},
|
||||
port_type: port
|
||||
.proplist
|
||||
.get_str("port.type")
|
||||
.as_deref()
|
||||
.map(|s| PortType::from_str(s).unwrap())
|
||||
.unwrap_or_default(),
|
||||
profile_port: port
|
||||
.proplist
|
||||
.get_str("card.profile.port")
|
||||
.and_then(|v| v.parse::<u32>().ok())
|
||||
.unwrap_or(0),
|
||||
priority: port.priority,
|
||||
profiles: collect_profiles(&port.profiles),
|
||||
availability: port.available.into(),
|
||||
})
|
||||
.collect(),
|
||||
profiles: collect_profiles(&card_info.profiles),
|
||||
active_profile: card_info.active_profile.as_deref().map(CardProfile::from),
|
||||
};
|
||||
|
||||
if block_on(self.sender.borrow_mut().send(Event::CardInfo(card))).is_err() {
|
||||
self.main_loop.borrow_mut().quit(Retval(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn server_info_cb(self: &Rc<Self>, server_info: &ServerInfo) {
|
||||
let new_default_sink_name = server_info
|
||||
.default_sink_name
|
||||
.as_ref()
|
||||
.map(|x| x.clone().into_owned());
|
||||
let mut default_sink_name = self.default_sink_name.borrow_mut();
|
||||
if new_default_sink_name != *default_sink_name {
|
||||
if let Some(name) = &new_default_sink_name {
|
||||
_ = block_on(
|
||||
self.sender
|
||||
.borrow_mut()
|
||||
.send(Event::DefaultSink(name.clone())),
|
||||
);
|
||||
self.get_sink_info_by_name(name);
|
||||
}
|
||||
*default_sink_name = new_default_sink_name;
|
||||
}
|
||||
|
||||
let new_default_source_name = server_info
|
||||
.default_source_name
|
||||
.as_ref()
|
||||
.map(|x| x.clone().into_owned());
|
||||
let mut default_source_name = self.default_source_name.borrow_mut();
|
||||
if new_default_source_name != *default_source_name {
|
||||
if let Some(name) = &new_default_source_name {
|
||||
_ = block_on(
|
||||
self.sender
|
||||
.borrow_mut()
|
||||
.send(Event::DefaultSource(name.clone())),
|
||||
);
|
||||
self.get_source_info_by_name(name);
|
||||
}
|
||||
*default_source_name = new_default_source_name;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_server_info(self: &Rc<Self>) {
|
||||
let data = self.clone();
|
||||
self.introspector
|
||||
.get_server_info(move |server_info| data.server_info_cb(server_info));
|
||||
}
|
||||
|
||||
fn sink_info_cb(&self, sink_info_res: ListResult<&SinkInfo>) {
|
||||
if let ListResult::Item(sink_info) = sink_info_res {
|
||||
if sink_info.name.as_deref() != self.default_sink_name.borrow().as_deref() {
|
||||
return;
|
||||
}
|
||||
let balance = (sink_info.channel_map.can_balance()
|
||||
&& sink_info.base_volume.is_normal())
|
||||
.then(|| sink_info.volume.get_balance(&sink_info.channel_map));
|
||||
|
||||
let volume = sink_info.volume.max().0 / (Volume::NORMAL.0 / 100);
|
||||
if self.sink_mute.get() != Some(sink_info.mute) {
|
||||
self.sink_mute.set(Some(sink_info.mute));
|
||||
if block_on(
|
||||
self.sender
|
||||
.borrow_mut()
|
||||
.send(Event::SinkMute(sink_info.mute)),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
self.main_loop.borrow_mut().quit(Retval(0));
|
||||
}
|
||||
}
|
||||
if self.sink_volume.get() != Some(volume) {
|
||||
self.sink_volume.set(Some(volume));
|
||||
if block_on(self.sender.borrow_mut().send(Event::SinkVolume(volume))).is_err() {
|
||||
self.main_loop.borrow_mut().quit(Retval(0));
|
||||
}
|
||||
}
|
||||
if block_on(self.sender.borrow_mut().send(Event::Balance(balance))).is_err() {
|
||||
self.main_loop.borrow_mut().quit(Retval(0));
|
||||
}
|
||||
let mut main_loop = self.main_loop.borrow_mut();
|
||||
let api = main_loop.get_api();
|
||||
if let Some(mut ctx) = Context::new(&*main_loop, "balance") {
|
||||
let _ = ctx.connect(None, FlagSet::NOFAIL, None);
|
||||
|
||||
let channels = PulseChannels::new(
|
||||
sink_info.volume,
|
||||
sink_info.channel_map,
|
||||
api,
|
||||
sink_info.index,
|
||||
ctx,
|
||||
);
|
||||
|
||||
if block_on(self.sender.borrow_mut().send(Event::Channels(channels))).is_err() {
|
||||
main_loop.quit(Retval(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn source_info_cb(&self, source_info_res: ListResult<&SourceInfo>) {
|
||||
if let ListResult::Item(source_info) = source_info_res {
|
||||
if source_info.name.as_deref() != self.default_source_name.borrow().as_deref() {
|
||||
return;
|
||||
}
|
||||
let volume = source_info.volume.max().0 / (Volume::NORMAL.0 / 100);
|
||||
if self.source_mute.get() != Some(source_info.mute) {
|
||||
self.source_mute.set(Some(source_info.mute));
|
||||
if block_on(
|
||||
self.sender
|
||||
.borrow_mut()
|
||||
.send(Event::SourceMute(source_info.mute)),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
self.main_loop.borrow_mut().quit(Retval(0));
|
||||
}
|
||||
}
|
||||
if self.source_volume.get() != Some(volume) {
|
||||
self.source_volume.set(Some(volume));
|
||||
if block_on(self.sender.borrow_mut().send(Event::SourceVolume(volume))).is_err() {
|
||||
self.main_loop.borrow_mut().quit(Retval(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_card_info_by_index(self: &Rc<Self>, index: u32) {
|
||||
let data = self.clone();
|
||||
self.introspector
|
||||
.get_card_info_by_index(index, move |card_info_res| {
|
||||
data.card_info_cb(card_info_res);
|
||||
});
|
||||
}
|
||||
|
||||
fn get_sink_info_by_index(self: &Rc<Self>, index: u32) {
|
||||
let data = self.clone();
|
||||
self.introspector.get_sink_info_by_index(
|
||||
index,
|
||||
move |sink_info_res: ListResult<&SinkInfo<'_>>| {
|
||||
if let ListResult::Item(ref info) = sink_info_res {
|
||||
if let Some(card_index) = info.card {
|
||||
let data_clone = data.clone();
|
||||
data.introspector.get_card_info_by_index(
|
||||
card_index,
|
||||
move |card_info_res| {
|
||||
data_clone.card_info_cb(card_info_res);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
data.sink_info_cb(sink_info_res);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn get_sink_info_by_name(self: &Rc<Self>, name: &str) {
|
||||
let data = self.clone();
|
||||
self.introspector
|
||||
.get_sink_info_by_name(name, move |sink_info_res| {
|
||||
if let ListResult::Item(ref info) = sink_info_res {
|
||||
if let Some(card_index) = info.card {
|
||||
let data_clone = data.clone();
|
||||
data.introspector.get_card_info_by_index(
|
||||
card_index,
|
||||
move |card_info_res| {
|
||||
data_clone.card_info_cb(card_info_res);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
data.sink_info_cb(sink_info_res);
|
||||
});
|
||||
}
|
||||
|
||||
fn get_source_info_by_index(self: &Rc<Self>, index: u32) {
|
||||
let data = self.clone();
|
||||
self.introspector
|
||||
.get_source_info_by_index(index, move |source_info_res| {
|
||||
if let ListResult::Item(ref info) = source_info_res {
|
||||
if let Some(card_index) = info.card {
|
||||
let data_clone = data.clone();
|
||||
data.introspector.get_card_info_by_index(
|
||||
card_index,
|
||||
move |card_info_res| {
|
||||
data_clone.card_info_cb(card_info_res);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
data.source_info_cb(source_info_res);
|
||||
});
|
||||
}
|
||||
|
||||
fn get_source_info_by_name(self: &Rc<Self>, name: &str) {
|
||||
let data = self.clone();
|
||||
self.introspector
|
||||
.get_source_info_by_name(name, move |source_info_res| {
|
||||
if let ListResult::Item(ref info) = source_info_res {
|
||||
if let Some(card_index) = info.card {
|
||||
let data_clone = data.clone();
|
||||
data.introspector.get_card_info_by_index(
|
||||
card_index,
|
||||
move |card_info_res| {
|
||||
data_clone.card_info_cb(card_info_res);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
data.source_info_cb(source_info_res);
|
||||
});
|
||||
}
|
||||
|
||||
fn subscribe_cb(
|
||||
self: &Rc<Self>,
|
||||
facility: Facility,
|
||||
_operation: Option<Operation>,
|
||||
index: u32,
|
||||
) {
|
||||
match facility {
|
||||
Facility::Server => {
|
||||
self.get_server_info();
|
||||
}
|
||||
Facility::Sink => {
|
||||
self.get_sink_info_by_index(index);
|
||||
}
|
||||
Facility::Source => {
|
||||
self.get_source_info_by_index(index);
|
||||
}
|
||||
Facility::Card => {
|
||||
self.get_card_info_by_index(index);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_profiles(profiles: &[CardProfileInfo]) -> Vec<CardProfile> {
|
||||
profiles.iter().map(CardProfile::from).collect()
|
||||
}
|
||||
|
||||
impl From<&CardProfileInfo<'_>> for CardProfile {
|
||||
fn from(profile: &CardProfileInfo) -> Self {
|
||||
CardProfile {
|
||||
name: profile
|
||||
.name
|
||||
.as_ref()
|
||||
.map(Cow::to_string)
|
||||
.unwrap_or_default(),
|
||||
description: profile
|
||||
.description
|
||||
.as_ref()
|
||||
.map(Cow::to_string)
|
||||
.unwrap_or_default(),
|
||||
available: profile.available,
|
||||
n_sinks: profile.n_sinks,
|
||||
n_sources: profile.n_sources,
|
||||
priority: profile.priority,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,14 +3,35 @@
|
|||
|
||||
// XXX error handling?
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use iced_futures::Subscription;
|
||||
use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
pub(crate) struct Wrapper {
|
||||
id: &'static str,
|
||||
conn: zbus::Connection,
|
||||
}
|
||||
|
||||
impl Hash for Wrapper {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscription(connection: zbus::Connection) -> iced_futures::Subscription<Event> {
|
||||
Subscription::run_with_id(
|
||||
"settings-daemon",
|
||||
Subscription::run_with(
|
||||
Wrapper {
|
||||
id: "settings-daemon",
|
||||
conn: connection,
|
||||
},
|
||||
|Wrapper {
|
||||
id: _id,
|
||||
conn: connection,
|
||||
}| {
|
||||
let connection = connection.clone();
|
||||
async move {
|
||||
let settings_daemon = match CosmicSettingsDaemonProxy::new(&connection).await {
|
||||
Ok(value) => value,
|
||||
|
|
@ -35,7 +56,8 @@ pub fn subscription(connection: zbus::Connection) -> iced_futures::Subscription<
|
|||
async move {
|
||||
match request {
|
||||
Request::SetDisplayBrightness(brightness) => {
|
||||
let _ = settings_daemon.set_display_brightness(brightness).await;
|
||||
let _ =
|
||||
settings_daemon.set_display_brightness(brightness).await;
|
||||
}
|
||||
}
|
||||
None::<Event>
|
||||
|
|
@ -49,7 +71,8 @@ pub fn subscription(connection: zbus::Connection) -> iced_futures::Subscription<
|
|||
}))
|
||||
))
|
||||
}
|
||||
.flatten_stream(),
|
||||
.flatten_stream()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ pub type ProfileId = i32;
|
|||
pub type RouteId = u32;
|
||||
|
||||
pub fn watch() -> impl Stream<Item = Message> + MaybeSend + 'static {
|
||||
cosmic::iced_futures::stream::channel(1, |mut emitter| async move {
|
||||
cosmic::iced_futures::stream::channel(
|
||||
1,
|
||||
|mut emitter: futures::channel::mpsc::Sender<Message>| async move {
|
||||
loop {
|
||||
let (cancel_tx, cancel_rx) = futures::channel::oneshot::channel::<()>();
|
||||
let sender = Arc::new((Mutex::new(Vec::new()), tokio::sync::Notify::const_new()));
|
||||
|
|
@ -48,7 +50,8 @@ pub fn watch() -> impl Stream<Item = Message> + MaybeSend + 'static {
|
|||
|
||||
futures::future::select(cancel_rx, forwarder).await;
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ pub mod device {
|
|||
pub fn device_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||
id: I,
|
||||
) -> iced_futures::Subscription<DeviceDbusEvent> {
|
||||
Subscription::run_with_id(
|
||||
id,
|
||||
Subscription::run_with(id, |_| {
|
||||
async move {
|
||||
match events().await {
|
||||
Ok(stream) => stream,
|
||||
|
|
@ -21,8 +20,8 @@ pub mod device {
|
|||
}
|
||||
}
|
||||
}
|
||||
.flatten_stream(),
|
||||
)
|
||||
.flatten_stream()
|
||||
})
|
||||
}
|
||||
|
||||
async fn display_device() -> zbus::Result<(UPowerProxy<'static>, DeviceProxy<'static>)> {
|
||||
|
|
@ -106,8 +105,7 @@ pub mod kbdbacklight {
|
|||
pub fn kbd_backlight_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||
id: I,
|
||||
) -> iced_futures::Subscription<KeyboardBacklightUpdate> {
|
||||
Subscription::run_with_id(
|
||||
id,
|
||||
Subscription::run_with(id, |_| {
|
||||
async move {
|
||||
match events().await {
|
||||
Ok(stream) => stream,
|
||||
|
|
@ -117,8 +115,8 @@ pub mod kbdbacklight {
|
|||
}
|
||||
}
|
||||
}
|
||||
.flatten_stream(),
|
||||
)
|
||||
.flatten_stream()
|
||||
})
|
||||
}
|
||||
|
||||
enum Event {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue