gio/subclass/
application.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{ffi::OsString, fmt, ops::Deref, ptr};
4
5use glib::{
6    prelude::*, subclass::prelude::*, translate::*, Error, ExitCode, Propagation, VariantDict,
7};
8use libc::{c_char, c_int, c_void};
9
10use crate::{ffi, Application, DBusConnection};
11
12pub struct ArgumentList {
13    pub(crate) ptr: *mut *mut *mut c_char,
14    items: Vec<OsString>,
15}
16
17impl ArgumentList {
18    pub(crate) fn new(arguments: *mut *mut *mut c_char) -> Self {
19        Self {
20            ptr: arguments,
21            items: unsafe { FromGlibPtrContainer::from_glib_none(ptr::read(arguments)) },
22        }
23    }
24
25    pub(crate) fn refresh(&mut self) {
26        self.items = unsafe { FromGlibPtrContainer::from_glib_none(ptr::read(self.ptr)) };
27    }
28
29    // remove the item at index `idx` and shift the raw array
30    pub fn remove(&mut self, idx: usize) {
31        unsafe {
32            let n_args = glib::ffi::g_strv_length(*self.ptr) as usize;
33            assert_eq!(n_args, self.items.len());
34            assert!(idx < n_args);
35
36            self.items.remove(idx);
37
38            glib::ffi::g_free(*(*self.ptr).add(idx) as *mut c_void);
39
40            for i in idx..n_args - 1 {
41                ptr::write((*self.ptr).add(i), *(*self.ptr).add(i + 1))
42            }
43            ptr::write((*self.ptr).add(n_args - 1), ptr::null_mut());
44        }
45    }
46}
47
48impl Deref for ArgumentList {
49    type Target = [OsString];
50
51    #[inline]
52    fn deref(&self) -> &Self::Target {
53        self.items.as_slice()
54    }
55}
56
57impl fmt::Debug for ArgumentList {
58    fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
59        self.items.fmt(formatter)
60    }
61}
62
63impl From<ArgumentList> for Vec<OsString> {
64    fn from(list: ArgumentList) -> Vec<OsString> {
65        list.items
66    }
67}
68
69pub trait ApplicationImpl: ObjectImpl + ApplicationImplExt {
70    /// Activates the application.
71    ///
72    /// In essence, this results in the #GApplication::activate signal being
73    /// emitted in the primary instance.
74    ///
75    /// The application must be registered before calling this function.
76    fn activate(&self) {
77        self.parent_activate()
78    }
79
80    /// invoked on the primary instance after 'activate', 'open',
81    ///     'command-line' or any action invocation, gets the 'platform data' from
82    ///     the calling instance
83    fn after_emit(&self, platform_data: &glib::Variant) {
84        self.parent_after_emit(platform_data)
85    }
86
87    /// invoked on the primary instance before 'activate', 'open',
88    ///     'command-line' or any action invocation, gets the 'platform data' from
89    ///     the calling instance
90    fn before_emit(&self, platform_data: &glib::Variant) {
91        self.parent_before_emit(platform_data)
92    }
93
94    /// invoked on the primary instance when a command-line is
95    ///   not handled locally
96    fn command_line(&self, command_line: &crate::ApplicationCommandLine) -> ExitCode {
97        self.parent_command_line(command_line)
98    }
99
100    /// This virtual function is always invoked in the local instance. It
101    /// gets passed a pointer to a [`None`]-terminated copy of @argv and is
102    /// expected to remove arguments that it handled (shifting up remaining
103    /// arguments).
104    ///
105    /// The last argument to local_command_line() is a pointer to the @status
106    /// variable which can used to set the exit status that is returned from
107    /// g_application_run().
108    ///
109    /// See g_application_run() for more details on #GApplication startup.
110    /// ## `arguments`
111    /// array of command line arguments
112    ///
113    /// # Returns
114    ///
115    /// [`true`] if the commandline has been completely handled
116    ///
117    /// ## `exit_status`
118    /// exit status to fill after processing the command line.
119    fn local_command_line(&self, arguments: &mut ArgumentList) -> Option<ExitCode> {
120        self.parent_local_command_line(arguments)
121    }
122
123    /// Opens the given files.
124    ///
125    /// In essence, this results in the #GApplication::open signal being emitted
126    /// in the primary instance.
127    ///
128    /// @n_files must be greater than zero.
129    ///
130    /// @hint is simply passed through to the ::open signal.  It is
131    /// intended to be used by applications that have multiple modes for
132    /// opening files (eg: "view" vs "edit", etc).  Unless you have a need
133    /// for this functionality, you should use "".
134    ///
135    /// The application must be registered before calling this function
136    /// and it must have the [`ApplicationFlags::HANDLES_OPEN`][crate::ApplicationFlags::HANDLES_OPEN] flag set.
137    /// ## `files`
138    /// an array of #GFiles to open
139    /// ## `hint`
140    /// a hint (or ""), but never [`None`]
141    fn open(&self, files: &[crate::File], hint: &str) {
142        self.parent_open(files, hint)
143    }
144
145    /// Used to be invoked on the primary instance when the use
146    ///     count of the application drops to zero (and after any inactivity
147    ///     timeout, if requested). Not used anymore since 2.32
148    fn quit_mainloop(&self) {
149        self.parent_quit_mainloop()
150    }
151
152    /// Used to be invoked on the primary instance from
153    ///     g_application_run() if the use-count is non-zero. Since 2.32,
154    ///     GApplication is iterating the main context directly and is not
155    ///     using @run_mainloop anymore
156    fn run_mainloop(&self) {
157        self.parent_run_mainloop()
158    }
159
160    /// invoked only on the registered primary instance immediately
161    ///      after the main loop terminates
162    fn shutdown(&self) {
163        self.parent_shutdown()
164    }
165
166    /// invoked on the primary instance immediately after registration
167    fn startup(&self) {
168        self.parent_startup()
169    }
170
171    /// invoked locally after the parsing of the commandline
172    ///  options has occurred. Since: 2.40
173    fn handle_local_options(&self, options: &VariantDict) -> ExitCode {
174        self.parent_handle_local_options(options)
175    }
176
177    /// invoked locally during registration, if the application is
178    ///     using its D-Bus backend. You can use this to export extra objects on the
179    ///     bus, that need to exist before the application tries to own the bus name.
180    ///     The function is passed the #GDBusConnection to to session bus, and the
181    ///     object path that #GApplication will use to export its D-Bus API.
182    ///     If this function returns [`true`], registration will proceed; otherwise
183    ///     registration will abort. Since: 2.34
184    fn dbus_register(&self, connection: &DBusConnection, object_path: &str) -> Result<(), Error> {
185        self.parent_dbus_register(connection, object_path)
186    }
187
188    /// invoked locally during unregistration, if the application
189    ///     is using its D-Bus backend. Use this to undo anything done by
190    ///     the @dbus_register vfunc. Since: 2.34
191    fn dbus_unregister(&self, connection: &DBusConnection, object_path: &str) {
192        self.parent_dbus_unregister(connection, object_path)
193    }
194
195    /// invoked when another instance is taking over the name. Since: 2.60
196    fn name_lost(&self) -> Propagation {
197        self.parent_name_lost()
198    }
199}
200
201mod sealed {
202    pub trait Sealed {}
203    impl<T: super::ApplicationImplExt> Sealed for T {}
204}
205
206pub trait ApplicationImplExt: sealed::Sealed + ObjectSubclass {
207    fn parent_activate(&self) {
208        unsafe {
209            let data = Self::type_data();
210            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
211            let f = (*parent_class)
212                .activate
213                .expect("No parent class implementation for \"activate\"");
214            f(self.obj().unsafe_cast_ref::<Application>().to_glib_none().0)
215        }
216    }
217
218    fn parent_after_emit(&self, platform_data: &glib::Variant) {
219        unsafe {
220            let data = Self::type_data();
221            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
222            let f = (*parent_class)
223                .after_emit
224                .expect("No parent class implementation for \"after_emit\"");
225            f(
226                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
227                platform_data.to_glib_none().0,
228            )
229        }
230    }
231
232    fn parent_before_emit(&self, platform_data: &glib::Variant) {
233        unsafe {
234            let data = Self::type_data();
235            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
236            let f = (*parent_class)
237                .before_emit
238                .expect("No parent class implementation for \"before_emit\"");
239            f(
240                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
241                platform_data.to_glib_none().0,
242            )
243        }
244    }
245
246    fn parent_command_line(&self, command_line: &crate::ApplicationCommandLine) -> ExitCode {
247        unsafe {
248            let data = Self::type_data();
249            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
250            let f = (*parent_class)
251                .command_line
252                .expect("No parent class implementation for \"command_line\"");
253            f(
254                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
255                command_line.to_glib_none().0,
256            )
257            .into()
258        }
259    }
260
261    fn parent_local_command_line(&self, arguments: &mut ArgumentList) -> Option<ExitCode> {
262        unsafe {
263            let data = Self::type_data();
264            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
265            let f = (*parent_class)
266                .local_command_line
267                .expect("No parent class implementation for \"local_command_line\"");
268
269            let mut exit_status = 0;
270            let res = f(
271                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
272                arguments.ptr,
273                &mut exit_status,
274            );
275            arguments.refresh();
276
277            match res {
278                glib::ffi::GFALSE => None,
279                _ => Some(exit_status.into()),
280            }
281        }
282    }
283
284    fn parent_open(&self, files: &[crate::File], hint: &str) {
285        unsafe {
286            let data = Self::type_data();
287            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
288            let f = (*parent_class)
289                .open
290                .expect("No parent class implementation for \"open\"");
291            f(
292                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
293                files.to_glib_none().0,
294                files.len() as i32,
295                hint.to_glib_none().0,
296            )
297        }
298    }
299
300    fn parent_quit_mainloop(&self) {
301        unsafe {
302            let data = Self::type_data();
303            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
304            let f = (*parent_class)
305                .quit_mainloop
306                .expect("No parent class implementation for \"quit_mainloop\"");
307            f(self.obj().unsafe_cast_ref::<Application>().to_glib_none().0)
308        }
309    }
310
311    fn parent_run_mainloop(&self) {
312        unsafe {
313            let data = Self::type_data();
314            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
315            let f = (*parent_class)
316                .run_mainloop
317                .expect("No parent class implementation for \"run_mainloop\"");
318            f(self.obj().unsafe_cast_ref::<Application>().to_glib_none().0)
319        }
320    }
321
322    fn parent_shutdown(&self) {
323        unsafe {
324            let data = Self::type_data();
325            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
326            let f = (*parent_class)
327                .shutdown
328                .expect("No parent class implementation for \"shutdown\"");
329            f(self.obj().unsafe_cast_ref::<Application>().to_glib_none().0)
330        }
331    }
332
333    fn parent_startup(&self) {
334        unsafe {
335            let data = Self::type_data();
336            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
337            let f = (*parent_class)
338                .startup
339                .expect("No parent class implementation for \"startup\"");
340            f(self.obj().unsafe_cast_ref::<Application>().to_glib_none().0)
341        }
342    }
343
344    fn parent_handle_local_options(&self, options: &VariantDict) -> ExitCode {
345        unsafe {
346            let data = Self::type_data();
347            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
348            if let Some(f) = (*parent_class).handle_local_options {
349                f(
350                    self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
351                    options.to_glib_none().0,
352                )
353                .into()
354            } else {
355                // Continue default handling
356                ExitCode::from(-1)
357            }
358        }
359    }
360
361    fn parent_dbus_register(
362        &self,
363        connection: &DBusConnection,
364        object_path: &str,
365    ) -> Result<(), glib::Error> {
366        unsafe {
367            let data = Self::type_data();
368            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
369            let f = (*parent_class)
370                .dbus_register
371                .expect("No parent class implementation for \"dbus_register\"");
372            let mut err = ptr::null_mut();
373            let res = f(
374                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
375                connection.to_glib_none().0,
376                object_path.to_glib_none().0,
377                &mut err,
378            );
379            if res == glib::ffi::GFALSE {
380                Err(from_glib_full(err))
381            } else {
382                debug_assert!(err.is_null());
383                Ok(())
384            }
385        }
386    }
387
388    fn parent_dbus_unregister(&self, connection: &DBusConnection, object_path: &str) {
389        unsafe {
390            let data = Self::type_data();
391            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
392            let f = (*parent_class)
393                .dbus_unregister
394                .expect("No parent class implementation for \"dbus_unregister\"");
395            f(
396                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
397                connection.to_glib_none().0,
398                object_path.to_glib_none().0,
399            );
400        }
401    }
402
403    fn parent_name_lost(&self) -> Propagation {
404        unsafe {
405            let data = Self::type_data();
406            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
407            let f = (*parent_class)
408                .name_lost
409                .expect("No parent class implementation for \"name_lost\"");
410            Propagation::from_glib(f(self
411                .obj()
412                .unsafe_cast_ref::<Application>()
413                .to_glib_none()
414                .0))
415        }
416    }
417}
418
419impl<T: ApplicationImpl> ApplicationImplExt for T {}
420
421unsafe impl<T: ApplicationImpl> IsSubclassable<T> for Application {
422    fn class_init(class: &mut ::glib::Class<Self>) {
423        Self::parent_class_init::<T>(class);
424
425        let klass = class.as_mut();
426        klass.activate = Some(application_activate::<T>);
427        klass.after_emit = Some(application_after_emit::<T>);
428        klass.before_emit = Some(application_before_emit::<T>);
429        klass.command_line = Some(application_command_line::<T>);
430        klass.local_command_line = Some(application_local_command_line::<T>);
431        klass.open = Some(application_open::<T>);
432        klass.quit_mainloop = Some(application_quit_mainloop::<T>);
433        klass.run_mainloop = Some(application_run_mainloop::<T>);
434        klass.shutdown = Some(application_shutdown::<T>);
435        klass.startup = Some(application_startup::<T>);
436        klass.handle_local_options = Some(application_handle_local_options::<T>);
437        klass.dbus_register = Some(application_dbus_register::<T>);
438        klass.dbus_unregister = Some(application_dbus_unregister::<T>);
439        klass.name_lost = Some(application_name_lost::<T>);
440    }
441}
442
443unsafe extern "C" fn application_activate<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
444    let instance = &*(ptr as *mut T::Instance);
445    let imp = instance.imp();
446
447    imp.activate()
448}
449
450unsafe extern "C" fn application_after_emit<T: ApplicationImpl>(
451    ptr: *mut ffi::GApplication,
452    platform_data: *mut glib::ffi::GVariant,
453) {
454    let instance = &*(ptr as *mut T::Instance);
455    let imp = instance.imp();
456
457    imp.after_emit(&from_glib_borrow(platform_data))
458}
459unsafe extern "C" fn application_before_emit<T: ApplicationImpl>(
460    ptr: *mut ffi::GApplication,
461    platform_data: *mut glib::ffi::GVariant,
462) {
463    let instance = &*(ptr as *mut T::Instance);
464    let imp = instance.imp();
465
466    imp.before_emit(&from_glib_borrow(platform_data))
467}
468unsafe extern "C" fn application_command_line<T: ApplicationImpl>(
469    ptr: *mut ffi::GApplication,
470    command_line: *mut ffi::GApplicationCommandLine,
471) -> i32 {
472    let instance = &*(ptr as *mut T::Instance);
473    let imp = instance.imp();
474
475    imp.command_line(&from_glib_borrow(command_line)).into()
476}
477unsafe extern "C" fn application_local_command_line<T: ApplicationImpl>(
478    ptr: *mut ffi::GApplication,
479    arguments: *mut *mut *mut c_char,
480    exit_status: *mut i32,
481) -> glib::ffi::gboolean {
482    let instance = &*(ptr as *mut T::Instance);
483    let imp = instance.imp();
484
485    let mut args = ArgumentList::new(arguments);
486    let res = imp.local_command_line(&mut args).map(i32::from);
487    args.refresh();
488
489    match res {
490        Some(ret) => {
491            *exit_status = ret;
492            glib::ffi::GTRUE
493        }
494        None => glib::ffi::GFALSE,
495    }
496}
497unsafe extern "C" fn application_open<T: ApplicationImpl>(
498    ptr: *mut ffi::GApplication,
499    files: *mut *mut ffi::GFile,
500    num_files: i32,
501    hint: *const c_char,
502) {
503    let instance = &*(ptr as *mut T::Instance);
504    let imp = instance.imp();
505
506    let files: Vec<crate::File> = FromGlibContainer::from_glib_none_num(files, num_files as usize);
507    imp.open(files.as_slice(), &glib::GString::from_glib_borrow(hint))
508}
509unsafe extern "C" fn application_quit_mainloop<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
510    let instance = &*(ptr as *mut T::Instance);
511    let imp = instance.imp();
512
513    imp.quit_mainloop()
514}
515unsafe extern "C" fn application_run_mainloop<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
516    let instance = &*(ptr as *mut T::Instance);
517    let imp = instance.imp();
518
519    imp.run_mainloop()
520}
521unsafe extern "C" fn application_shutdown<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
522    let instance = &*(ptr as *mut T::Instance);
523    let imp = instance.imp();
524
525    imp.shutdown()
526}
527unsafe extern "C" fn application_startup<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
528    let instance = &*(ptr as *mut T::Instance);
529    let imp = instance.imp();
530
531    imp.startup()
532}
533
534unsafe extern "C" fn application_handle_local_options<T: ApplicationImpl>(
535    ptr: *mut ffi::GApplication,
536    options: *mut glib::ffi::GVariantDict,
537) -> c_int {
538    let instance = &*(ptr as *mut T::Instance);
539    let imp = instance.imp();
540
541    imp.handle_local_options(&from_glib_borrow(options)).into()
542}
543
544unsafe extern "C" fn application_dbus_register<T: ApplicationImpl>(
545    ptr: *mut ffi::GApplication,
546    connection: *mut ffi::GDBusConnection,
547    object_path: *const c_char,
548    error: *mut *mut glib::ffi::GError,
549) -> glib::ffi::gboolean {
550    let instance = &*(ptr as *mut T::Instance);
551    let imp = instance.imp();
552
553    match imp.dbus_register(
554        &from_glib_borrow(connection),
555        &glib::GString::from_glib_borrow(object_path),
556    ) {
557        Ok(()) => glib::ffi::GTRUE,
558        Err(e) => {
559            if !error.is_null() {
560                *error = e.into_glib_ptr();
561            }
562            glib::ffi::GFALSE
563        }
564    }
565}
566
567unsafe extern "C" fn application_dbus_unregister<T: ApplicationImpl>(
568    ptr: *mut ffi::GApplication,
569    connection: *mut ffi::GDBusConnection,
570    object_path: *const c_char,
571) {
572    let instance = &*(ptr as *mut T::Instance);
573    let imp = instance.imp();
574    imp.dbus_unregister(
575        &from_glib_borrow(connection),
576        &glib::GString::from_glib_borrow(object_path),
577    );
578}
579
580unsafe extern "C" fn application_name_lost<T: ApplicationImpl>(
581    ptr: *mut ffi::GApplication,
582) -> glib::ffi::gboolean {
583    let instance = &*(ptr as *mut T::Instance);
584    let imp = instance.imp();
585    imp.name_lost().into_glib()
586}
587
588#[cfg(test)]
589mod tests {
590    use super::*;
591    use crate::prelude::*;
592
593    const EXIT_STATUS: i32 = 20;
594
595    mod imp {
596        use super::*;
597
598        #[derive(Default)]
599        pub struct SimpleApplication;
600
601        #[glib::object_subclass]
602        impl ObjectSubclass for SimpleApplication {
603            const NAME: &'static str = "SimpleApplication";
604            type Type = super::SimpleApplication;
605            type ParentType = Application;
606        }
607
608        impl ObjectImpl for SimpleApplication {}
609
610        impl ApplicationImpl for SimpleApplication {
611            fn command_line(&self, _cmd_line: &crate::ApplicationCommandLine) -> ExitCode {
612                #[cfg(not(target_os = "windows"))]
613                {
614                    let arguments = _cmd_line.arguments();
615
616                    // NOTE: on windows argc and argv are ignored, even if the arguments
617                    // were passed explicitly.
618                    //
619                    // Source: https://gitlab.gnome.org/GNOME/glib/-/blob/e64a93269d09302d7a4facbc164b7fe9c2ad0836/gio/gapplication.c#L2513-2515
620                    assert_eq!(arguments.to_vec(), &["--global-1", "--global-2"]);
621                };
622                EXIT_STATUS.into()
623            }
624
625            fn local_command_line(&self, arguments: &mut ArgumentList) -> Option<ExitCode> {
626                let mut rm = Vec::new();
627
628                for (i, line) in arguments.iter().enumerate() {
629                    // TODO: we need https://github.com/rust-lang/rust/issues/49802
630                    let l = line.to_str().unwrap();
631                    if l.starts_with("--local-") {
632                        rm.push(i)
633                    }
634                }
635
636                rm.reverse();
637
638                for i in rm.iter() {
639                    arguments.remove(*i);
640                }
641
642                None
643            }
644        }
645    }
646
647    glib::wrapper! {
648        pub struct SimpleApplication(ObjectSubclass<imp::SimpleApplication>)
649        @implements crate::Application;
650    }
651
652    #[test]
653    fn test_simple_application() {
654        let app = glib::Object::builder::<SimpleApplication>()
655            .property("application-id", "org.gtk-rs.SimpleApplication")
656            .property("flags", crate::ApplicationFlags::empty())
657            .build();
658
659        app.set_inactivity_timeout(10000);
660
661        assert_eq!(
662            app.run_with_args(&["--local-1", "--global-1", "--local-2", "--global-2"]),
663            EXIT_STATUS.into()
664        );
665    }
666}