2 * Copyright © 2010 Codethink Limited
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the licence, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
19 * Author: Ryan Lortie <desrt@desrt.ca>
30 #include "gtkapplication.h"
31 #include "gtkmarshalers.h"
33 #include "gtkapplicationwindow.h"
34 #include "gtkaccelmapprivate.h"
35 #include "gactionmuxer.h"
37 #ifdef GDK_WINDOWING_QUARTZ
38 #include "gtkquartz-menu.h"
39 #import <Cocoa/Cocoa.h>
43 #ifdef GDK_WINDOWING_X11
44 #include <gdk/x11/gdkx.h>
48 * SECTION:gtkapplication
49 * @title: GtkApplication
50 * @short_description: Application class
52 * #GtkApplication is a class that handles many important aspects
53 * of a GTK+ application in a convenient fashion, without enforcing
54 * a one-size-fits-all application model.
56 * Currently, GtkApplication handles GTK+ initialization, application
57 * uniqueness, provides some basic scriptability and desktop shell integration
58 * by exporting actions and menus and manages a list of toplevel windows whose
59 * life-cycle is automatically tied to the life-cycle of your application.
61 * While GtkApplication works fine with plain #GtkWindows, it is recommended
62 * to use it together with #GtkApplicationWindow.
64 * To set an application menu on a GtkApplication, use
65 * g_application_set_app_menu(). The #GMenuModel that this function
66 * expects is usually constructed using #GtkBuilder, as seen in the
67 * following example. To set a menubar that will be automatically picked
68 * up by #GApplicationWindows, use g_application_set_menubar(). GTK+
69 * makes these menus appear as expected, depending on the platform
70 * the application is running on.
72 * <figure label="Menu integration in OS X">
73 * <graphic fileref="bloatpad-osx.png" format="PNG"/>
76 * <figure label="Menu integration in GNOME">
77 * <graphic fileref="bloatpad-gnome.png" format="PNG"/>
80 * <example id="gtkapplication"><title>A simple application</title>
82 * <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../examples/bloatpad.c">
83 * <xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
95 static guint gtk_application_signals[LAST_SIGNAL];
97 G_DEFINE_TYPE (GtkApplication, gtk_application, G_TYPE_APPLICATION)
99 struct _GtkApplicationPrivate
103 #ifdef GDK_WINDOWING_X11
104 GDBusConnection *session;
105 gchar *window_prefix;
109 #ifdef GDK_WINDOWING_QUARTZ
115 #ifdef GDK_WINDOWING_X11
118 guint action_export_id;
120 } GtkApplicationWindowInfo;
123 gtk_application_window_realized_x11 (GtkWindow *window,
126 GtkApplication *application = user_data;
127 GtkApplicationWindowInfo *info;
128 GdkWindow *gdkwindow;
130 const gchar *unique_id;
133 gdkwindow = gtk_widget_get_window (GTK_WIDGET (window));
135 if (!GDK_IS_X11_WINDOW (gdkwindow))
138 info = g_object_get_data (G_OBJECT (window), "GtkApplication::window-info");
140 window_path = g_strdup_printf ("%s%d", application->priv->window_prefix, info->window_id);
141 gdk_x11_window_set_utf8_property (gdkwindow, "_DBUS_OBJECT_PATH", window_path);
142 g_free (window_path);
144 unique_id = g_dbus_connection_get_unique_name (application->priv->session);
145 gdk_x11_window_set_utf8_property (gdkwindow, "_DBUS_UNIQUE_NAME", unique_id);
147 app_id = g_application_get_application_id (G_APPLICATION (application));
148 gdk_x11_window_set_utf8_property (gdkwindow, "_DBUS_APPLICATION_ID", app_id);
152 gtk_application_window_added_x11 (GtkApplication *application,
155 GtkApplicationWindowInfo *info;
157 if (application->priv->session == NULL)
160 if (!GTK_IS_APPLICATION_WINDOW (window))
163 /* GtkApplicationWindow associates with us when it is first created,
164 * so surely it's not realized yet...
166 g_assert (!gtk_widget_get_realized (GTK_WIDGET (window)));
168 /* ...but we want to know when it is. */
169 g_signal_connect (window, "realize", G_CALLBACK (gtk_application_window_realized_x11), application);
171 info = g_slice_new (GtkApplicationWindowInfo);
176 info->window_id = application->priv->next_id++;
177 window_path = g_strdup_printf ("%s%d", application->priv->window_prefix, info->window_id);
178 info->action_export_id = g_dbus_connection_export_action_group (application->priv->session, window_path,
179 G_ACTION_GROUP (window), NULL);
180 g_free (window_path);
182 while (!info->action_export_id);
184 g_object_set_data (G_OBJECT (window), "GtkApplication::window-info", info);
188 gtk_application_window_removed_x11 (GtkApplication *application,
191 GtkApplicationWindowInfo *info;
193 if (application->priv->session == NULL)
196 if (!GTK_IS_APPLICATION_WINDOW (window))
199 info = g_object_steal_data (G_OBJECT (window), "GtkApplication::window-info");
201 g_dbus_connection_unexport_action_group (application->priv->session, info->action_export_id);
203 g_slice_free (GtkApplicationWindowInfo, info);
207 window_prefix_from_appid (const gchar *appid)
209 gchar *appid_path, *iter;
211 appid_path = g_strconcat ("/", appid, "/windows/", NULL);
212 for (iter = appid_path; *iter; iter++)
225 gtk_application_startup_x11 (GtkApplication *application)
227 const gchar *application_id;
229 application_id = g_application_get_application_id (G_APPLICATION (application));
230 application->priv->session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
231 application->priv->window_prefix = window_prefix_from_appid (application_id);
235 gtk_application_shutdown_x11 (GtkApplication *application)
237 g_free (application->priv->window_prefix);
238 application->priv->window_prefix = NULL;
239 if (application->priv->session)
241 g_object_unref (application->priv->session);
242 application->priv->session = NULL;
247 #ifdef GDK_WINDOWING_QUARTZ
249 gtk_application_menu_changed_quartz (GObject *object,
253 GtkApplication *application = GTK_APPLICATION (object);
256 combined = g_menu_new ();
257 g_menu_append_submenu (combined, "Application", g_application_get_app_menu (G_APPLICATION (object)));
258 g_menu_append_section (combined, NULL, g_application_get_menubar (G_APPLICATION (object)));
260 gtk_quartz_set_main_menu (G_MENU_MODEL (combined), G_ACTION_OBSERVABLE (application->priv->muxer));
264 gtk_application_startup_quartz (GtkApplication *application)
266 [NSApp finishLaunching];
268 application->priv->muxer = g_action_muxer_new ();
269 g_action_muxer_insert (application->priv->muxer, "app", G_ACTION_GROUP (application));
271 g_signal_connect (application, "notify::app-menu", G_CALLBACK (gtk_application_menu_changed_quartz), NULL);
272 g_signal_connect (application, "notify::menubar", G_CALLBACK (gtk_application_menu_changed_quartz), NULL);
273 gtk_application_menu_changed_quartz (G_OBJECT (application), NULL, NULL);
277 gtk_application_shutdown_quartz (GtkApplication *application)
279 g_signal_handlers_disconnect_by_func (application, gtk_application_menu_changed_quartz, NULL);
281 g_object_unref (application->priv->muxer);
282 application->priv->muxer = NULL;
286 gtk_application_focus_changed (GtkApplication *application,
289 if (G_IS_ACTION_GROUP (window))
290 g_action_muxer_insert (application->priv->muxer, "win", G_ACTION_GROUP (window));
292 g_action_muxer_remove (application->priv->muxer, "win");
297 gtk_application_focus_in_event_cb (GtkWindow *window,
298 GdkEventFocus *event,
299 GtkApplication *application)
301 GtkApplicationPrivate *priv = application->priv;
304 /* Keep the window list sorted by most-recently-focused. */
305 link = g_list_find (priv->windows, window);
306 if (link != NULL && link != priv->windows)
308 priv->windows = g_list_remove_link (priv->windows, link);
309 priv->windows = g_list_concat (link, priv->windows);
312 #ifdef GDK_WINDOWING_QUARTZ
313 gtk_application_focus_changed (application, window);
320 gtk_application_startup (GApplication *application)
322 G_APPLICATION_CLASS (gtk_application_parent_class)
323 ->startup (application);
327 #ifdef GDK_WINDOWING_X11
328 gtk_application_startup_x11 (GTK_APPLICATION (application));
331 #ifdef GDK_WINDOWING_QUARTZ
332 gtk_application_startup_quartz (GTK_APPLICATION (application));
337 gtk_application_shutdown (GApplication *application)
339 #ifdef GDK_WINDOWING_X11
340 gtk_application_shutdown_x11 (GTK_APPLICATION (application));
343 #ifdef GDK_WINDOWING_QUARTZ
344 gtk_application_shutdown_quartz (GTK_APPLICATION (application));
347 G_APPLICATION_CLASS (gtk_application_parent_class)
348 ->shutdown (application);
352 gtk_application_add_platform_data (GApplication *application,
353 GVariantBuilder *builder)
355 const gchar *startup_id;
357 startup_id = getenv ("DESKTOP_STARTUP_ID");
359 if (startup_id && g_utf8_validate (startup_id, -1, NULL))
360 g_variant_builder_add (builder, "{sv}", "desktop-startup-id",
361 g_variant_new_string (startup_id));
365 gtk_application_before_emit (GApplication *application,
366 GVariant *platform_data)
372 g_variant_iter_init (&iter, platform_data);
373 while (g_variant_iter_loop (&iter, "{&sv}", &key, &value))
375 #ifdef GDK_WINDOWING_X11
376 if (strcmp (key, "desktop-startup-id") == 0)
381 display = gdk_display_get_default ();
382 id = g_variant_get_string (value, NULL);
383 if (GDK_IS_X11_DISPLAY (display))
384 gdk_x11_display_set_startup_notification_id (display, id);
391 gtk_application_after_emit (GApplication *application,
392 GVariant *platform_data)
394 gdk_notify_startup_complete ();
398 gtk_application_init (GtkApplication *application)
400 application->priv = G_TYPE_INSTANCE_GET_PRIVATE (application,
401 GTK_TYPE_APPLICATION,
402 GtkApplicationPrivate);
406 gtk_application_window_added (GtkApplication *application,
409 GtkApplicationPrivate *priv = application->priv;
411 priv->windows = g_list_prepend (priv->windows, window);
412 gtk_window_set_application (window, application);
413 g_application_hold (G_APPLICATION (application));
415 g_signal_connect (window, "focus-in-event",
416 G_CALLBACK (gtk_application_focus_in_event_cb),
419 #ifdef GDK_WINDOWING_X11
420 gtk_application_window_added_x11 (application, window);
425 gtk_application_window_removed (GtkApplication *application,
428 GtkApplicationPrivate *priv = application->priv;
430 #ifdef GDK_WINDOWING_X11
431 gtk_application_window_removed_x11 (application, window);
434 g_signal_handlers_disconnect_by_func (window,
435 gtk_application_focus_in_event_cb,
438 g_application_release (G_APPLICATION (application));
439 priv->windows = g_list_remove (priv->windows, window);
440 gtk_window_set_application (window, NULL);
444 extract_accel_from_menu_item (GMenuModel *model,
448 GMenuAttributeIter *iter;
451 const gchar *accel = NULL;
452 const gchar *action = NULL;
453 GVariant *target = NULL;
455 iter = g_menu_model_iterate_item_attributes (model, item);
456 while (g_menu_attribute_iter_get_next (iter, &key, &value))
458 if (g_str_equal (key, "action") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
459 action = g_variant_get_string (value, NULL);
460 else if (g_str_equal (key, "accel") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
461 accel = g_variant_get_string (value, NULL);
462 else if (g_str_equal (key, "target"))
463 target = g_variant_ref (value);
464 g_variant_unref (value);
466 g_object_unref (iter);
469 gtk_application_add_accelerator (app, accel, action, target);
472 g_variant_unref (target);
476 extract_accels_from_menu (GMenuModel *model,
484 for (i = 0; i < g_menu_model_get_n_items (model); i++)
486 extract_accel_from_menu_item (model, i, app);
488 iter = g_menu_model_iterate_item_links (model, i);
489 while (g_menu_link_iter_get_next (iter, &key, &m))
491 extract_accels_from_menu (m, app);
494 g_object_unref (iter);
499 gtk_application_notify (GObject *object,
502 if (strcmp (pspec->name, "app-menu") == 0 ||
503 strcmp (pspec->name, "menubar") == 0)
506 g_object_get (object, pspec->name, &model, NULL);
509 extract_accels_from_menu (model, GTK_APPLICATION (object));
510 g_object_unref (model);
514 if (G_OBJECT_CLASS (gtk_application_parent_class)->notify)
515 G_OBJECT_CLASS (gtk_application_parent_class)->notify (object, pspec);
519 gtk_application_class_init (GtkApplicationClass *class)
521 GObjectClass *object_class = G_OBJECT_CLASS (class);
522 GApplicationClass *application_class = G_APPLICATION_CLASS (class);
524 object_class->notify = gtk_application_notify;
526 application_class->add_platform_data = gtk_application_add_platform_data;
527 application_class->before_emit = gtk_application_before_emit;
528 application_class->after_emit = gtk_application_after_emit;
529 application_class->startup = gtk_application_startup;
530 application_class->shutdown = gtk_application_shutdown;
532 class->window_added = gtk_application_window_added;
533 class->window_removed = gtk_application_window_removed;
535 g_type_class_add_private (class, sizeof (GtkApplicationPrivate));
538 * GtkApplication::window-added:
539 * @application: the #GtkApplication which emitted the signal
540 * @window: the newly-added #GtkWindow
542 * Emitted when a #GtkWindow is added to @application through
543 * gtk_application_add_window().
547 gtk_application_signals[WINDOW_ADDED] =
548 g_signal_new ("window-added", GTK_TYPE_APPLICATION, G_SIGNAL_RUN_FIRST,
549 G_STRUCT_OFFSET (GtkApplicationClass, window_added),
551 g_cclosure_marshal_VOID__OBJECT,
552 G_TYPE_NONE, 1, GTK_TYPE_WINDOW);
555 * GtkApplication::window-removed:
556 * @application: the #GtkApplication which emitted the signal
557 * @window: the #GtkWindow that is being removed
559 * Emitted when a #GtkWindow is removed from @application,
560 * either as a side-effect of being destroyed or explicitly
561 * through gtk_application_remove_window().
565 gtk_application_signals[WINDOW_REMOVED] =
566 g_signal_new ("window-removed", GTK_TYPE_APPLICATION, G_SIGNAL_RUN_FIRST,
567 G_STRUCT_OFFSET (GtkApplicationClass, window_removed),
569 g_cclosure_marshal_VOID__OBJECT,
570 G_TYPE_NONE, 1, GTK_TYPE_WINDOW);
574 * gtk_application_new:
575 * @application_id: the application id
576 * @flags: the application flags
578 * Creates a new #GtkApplication instance.
580 * This function calls g_type_init() for you. gtk_init() is called
581 * as soon as the application gets registered as the primary instance.
583 * The application id must be valid. See g_application_id_is_valid().
585 * Returns: a new #GtkApplication instance
588 gtk_application_new (const gchar *application_id,
589 GApplicationFlags flags)
591 g_return_val_if_fail (g_application_id_is_valid (application_id), NULL);
595 return g_object_new (GTK_TYPE_APPLICATION,
596 "application-id", application_id,
602 * gtk_application_add_window:
603 * @application: a #GtkApplication
604 * @window: a #GtkWindow
606 * Adds a window from @application.
608 * This call is equivalent to setting the #GtkWindow:application
609 * property of @window to @application.
611 * Normally, the connection between the application and the window
612 * will remain until the window is destroyed, but you can explicitly
613 * remove it with gtk_application_remove_window().
615 * GTK+ will keep the application running as long as it has
621 gtk_application_add_window (GtkApplication *application,
624 g_return_if_fail (GTK_IS_APPLICATION (application));
626 if (!g_list_find (application->priv->windows, window))
627 g_signal_emit (application,
628 gtk_application_signals[WINDOW_ADDED], 0, window);
632 * gtk_application_remove_window:
633 * @application: a #GtkApplication
634 * @window: a #GtkWindow
636 * Remove a window from @application.
638 * If @window belongs to @application then this call is equivalent to
639 * setting the #GtkWindow:application property of @window to
642 * The application may stop running as a result of a call to this
648 gtk_application_remove_window (GtkApplication *application,
651 g_return_if_fail (GTK_IS_APPLICATION (application));
653 if (g_list_find (application->priv->windows, window))
654 g_signal_emit (application,
655 gtk_application_signals[WINDOW_REMOVED], 0, window);
659 * gtk_application_get_windows:
660 * @application: a #GtkApplication
662 * Gets a list of the #GtkWindows associated with @application.
664 * The list is sorted by most recently focused window, such that the first
665 * element is the currently focused window. (Useful for choosing a parent
666 * for a transient window.)
668 * The list that is returned should not be modified in any way. It will
669 * only remain valid until the next focus change or window creation or
672 * Returns: (element-type GtkWindow) (transfer none): a #GList of #GtkWindow
677 gtk_application_get_windows (GtkApplication *application)
679 g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
681 return application->priv->windows;
685 * gtk_application_add_accelerator:
686 * @application: a #GtkApplication
687 * @accelerator: accelerator string
688 * @action_name: the name of the action to activate
689 * @parameter: (allow-none): parameter to pass when activating the action,
690 * or %NULL if the action does not accept an activation parameter
692 * Installs an accelerator that will cause the named action
693 * to be activated when the key combination specificed by @accelerator
696 * @accelerator must be a string that can be parsed by
697 * gtk_accelerator_parse(), e.g. "<Primary>q" or "<Control><Alt>p".
699 * @action_name must be the name of an action as it would be used
700 * in the app menu, i.e. actions that have been added to the application
701 * are referred to with an "app." prefix, and window-specific actions
702 * with a "win." prefix.
704 * GtkApplication also extracts accelerators out of 'accel' attributes
705 * in the #GMenuModels passed to g_application_set_app_menu() and
706 * g_application_set_menubar(), which is usually more convenient
707 * than calling this function for each accelerator.
712 gtk_application_add_accelerator (GtkApplication *application,
713 const gchar *accelerator,
714 const gchar *action_name,
719 GdkModifierType accel_mods;
721 g_return_if_fail (GTK_IS_APPLICATION (application));
723 /* Call this here, since gtk_init() is only getting called in startup() */
724 _gtk_accel_map_init ();
726 gtk_accelerator_parse (accelerator, &accel_key, &accel_mods);
730 g_warning ("Failed to parse accelerator: '%s'\n", accelerator);
734 accel_path = _gtk_accel_path_for_action (action_name, parameter);
736 if (gtk_accel_map_lookup_entry (accel_path, NULL))
737 gtk_accel_map_change_entry (accel_path, accel_key, accel_mods, TRUE);
739 gtk_accel_map_add_entry (accel_path, accel_key, accel_mods);
745 * gtk_application_remove_accelerator:
746 * @application: a #GtkApplication
747 * @action_name: the name of the action to activate
748 * @parameter: (allow-none): parameter to pass when activating the action,
749 * or %NULL if the action does not accept an activation parameter
751 * Removes an accelerator that has been previously added
752 * with gtk_application_add_accelerator().
757 gtk_application_remove_accelerator (GtkApplication *application,
758 const gchar *action_name,
763 g_return_if_fail (GTK_IS_APPLICATION (application));
765 accel_path = _gtk_accel_path_for_action (action_name, parameter);
767 if (!gtk_accel_map_lookup_entry (accel_path, NULL))
769 g_warning ("No accelerator found for '%s'\n", accel_path);
774 gtk_accel_map_change_entry (accel_path, 0, 0, FALSE);