1 /*
2 * Copyright © 2010 Codethink Limited
3 * Copyright © 2013 Canonical Limited
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the licence, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Ryan Lortie <desrt@desrt.ca>
19 */
20
21 #include "config.h"
22
23 #include "gtkapplicationprivate.h"
24 #include "gtkbuilder.h"
25 #import <Cocoa/Cocoa.h>
26
27 typedef struct
28 {
29 guint cookie;
30 GtkApplicationInhibitFlags flags;
31 char *reason;
32 GtkWindow *window;
33 } GtkApplicationQuartzInhibitor;
34
35 static void
gtk_application_quartz_inhibitor_free(GtkApplicationQuartzInhibitor * inhibitor)36 gtk_application_quartz_inhibitor_free (GtkApplicationQuartzInhibitor *inhibitor)
37 {
38 g_free (inhibitor->reason);
39 g_clear_object (&inhibitor->window);
40 g_slice_free (GtkApplicationQuartzInhibitor, inhibitor);
41 }
42
43 typedef GtkApplicationImplClass GtkApplicationImplQuartzClass;
44
45 typedef struct
46 {
47 GtkApplicationImpl impl;
48
49 GtkActionMuxer *muxer;
50 GMenu *combined;
51
52 GSList *inhibitors;
53 int quit_inhibit;
54 guint next_cookie;
55 NSObject *delegate;
56 } GtkApplicationImplQuartz;
57
G_DEFINE_TYPE(GtkApplicationImplQuartz,gtk_application_impl_quartz,GTK_TYPE_APPLICATION_IMPL)58 G_DEFINE_TYPE (GtkApplicationImplQuartz, gtk_application_impl_quartz, GTK_TYPE_APPLICATION_IMPL)
59
60 @interface GtkApplicationQuartzDelegate : NSObject
61 {
62 GtkApplicationImplQuartz *quartz;
63 }
64
65 - (id)initWithImpl:(GtkApplicationImplQuartz*)impl;
66 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender;
67 - (void)application:(NSApplication *)theApplication openFiles:(NSArray *)filenames;
68 @end
69
70 @implementation GtkApplicationQuartzDelegate
71 -(id)initWithImpl:(GtkApplicationImplQuartz*)impl
72 {
73 [super init];
74 quartz = impl;
75 return self;
76 }
77
78 -(NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
79 {
80 /* We have no way to give our message other than to pop up a dialog
81 * ourselves, which we should not do since the OS will already show
82 * one when we return NSTerminateNow.
83 *
84 * Just let the OS show the generic message...
85 */
86 return quartz->quit_inhibit == 0 ? NSTerminateNow : NSTerminateCancel;
87 }
88
89 -(void)application:(NSApplication *)theApplication openFiles:(NSArray *)filenames
90 {
91 GFile **files;
92 int i;
93 GApplicationFlags flags;
94
95 flags = g_application_get_flags (G_APPLICATION (quartz->impl.application));
96
97 if (~flags & G_APPLICATION_HANDLES_OPEN)
98 {
99 [theApplication replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
100 return;
101 }
102
103 files = g_new (GFile *, [filenames count]);
104
105 for (i = 0; i < [filenames count]; i++)
106 files[i] = g_file_new_for_path ([(NSString *)[filenames objectAtIndex:i] UTF8String]);
107
108 g_application_open (G_APPLICATION (quartz->impl.application), files, [filenames count], "");
109
110 for (i = 0; i < [filenames count]; i++)
111 g_object_unref (files[i]);
112
113 g_free (files);
114
115 [theApplication replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
116 }
117 @end
118
119 /* these exist only for accel handling */
120 static void
gtk_application_impl_quartz_hide(GSimpleAction * action,GVariant * parameter,gpointer user_data)121 gtk_application_impl_quartz_hide (GSimpleAction *action,
122 GVariant *parameter,
123 gpointer user_data)
124 {
125 [NSApp hide:NSApp];
126 }
127
128 static void
gtk_application_impl_quartz_hide_others(GSimpleAction * action,GVariant * parameter,gpointer user_data)129 gtk_application_impl_quartz_hide_others (GSimpleAction *action,
130 GVariant *parameter,
131 gpointer user_data)
132 {
133 [NSApp hideOtherApplications:NSApp];
134 }
135
136 static void
gtk_application_impl_quartz_show_all(GSimpleAction * action,GVariant * parameter,gpointer user_data)137 gtk_application_impl_quartz_show_all (GSimpleAction *action,
138 GVariant *parameter,
139 gpointer user_data)
140 {
141 [NSApp unhideAllApplications:NSApp];
142 }
143
144 static GActionEntry gtk_application_impl_quartz_actions[] = {
145 { "hide", gtk_application_impl_quartz_hide },
146 { "hide-others", gtk_application_impl_quartz_hide_others },
147 { "show-all", gtk_application_impl_quartz_show_all }
148 };
149
150 static void
gtk_application_impl_quartz_startup(GtkApplicationImpl * impl,gboolean register_session)151 gtk_application_impl_quartz_startup (GtkApplicationImpl *impl,
152 gboolean register_session)
153 {
154 GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
155 GSimpleActionGroup *gtkinternal;
156 const char *pref_accel[] = {"<Control>comma", NULL};
157 const char *hide_others_accel[] = {"<Control><Alt>h", NULL};
158 const char *hide_accel[] = {"<Control>h", NULL};
159 const char *quit_accel[] = {"<Control>q", NULL};
160
161 if (register_session)
162 {
163 quartz->delegate = [[GtkApplicationQuartzDelegate alloc] initWithImpl:quartz];
164 [NSApp setDelegate: (id<NSApplicationDelegate>)quartz->delegate];
165 }
166
167 quartz->muxer = gtk_action_muxer_new (NULL);
168 gtk_action_muxer_set_parent (quartz->muxer, gtk_application_get_action_muxer (impl->application));
169
170 /* Add the default accels */
171 gtk_application_set_accels_for_action (impl->application, "app.preferences", pref_accel);
172 gtk_application_set_accels_for_action (impl->application, "gtkinternal.hide-others", hide_others_accel);
173 gtk_application_set_accels_for_action (impl->application, "gtkinternal.hide", hide_accel);
174 gtk_application_set_accels_for_action (impl->application, "app.quit", quit_accel);
175
176 /* and put code behind the 'special' accels */
177 gtkinternal = g_simple_action_group_new ();
178 g_action_map_add_action_entries (G_ACTION_MAP (gtkinternal), gtk_application_impl_quartz_actions,
179 G_N_ELEMENTS (gtk_application_impl_quartz_actions), quartz);
180 gtk_application_insert_action_group (impl->application, "gtkinternal", G_ACTION_GROUP (gtkinternal));
181 g_object_unref (gtkinternal);
182
183 /* now setup the menu */
184 gtk_application_impl_set_menubar (impl, gtk_application_get_menubar (impl->application));
185
186 /* OK. Now put it in the menu. */
187 gtk_application_impl_quartz_setup_menu (G_MENU_MODEL (quartz->combined), quartz->muxer);
188
189 [NSApp finishLaunching];
190 }
191
192 static void
gtk_application_impl_quartz_shutdown(GtkApplicationImpl * impl)193 gtk_application_impl_quartz_shutdown (GtkApplicationImpl *impl)
194 {
195 GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
196
197 /* destroy our custom menubar */
198 [NSApp setMainMenu:[[[NSMenu alloc] init] autorelease]];
199
200 if (quartz->delegate)
201 {
202 [quartz->delegate release];
203 quartz->delegate = NULL;
204 }
205
206 g_slist_free_full (quartz->inhibitors, (GDestroyNotify) gtk_application_quartz_inhibitor_free);
207 quartz->inhibitors = NULL;
208 }
209
210 static void
on_window_unmap_cb(GtkApplicationImpl * impl,GtkWindow * window)211 on_window_unmap_cb (GtkApplicationImpl *impl,
212 GtkWindow *window)
213 {
214 GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
215
216 if ((GActionGroup *)window == gtk_action_muxer_get_group (quartz->muxer, "win"))
217 gtk_action_muxer_remove (quartz->muxer, "win");
218 }
219
220 static void
gtk_application_impl_quartz_active_window_changed(GtkApplicationImpl * impl,GtkWindow * window)221 gtk_application_impl_quartz_active_window_changed (GtkApplicationImpl *impl,
222 GtkWindow *window)
223 {
224 GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
225
226 /* Track unmapping of the window so we can clear the "win" field.
227 * Without this, we might hold on to a reference of the window
228 * preventing it from getting disposed.
229 */
230 if (window != NULL && !g_object_get_data (G_OBJECT (window), "quartz-muxer-umap"))
231 {
232 gulong handler_id = g_signal_connect_object (window,
233 "unmap",
234 G_CALLBACK (on_window_unmap_cb),
235 impl,
236 G_CONNECT_SWAPPED);
237 g_object_set_data (G_OBJECT (window),
238 "quartz-muxer-unmap",
239 GSIZE_TO_POINTER (handler_id));
240 }
241
242 gtk_action_muxer_remove (quartz->muxer, "win");
243
244 if (G_IS_ACTION_GROUP (window))
245 gtk_action_muxer_insert (quartz->muxer, "win", G_ACTION_GROUP (window));
246 }
247
248 static void
gtk_application_impl_quartz_set_menubar(GtkApplicationImpl * impl,GMenuModel * menubar)249 gtk_application_impl_quartz_set_menubar (GtkApplicationImpl *impl,
250 GMenuModel *menubar)
251 {
252 GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
253
254 /* If we have the menubar, it is a section at index '0' */
255 if (g_menu_model_get_n_items (G_MENU_MODEL (quartz->combined)))
256 g_menu_remove (quartz->combined, 0);
257
258 if (menubar)
259 g_menu_append_section (quartz->combined, NULL, menubar);
260 else
261 {
262 // Ensure that we will always have one menu.
263 char app_menu_key[] = "APP_MENU";
264 GMenuModel *app_menu = g_object_get_data (G_OBJECT (impl), app_menu_key);
265 if (app_menu == NULL)
266 {
267 GtkBuilder *builder;
268
269 // If the user didn't fill in their own menu yet, add ours.
270 builder = gtk_builder_new_from_resource ("/org/gtk/libgtk/ui/gtkapplication-quartz.ui");
271 app_menu = G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu"));
272 g_object_set_data_full (G_OBJECT (impl), app_menu_key, g_object_ref (app_menu), g_object_unref);
273 g_object_unref (builder);
274 }
275
276 g_menu_append_submenu (quartz->combined, "Application", app_menu);
277 }
278 }
279
280 static guint
gtk_application_impl_quartz_inhibit(GtkApplicationImpl * impl,GtkWindow * window,GtkApplicationInhibitFlags flags,const char * reason)281 gtk_application_impl_quartz_inhibit (GtkApplicationImpl *impl,
282 GtkWindow *window,
283 GtkApplicationInhibitFlags flags,
284 const char *reason)
285 {
286 GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
287 GtkApplicationQuartzInhibitor *inhibitor;
288
289 inhibitor = g_slice_new (GtkApplicationQuartzInhibitor);
290 inhibitor->cookie = ++quartz->next_cookie;
291 inhibitor->flags = flags;
292 inhibitor->reason = g_strdup (reason);
293 inhibitor->window = window ? g_object_ref (window) : NULL;
294
295 quartz->inhibitors = g_slist_prepend (quartz->inhibitors, inhibitor);
296
297 if (flags & GTK_APPLICATION_INHIBIT_LOGOUT)
298 quartz->quit_inhibit++;
299
300 return inhibitor->cookie;
301 }
302
303 static void
gtk_application_impl_quartz_uninhibit(GtkApplicationImpl * impl,guint cookie)304 gtk_application_impl_quartz_uninhibit (GtkApplicationImpl *impl,
305 guint cookie)
306 {
307 GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
308 GSList *iter;
309
310 for (iter = quartz->inhibitors; iter; iter = iter->next)
311 {
312 GtkApplicationQuartzInhibitor *inhibitor = iter->data;
313
314 if (inhibitor->cookie == cookie)
315 {
316 if (inhibitor->flags & GTK_APPLICATION_INHIBIT_LOGOUT)
317 quartz->quit_inhibit--;
318 gtk_application_quartz_inhibitor_free (inhibitor);
319 quartz->inhibitors = g_slist_delete_link (quartz->inhibitors, iter);
320 return;
321 }
322 }
323
324 g_warning ("Invalid inhibitor cookie");
325 }
326
327 static void
gtk_application_impl_quartz_init(GtkApplicationImplQuartz * quartz)328 gtk_application_impl_quartz_init (GtkApplicationImplQuartz *quartz)
329 {
330 /* This is required so that Cocoa is not going to parse the
331 command line arguments by itself and generate OpenFile events.
332 We already parse the command line ourselves, so this is needed
333 to prevent opening files twice, etc. */
334 [[NSUserDefaults standardUserDefaults] setObject:@"NO"
335 forKey:@"NSTreatUnknownArgumentsAsOpen"];
336
337 quartz->combined = g_menu_new ();
338 }
339
340 static void
gtk_application_impl_quartz_finalize(GObject * object)341 gtk_application_impl_quartz_finalize (GObject *object)
342 {
343 GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) object;
344
345 g_clear_object (&quartz->combined);
346
347 G_OBJECT_CLASS (gtk_application_impl_quartz_parent_class)->finalize (object);
348 }
349
350 static void
gtk_application_impl_quartz_class_init(GtkApplicationImplClass * class)351 gtk_application_impl_quartz_class_init (GtkApplicationImplClass *class)
352 {
353 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
354
355 class->startup = gtk_application_impl_quartz_startup;
356 class->shutdown = gtk_application_impl_quartz_shutdown;
357 class->active_window_changed = gtk_application_impl_quartz_active_window_changed;
358 class->set_menubar = gtk_application_impl_quartz_set_menubar;
359 class->inhibit = gtk_application_impl_quartz_inhibit;
360 class->uninhibit = gtk_application_impl_quartz_uninhibit;
361
362 gobject_class->finalize = gtk_application_impl_quartz_finalize;
363 }
364