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) {
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 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 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}