refactor(power): use cosmic-osd for confirmation dialog
This commit is contained in:
parent
0ce07ffac3
commit
5a11eff70e
3 changed files with 33 additions and 190 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1253,6 +1253,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rust-embed 8.5.0",
|
"rust-embed 8.5.0",
|
||||||
"rustix 0.38.43",
|
"rustix 0.38.43",
|
||||||
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ logind-zbus = "4.0.3"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
rust-embed.workspace = true
|
rust-embed.workspace = true
|
||||||
rustix.workspace = true
|
rustix.workspace = true
|
||||||
|
tokio = { version = "1.36.0", features = ["process"] }
|
||||||
tracing-log.workspace = true
|
tracing-log.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,19 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use std::{collections::HashMap, process, time::Duration};
|
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
app,
|
app,
|
||||||
applet::{menu_button, padded_control},
|
applet::{menu_button, padded_control},
|
||||||
cosmic_theme::Spacing,
|
cosmic_theme::Spacing,
|
||||||
iced::{
|
iced::{
|
||||||
self,
|
self,
|
||||||
event::{
|
platform_specific::shell::commands::popup::{destroy_popup, get_popup},
|
||||||
listen_with,
|
widget::{self, column, row},
|
||||||
wayland::{self, LayerEvent},
|
window, Alignment, Length,
|
||||||
PlatformSpecific,
|
|
||||||
},
|
|
||||||
keyboard::{key::Named, Key},
|
|
||||||
platform_specific::{
|
|
||||||
runtime::wayland::layer_surface::SctkLayerSurfaceSettings,
|
|
||||||
shell::commands::{
|
|
||||||
layer_surface::{
|
|
||||||
destroy_layer_surface, get_layer_surface, Anchor, KeyboardInteractivity,
|
|
||||||
},
|
|
||||||
popup::{destroy_popup, get_popup},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
time,
|
|
||||||
widget::{self, column, container, row},
|
|
||||||
window, Alignment, Length, Subscription,
|
|
||||||
},
|
},
|
||||||
iced_runtime::core::layout::Limits,
|
iced_runtime::core::layout::Limits,
|
||||||
iced_widget::mouse_area,
|
|
||||||
theme,
|
theme,
|
||||||
widget::{
|
widget::{button, divider, icon, text, Space},
|
||||||
autosize::autosize, button, divider, horizontal_space, icon, text, vertical_space, Column,
|
|
||||||
Space,
|
|
||||||
},
|
|
||||||
Element, Task,
|
Element, Task,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -43,8 +22,8 @@ use logind_zbus::{
|
||||||
session::{SessionClass, SessionProxy, SessionType},
|
session::{SessionClass, SessionProxy, SessionType},
|
||||||
user::UserProxy,
|
user::UserProxy,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use rustix::process::getuid;
|
use rustix::process::getuid;
|
||||||
|
use tokio::process;
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
pub mod cosmic_session;
|
pub mod cosmic_session;
|
||||||
|
|
@ -59,16 +38,11 @@ pub fn run() -> cosmic::iced::Result {
|
||||||
cosmic::applet::run::<Power>(())
|
cosmic::applet::run::<Power>(())
|
||||||
}
|
}
|
||||||
|
|
||||||
const COUNTDOWN_LENGTH: u8 = 60;
|
|
||||||
static CONFIRM_ID: Lazy<iced::id::Id> = Lazy::new(|| iced::id::Id::new("confirm-id"));
|
|
||||||
static AUTOSIZE_DIALOG_ID: Lazy<iced::id::Id> = Lazy::new(|| iced::id::Id::new("autosize-id"));
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Power {
|
struct Power {
|
||||||
core: cosmic::app::Core,
|
core: cosmic::app::Core,
|
||||||
icon_name: String,
|
icon_name: String,
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
action_to_confirm: Option<(window::Id, PowerAction, u8)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -95,15 +69,11 @@ impl PowerAction {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
Countdown,
|
|
||||||
Action(PowerAction),
|
Action(PowerAction),
|
||||||
TogglePopup,
|
TogglePopup,
|
||||||
Settings,
|
Settings,
|
||||||
Confirm,
|
|
||||||
Cancel,
|
|
||||||
Zbus(Result<(), zbus::Error>),
|
Zbus(Result<(), zbus::Error>),
|
||||||
Closed(window::Id),
|
Closed(window::Id),
|
||||||
LayerFocus,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl cosmic::Application for Power {
|
impl cosmic::Application for Power {
|
||||||
|
|
@ -135,33 +105,6 @@ impl cosmic::Application for Power {
|
||||||
Some(Message::Closed(id))
|
Some(Message::Closed(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
|
||||||
let mut subscriptions = Vec::with_capacity(2);
|
|
||||||
subscriptions.push(listen_with(|e, _status, _| match e {
|
|
||||||
cosmic::iced::Event::PlatformSpecific(PlatformSpecific::Wayland(
|
|
||||||
wayland::Event::Layer(LayerEvent::Focused, ..),
|
|
||||||
)) => Some(Message::LayerFocus),
|
|
||||||
cosmic::iced::Event::PlatformSpecific(PlatformSpecific::Wayland(
|
|
||||||
wayland::Event::Layer(LayerEvent::Unfocused, ..),
|
|
||||||
)) => Some(Message::Cancel),
|
|
||||||
cosmic::iced::Event::Keyboard(iced::keyboard::Event::KeyPressed {
|
|
||||||
key,
|
|
||||||
text: _,
|
|
||||||
modifiers,
|
|
||||||
..
|
|
||||||
}) => match key {
|
|
||||||
Key::Named(Named::Escape) => Some(Message::Cancel),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}));
|
|
||||||
if self.action_to_confirm.is_some() {
|
|
||||||
subscriptions
|
|
||||||
.push(time::every(Duration::from_millis(1000)).map(|_| Message::Countdown));
|
|
||||||
}
|
|
||||||
Subscription::batch(subscriptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> app::Task<Message> {
|
fn update(&mut self, message: Message) -> app::Task<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::TogglePopup => {
|
Message::TogglePopup => {
|
||||||
|
|
@ -191,25 +134,32 @@ impl cosmic::Application for Power {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Action(action) => {
|
Message::Action(action) => {
|
||||||
// Ask for user confirmation of non-destructive actions only
|
match action {
|
||||||
if matches!(action, PowerAction::Lock | PowerAction::Suspend)
|
PowerAction::LogOut => {
|
||||||
|| matches!(action, PowerAction::Restart)
|
if let Err(err) = process::Command::new("cosmic-osd").arg("log-out").spawn()
|
||||||
&& matches!(self.action_to_confirm, Some((_, PowerAction::Shutdown, _)))
|
{
|
||||||
{
|
tracing::error!("Failed to spawn cosmic-osd. {err:?}");
|
||||||
action.perform()
|
return PowerAction::LogOut.perform();
|
||||||
} else {
|
}
|
||||||
let id = window::Id::unique();
|
}
|
||||||
self.action_to_confirm = Some((id, action, COUNTDOWN_LENGTH));
|
PowerAction::Restart => {
|
||||||
get_layer_surface(SctkLayerSurfaceSettings {
|
if let Err(err) = process::Command::new("cosmic-osd").arg("restart").spawn()
|
||||||
id,
|
{
|
||||||
keyboard_interactivity: KeyboardInteractivity::Exclusive,
|
tracing::error!("Failed to spawn cosmic-osd. {err:?}");
|
||||||
anchor: Anchor::empty(),
|
return PowerAction::Restart.perform();
|
||||||
namespace: "dialog".into(),
|
}
|
||||||
size: None,
|
}
|
||||||
size_limits: Limits::NONE.min_width(1.0).min_height(1.0),
|
PowerAction::Shutdown => {
|
||||||
..Default::default()
|
if let Err(err) =
|
||||||
})
|
process::Command::new("cosmic-osd").arg("shutdown").spawn()
|
||||||
}
|
{
|
||||||
|
tracing::error!("Failed to spawn cosmic-osd. {err:?}");
|
||||||
|
return PowerAction::Shutdown.perform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a => return a.perform(),
|
||||||
|
};
|
||||||
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Zbus(result) => {
|
Message::Zbus(result) => {
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
|
|
@ -217,39 +167,12 @@ impl cosmic::Application for Power {
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Confirm => {
|
|
||||||
if let Some((id, a, _)) = self.action_to_confirm.take() {
|
|
||||||
app::Task::batch(vec![destroy_layer_surface(id), a.perform()])
|
|
||||||
} else {
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::Cancel => {
|
|
||||||
if let Some((id, _, _)) = self.action_to_confirm.take() {
|
|
||||||
return destroy_layer_surface(id);
|
|
||||||
}
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Message::Countdown => {
|
|
||||||
if let Some((surface_id, a, countdown)) = self.action_to_confirm.as_mut() {
|
|
||||||
*countdown -= 1;
|
|
||||||
if *countdown == 0 {
|
|
||||||
let id = *surface_id;
|
|
||||||
let a = *a;
|
|
||||||
|
|
||||||
self.action_to_confirm = None;
|
|
||||||
return app::Task::batch(vec![destroy_layer_surface(id), a.perform()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Message::Closed(id) => {
|
Message::Closed(id) => {
|
||||||
if self.popup == Some(id) {
|
if self.popup == Some(id) {
|
||||||
self.popup = None;
|
self.popup = None;
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::LayerFocus => button::focus(CONFIRM_ID.clone()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,80 +247,10 @@ impl cosmic::Application for Power {
|
||||||
.max_height(400.)
|
.max_height(400.)
|
||||||
.max_width(500.)
|
.max_width(500.)
|
||||||
.into()
|
.into()
|
||||||
} else if matches!(self.action_to_confirm, Some((c_id, _, _)) if c_id == id) {
|
|
||||||
let cosmic_theme = self.core.system_theme().cosmic();
|
|
||||||
let (_, power_action, countdown) = self.action_to_confirm.as_ref().unwrap();
|
|
||||||
let action = match power_action {
|
|
||||||
PowerAction::Lock => "lock-screen",
|
|
||||||
PowerAction::LogOut => "log-out",
|
|
||||||
PowerAction::Suspend => "suspend",
|
|
||||||
PowerAction::Restart => "restart",
|
|
||||||
PowerAction::Shutdown => "shutdown",
|
|
||||||
};
|
|
||||||
|
|
||||||
let title = fl!(
|
|
||||||
"confirm-title",
|
|
||||||
HashMap::from_iter(vec![("action", action)])
|
|
||||||
);
|
|
||||||
let countdown = &countdown.to_string();
|
|
||||||
let mut dialog = cosmic::widget::dialog()
|
|
||||||
.title(title)
|
|
||||||
.body(fl!(
|
|
||||||
"confirm-body",
|
|
||||||
HashMap::from_iter(vec![("action", action), ("countdown", countdown)])
|
|
||||||
))
|
|
||||||
.primary_action(
|
|
||||||
button::custom(min_width_and_height(
|
|
||||||
text::body(fl!("confirm", HashMap::from_iter(vec![("action", action)])))
|
|
||||||
.into(),
|
|
||||||
142.0,
|
|
||||||
32.0,
|
|
||||||
))
|
|
||||||
.padding([0, cosmic_theme.space_s()])
|
|
||||||
.id(CONFIRM_ID.clone())
|
|
||||||
.class(theme::Button::Suggested)
|
|
||||||
.on_press(Message::Confirm),
|
|
||||||
)
|
|
||||||
.secondary_action(
|
|
||||||
button::custom(min_width_and_height(
|
|
||||||
text::body(fl!("cancel")).into(),
|
|
||||||
142.0,
|
|
||||||
32.0,
|
|
||||||
))
|
|
||||||
.padding([0, cosmic_theme.space_s()])
|
|
||||||
.class(theme::Button::Standard)
|
|
||||||
.on_press(Message::Cancel),
|
|
||||||
)
|
|
||||||
.icon(text_icon(
|
|
||||||
match power_action {
|
|
||||||
PowerAction::Lock => "system-lock-screen-symbolic",
|
|
||||||
PowerAction::LogOut => "system-log-out-symbolic",
|
|
||||||
PowerAction::Suspend => "system-suspend-symbolic",
|
|
||||||
PowerAction::Restart => "system-restart-symbolic",
|
|
||||||
PowerAction::Shutdown => "system-shutdown-symbolic",
|
|
||||||
},
|
|
||||||
60,
|
|
||||||
));
|
|
||||||
|
|
||||||
if matches!(power_action, PowerAction::Shutdown) {
|
|
||||||
dialog = dialog.tertiary_action(
|
|
||||||
button::text(fl!("restart")).on_press(Message::Action(PowerAction::Restart)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Element::from(
|
|
||||||
autosize(Element::from(container(dialog)), AUTOSIZE_DIALOG_ID.clone()).limits(
|
|
||||||
Limits::NONE
|
|
||||||
.min_width(1.)
|
|
||||||
.min_height(1.)
|
|
||||||
.max_width(900.)
|
|
||||||
.max_height(900.),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
//panic!("no view for window {}", id.0)
|
//panic!("no view for window {}", id.0)
|
||||||
widget::text("").into()
|
widget::text("").into()
|
||||||
}
|
}
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
|
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
|
||||||
|
|
@ -494,15 +347,3 @@ async fn log_out() -> zbus::Result<()> {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn min_width_and_height<'a>(
|
|
||||||
e: Element<'a, Message>,
|
|
||||||
width: impl Into<Length>,
|
|
||||||
height: impl Into<Length>,
|
|
||||||
) -> Column<'a, Message> {
|
|
||||||
column![
|
|
||||||
row![e, vertical_space().height(height)].align_y(Alignment::Center),
|
|
||||||
horizontal_space().width(width)
|
|
||||||
]
|
|
||||||
.align_x(Alignment::Center)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue