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    fn activate(&self) {
78        self.parent_activate()
79    }
80
81    fn after_emit(&self, platform_data: &glib::Variant) {
82        self.parent_after_emit(platform_data)
83    }
84
85    fn before_emit(&self, platform_data: &glib::Variant) {
86        self.parent_before_emit(platform_data)
87    }
88
89    fn command_line(&self, command_line: &crate::ApplicationCommandLine) -> ExitCode {
90        self.parent_command_line(command_line)
91    }
92
93    fn local_command_line(&self, arguments: &mut ArgumentList) -> ControlFlow<ExitCode> {
94        self.parent_local_command_line(arguments)
95    }
96
97    fn open(&self, files: &[crate::File], hint: &str) {
98        self.parent_open(files, hint)
99    }
100
101    fn quit_mainloop(&self) {
102        self.parent_quit_mainloop()
103    }
104
105    fn run_mainloop(&self) {
106        self.parent_run_mainloop()
107    }
108
109    fn shutdown(&self) {
110        self.parent_shutdown()
111    }
112
113    fn startup(&self) {
114        self.parent_startup()
115    }
116
117    fn handle_local_options(&self, options: &VariantDict) -> ControlFlow<ExitCode> {
118        self.parent_handle_local_options(options)
119    }
120
121    fn dbus_register(&self, connection: &DBusConnection, object_path: &str) -> Result<(), Error> {
122        self.parent_dbus_register(connection, object_path)
123    }
124
125    fn dbus_unregister(&self, connection: &DBusConnection, object_path: &str) {
126        self.parent_dbus_unregister(connection, object_path)
127    }
128
129    fn name_lost(&self) -> Propagation {
130        self.parent_name_lost()
131    }
132}
133
134pub trait ApplicationImplExt: ApplicationImpl {
135    fn parent_activate(&self) {
136        unsafe {
137            let data = Self::type_data();
138            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
139            let f = (*parent_class)
140                .activate
141                .expect("No parent class implementation for \"activate\"");
142            f(self.obj().unsafe_cast_ref::<Application>().to_glib_none().0)
143        }
144    }
145
146    fn parent_after_emit(&self, platform_data: &glib::Variant) {
147        unsafe {
148            let data = Self::type_data();
149            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
150            let f = (*parent_class)
151                .after_emit
152                .expect("No parent class implementation for \"after_emit\"");
153            f(
154                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
155                platform_data.to_glib_none().0,
156            )
157        }
158    }
159
160    fn parent_before_emit(&self, platform_data: &glib::Variant) {
161        unsafe {
162            let data = Self::type_data();
163            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
164            let f = (*parent_class)
165                .before_emit
166                .expect("No parent class implementation for \"before_emit\"");
167            f(
168                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
169                platform_data.to_glib_none().0,
170            )
171        }
172    }
173
174    fn parent_command_line(&self, command_line: &crate::ApplicationCommandLine) -> ExitCode {
175        unsafe {
176            let data = Self::type_data();
177            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
178            let f = (*parent_class)
179                .command_line
180                .expect("No parent class implementation for \"command_line\"");
181            f(
182                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
183                command_line.to_glib_none().0,
184            )
185            .try_into()
186            .unwrap()
187        }
188    }
189
190    fn parent_local_command_line(&self, arguments: &mut ArgumentList) -> ControlFlow<ExitCode> {
191        unsafe {
192            let data = Self::type_data();
193            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
194            let f = (*parent_class)
195                .local_command_line
196                .expect("No parent class implementation for \"local_command_line\"");
197
198            let mut exit_status = 0;
199            let res = f(
200                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
201                arguments.ptr,
202                &mut exit_status,
203            );
204            arguments.refresh();
205
206            match res {
207                glib::ffi::GFALSE => ControlFlow::Continue(()),
208                _ => ControlFlow::Break(exit_status.try_into().unwrap()),
209            }
210        }
211    }
212
213    fn parent_open(&self, files: &[crate::File], hint: &str) {
214        unsafe {
215            let data = Self::type_data();
216            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
217            let f = (*parent_class)
218                .open
219                .expect("No parent class implementation for \"open\"");
220            f(
221                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
222                files.to_glib_none().0,
223                files.len() as i32,
224                hint.to_glib_none().0,
225            )
226        }
227    }
228
229    fn parent_quit_mainloop(&self) {
230        unsafe {
231            let data = Self::type_data();
232            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
233            let f = (*parent_class)
234                .quit_mainloop
235                .expect("No parent class implementation for \"quit_mainloop\"");
236            f(self.obj().unsafe_cast_ref::<Application>().to_glib_none().0)
237        }
238    }
239
240    fn parent_run_mainloop(&self) {
241        unsafe {
242            let data = Self::type_data();
243            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
244            let f = (*parent_class)
245                .run_mainloop
246                .expect("No parent class implementation for \"run_mainloop\"");
247            f(self.obj().unsafe_cast_ref::<Application>().to_glib_none().0)
248        }
249    }
250
251    fn parent_shutdown(&self) {
252        unsafe {
253            let data = Self::type_data();
254            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
255            let f = (*parent_class)
256                .shutdown
257                .expect("No parent class implementation for \"shutdown\"");
258            f(self.obj().unsafe_cast_ref::<Application>().to_glib_none().0)
259        }
260    }
261
262    fn parent_startup(&self) {
263        unsafe {
264            let data = Self::type_data();
265            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
266            let f = (*parent_class)
267                .startup
268                .expect("No parent class implementation for \"startup\"");
269            f(self.obj().unsafe_cast_ref::<Application>().to_glib_none().0)
270        }
271    }
272
273    fn parent_handle_local_options(&self, options: &VariantDict) -> ControlFlow<ExitCode> {
274        unsafe {
275            let data = Self::type_data();
276            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
277            if let Some(f) = (*parent_class).handle_local_options {
278                let ret = f(
279                    self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
280                    options.to_glib_none().0,
281                );
282
283                match ret {
284                    -1 => ControlFlow::Continue(()),
285                    _ => ControlFlow::Break(ret.try_into().unwrap()),
286                }
287            } else {
288                ControlFlow::Continue(())
289            }
290        }
291    }
292
293    fn parent_dbus_register(
294        &self,
295        connection: &DBusConnection,
296        object_path: &str,
297    ) -> Result<(), glib::Error> {
298        unsafe {
299            let data = Self::type_data();
300            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
301            let f = (*parent_class)
302                .dbus_register
303                .expect("No parent class implementation for \"dbus_register\"");
304            let mut err = ptr::null_mut();
305            let res = f(
306                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
307                connection.to_glib_none().0,
308                object_path.to_glib_none().0,
309                &mut err,
310            );
311            if res == glib::ffi::GFALSE {
312                Err(from_glib_full(err))
313            } else {
314                debug_assert!(err.is_null());
315                Ok(())
316            }
317        }
318    }
319
320    fn parent_dbus_unregister(&self, connection: &DBusConnection, object_path: &str) {
321        unsafe {
322            let data = Self::type_data();
323            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
324            let f = (*parent_class)
325                .dbus_unregister
326                .expect("No parent class implementation for \"dbus_unregister\"");
327            f(
328                self.obj().unsafe_cast_ref::<Application>().to_glib_none().0,
329                connection.to_glib_none().0,
330                object_path.to_glib_none().0,
331            );
332        }
333    }
334
335    fn parent_name_lost(&self) -> Propagation {
336        unsafe {
337            let data = Self::type_data();
338            let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass;
339            let f = (*parent_class)
340                .name_lost
341                .expect("No parent class implementation for \"name_lost\"");
342            Propagation::from_glib(f(self
343                .obj()
344                .unsafe_cast_ref::<Application>()
345                .to_glib_none()
346                .0))
347        }
348    }
349}
350
351impl<T: ApplicationImpl> ApplicationImplExt for T {}
352
353unsafe impl<T: ApplicationImpl> IsSubclassable<T> for Application {
354    fn class_init(class: &mut ::glib::Class<Self>) {
355        Self::parent_class_init::<T>(class);
356
357        let klass = class.as_mut();
358        klass.activate = Some(application_activate::<T>);
359        klass.after_emit = Some(application_after_emit::<T>);
360        klass.before_emit = Some(application_before_emit::<T>);
361        klass.command_line = Some(application_command_line::<T>);
362        klass.local_command_line = Some(application_local_command_line::<T>);
363        klass.open = Some(application_open::<T>);
364        klass.quit_mainloop = Some(application_quit_mainloop::<T>);
365        klass.run_mainloop = Some(application_run_mainloop::<T>);
366        klass.shutdown = Some(application_shutdown::<T>);
367        klass.startup = Some(application_startup::<T>);
368        klass.handle_local_options = Some(application_handle_local_options::<T>);
369        klass.dbus_register = Some(application_dbus_register::<T>);
370        klass.dbus_unregister = Some(application_dbus_unregister::<T>);
371        klass.name_lost = Some(application_name_lost::<T>);
372    }
373}
374
375unsafe extern "C" fn application_activate<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
376    let instance = &*(ptr as *mut T::Instance);
377    let imp = instance.imp();
378
379    imp.activate()
380}
381
382unsafe extern "C" fn application_after_emit<T: ApplicationImpl>(
383    ptr: *mut ffi::GApplication,
384    platform_data: *mut glib::ffi::GVariant,
385) {
386    let instance = &*(ptr as *mut T::Instance);
387    let imp = instance.imp();
388
389    imp.after_emit(&from_glib_borrow(platform_data))
390}
391unsafe extern "C" fn application_before_emit<T: ApplicationImpl>(
392    ptr: *mut ffi::GApplication,
393    platform_data: *mut glib::ffi::GVariant,
394) {
395    let instance = &*(ptr as *mut T::Instance);
396    let imp = instance.imp();
397
398    imp.before_emit(&from_glib_borrow(platform_data))
399}
400unsafe extern "C" fn application_command_line<T: ApplicationImpl>(
401    ptr: *mut ffi::GApplication,
402    command_line: *mut ffi::GApplicationCommandLine,
403) -> i32 {
404    let instance = &*(ptr as *mut T::Instance);
405    let imp = instance.imp();
406
407    imp.command_line(&from_glib_borrow(command_line)).into()
408}
409unsafe extern "C" fn application_local_command_line<T: ApplicationImpl>(
410    ptr: *mut ffi::GApplication,
411    arguments: *mut *mut *mut c_char,
412    exit_status: *mut i32,
413) -> glib::ffi::gboolean {
414    let instance = &*(ptr as *mut T::Instance);
415    let imp = instance.imp();
416
417    let mut args = ArgumentList::new(arguments);
418    let res = imp.local_command_line(&mut args);
419    args.refresh();
420
421    match res {
422        ControlFlow::Break(ret) => {
423            *exit_status = ret.into();
424            glib::ffi::GTRUE
425        }
426        ControlFlow::Continue(()) => glib::ffi::GFALSE,
427    }
428}
429unsafe extern "C" fn application_open<T: ApplicationImpl>(
430    ptr: *mut ffi::GApplication,
431    files: *mut *mut ffi::GFile,
432    num_files: i32,
433    hint: *const c_char,
434) {
435    let instance = &*(ptr as *mut T::Instance);
436    let imp = instance.imp();
437
438    let files: Vec<crate::File> = FromGlibContainer::from_glib_none_num(files, num_files as usize);
439    imp.open(files.as_slice(), &glib::GString::from_glib_borrow(hint))
440}
441unsafe extern "C" fn application_quit_mainloop<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
442    let instance = &*(ptr as *mut T::Instance);
443    let imp = instance.imp();
444
445    imp.quit_mainloop()
446}
447unsafe extern "C" fn application_run_mainloop<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
448    let instance = &*(ptr as *mut T::Instance);
449    let imp = instance.imp();
450
451    imp.run_mainloop()
452}
453unsafe extern "C" fn application_shutdown<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
454    let instance = &*(ptr as *mut T::Instance);
455    let imp = instance.imp();
456
457    imp.shutdown()
458}
459unsafe extern "C" fn application_startup<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
460    let instance = &*(ptr as *mut T::Instance);
461    let imp = instance.imp();
462
463    imp.startup()
464}
465
466unsafe extern "C" fn application_handle_local_options<T: ApplicationImpl>(
467    ptr: *mut ffi::GApplication,
468    options: *mut glib::ffi::GVariantDict,
469) -> c_int {
470    let instance = &*(ptr as *mut T::Instance);
471    let imp = instance.imp();
472
473    imp.handle_local_options(&from_glib_borrow(options))
474        .break_value()
475        .map(i32::from)
476        .unwrap_or(-1)
477}
478
479unsafe extern "C" fn application_dbus_register<T: ApplicationImpl>(
480    ptr: *mut ffi::GApplication,
481    connection: *mut ffi::GDBusConnection,
482    object_path: *const c_char,
483    error: *mut *mut glib::ffi::GError,
484) -> glib::ffi::gboolean {
485    let instance = &*(ptr as *mut T::Instance);
486    let imp = instance.imp();
487
488    match imp.dbus_register(
489        &from_glib_borrow(connection),
490        &glib::GString::from_glib_borrow(object_path),
491    ) {
492        Ok(()) => glib::ffi::GTRUE,
493        Err(e) => {
494            if !error.is_null() {
495                *error = e.into_glib_ptr();
496            }
497            glib::ffi::GFALSE
498        }
499    }
500}
501
502unsafe extern "C" fn application_dbus_unregister<T: ApplicationImpl>(
503    ptr: *mut ffi::GApplication,
504    connection: *mut ffi::GDBusConnection,
505    object_path: *const c_char,
506) {
507    let instance = &*(ptr as *mut T::Instance);
508    let imp = instance.imp();
509    imp.dbus_unregister(
510        &from_glib_borrow(connection),
511        &glib::GString::from_glib_borrow(object_path),
512    );
513}
514
515unsafe extern "C" fn application_name_lost<T: ApplicationImpl>(
516    ptr: *mut ffi::GApplication,
517) -> glib::ffi::gboolean {
518    let instance = &*(ptr as *mut T::Instance);
519    let imp = instance.imp();
520    imp.name_lost().into_glib()
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526    use crate::prelude::*;
527
528    const EXIT_STATUS: u8 = 20;
529
530    mod imp {
531        use super::*;
532
533        #[derive(Default)]
534        pub struct SimpleApplication;
535
536        #[glib::object_subclass]
537        impl ObjectSubclass for SimpleApplication {
538            const NAME: &'static str = "SimpleApplication";
539            type Type = super::SimpleApplication;
540            type ParentType = Application;
541        }
542
543        impl ObjectImpl for SimpleApplication {}
544
545        impl ApplicationImpl for SimpleApplication {
546            fn command_line(&self, _cmd_line: &crate::ApplicationCommandLine) -> ExitCode {
547                #[cfg(not(target_os = "windows"))]
548                {
549                    let arguments = _cmd_line.arguments();
550
551                    // NOTE: on windows argc and argv are ignored, even if the arguments
552                    // were passed explicitly.
553                    //
554                    // Source: https://gitlab.gnome.org/GNOME/glib/-/blob/e64a93269d09302d7a4facbc164b7fe9c2ad0836/gio/gapplication.c#L2513-2515
555                    assert_eq!(arguments.to_vec(), &["--global-1", "--global-2"]);
556                };
557                EXIT_STATUS.into()
558            }
559
560            fn local_command_line(&self, arguments: &mut ArgumentList) -> ControlFlow<ExitCode> {
561                let mut rm = Vec::new();
562
563                for (i, line) in arguments.iter().enumerate() {
564                    // TODO: we need https://github.com/rust-lang/rust/issues/49802
565                    let l = line.to_str().unwrap();
566                    if l.starts_with("--local-") {
567                        rm.push(i)
568                    }
569                }
570
571                rm.reverse();
572
573                for i in rm.iter() {
574                    arguments.remove(*i);
575                }
576
577                ControlFlow::Continue(())
578            }
579        }
580    }
581
582    glib::wrapper! {
583        pub struct SimpleApplication(ObjectSubclass<imp::SimpleApplication>)
584        @implements Application, ActionMap, ActionGroup;
585    }
586
587    #[test]
588    fn test_simple_application() {
589        let app = glib::Object::builder::<SimpleApplication>()
590            .property("application-id", "org.gtk-rs.SimpleApplication")
591            .property("flags", crate::ApplicationFlags::empty())
592            .build();
593
594        app.set_inactivity_timeout(10000);
595
596        assert_eq!(
597            app.run_with_args(&["--local-1", "--global-1", "--local-2", "--global-2"]),
598            EXIT_STATUS.into()
599        );
600    }
601}