Skip to content

Commit 535c254

Browse files
Evaluate background apps using systemd
(This commit also holds any squashed changes from rebases)
1 parent 8ededed commit 535c254

File tree

5 files changed

+263
-38
lines changed

5 files changed

+263
-38
lines changed

examples/background.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl cosmic::Application for App {
7070
})
7171
.width(Length::Fill)
7272
.into(),
73-
widget::button("Run in background")
73+
widget::button::standard("Run in background")
7474
.on_press(Message::RequestBackground)
7575
.padding(8.0)
7676
.into(),

src/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ impl cosmic::Application for CosmicPortal {
155155
screencast_dialog::cancel(self, handle).map(cosmic::Action::App)
156156
}
157157
subscription::Event::Background(args) => {
158-
background::update_args(self, args).map(cosmic::app::Message::App)
158+
background::update_args(self, args).map(cosmic::Action::App)
159159
}
160160
subscription::Event::Config(config) => self.update(Msg::ConfigSubUpdate(config)),
161161
subscription::Event::Accent(_)

src/background.rs

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
use std::{
44
borrow::Cow,
5+
collections::HashSet,
6+
hash::{Hash, Hasher},
57
io,
68
path::Path,
79
sync::{Arc, Condvar, Mutex},
@@ -21,7 +23,7 @@ use zbus::{fdo, object_server::SignalContext, zvariant};
2123
use crate::{
2224
app::CosmicPortal,
2325
config::{self, background::PermissionDialog},
24-
fl, subscription,
26+
fl, subscription, systemd,
2527
wayland::WaylandHelper,
2628
PortalResponse,
2729
};
@@ -80,7 +82,7 @@ impl Background {
8082
/// The primary purpose of this function is to ease error handling.
8183
async fn write_autostart(
8284
autostart_entry: &Path,
83-
desktop_entry: &freedesktop_desktop_entry::DesktopEntry<'_>,
85+
desktop_entry: &freedesktop_desktop_entry::DesktopEntry,
8486
) -> io::Result<()> {
8587
let mut file = fs::OpenOptions::new()
8688
.create(true)
@@ -92,46 +94,64 @@ impl Background {
9294
.await?;
9395

9496
file.write_all(desktop_entry.to_string().as_bytes()).await?;
95-
/// Shouldn't be needed, but the file never seemed to flush to disk until I did it manually
97+
// Shouldn't be needed, but the file never seemed to flush to disk until I did it manually
9698
file.flush().await
9799
}
98100
}
99101

100102
#[zbus::interface(name = "org.freedesktop.impl.portal.Background")]
101103
impl Background {
102104
/// Status on running apps (active, running, or background)
103-
async fn get_app_state(&self) -> fdo::Result<AppStates> {
104-
let apps: Vec<_> = self
105+
async fn get_app_state(
106+
&self,
107+
#[zbus(connection)] connection: &zbus::Connection,
108+
) -> fdo::Result<AppStates> {
109+
let apps: HashSet<_> = self
105110
.wayland_helper
106111
.toplevels()
107112
.into_iter()
108-
.map(|(_, info)| {
113+
// Evaluate apps with open top levels first as our initial state
114+
.map(|info| {
109115
let status = if info
110116
.state
111117
.contains(&zcosmic_toplevel_handle_v1::State::Activated)
112118
{
119+
// Focused top levels
113120
AppStatus::Active
114-
} else if !info.state.is_empty() {
115-
AppStatus::Running
116121
} else {
117-
// xxx Is this the correct way to determine if a program is running in the
118-
// background? If a toplevel exists but isn't running, activated, et cetera,
119-
// then it logically must be in the background (?)
120-
AppStatus::Background
122+
// Unfocused top levels
123+
AppStatus::Running
121124
};
122125

123126
AppState {
124127
app_id: info.app_id,
125128
status,
126129
}
127130
})
131+
.chain(
132+
systemd::Systemd1Proxy::new(connection)
133+
.await?
134+
.list_units()
135+
.await?
136+
.into_iter()
137+
// Apps launched by COSMIC/Flatpak are considered to be running in the
138+
// background by default as they don't have open top levels
139+
.filter_map(|unit| {
140+
unit.cosmic_flatpak_name().map(|app_id| AppState {
141+
app_id: app_id.to_owned(),
142+
status: AppStatus::Background,
143+
})
144+
}),
145+
)
128146
.collect();
129147

130-
log::debug!("GetAppState returning {} toplevels", apps.len());
148+
log::debug!("GetAppState is returning {} open apps", apps.len());
131149
#[cfg(debug_assertions)]
132-
log::trace!("App status: {apps:#?}");
150+
log::trace!("App statuses: {apps:#?}");
133151

134-
Ok(AppStates { apps })
152+
Ok(AppStates {
153+
apps: apps.into_iter().collect(),
154+
})
135155
}
136156

137157
/// Notifies the user that an app is running in the background
@@ -145,7 +165,10 @@ impl Background {
145165

146166
// Request a copy of the config from the main app instance
147167
// This is also cleaner than storing the config because it's difficult to keep it
148-
// updated without synch primitives and we also avoid &mut self
168+
// updated without synch primitives and we also avoid &mut self.
169+
//
170+
// &mut self with Zbus can lead to deadlocks.
171+
// See: https://dbus2.github.io/zbus/faq.html#1-a-interface-method-that-takes-a-mut-self-argument-is-taking-too-long
149172
let config = self.rx_conf.borrow().background;
150173

151174
match config.default_perm {
@@ -192,7 +215,7 @@ impl Background {
192215
///
193216
/// Deprecated in terms of the portal but seemingly still in use
194217
/// Spec: https://specifications.freedesktop.org/autostart-spec/latest/
195-
pub async fn enable_autostart(
218+
async fn enable_autostart(
196219
&self,
197220
appid: String,
198221
enable: bool,
@@ -250,13 +273,13 @@ impl Background {
250273
}
251274

252275
let mut autostart_fde = freedesktop_desktop_entry::DesktopEntry {
253-
appid: Cow::Borrowed(&appid),
276+
appid: appid.clone(),
254277
path: Default::default(),
255278
groups: Default::default(),
256279
ubuntu_gettext_domain: None,
257280
};
258-
autostart_fde.add_desktop_entry("Type", "Application");
259-
autostart_fde.add_desktop_entry("Name", &appid);
281+
autostart_fde.add_desktop_entry("Type".into(), "Application".into());
282+
autostart_fde.add_desktop_entry("Name".into(), appid.clone());
260283

261284
log::debug!("{appid} autostart command line: {exec:?}");
262285
let exec = match shlex::try_join(exec.iter().map(|term| term.as_str())) {
@@ -267,17 +290,17 @@ impl Background {
267290
}
268291
};
269292
log::debug!("{appid} sanitized autostart command line: {exec}");
270-
autostart_fde.add_desktop_entry("Exec", &exec);
293+
autostart_fde.add_desktop_entry("Exec".into(), exec);
271294

272-
/// xxx Replace with enumflags later when it's added as a dependency instead of adding
273-
/// it now for one bit (literally)
295+
// TODO: Replace with enumflags later when it's added as a dependency instead of adding
296+
// it now for one bit (literally)
274297
let dbus_activation = flags & 0x1 == 1;
275298
if dbus_activation {
276-
autostart_fde.add_desktop_entry("DBusActivatable", "true");
299+
autostart_fde.add_desktop_entry("DBusActivatable".into(), "true".into());
277300
}
278301

279302
// GNOME and KDE both set this key
280-
autostart_fde.add_desktop_entry("X-Flatpak", &appid);
303+
autostart_fde.add_desktop_entry("X-Flatpak".into(), appid.clone());
281304

282305
Self::write_autostart(&launch_entry, &autostart_fde)
283306
.inspect_err(|e| {
@@ -297,9 +320,9 @@ impl Background {
297320
pub async fn running_applications_changed(context: &SignalContext<'_>) -> zbus::Result<()>;
298321
}
299322

300-
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, zvariant::Type)]
323+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, zvariant::Type)]
301324
#[zvariant(signature = "u")]
302-
pub enum AppStatus {
325+
enum AppStatus {
303326
/// No open windows
304327
Background = 0,
305328
/// At least one opened window
@@ -314,17 +337,29 @@ struct AppStates {
314337
apps: Vec<AppState>,
315338
}
316339

317-
#[derive(Clone, Debug, zvariant::SerializeDict, zvariant::Type)]
340+
#[derive(Clone, Debug, Eq, zvariant::SerializeDict, zvariant::Type)]
318341
#[zvariant(signature = "{sv}")]
319342
struct AppState {
320343
app_id: String,
321344
status: AppStatus,
322345
}
323346

347+
impl Hash for AppState {
348+
fn hash<H: Hasher>(&self, state: &mut H) {
349+
state.write(self.app_id.as_bytes());
350+
}
351+
}
352+
353+
impl PartialEq for AppState {
354+
fn eq(&self, other: &Self) -> bool {
355+
self.app_id == other.app_id
356+
}
357+
}
358+
324359
/// Result vardict for [`Background::notify_background`]
325360
#[derive(Clone, Debug, zvariant::SerializeDict, zvariant::Type)]
326361
#[zvariant(signature = "a{sv}")]
327-
pub struct NotifyBackgroundResult {
362+
struct NotifyBackgroundResult {
328363
result: PermissionResponse,
329364
}
330365

@@ -376,7 +411,8 @@ pub(crate) fn view(portal: &CosmicPortal, id: window::Id) -> cosmic::Element<Msg
376411
.unwrap_or("Invalid window id");
377412

378413
// TODO: Add cancel
379-
widget::dialog(fl!("bg-dialog-title"))
414+
widget::dialog()
415+
.title(fl!("bg-dialog-title"))
380416
.body(fl!("bg-dialog-body", appname = name))
381417
.icon(widget::icon::from_name("dialog-warning-symbolic").size(64))
382418
.primary_action(
@@ -401,7 +437,7 @@ pub(crate) fn view(portal: &CosmicPortal, id: window::Id) -> cosmic::Element<Msg
401437
}
402438

403439
/// Update Background dialog args for a specific window
404-
pub fn update_args(portal: &mut CosmicPortal, args: Args) -> cosmic::Command<crate::app::Msg> {
440+
pub fn update_args(portal: &mut CosmicPortal, args: Args) -> cosmic::Task<crate::app::Msg> {
405441
if let Some(old) = portal.background_prompts.insert(args.id, args) {
406442
// xxx Can this even happen?
407443
log::trace!(
@@ -412,10 +448,10 @@ pub fn update_args(portal: &mut CosmicPortal, args: Args) -> cosmic::Command<cra
412448
)
413449
}
414450

415-
cosmic::Command::none()
451+
cosmic::Task::none()
416452
}
417453

418-
pub fn update_msg(portal: &mut CosmicPortal, msg: Msg) -> cosmic::Command<crate::app::Msg> {
454+
pub fn update_msg(portal: &mut CosmicPortal, msg: Msg) -> cosmic::Task<crate::app::Msg> {
419455
match msg {
420456
Msg::Response { id, choice } => {
421457
let Some(Args {
@@ -426,7 +462,7 @@ pub fn update_msg(portal: &mut CosmicPortal, msg: Msg) -> cosmic::Command<crate:
426462
}) = portal.background_prompts.remove(&id)
427463
else {
428464
log::warn!("Window {id:?} doesn't exist for some reason");
429-
return cosmic::Command::none();
465+
return cosmic::Task::none();
430466
};
431467

432468
log::trace!(
@@ -455,7 +491,7 @@ pub fn update_msg(portal: &mut CosmicPortal, msg: Msg) -> cosmic::Command<crate:
455491
}) = portal.background_prompts.remove(&id)
456492
else {
457493
log::warn!("Window {id:?} doesn't exist for some reason");
458-
return cosmic::Command::none();
494+
return cosmic::Task::none();
459495
};
460496

461497
log::trace!(
@@ -472,5 +508,5 @@ pub fn update_msg(portal: &mut CosmicPortal, msg: Msg) -> cosmic::Command<crate:
472508
}
473509
}
474510

475-
cosmic::Command::none()
511+
cosmic::Task::none()
476512
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod screencast_dialog;
1717
mod screencast_thread;
1818
mod screenshot;
1919
mod subscription;
20+
mod systemd;
2021
mod wayland;
2122
mod widget;
2223

0 commit comments

Comments
 (0)