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