Skip to main content

gio/subclass/
application.rs

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