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