1use 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 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 unsafe {
377 let instance = &*(ptr as *mut T::Instance);
378 let imp = instance.imp();
379
380 imp.activate()
381 }
382}
383
384unsafe extern "C" fn application_after_emit<T: ApplicationImpl>(
385 ptr: *mut ffi::GApplication,
386 platform_data: *mut glib::ffi::GVariant,
387) {
388 unsafe {
389 let instance = &*(ptr as *mut T::Instance);
390 let imp = instance.imp();
391
392 imp.after_emit(&from_glib_borrow(platform_data))
393 }
394}
395unsafe extern "C" fn application_before_emit<T: ApplicationImpl>(
396 ptr: *mut ffi::GApplication,
397 platform_data: *mut glib::ffi::GVariant,
398) {
399 unsafe {
400 let instance = &*(ptr as *mut T::Instance);
401 let imp = instance.imp();
402
403 imp.before_emit(&from_glib_borrow(platform_data))
404 }
405}
406unsafe extern "C" fn application_command_line<T: ApplicationImpl>(
407 ptr: *mut ffi::GApplication,
408 command_line: *mut ffi::GApplicationCommandLine,
409) -> i32 {
410 unsafe {
411 let instance = &*(ptr as *mut T::Instance);
412 let imp = instance.imp();
413
414 imp.command_line(&from_glib_borrow(command_line)).into()
415 }
416}
417unsafe extern "C" fn application_local_command_line<T: ApplicationImpl>(
418 ptr: *mut ffi::GApplication,
419 arguments: *mut *mut *mut c_char,
420 exit_status: *mut i32,
421) -> glib::ffi::gboolean {
422 unsafe {
423 let instance = &*(ptr as *mut T::Instance);
424 let imp = instance.imp();
425
426 let mut args = ArgumentList::new(arguments);
427 let res = imp.local_command_line(&mut args);
428 args.refresh();
429
430 match res {
431 ControlFlow::Break(ret) => {
432 *exit_status = ret.into();
433 glib::ffi::GTRUE
434 }
435 ControlFlow::Continue(()) => glib::ffi::GFALSE,
436 }
437 }
438}
439unsafe extern "C" fn application_open<T: ApplicationImpl>(
440 ptr: *mut ffi::GApplication,
441 files: *mut *mut ffi::GFile,
442 num_files: i32,
443 hint: *const c_char,
444) {
445 unsafe {
446 let instance = &*(ptr as *mut T::Instance);
447 let imp = instance.imp();
448
449 let files: Vec<crate::File> =
450 FromGlibContainer::from_glib_none_num(files, num_files as usize);
451 imp.open(files.as_slice(), &glib::GString::from_glib_borrow(hint))
452 }
453}
454unsafe extern "C" fn application_quit_mainloop<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
455 unsafe {
456 let instance = &*(ptr as *mut T::Instance);
457 let imp = instance.imp();
458
459 imp.quit_mainloop()
460 }
461}
462unsafe extern "C" fn application_run_mainloop<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
463 unsafe {
464 let instance = &*(ptr as *mut T::Instance);
465 let imp = instance.imp();
466
467 imp.run_mainloop()
468 }
469}
470unsafe extern "C" fn application_shutdown<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
471 unsafe {
472 let instance = &*(ptr as *mut T::Instance);
473 let imp = instance.imp();
474
475 imp.shutdown()
476 }
477}
478unsafe extern "C" fn application_startup<T: ApplicationImpl>(ptr: *mut ffi::GApplication) {
479 unsafe {
480 let instance = &*(ptr as *mut T::Instance);
481 let imp = instance.imp();
482
483 imp.startup()
484 }
485}
486
487unsafe extern "C" fn application_handle_local_options<T: ApplicationImpl>(
488 ptr: *mut ffi::GApplication,
489 options: *mut glib::ffi::GVariantDict,
490) -> c_int {
491 unsafe {
492 let instance = &*(ptr as *mut T::Instance);
493 let imp = instance.imp();
494
495 imp.handle_local_options(&from_glib_borrow(options))
496 .break_value()
497 .map(i32::from)
498 .unwrap_or(-1)
499 }
500}
501
502unsafe extern "C" fn application_dbus_register<T: ApplicationImpl>(
503 ptr: *mut ffi::GApplication,
504 connection: *mut ffi::GDBusConnection,
505 object_path: *const c_char,
506 error: *mut *mut glib::ffi::GError,
507) -> glib::ffi::gboolean {
508 unsafe {
509 let instance = &*(ptr as *mut T::Instance);
510 let imp = instance.imp();
511
512 match imp.dbus_register(
513 &from_glib_borrow(connection),
514 &glib::GString::from_glib_borrow(object_path),
515 ) {
516 Ok(()) => glib::ffi::GTRUE,
517 Err(e) => {
518 if !error.is_null() {
519 *error = e.into_glib_ptr();
520 }
521 glib::ffi::GFALSE
522 }
523 }
524 }
525}
526
527unsafe extern "C" fn application_dbus_unregister<T: ApplicationImpl>(
528 ptr: *mut ffi::GApplication,
529 connection: *mut ffi::GDBusConnection,
530 object_path: *const c_char,
531) {
532 unsafe {
533 let instance = &*(ptr as *mut T::Instance);
534 let imp = instance.imp();
535 imp.dbus_unregister(
536 &from_glib_borrow(connection),
537 &glib::GString::from_glib_borrow(object_path),
538 );
539 }
540}
541
542unsafe extern "C" fn application_name_lost<T: ApplicationImpl>(
543 ptr: *mut ffi::GApplication,
544) -> glib::ffi::gboolean {
545 unsafe {
546 let instance = &*(ptr as *mut T::Instance);
547 let imp = instance.imp();
548 imp.name_lost().into_glib()
549 }
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555 use crate::prelude::*;
556
557 const EXIT_STATUS: u8 = 20;
558
559 mod imp {
560 use super::*;
561
562 #[derive(Default)]
563 pub struct SimpleApplication;
564
565 #[glib::object_subclass]
566 impl ObjectSubclass for SimpleApplication {
567 const NAME: &'static str = "SimpleApplication";
568 type Type = super::SimpleApplication;
569 type ParentType = Application;
570 }
571
572 impl ObjectImpl for SimpleApplication {}
573
574 impl ApplicationImpl for SimpleApplication {
575 fn command_line(&self, _cmd_line: &crate::ApplicationCommandLine) -> ExitCode {
576 #[cfg(not(target_os = "windows"))]
577 {
578 let arguments = _cmd_line.arguments();
579
580 assert_eq!(arguments.to_vec(), &["--global-1", "--global-2"]);
585 };
586 EXIT_STATUS.into()
587 }
588
589 fn local_command_line(&self, arguments: &mut ArgumentList) -> ControlFlow<ExitCode> {
590 let mut rm = Vec::new();
591
592 for (i, line) in arguments.iter().enumerate() {
593 let l = line.to_str().unwrap();
595 if l.starts_with("--local-") {
596 rm.push(i)
597 }
598 }
599
600 rm.reverse();
601
602 for i in rm.iter() {
603 arguments.remove(*i);
604 }
605
606 ControlFlow::Continue(())
607 }
608 }
609 }
610
611 glib::wrapper! {
612 pub struct SimpleApplication(ObjectSubclass<imp::SimpleApplication>)
613 @implements Application, ActionMap, ActionGroup;
614 }
615
616 #[test]
617 fn test_simple_application() {
618 let app = glib::Object::builder::<SimpleApplication>()
619 .property("application-id", "org.gtk-rs.SimpleApplication")
620 .property("flags", crate::ApplicationFlags::empty())
621 .build();
622
623 app.set_inactivity_timeout(10000);
624
625 assert_eq!(
626 app.run_with_args(&["--local-1", "--global-1", "--local-2", "--global-2"]),
627 EXIT_STATUS.into()
628 );
629 }
630}