1use 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 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) {
84 self.parent_activate()
85 }
86
87 fn after_emit(&self, platform_data: &glib::Variant) {
91 self.parent_after_emit(platform_data)
92 }
93
94 fn before_emit(&self, platform_data: &glib::Variant) {
98 self.parent_before_emit(platform_data)
99 }
100
101 fn command_line(&self, command_line: &crate::ApplicationCommandLine) -> ExitCode {
104 self.parent_command_line(command_line)
105 }
106
107 fn local_command_line(&self, arguments: &mut ArgumentList) -> ControlFlow<ExitCode> {
127 self.parent_local_command_line(arguments)
128 }
129
130 fn open(&self, files: &[crate::File], hint: &str) {
149 self.parent_open(files, hint)
150 }
151
152 fn quit_mainloop(&self) {
156 self.parent_quit_mainloop()
157 }
158
159 fn run_mainloop(&self) {
164 self.parent_run_mainloop()
165 }
166
167 fn shutdown(&self) {
170 self.parent_shutdown()
171 }
172
173 fn startup(&self) {
175 self.parent_startup()
176 }
177
178 fn handle_local_options(&self, options: &VariantDict) -> ControlFlow<ExitCode> {
181 self.parent_handle_local_options(options)
182 }
183
184 fn dbus_register(&self, connection: &DBusConnection, object_path: &str) -> Result<(), Error> {
192 self.parent_dbus_register(connection, object_path)
193 }
194
195 fn dbus_unregister(&self, connection: &DBusConnection, object_path: &str) {
199 self.parent_dbus_unregister(connection, object_path)
200 }
201
202 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 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 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}