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    prelude::*, subclass::prelude::*, translate::*, Error, ExitCode, Propagation, VariantDict,
12};
13use libc::{c_char, c_int, c_void};
14
15use crate::{ffi, ActionGroup, ActionMap, Application, DBusConnection};
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
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
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    let instance = &*(ptr as *mut T::Instance);
451    let imp = instance.imp();
452
453    imp.activate()
454}
455
456unsafe extern "C" fn application_after_emit<T: ApplicationImpl>(
457    ptr: *mut ffi::GApplication,
458    platform_data: *mut glib::ffi::GVariant,
459) {
460    let instance = &*(ptr as *mut T::Instance);
461    let imp = instance.imp();
462
463    imp.after_emit(&from_glib_borrow(platform_data))
464}
465unsafe extern "C" fn application_before_emit<T: ApplicationImpl>(
466    ptr: *mut ffi::GApplication,
467    platform_data: *mut glib::ffi::GVariant,
468) {
469    let instance = &*(ptr as *mut T::Instance);
470    let imp = instance.imp();
471
472    imp.before_emit(&from_glib_borrow(platform_data))
473}
474unsafe extern "C" fn application_command_line<T: ApplicationImpl>(
475    ptr: *mut ffi::GApplication,
476    command_line: *mut ffi::GApplicationCommandLine,
477) -> i32 {
478    let instance = &*(ptr as *mut T::Instance);
479    let imp = instance.imp();
480
481    imp.command_line(&from_glib_borrow(command_line)).into()
482}
483unsafe extern "C" fn application_local_command_line<T: ApplicationImpl>(
484    ptr: *mut ffi::GApplication,
485    arguments: *mut *mut *mut c_char,
486    exit_status: *mut i32,
487) -> glib::ffi::gboolean {
488    let instance = &*(ptr as *mut T::Instance);
489    let imp = instance.imp();
490
491    let mut args = ArgumentList::new(arguments);
492    let res = imp.local_command_line(&mut args);
493    args.refresh();
494
495    match res {
496        ControlFlow::Break(ret) => {
497            *exit_status = ret.into();
498            glib::ffi::GTRUE
499        }
500        ControlFlow::Continue(()) => glib::ffi::GFALSE,
501    }
502}
503unsafe extern "C" fn application_open<T: ApplicationImpl>(
504    ptr: *mut ffi::GApplication,
505    files: *mut *mut ffi::GFile,
506    num_files: i32,
507    hint: *const c_char,
508) {
509    let instance = &*(ptr as *mut T::Instance);
510    let imp = instance.imp();
511
512    let files: Vec<crate::File> = FromGlibContainer::from_glib_none_num(files, num_files as usize);
513    imp.open(files.as_slice(), &glib::GString::from_glib_borrow(hint))
514}
515unsafe extern "C" fn application_quit_mainloop<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
516    let instance = &*(ptr as *mut T::Instance);
517    let imp = instance.imp();
518
519    imp.quit_mainloop()
520}
521unsafe extern "C" fn application_run_mainloop<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
522    let instance = &*(ptr as *mut T::Instance);
523    let imp = instance.imp();
524
525    imp.run_mainloop()
526}
527unsafe extern "C" fn application_shutdown<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
528    let instance = &*(ptr as *mut T::Instance);
529    let imp = instance.imp();
530
531    imp.shutdown()
532}
533unsafe extern "C" fn application_startup<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
534    let instance = &*(ptr as *mut T::Instance);
535    let imp = instance.imp();
536
537    imp.startup()
538}
539
540unsafe extern "C" fn application_handle_local_options<T: ApplicationImpl>(
541    ptr: *mut ffi::GApplication,
542    options: *mut glib::ffi::GVariantDict,
543) -> c_int {
544    let instance = &*(ptr as *mut T::Instance);
545    let imp = instance.imp();
546
547    imp.handle_local_options(&from_glib_borrow(options))
548        .break_value()
549        .map(i32::from)
550        .unwrap_or(-1)
551}
552
553unsafe extern "C" fn application_dbus_register<T: ApplicationImpl>(
554    ptr: *mut ffi::GApplication,
555    connection: *mut ffi::GDBusConnection,
556    object_path: *const c_char,
557    error: *mut *mut glib::ffi::GError,
558) -> glib::ffi::gboolean {
559    let instance = &*(ptr as *mut T::Instance);
560    let imp = instance.imp();
561
562    match imp.dbus_register(
563        &from_glib_borrow(connection),
564        &glib::GString::from_glib_borrow(object_path),
565    ) {
566        Ok(()) => glib::ffi::GTRUE,
567        Err(e) => {
568            if !error.is_null() {
569                *error = e.into_glib_ptr();
570            }
571            glib::ffi::GFALSE
572        }
573    }
574}
575
576unsafe extern "C" fn application_dbus_unregister<T: ApplicationImpl>(
577    ptr: *mut ffi::GApplication,
578    connection: *mut ffi::GDBusConnection,
579    object_path: *const c_char,
580) {
581    let instance = &*(ptr as *mut T::Instance);
582    let imp = instance.imp();
583    imp.dbus_unregister(
584        &from_glib_borrow(connection),
585        &glib::GString::from_glib_borrow(object_path),
586    );
587}
588
589unsafe extern "C" fn application_name_lost<T: ApplicationImpl>(
590    ptr: *mut ffi::GApplication,
591) -> glib::ffi::gboolean {
592    let instance = &*(ptr as *mut T::Instance);
593    let imp = instance.imp();
594    imp.name_lost().into_glib()
595}
596
597#[cfg(test)]
598mod tests {
599    use super::*;
600    use crate::prelude::*;
601
602    const EXIT_STATUS: u8 = 20;
603
604    mod imp {
605        use super::*;
606
607        #[derive(Default)]
608        pub struct SimpleApplication;
609
610        #[glib::object_subclass]
611        impl ObjectSubclass for SimpleApplication {
612            const NAME: &'static str = "SimpleApplication";
613            type Type = super::SimpleApplication;
614            type ParentType = Application;
615        }
616
617        impl ObjectImpl for SimpleApplication {}
618
619        impl ApplicationImpl for SimpleApplication {
620            fn command_line(&self, _cmd_line: &crate::ApplicationCommandLine) -> ExitCode {
621                #[cfg(not(target_os = "windows"))]
622                {
623                    let arguments = _cmd_line.arguments();
624
625                    // NOTE: on windows argc and argv are ignored, even if the arguments
626                    // were passed explicitly.
627                    //
628                    // Source: https://gitlab.gnome.org/GNOME/glib/-/blob/e64a93269d09302d7a4facbc164b7fe9c2ad0836/gio/gapplication.c#L2513-2515
629                    assert_eq!(arguments.to_vec(), &["--global-1", "--global-2"]);
630                };
631                EXIT_STATUS.into()
632            }
633
634            fn local_command_line(&self, arguments: &mut ArgumentList) -> ControlFlow<ExitCode> {
635                let mut rm = Vec::new();
636
637                for (i, line) in arguments.iter().enumerate() {
638                    // TODO: we need https://github.com/rust-lang/rust/issues/49802
639                    let l = line.to_str().unwrap();
640                    if l.starts_with("--local-") {
641                        rm.push(i)
642                    }
643                }
644
645                rm.reverse();
646
647                for i in rm.iter() {
648                    arguments.remove(*i);
649                }
650
651                ControlFlow::Continue(())
652            }
653        }
654    }
655
656    glib::wrapper! {
657        pub struct SimpleApplication(ObjectSubclass<imp::SimpleApplication>)
658        @implements Application, ActionMap, ActionGroup;
659    }
660
661    #[test]
662    fn test_simple_application() {
663        let app = glib::Object::builder::<SimpleApplication>()
664            .property("application-id", "org.gtk-rs.SimpleApplication")
665            .property("flags", crate::ApplicationFlags::empty())
666            .build();
667
668        app.set_inactivity_timeout(10000);
669
670        assert_eq!(
671            app.run_with_args(&["--local-1", "--global-1", "--local-2", "--global-2"]),
672            EXIT_STATUS.into()
673        );
674    }
675}