Skip to content

Commit 8ededed

Browse files
feat(background): Autostart method
1 parent f9ea365 commit 8ededed

File tree

3 files changed

+142
-14
lines changed

3 files changed

+142
-14
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ env_logger = "0.11.6"
4040
dirs = "6.0.0"
4141
chrono = "0.4"
4242
url = "2.5"
43+
shlex = "1"
4344
# i18n
4445
i18n-embed = { version = "0.15.3", features = [
4546
"fluent-system",

src/background.rs

Lines changed: 140 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
// SPDX-License-Identifier: GPL-3.0-only
22

3-
use std::sync::{Arc, Condvar, Mutex};
3+
use std::{
4+
borrow::Cow,
5+
io,
6+
path::Path,
7+
sync::{Arc, Condvar, Mutex},
8+
};
49

510
// use ashpd::enumflags2::{bitflags, BitFlag, BitFlags};
611
use cosmic::{iced::window, widget};
712
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1;
813
use futures::{FutureExt, TryFutureExt};
9-
use tokio::sync::{mpsc, watch};
14+
use tokio::{
15+
fs,
16+
io::AsyncWriteExt,
17+
sync::{mpsc, watch},
18+
};
1019
use zbus::{fdo, object_server::SignalContext, zvariant};
1120

1221
use crate::{
@@ -65,13 +74,34 @@ impl Background {
6574
}
6675
}
6776
}
77+
78+
/// Write `desktop_entry` to path `launch_entry`.
79+
///
80+
/// The primary purpose of this function is to ease error handling.
81+
async fn write_autostart(
82+
autostart_entry: &Path,
83+
desktop_entry: &freedesktop_desktop_entry::DesktopEntry<'_>,
84+
) -> io::Result<()> {
85+
let mut file = fs::OpenOptions::new()
86+
.create(true)
87+
.write(true)
88+
.truncate(true)
89+
.mode(0o644)
90+
.open(&autostart_entry)
91+
.map_ok(tokio::io::BufWriter::new)
92+
.await?;
93+
94+
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
96+
file.flush().await
97+
}
6898
}
6999

70100
#[zbus::interface(name = "org.freedesktop.impl.portal.Background")]
71101
impl Background {
72102
/// Status on running apps (active, running, or background)
73-
async fn get_app_state(&self) -> fdo::Result<Vec<AppState>> {
74-
let toplevels: Vec<_> = self
103+
async fn get_app_state(&self) -> fdo::Result<AppStates> {
104+
let apps: Vec<_> = self
75105
.wayland_helper
76106
.toplevels()
77107
.into_iter()
@@ -97,11 +127,11 @@ impl Background {
97127
})
98128
.collect();
99129

100-
log::debug!("GetAppState returning {} toplevels", toplevels.len());
130+
log::debug!("GetAppState returning {} toplevels", apps.len());
101131
#[cfg(debug_assertions)]
102-
log::trace!("App status: {toplevels:#?}");
132+
log::trace!("App status: {apps:#?}");
103133

104-
Ok(toplevels)
134+
Ok(AppStates { apps })
105135
}
106136

107137
/// Notifies the user that an app is running in the background
@@ -134,7 +164,7 @@ impl Background {
134164
}
135165
// Dialog
136166
PermissionDialog::Ask => {
137-
log::debug!("Requesting user permission for {app_id} ({name})",);
167+
log::debug!("Requesting background permission for running app {app_id} ({name})",);
138168

139169
let handle = handle.to_owned();
140170
let id = window::Id::unique();
@@ -160,16 +190,106 @@ impl Background {
160190

161191
/// Enable or disable autostart for an application
162192
///
163-
/// Deprecated but seemingly still in use
193+
/// Deprecated in terms of the portal but seemingly still in use
194+
/// Spec: https://specifications.freedesktop.org/autostart-spec/latest/
164195
pub async fn enable_autostart(
165196
&self,
166-
app_id: String,
197+
appid: String,
167198
enable: bool,
168-
commandline: Vec<String>,
199+
exec: Vec<String>,
169200
flags: u32,
170201
) -> fdo::Result<bool> {
171-
log::warn!("Autostart not implemented");
172-
Ok(enable)
202+
log::info!(
203+
"{} autostart for {appid}",
204+
if enable { "Enabling" } else { "Disabling" }
205+
);
206+
207+
let Some((autostart_dir, launch_entry)) = dirs::config_dir().map(|config| {
208+
let autostart = config.join("autostart");
209+
(
210+
autostart.clone(),
211+
autostart.join(format!("{appid}.desktop")),
212+
)
213+
}) else {
214+
return Err(fdo::Error::FileNotFound("XDG_CONFIG_HOME".into()));
215+
};
216+
217+
if !enable {
218+
log::debug!("Removing autostart entry {}", launch_entry.display());
219+
match fs::remove_file(&launch_entry).await {
220+
Ok(()) => Ok(false),
221+
Err(e) if e.kind() == io::ErrorKind::NotFound => {
222+
log::warn!("Service asked to disable autostart for {appid} but the entry doesn't exist");
223+
Ok(false)
224+
}
225+
Err(e) => {
226+
log::error!(
227+
"Error removing autostart entry for {appid}\n\tPath: {}\n\tError: {e}",
228+
launch_entry.display()
229+
);
230+
Err(fdo::Error::FileNotFound(format!(
231+
"{e}: ({})",
232+
launch_entry.display()
233+
)))
234+
}
235+
}
236+
} else {
237+
match fs::create_dir(&autostart_dir).await {
238+
Ok(()) => log::debug!("Created autostart directory at {}", autostart_dir.display()),
239+
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => (),
240+
Err(e) => {
241+
log::error!(
242+
"Error creating autostart directory: {e} (app: {appid}) (dir: {})",
243+
autostart_dir.display()
244+
);
245+
return Err(fdo::Error::IOError(format!(
246+
"{e}: ({})",
247+
autostart_dir.display()
248+
)));
249+
}
250+
}
251+
252+
let mut autostart_fde = freedesktop_desktop_entry::DesktopEntry {
253+
appid: Cow::Borrowed(&appid),
254+
path: Default::default(),
255+
groups: Default::default(),
256+
ubuntu_gettext_domain: None,
257+
};
258+
autostart_fde.add_desktop_entry("Type", "Application");
259+
autostart_fde.add_desktop_entry("Name", &appid);
260+
261+
log::debug!("{appid} autostart command line: {exec:?}");
262+
let exec = match shlex::try_join(exec.iter().map(|term| term.as_str())) {
263+
Ok(exec) => exec,
264+
Err(e) => {
265+
log::error!("Failed to sanitize command line for {appid}\n\tCommand: {exec:?}\n\tError: {e}");
266+
return Err(fdo::Error::InvalidArgs(format!("{e}: {exec:?}")));
267+
}
268+
};
269+
log::debug!("{appid} sanitized autostart command line: {exec}");
270+
autostart_fde.add_desktop_entry("Exec", &exec);
271+
272+
/// xxx Replace with enumflags later when it's added as a dependency instead of adding
273+
/// it now for one bit (literally)
274+
let dbus_activation = flags & 0x1 == 1;
275+
if dbus_activation {
276+
autostart_fde.add_desktop_entry("DBusActivatable", "true");
277+
}
278+
279+
// GNOME and KDE both set this key
280+
autostart_fde.add_desktop_entry("X-Flatpak", &appid);
281+
282+
Self::write_autostart(&launch_entry, &autostart_fde)
283+
.inspect_err(|e| {
284+
log::error!(
285+
"Failed to write autostart entry for {appid} to `{}`: {e}",
286+
launch_entry.display()
287+
);
288+
})
289+
.map_err(|e| fdo::Error::IOError(format!("{e}: {}", launch_entry.display())))
290+
.map_ok(|()| true)
291+
.await
292+
}
173293
}
174294

175295
/// Emitted when running applications change their state
@@ -188,7 +308,13 @@ pub enum AppStatus {
188308
Active,
189309
}
190310

191-
#[derive(Clone, Debug, serde::Serialize, zvariant::Type)]
311+
#[derive(Clone, Debug, zvariant::SerializeDict, zvariant::Type)]
312+
#[zvariant(signature = "a{sv}")]
313+
struct AppStates {
314+
apps: Vec<AppState>,
315+
}
316+
317+
#[derive(Clone, Debug, zvariant::SerializeDict, zvariant::Type)]
192318
#[zvariant(signature = "{sv}")]
193319
struct AppState {
194320
app_id: String,

0 commit comments

Comments
 (0)