1
1
// SPDX-License-Identifier: GPL-3.0-only
2
2
3
- use std:: sync:: { Arc , Condvar , Mutex } ;
3
+ use std:: {
4
+ borrow:: Cow ,
5
+ io,
6
+ path:: Path ,
7
+ sync:: { Arc , Condvar , Mutex } ,
8
+ } ;
4
9
5
10
// use ashpd::enumflags2::{bitflags, BitFlag, BitFlags};
6
11
use cosmic:: { iced:: window, widget} ;
7
12
use cosmic_protocols:: toplevel_info:: v1:: client:: zcosmic_toplevel_handle_v1;
8
13
use futures:: { FutureExt , TryFutureExt } ;
9
- use tokio:: sync:: { mpsc, watch} ;
14
+ use tokio:: {
15
+ fs,
16
+ io:: AsyncWriteExt ,
17
+ sync:: { mpsc, watch} ,
18
+ } ;
10
19
use zbus:: { fdo, object_server:: SignalContext , zvariant} ;
11
20
12
21
use crate :: {
@@ -65,13 +74,34 @@ impl Background {
65
74
}
66
75
}
67
76
}
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
+ }
68
98
}
69
99
70
100
#[ zbus:: interface( name = "org.freedesktop.impl.portal.Background" ) ]
71
101
impl Background {
72
102
/// 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
75
105
. wayland_helper
76
106
. toplevels ( )
77
107
. into_iter ( )
@@ -97,11 +127,11 @@ impl Background {
97
127
} )
98
128
. collect ( ) ;
99
129
100
- log:: debug!( "GetAppState returning {} toplevels" , toplevels . len( ) ) ;
130
+ log:: debug!( "GetAppState returning {} toplevels" , apps . len( ) ) ;
101
131
#[ cfg( debug_assertions) ]
102
- log:: trace!( "App status: {toplevels :#?}" ) ;
132
+ log:: trace!( "App status: {apps :#?}" ) ;
103
133
104
- Ok ( toplevels )
134
+ Ok ( AppStates { apps } )
105
135
}
106
136
107
137
/// Notifies the user that an app is running in the background
@@ -134,7 +164,7 @@ impl Background {
134
164
}
135
165
// Dialog
136
166
PermissionDialog :: Ask => {
137
- log:: debug!( "Requesting user permission for {app_id} ({name})" , ) ;
167
+ log:: debug!( "Requesting background permission for running app {app_id} ({name})" , ) ;
138
168
139
169
let handle = handle. to_owned ( ) ;
140
170
let id = window:: Id :: unique ( ) ;
@@ -160,16 +190,106 @@ impl Background {
160
190
161
191
/// Enable or disable autostart for an application
162
192
///
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/
164
195
pub async fn enable_autostart (
165
196
& self ,
166
- app_id : String ,
197
+ appid : String ,
167
198
enable : bool ,
168
- commandline : Vec < String > ,
199
+ exec : Vec < String > ,
169
200
flags : u32 ,
170
201
) -> 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 \t Path: {}\n \t Error: {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 \t Command: {exec:?}\n \t Error: {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
+ }
173
293
}
174
294
175
295
/// Emitted when running applications change their state
@@ -188,7 +308,13 @@ pub enum AppStatus {
188
308
Active ,
189
309
}
190
310
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 ) ]
192
318
#[ zvariant( signature = "{sv}" ) ]
193
319
struct AppState {
194
320
app_id : String ,
0 commit comments