1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 #include "config.h"
4 
5 #include "cinnamon-global-private.h"
6 
7 static CinnamonGlobal *the_object = NULL;
8 
9 static void grab_notify (GtkWidget *widget, gboolean is_grab, gpointer user_data);
10 
11 enum {
12   PROP_0,
13 
14   PROP_OVERLAY_GROUP,
15   PROP_SCREEN,
16   PROP_GDK_SCREEN,
17   PROP_DISPLAY,
18   PROP_SCREEN_WIDTH,
19   PROP_SCREEN_HEIGHT,
20   PROP_STAGE,
21   PROP_STAGE_INPUT_MODE,
22   PROP_BOTTOM_WINDOW_GROUP,
23   PROP_WINDOW_GROUP,
24   PROP_TOP_WINDOW_GROUP,
25   PROP_BACKGROUND_ACTOR,
26   PROP_WINDOW_MANAGER,
27   PROP_SETTINGS,
28   PROP_DATADIR,
29   PROP_IMAGEDIR,
30   PROP_USERDATADIR,
31   PROP_FOCUS_MANAGER,
32   PROP_UI_SCALE,
33   PROP_SESSION_RUNNING
34 };
35 
36 /* Signals */
37 enum
38 {
39  XDND_POSITION_CHANGED,
40  XDND_LEAVE,
41  XDND_ENTER,
42  NOTIFY_ERROR,
43  SCALE_CHANGED,
44  SHUTDOWN,
45  LAST_SIGNAL
46 };
47 
48 G_DEFINE_TYPE(CinnamonGlobal, cinnamon_global, G_TYPE_OBJECT);
49 
50 static guint cinnamon_global_signals [LAST_SIGNAL] = { 0 };
51 
52 static void
cinnamon_global_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)53 cinnamon_global_set_property(GObject         *object,
54                           guint            prop_id,
55                           const GValue    *value,
56                           GParamSpec      *pspec)
57 {
58   CinnamonGlobal *global = CINNAMON_GLOBAL (object);
59 
60   switch (prop_id)
61     {
62     case PROP_STAGE_INPUT_MODE:
63       cinnamon_global_set_stage_input_mode (global, g_value_get_enum (value));
64       break;
65     case PROP_SESSION_RUNNING:
66       global->session_running = g_value_get_boolean (value);
67       break;
68     default:
69       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
70       break;
71     }
72 }
73 
74 static void
cinnamon_global_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)75 cinnamon_global_get_property(GObject         *object,
76                           guint            prop_id,
77                           GValue          *value,
78                           GParamSpec      *pspec)
79 {
80   CinnamonGlobal *global = CINNAMON_GLOBAL (object);
81 
82   switch (prop_id)
83     {
84     case PROP_OVERLAY_GROUP:
85       g_value_set_object (value, meta_get_overlay_group_for_screen (global->meta_screen));
86       break;
87     case PROP_SCREEN:
88       g_value_set_object (value, global->meta_screen);
89       break;
90     case PROP_GDK_SCREEN:
91       g_value_set_object (value, global->gdk_screen);
92       break;
93     case PROP_DISPLAY:
94       g_value_set_object (value, global->meta_display);
95       break;
96     case PROP_SCREEN_WIDTH:
97       {
98         int width, height;
99 
100         meta_screen_get_size (global->meta_screen, &width, &height);
101         g_value_set_int (value, width);
102       }
103       break;
104     case PROP_SCREEN_HEIGHT:
105       {
106         int width, height;
107 
108         meta_screen_get_size (global->meta_screen, &width, &height);
109         g_value_set_int (value, height);
110       }
111       break;
112     case PROP_STAGE:
113       g_value_set_object (value, global->stage);
114       break;
115     case PROP_STAGE_INPUT_MODE:
116       g_value_set_enum (value, global->input_mode);
117       break;
118     case PROP_BOTTOM_WINDOW_GROUP:
119       g_value_set_object (value, meta_get_bottom_window_group_for_screen (global->meta_screen));
120       break;
121     case PROP_WINDOW_GROUP:
122       g_value_set_object (value, meta_get_window_group_for_screen (global->meta_screen));
123       break;
124     case PROP_TOP_WINDOW_GROUP:
125       g_value_set_object (value, meta_get_top_window_group_for_screen (global->meta_screen));
126       break;
127     case PROP_BACKGROUND_ACTOR:
128       g_value_set_object (value, meta_get_background_actor_for_screen (global->meta_screen));
129       break;
130     case PROP_WINDOW_MANAGER:
131       g_value_set_object (value, global->wm);
132       break;
133     case PROP_SETTINGS:
134       g_value_set_object (value, global->settings);
135       break;
136     case PROP_DATADIR:
137       g_value_set_string (value, global->datadir);
138       break;
139     case PROP_IMAGEDIR:
140       g_value_set_string (value, global->imagedir);
141       break;
142     case PROP_USERDATADIR:
143       g_value_set_string (value, global->userdatadir);
144       break;
145     case PROP_FOCUS_MANAGER:
146       g_value_set_object (value, global->focus_manager);
147       break;
148     case PROP_UI_SCALE:
149       g_value_set_uint (value, global->ui_scale);
150       break;
151     case PROP_SESSION_RUNNING:
152       g_value_set_boolean (value, global->session_running);
153       break;
154     default:
155       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
156       break;
157     }
158 }
159 
160 static void
cinnamon_global_init(CinnamonGlobal * global)161 cinnamon_global_init (CinnamonGlobal *global)
162 {
163   const char *datadir = g_getenv ("CINNAMON_DATADIR");
164   const char *cinnamon_js = g_getenv("CINNAMON_JS");
165   char *imagedir, **search_path;
166 
167   if (!datadir)
168     datadir = CINNAMON_DATADIR;
169   global->datadir = datadir;
170 
171   /* We make sure imagedir ends with a '/', since the JS won't have
172    * access to g_build_filename() and so will end up just
173    * concatenating global.imagedir to a filename.
174    */
175   imagedir = g_build_filename (datadir, "images/", NULL);
176   if (g_file_test (imagedir, G_FILE_TEST_IS_DIR))
177     global->imagedir = imagedir;
178   else
179     {
180       g_free (imagedir);
181       global->imagedir = g_strdup_printf ("%s/", datadir);
182     }
183 
184   /* Ensure config dir exists for later use */
185   global->userdatadir = g_build_filename (g_get_user_data_dir (), "cinnamon", NULL);
186   g_mkdir_with_parents (global->userdatadir, 0700);
187 
188   global->settings = g_settings_new ("org.cinnamon");
189 
190   global->ui_scale = 1;
191 
192   global->grab_notifier = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
193   g_signal_connect (global->grab_notifier, "grab-notify", G_CALLBACK (grab_notify), global);
194   global->gtk_grab_active = FALSE;
195 
196   global->input_mode = CINNAMON_STAGE_INPUT_MODE_NORMAL;
197 
198   if (!cinnamon_js)
199     cinnamon_js = JSDIR;
200   search_path = g_strsplit (cinnamon_js, ":", -1);
201   global->js_context = g_object_new (GJS_TYPE_CONTEXT,
202                                      "profiler-sigusr2", true,
203                                      "search-path", search_path,
204                                      NULL);
205 
206   g_strfreev (search_path);
207 }
208 
209 static void
cinnamon_global_finalize(GObject * object)210 cinnamon_global_finalize (GObject *object)
211 {
212   CinnamonGlobal *global = CINNAMON_GLOBAL (object);
213   g_object_unref (global->js_context);
214 
215   gtk_widget_destroy (GTK_WIDGET (global->grab_notifier));
216   g_object_unref (global->settings);
217   g_object_unref (global->interface_settings);
218 
219   the_object = NULL;
220 
221   G_OBJECT_CLASS(cinnamon_global_parent_class)->finalize (object);
222 }
223 
224 static void
cinnamon_global_class_init(CinnamonGlobalClass * klass)225 cinnamon_global_class_init (CinnamonGlobalClass *klass)
226 {
227   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
228 
229   gobject_class->get_property = cinnamon_global_get_property;
230   gobject_class->set_property = cinnamon_global_set_property;
231   gobject_class->finalize = cinnamon_global_finalize;
232 
233   /* Emitted from cinnamon-plugin.c during event handling */
234   cinnamon_global_signals[XDND_POSITION_CHANGED] =
235       g_signal_new ("xdnd-position-changed",
236                     G_TYPE_FROM_CLASS (klass),
237                     G_SIGNAL_RUN_LAST,
238                     0,
239                     NULL, NULL, NULL,
240                     G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
241 
242   /* Emitted from cinnamon-plugin.c during event handling */
243   cinnamon_global_signals[XDND_LEAVE] =
244       g_signal_new ("xdnd-leave",
245                     G_TYPE_FROM_CLASS (klass),
246                     G_SIGNAL_RUN_LAST,
247                     0,
248                     NULL, NULL, NULL,
249                     G_TYPE_NONE, 0);
250 
251   /* Emitted from cinnamon-plugin.c during event handling */
252   cinnamon_global_signals[XDND_ENTER] =
253       g_signal_new ("xdnd-enter",
254                     G_TYPE_FROM_CLASS (klass),
255                     G_SIGNAL_RUN_LAST,
256                     0,
257                     NULL, NULL, NULL,
258                     G_TYPE_NONE, 0);
259 
260   cinnamon_global_signals[NOTIFY_ERROR] =
261       g_signal_new ("notify-error",
262                     G_TYPE_FROM_CLASS (klass),
263                     G_SIGNAL_RUN_LAST,
264                     0,
265                     NULL, NULL, NULL,
266                     G_TYPE_NONE, 2,
267                     G_TYPE_STRING,
268                     G_TYPE_STRING);
269 
270   cinnamon_global_signals[SCALE_CHANGED] =
271       g_signal_new ("scale-changed",
272                     G_TYPE_FROM_CLASS (klass),
273                     G_SIGNAL_RUN_LAST,
274                     0,
275                     NULL, NULL, NULL,
276                     G_TYPE_NONE, 0);
277 
278   cinnamon_global_signals[SHUTDOWN] =
279       g_signal_new ("shutdown",
280                     G_TYPE_FROM_CLASS (klass),
281                     G_SIGNAL_RUN_LAST,
282                     0,
283                     NULL, NULL, NULL,
284                     G_TYPE_NONE, 0);
285 
286   g_object_class_install_property (gobject_class,
287                                    PROP_OVERLAY_GROUP,
288                                    g_param_spec_object ("overlay-group",
289                                                         "Overlay Group",
290                                                         "Actor holding objects that appear above the desktop contents",
291                                                         CLUTTER_TYPE_ACTOR,
292                                                         G_PARAM_READABLE));
293   g_object_class_install_property (gobject_class,
294                                    PROP_SCREEN,
295                                    g_param_spec_object ("screen",
296                                                         "Screen",
297                                                         "Metacity screen object for Cinnamon",
298                                                         META_TYPE_SCREEN,
299                                                         G_PARAM_READABLE));
300 
301   g_object_class_install_property (gobject_class,
302                                    PROP_GDK_SCREEN,
303                                    g_param_spec_object ("gdk-screen",
304                                                         "GdkScreen",
305                                                         "Gdk screen object for Cinnamon",
306                                                         GDK_TYPE_SCREEN,
307                                                         G_PARAM_READABLE));
308 
309   g_object_class_install_property (gobject_class,
310                                    PROP_SCREEN_WIDTH,
311                                    g_param_spec_int ("screen-width",
312                                                      "Screen Width",
313                                                      "Screen width, in pixels",
314                                                      0, G_MAXINT, 1,
315                                                      G_PARAM_READABLE));
316 
317   g_object_class_install_property (gobject_class,
318                                    PROP_SCREEN_HEIGHT,
319                                    g_param_spec_int ("screen-height",
320                                                      "Screen Height",
321                                                      "Screen height, in pixels",
322                                                      0, G_MAXINT, 1,
323                                                      G_PARAM_READABLE));
324   g_object_class_install_property (gobject_class,
325                                    PROP_DISPLAY,
326                                    g_param_spec_object ("display",
327                                                         "Display",
328                                                         "Metacity display object for Cinnamon",
329                                                         META_TYPE_DISPLAY,
330                                                         G_PARAM_READABLE));
331 
332   g_object_class_install_property (gobject_class,
333                                    PROP_STAGE,
334                                    g_param_spec_object ("stage",
335                                                         "Stage",
336                                                         "Stage holding the desktop scene graph",
337                                                         CLUTTER_TYPE_ACTOR,
338                                                         G_PARAM_READABLE));
339   g_object_class_install_property (gobject_class,
340                                    PROP_STAGE_INPUT_MODE,
341                                    g_param_spec_enum ("stage-input-mode",
342                                                       "Stage input mode",
343                                                       "The stage input mode",
344                                                       CINNAMON_TYPE_STAGE_INPUT_MODE,
345                                                       CINNAMON_STAGE_INPUT_MODE_NORMAL,
346                                                       G_PARAM_READWRITE));
347   g_object_class_install_property (gobject_class,
348                                    PROP_BOTTOM_WINDOW_GROUP,
349                                    g_param_spec_object ("bottom-window-group",
350                                                         "Bottom Window Group",
351                                                         "Actor holding window actors that must appear below desklets",
352                                                         CLUTTER_TYPE_ACTOR,
353                                                         G_PARAM_READABLE));
354   g_object_class_install_property (gobject_class,
355                                    PROP_WINDOW_GROUP,
356                                    g_param_spec_object ("window-group",
357                                                         "Window Group",
358                                                         "Actor holding window actors",
359                                                         CLUTTER_TYPE_ACTOR,
360                                                         G_PARAM_READABLE));
361   g_object_class_install_property (gobject_class,
362                                    PROP_TOP_WINDOW_GROUP,
363                                    g_param_spec_object ("top-window-group",
364                                                         "Top Window Group",
365                                                         "Actor holding popup menus and other actors which must appear on top of the panels",
366                                                         CLUTTER_TYPE_ACTOR,
367                                                         G_PARAM_READABLE));
368   g_object_class_install_property (gobject_class,
369                                    PROP_BACKGROUND_ACTOR,
370                                    g_param_spec_object ("background-actor",
371                                                         "Background Actor",
372                                                         "Actor drawing root window background",
373                                                         CLUTTER_TYPE_ACTOR,
374                                                         G_PARAM_READABLE));
375   g_object_class_install_property (gobject_class,
376                                    PROP_WINDOW_MANAGER,
377                                    g_param_spec_object ("window-manager",
378                                                         "Window Manager",
379                                                         "Window management interface",
380                                                         CINNAMON_TYPE_WM,
381                                                         G_PARAM_READABLE));
382   g_object_class_install_property (gobject_class,
383                                    PROP_SETTINGS,
384                                    g_param_spec_object ("settings",
385                                                         "Settings",
386                                                         "GSettings instance for Cinnamon configuration",
387                                                         G_TYPE_SETTINGS,
388                                                         G_PARAM_READABLE));
389   g_object_class_install_property (gobject_class,
390                                    PROP_DATADIR,
391                                    g_param_spec_string ("datadir",
392                                                         "Data directory",
393                                                         "Directory containing Cinnamon data files",
394                                                         NULL,
395                                                         G_PARAM_READABLE));
396   g_object_class_install_property (gobject_class,
397                                    PROP_IMAGEDIR,
398                                    g_param_spec_string ("imagedir",
399                                                         "Image directory",
400                                                         "Directory containing Cinnamon image files",
401                                                         NULL,
402                                                         G_PARAM_READABLE));
403   g_object_class_install_property (gobject_class,
404                                    PROP_USERDATADIR,
405                                    g_param_spec_string ("userdatadir",
406                                                         "User data directory",
407                                                         "Directory containing Cinnamon user data",
408                                                         NULL,
409                                                         G_PARAM_READABLE));
410   g_object_class_install_property (gobject_class,
411                                    PROP_FOCUS_MANAGER,
412                                    g_param_spec_object ("focus-manager",
413                                                         "Focus manager",
414                                                         "Cinnamon's StFocusManager",
415                                                         ST_TYPE_FOCUS_MANAGER,
416                                                         G_PARAM_READABLE));
417 
418   g_object_class_install_property (gobject_class,
419                                    PROP_UI_SCALE,
420                                    g_param_spec_uint ("ui-scale",
421                                                       "Current UI Scale",
422                                                       "Current UI Scale",
423                                                       0, G_MAXUINT, 1,
424                                                       G_PARAM_READABLE));
425 
426   g_object_class_install_property (gobject_class,
427                                    PROP_SESSION_RUNNING,
428                                    g_param_spec_boolean ("session-running",
429                                                          "Session state",
430                                                          "If the session startup has already finished",
431                                                          FALSE,
432                                                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
433 }
434 
435 /**
436  * _cinnamon_global_init: (skip)
437  * @first_property_name: the name of the first property
438  * @...: the value of the first property, followed optionally by more
439  *  name/value pairs, followed by %NULL
440  *
441  * Initializes Cinnamon global singleton with the construction-time
442  * properties.
443  *
444  * There are currently no such properties, so @first_property_name should
445  * always be %NULL.
446  *
447  * This call must be called before cinnamon_global_get() and shouldn't be called
448  * more than once.
449  */
450 void
_cinnamon_global_init(const char * first_property_name,...)451 _cinnamon_global_init (const char *first_property_name,
452                     ...)
453 {
454   va_list argument_list;
455 
456   g_return_if_fail (the_object == NULL);
457 
458   va_start (argument_list, first_property_name);
459   the_object = CINNAMON_GLOBAL (g_object_new_valist (CINNAMON_TYPE_GLOBAL,
460                                                   first_property_name,
461                                                   argument_list));
462   va_end (argument_list);
463 
464 }
465 
466 /**
467  * cinnamon_global_get:
468  *
469  * Gets the singleton global object that represents the desktop.
470  *
471  * Return value: (transfer none): the singleton global object
472  */
473 CinnamonGlobal *
cinnamon_global_get(void)474 cinnamon_global_get (void)
475 {
476   return the_object;
477 }
478 
479 static void
focus_window_changed(MetaDisplay * display,GParamSpec * param,gpointer user_data)480 focus_window_changed (MetaDisplay *display,
481                       GParamSpec  *param,
482                       gpointer     user_data)
483 {
484   CinnamonGlobal *global = user_data;
485 
486   if (global->input_mode == CINNAMON_STAGE_INPUT_MODE_FOCUSED &&
487       meta_display_get_focus_window (display) != NULL)
488     cinnamon_global_set_stage_input_mode (global, CINNAMON_STAGE_INPUT_MODE_NORMAL);
489 }
490 
491 static void
cinnamon_global_focus_stage(CinnamonGlobal * global)492 cinnamon_global_focus_stage (CinnamonGlobal *global)
493 {
494   XSetInputFocus (global->xdisplay, global->stage_xwindow,
495                   RevertToPointerRoot,
496                   cinnamon_global_get_current_time (global));
497 }
498 
499 /**
500  * cinnamon_global_set_stage_input_mode:
501  * @global: the #CinnamonGlobal
502  * @mode: the stage input mode
503  *
504  * Sets the input mode of the stage; when @mode is
505  * %CINNAMON_STAGE_INPUT_MODE_NONREACTIVE, then the stage does not absorb
506  * any clicks, but just passes them through to underlying windows.
507  * When it is %CINNAMON_STAGE_INPUT_MODE_NORMAL, then the stage accepts
508  * clicks in the region defined by
509  * cinnamon_global_set_stage_input_region() but passes through clicks
510  * outside that region. When it is %CINNAMON_STAGE_INPUT_MODE_FULLSCREEN,
511  * the stage absorbs all input.
512  *
513  * When the input mode is %CINNAMON_STAGE_INPUT_MODE_FOCUSED, the pointer
514  * is handled as with %CINNAMON_STAGE_INPUT_MODE_NORMAL, but additionally
515  * the stage window has the keyboard focus. If the stage loses the
516  * focus (eg, because the user clicked into a window) the input mode
517  * will revert to %CINNAMON_STAGE_INPUT_MODE_NORMAL.
518  *
519  * Note that whenever a muffin-internal Gtk widget has a pointer grab,
520  * Cinnamon behaves as though it was in
521  * %CINNAMON_STAGE_INPUT_MODE_NONREACTIVE, to ensure that the widget gets
522  * any clicks it is expecting.
523  */
524 void
cinnamon_global_set_stage_input_mode(CinnamonGlobal * global,CinnamonStageInputMode mode)525 cinnamon_global_set_stage_input_mode (CinnamonGlobal         *global,
526                                    CinnamonStageInputMode  mode)
527 {
528   MetaScreen *screen;
529 
530   g_return_if_fail (CINNAMON_IS_GLOBAL (global));
531 
532   screen = meta_plugin_get_screen (global->plugin);
533 
534   if (mode == CINNAMON_STAGE_INPUT_MODE_NONREACTIVE || global->gtk_grab_active)
535     meta_empty_stage_input_region (screen);
536   else if (mode == CINNAMON_STAGE_INPUT_MODE_FULLSCREEN || !global->input_region)
537     meta_set_stage_input_region (screen, None);
538   else
539     meta_set_stage_input_region (screen, global->input_region);
540 
541   if (mode == CINNAMON_STAGE_INPUT_MODE_FOCUSED)
542     cinnamon_global_focus_stage (global);
543 
544   if (mode != global->input_mode)
545     {
546       global->input_mode = mode;
547       g_object_notify (G_OBJECT (global), "stage-input-mode");
548     }
549 }
550 
551 /**
552  * cinnamon_global_set_cursor:
553  * @global: A #CinnamonGlobal
554  * @type: the type of the cursor
555  *
556  * Set the cursor on the stage window.
557  */
558 void
cinnamon_global_set_cursor(CinnamonGlobal * global,CinnamonCursor type)559 cinnamon_global_set_cursor (CinnamonGlobal *global,
560                          CinnamonCursor type)
561 {
562   const char *name;
563   GdkCursor *cursor;
564 
565   switch (type)
566     {
567     case CINNAMON_CURSOR_DND_IN_DRAG:
568       name = "dnd-none";
569       break;
570     case CINNAMON_CURSOR_DND_MOVE:
571       name = "dnd-move";
572       break;
573     case CINNAMON_CURSOR_DND_COPY:
574       name = "dnd-copy";
575       break;
576     case CINNAMON_CURSOR_DND_UNSUPPORTED_TARGET:
577       name = "X_cursor";
578       break;
579     case CINNAMON_CURSOR_POINTING_HAND:
580       name = "hand";
581       break;
582     case CINNAMON_CURSOR_RESIZE_BOTTOM:
583       name = "bottom_side";
584       break;
585     case CINNAMON_CURSOR_RESIZE_TOP:
586       name = "top_side";
587       break;
588     case CINNAMON_CURSOR_RESIZE_LEFT:
589       name = "left_side";
590       break;
591     case CINNAMON_CURSOR_RESIZE_RIGHT:
592       name = "right_side";
593       break;
594     case CINNAMON_CURSOR_RESIZE_BOTTOM_RIGHT:
595       name = "bottom_right_corner";
596       break;
597     case CINNAMON_CURSOR_RESIZE_BOTTOM_LEFT:
598       name = "bottom_left_corner";
599       break;
600     case CINNAMON_CURSOR_RESIZE_TOP_RIGHT:
601       name = "top_right_corner";
602       break;
603     case CINNAMON_CURSOR_RESIZE_TOP_LEFT:
604       name = "top_left_corner";
605       break;
606     case CINNAMON_CURSOR_CROSSHAIR:
607       name = "crosshair";
608       break;
609     case CINNAMON_CURSOR_TEXT:
610       name = "xterm";
611       break;
612     default:
613       g_return_if_reached ();
614     }
615 
616   cursor = gdk_cursor_new_from_name (global->gdk_display, name);
617   if (!cursor)
618     {
619       GdkCursorType cursor_type;
620       switch (type)
621         {
622         case CINNAMON_CURSOR_DND_IN_DRAG:
623           cursor_type = GDK_FLEUR;
624           break;
625         case CINNAMON_CURSOR_DND_MOVE:
626           cursor_type = GDK_TARGET;
627           break;
628         case CINNAMON_CURSOR_DND_COPY:
629           cursor_type = GDK_PLUS;
630           break;
631         case CINNAMON_CURSOR_POINTING_HAND:
632           cursor_type = GDK_HAND2;
633           break;
634         case CINNAMON_CURSOR_DND_UNSUPPORTED_TARGET:
635           cursor_type = GDK_X_CURSOR;
636           break;
637         case CINNAMON_CURSOR_RESIZE_BOTTOM:
638           cursor_type = GDK_BOTTOM_SIDE;
639           break;
640         case CINNAMON_CURSOR_RESIZE_TOP:
641           cursor_type = GDK_TOP_SIDE;
642           break;
643         case CINNAMON_CURSOR_RESIZE_LEFT:
644           cursor_type = GDK_LEFT_SIDE;
645           break;
646         case CINNAMON_CURSOR_RESIZE_RIGHT:
647           cursor_type = GDK_RIGHT_SIDE;
648           break;
649         case CINNAMON_CURSOR_RESIZE_BOTTOM_RIGHT:
650           cursor_type = GDK_BOTTOM_RIGHT_CORNER;
651           break;
652         case CINNAMON_CURSOR_RESIZE_BOTTOM_LEFT:
653           cursor_type = GDK_BOTTOM_LEFT_CORNER;
654           break;
655         case CINNAMON_CURSOR_RESIZE_TOP_RIGHT:
656           cursor_type = GDK_TOP_RIGHT_CORNER;
657           break;
658         case CINNAMON_CURSOR_RESIZE_TOP_LEFT:
659           cursor_type = GDK_TOP_LEFT_CORNER;
660           break;
661         case CINNAMON_CURSOR_CROSSHAIR:
662           cursor_type = GDK_CROSSHAIR;
663           break;
664         case CINNAMON_CURSOR_TEXT:
665           cursor_type = GDK_XTERM;
666           break;
667         default:
668           g_return_if_reached ();
669         }
670       cursor = gdk_cursor_new_for_display (gdk_display_get_default(), cursor_type);
671     }
672 
673   gdk_window_set_cursor (global->stage_gdk_window, cursor);
674 
675   g_object_unref (cursor);
676 }
677 
678 /**
679  * cinnamon_global_unset_cursor:
680  * @global: A #CinnamonGlobal
681  *
682  * Unset the cursor on the stage window.
683  */
684 void
cinnamon_global_unset_cursor(CinnamonGlobal * global)685 cinnamon_global_unset_cursor (CinnamonGlobal  *global)
686 {
687   gdk_window_set_cursor (global->stage_gdk_window, NULL);
688 }
689 
690 /**
691  * cinnamon_global_set_stage_input_region:
692  * @global: the #CinnamonGlobal
693  * @rectangles: (element-type Meta.Rectangle): a list of #MetaRectangle
694  * describing the input region.
695  *
696  * Sets the area of the stage that is responsive to mouse clicks when
697  * the stage mode is %CINNAMON_STAGE_INPUT_MODE_NORMAL (but does not change the
698  * current stage mode).
699  */
700 void
cinnamon_global_set_stage_input_region(CinnamonGlobal * global,GSList * rectangles)701 cinnamon_global_set_stage_input_region (CinnamonGlobal *global,
702                                      GSList      *rectangles)
703 {
704   MetaRectangle *rect;
705   XRectangle *rects;
706   int nrects, i;
707   GSList *r;
708 
709   g_return_if_fail (CINNAMON_IS_GLOBAL (global));
710 
711   nrects = g_slist_length (rectangles);
712   rects = g_new (XRectangle, nrects);
713   for (r = rectangles, i = 0; r; r = r->next, i++)
714     {
715       rect = (MetaRectangle *)r->data;
716       rects[i].x = rect->x;
717       rects[i].y = rect->y;
718       rects[i].width = rect->width;
719       rects[i].height = rect->height;
720     }
721 
722   if (global->input_region)
723     XFixesDestroyRegion (global->xdisplay, global->input_region);
724 
725   global->input_region = XFixesCreateRegion (global->xdisplay, rects, nrects);
726   g_free (rects);
727 
728   /* set_stage_input_mode() will figure out whether or not we
729    * should actually change the input region right now.
730    */
731   cinnamon_global_set_stage_input_mode (global, global->input_mode);
732 }
733 
734 /**
735  * cinnamon_global_get_stage:
736  *
737  * Return value: (transfer none): The default #ClutterStage
738  */
739 ClutterStage *
cinnamon_global_get_stage(CinnamonGlobal * global)740 cinnamon_global_get_stage (CinnamonGlobal  *global)
741 {
742   return global->stage;
743 }
744 
745 /**
746  * cinnamon_global_get_screen:
747  *
748  * Return value: (transfer none): The default #MetaScreen
749  */
750 MetaScreen *
cinnamon_global_get_screen(CinnamonGlobal * global)751 cinnamon_global_get_screen (CinnamonGlobal  *global)
752 {
753   return global->meta_screen;
754 }
755 
756 /**
757  * cinnamon_global_get_gdk_screen:
758  *
759  * Return value: (transfer none): Gdk screen object for Cinnamon
760  */
761 GdkScreen *
cinnamon_global_get_gdk_screen(CinnamonGlobal * global)762 cinnamon_global_get_gdk_screen (CinnamonGlobal *global)
763 {
764   g_return_val_if_fail (CINNAMON_IS_GLOBAL (global), NULL);
765 
766   return global->gdk_screen;
767 }
768 
769 /**
770  * cinnamon_global_get_display:
771  *
772  * Return value: (transfer none): The default #MetaDisplay
773  */
774 MetaDisplay *
cinnamon_global_get_display(CinnamonGlobal * global)775 cinnamon_global_get_display (CinnamonGlobal  *global)
776 {
777   return global->meta_display;
778 }
779 
780 /**
781  * cinnamon_global_get_window_actors:
782  *
783  * Gets the list of #MetaWindowActor for the plugin's screen
784  *
785  * Return value: (element-type Meta.WindowActor) (transfer none): the list of windows
786  */
787 GList *
cinnamon_global_get_window_actors(CinnamonGlobal * global)788 cinnamon_global_get_window_actors (CinnamonGlobal *global)
789 {
790   g_return_val_if_fail (CINNAMON_IS_GLOBAL (global), NULL);
791 
792   return meta_get_window_actors (global->meta_screen);
793 }
794 
795 static void
global_stage_notify_width(GObject * gobject,GParamSpec * pspec,gpointer data)796 global_stage_notify_width (GObject    *gobject,
797                            GParamSpec *pspec,
798                            gpointer    data)
799 {
800   CinnamonGlobal *global = CINNAMON_GLOBAL (data);
801 
802   g_object_notify (G_OBJECT (global), "screen-width");
803 }
804 
805 static void
global_stage_notify_height(GObject * gobject,GParamSpec * pspec,gpointer data)806 global_stage_notify_height (GObject    *gobject,
807                             GParamSpec *pspec,
808                             gpointer    data)
809 {
810   CinnamonGlobal *global = CINNAMON_GLOBAL (data);
811 
812   g_object_notify (G_OBJECT (global), "screen-height");
813 }
814 
815 static gboolean
global_stage_before_paint(gpointer data)816 global_stage_before_paint (gpointer data)
817 {
818   cinnamon_perf_log_event (cinnamon_perf_log_get_default (),
819                         "clutter.stagePaintStart");
820 
821   return TRUE;
822 }
823 
824 static gboolean
global_stage_after_paint(gpointer data)825 global_stage_after_paint (gpointer data)
826 {
827   cinnamon_perf_log_event (cinnamon_perf_log_get_default (),
828                         "clutter.stagePaintDone");
829 
830   return TRUE;
831 }
832 
833 static void
cinnamon_fonts_init(ClutterStage * stage)834 cinnamon_fonts_init (ClutterStage *stage)
835 {
836   CoglPangoFontMap *fontmap;
837 
838   /* Disable text mipmapping; it causes problems on pre-GEM Intel
839    * drivers and we should just be rendering text at the right
840    * size rather than scaling it. If we do effects where we dynamically
841    * zoom labels, then we might want to reconsider.
842    */
843   fontmap = COGL_PANGO_FONT_MAP (clutter_get_font_map ());
844   cogl_pango_font_map_set_use_mipmapping (fontmap, FALSE);
845 }
846 
847 /* This is an IBus workaround. The flow of events with IBus is that every time
848  * it gets gets a key event, it:
849  *
850  *  Sends it to the daemon via D-Bus asynchronously
851  *  When it gets an reply, synthesizes a new GdkEvent and puts it into the
852  *   GDK event queue with gdk_event_put(), including
853  *   IBUS_FORWARD_MASK = 1 << 25 in the state to prevent a loop.
854  *
855  * (Normally, IBus uses the GTK+ key snooper mechanism to get the key
856  * events early, but since our key events aren't visible to GTK+ key snoopers,
857  * IBus will instead get the events via the standard
858  * GtkIMContext.filter_keypress() mechanism.)
859  *
860  * There are a number of potential problems here; probably the worst
861  * problem is that IBus doesn't forward the timestamp with the event
862  * so that every key event that gets delivered ends up with
863  * GDK_CURRENT_TIME.  This creates some very subtle bugs; for example
864  * if you have IBus running and a keystroke is used to trigger
865  * launching an application, focus stealing prevention won't work
866  * right. http://code.google.com/p/ibus/issues/detail?id=1184
867  *
868  * In any case, our normal flow of key events is:
869  *
870  *  GDK filter function => clutter_x11_handle_event => clutter actor
871  *
872  * So, if we see a key event that gets delivered via the GDK event handler
873  * function - then we know it must be one of these synthesized events, and
874  * we should push it back to clutter.
875  *
876  * To summarize, the full key event flow with IBus is:
877  *
878  *   GDK filter function
879  *     => Mutter
880  *     => gnome_cinnamon_plugin_xevent_filter()
881  *     => clutter_x11_handle_event()
882  *     => clutter event delivery to actor
883  *     => gtk_im_context_filter_event()
884  *     => sent to IBus daemon
885  *     => response received from IBus daemon
886  *     => gdk_event_put()
887  *     => GDK event handler
888  *     => <this function>
889  *     => clutter_event_put()
890  *     => clutter event delivery to actor
891  *
892  * Anything else we see here we just pass on to the normal GDK event handler
893  * gtk_main_do_event().
894  */
895 static void
gnome_cinnamon_gdk_event_handler(GdkEvent * event_gdk,gpointer data)896 gnome_cinnamon_gdk_event_handler (GdkEvent *event_gdk,
897                                gpointer  data)
898 {
899   if (event_gdk->type == GDK_KEY_PRESS || event_gdk->type == GDK_KEY_RELEASE)
900     {
901       ClutterActor *stage;
902       Window stage_xwindow;
903 
904       stage = CLUTTER_ACTOR (data);
905       stage_xwindow = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));
906 
907       if (GDK_WINDOW_XID (event_gdk->key.window) == stage_xwindow)
908         {
909           ClutterDeviceManager *device_manager = clutter_device_manager_get_default ();
910           ClutterInputDevice *keyboard = clutter_device_manager_get_core_device (device_manager,
911                                                                                  CLUTTER_KEYBOARD_DEVICE);
912 
913           ClutterEvent *event_clutter = clutter_event_new ((event_gdk->type == GDK_KEY_PRESS) ?
914                                                            CLUTTER_KEY_PRESS : CLUTTER_KEY_RELEASE);
915           event_clutter->key.time = event_gdk->key.time;
916           event_clutter->key.flags = CLUTTER_EVENT_NONE;
917           event_clutter->key.stage = CLUTTER_STAGE (stage);
918           event_clutter->key.source = NULL;
919 
920           /* This depends on ClutterModifierType and GdkModifierType being
921            * identical, which they are currently. (They both match the X
922            * modifier state in the low 16-bits and have the same extensions.) */
923           event_clutter->key.modifier_state = event_gdk->key.state;
924 
925           event_clutter->key.keyval = event_gdk->key.keyval;
926           event_clutter->key.hardware_keycode = event_gdk->key.hardware_keycode;
927           event_clutter->key.unicode_value = gdk_keyval_to_unicode (event_clutter->key.keyval);
928           event_clutter->key.device = keyboard;
929 
930           clutter_event_put (event_clutter);
931           clutter_event_free (event_clutter);
932 
933           return;
934         }
935     }
936 
937   gtk_main_do_event (event_gdk);
938 }
939 
940 static void
update_scale_factor(GtkSettings * settings,GParamSpec * pspec,gpointer data)941 update_scale_factor (GtkSettings *settings,
942                      GParamSpec *pspec,
943                      gpointer data)
944 {
945   guint scale = 1;
946   int xft_dpi;
947   GtkSettings *gtk_settings;
948   CinnamonGlobal *global = CINNAMON_GLOBAL (data);
949   ClutterStage *stage = CLUTTER_STAGE (global->stage);
950   StThemeContext *context = st_theme_context_get_for_stage (stage);
951   GValue value = G_VALUE_INIT;
952 
953   g_value_init (&value, G_TYPE_UINT);
954   if (gdk_screen_get_setting (global->gdk_screen, "gdk-window-scaling-factor", &value)) {
955     scale = g_value_get_uint (&value);
956     g_object_set (context, "scale-factor", scale, NULL);
957 
958     if (scale != global->ui_scale) {
959         global->ui_scale = scale;
960         g_signal_emit_by_name (global, "scale-changed");
961     }
962   }
963 
964   meta_prefs_set_ui_scale (global->ui_scale);
965 
966   gtk_settings = gtk_settings_get_default ();
967 
968   g_object_get (gtk_settings, "gtk-xft-dpi", &xft_dpi, NULL);
969   g_object_set (clutter_settings_get_default (), "font-dpi", xft_dpi, NULL);
970 
971    /* Make sure gdk scaling stays disabled */
972   gdk_x11_display_set_window_scale (gdk_display_get_default (), 1);
973 }
974 
975 void
_cinnamon_global_set_plugin(CinnamonGlobal * global,MetaPlugin * plugin)976 _cinnamon_global_set_plugin (CinnamonGlobal *global,
977                           MetaPlugin  *plugin)
978 {
979   g_return_if_fail (CINNAMON_IS_GLOBAL (global));
980   g_return_if_fail (global->plugin == NULL);
981 
982   global->plugin = plugin;
983   global->wm = cinnamon_wm_new (plugin);
984 
985   global->meta_screen = meta_plugin_get_screen (plugin);
986   global->meta_display = meta_screen_get_display (global->meta_screen);
987   global->xdisplay = meta_display_get_xdisplay (global->meta_display);
988 
989   global->gdk_display = gdk_x11_lookup_xdisplay (global->xdisplay);
990   global->gdk_screen = gdk_display_get_screen (global->gdk_display,
991                                                meta_screen_get_screen_number (global->meta_screen));
992 
993   global->stage = CLUTTER_STAGE (meta_get_stage_for_screen (global->meta_screen));
994   global->stage_xwindow = clutter_x11_get_stage_window (global->stage);
995   global->stage_gdk_window = gdk_x11_window_foreign_new_for_display (global->gdk_display,
996                                                                      global->stage_xwindow);
997 
998   g_signal_connect (global->stage, "notify::width",
999                     G_CALLBACK (global_stage_notify_width), global);
1000   g_signal_connect (global->stage, "notify::height",
1001                     G_CALLBACK (global_stage_notify_height), global);
1002 
1003 
1004 
1005   if (g_getenv ("CINNAMON_PERF_OUTPUT") != NULL)
1006     {
1007       clutter_threads_add_repaint_func_full (CLUTTER_REPAINT_FLAGS_PRE_PAINT,
1008                                              (GSourceFunc) global_stage_before_paint,
1009                                              NULL, NULL);
1010       clutter_threads_add_repaint_func_full (CLUTTER_REPAINT_FLAGS_POST_PAINT,
1011                                              (GSourceFunc) global_stage_after_paint,
1012                                              NULL, NULL);
1013       cinnamon_perf_log_define_event (cinnamon_perf_log_get_default(),
1014                                       "clutter.stagePaintStart",
1015                                       "Start of stage page repaint",
1016                                       "");
1017       cinnamon_perf_log_define_event (cinnamon_perf_log_get_default(),
1018                                       "clutter.stagePaintDone",
1019                                       "End of stage page repaint",
1020                                       "");
1021     }
1022 
1023   g_signal_connect (global->meta_display, "notify::focus-window",
1024                     G_CALLBACK (focus_window_changed), global);
1025 
1026   cinnamon_fonts_init (global->stage);
1027 
1028   g_signal_connect (gtk_settings_get_default (), "notify::gtk-xft-dpi",
1029                     G_CALLBACK (update_scale_factor), global);
1030 
1031   gdk_event_handler_set (gnome_cinnamon_gdk_event_handler, global->stage, NULL);
1032 
1033   global->focus_manager = st_focus_manager_get_for_stage (global->stage);
1034 
1035   update_scale_factor (gtk_settings_get_default (), NULL, global);
1036 }
1037 
1038 /**
1039  * cinnamon_global_dump_gjs_stack:
1040  * @global: A #CinnamonGlobal
1041  *
1042  * Prints out the gjs stack
1043  */
1044 void
cinnamon_global_dump_gjs_stack(CinnamonGlobal * global)1045 cinnamon_global_dump_gjs_stack (CinnamonGlobal *global)
1046 {
1047   gjs_dumpstack ();
1048 }
1049 
1050 GjsContext *
_cinnamon_global_get_gjs_context(CinnamonGlobal * global)1051 _cinnamon_global_get_gjs_context (CinnamonGlobal *global)
1052 {
1053   return global->js_context;
1054 }
1055 
1056 /**
1057  * cinnamon_global_begin_modal:
1058  * @global: a #CinnamonGlobal
1059  *
1060  * Grabs the keyboard and mouse to the stage window. The stage will
1061  * receive all keyboard and mouse events until cinnamon_global_end_modal()
1062  * is called. This is used to implement "modes" for Cinnamon, such as the
1063  * overview mode or the "looking glass" debug overlay, that block
1064  * application and normal key shortcuts.
1065  *
1066  * Returns value: %TRUE if we successfully entered the mode. %FALSE if we couldn't
1067  *  enter the mode. Failure may occur because an application has the pointer
1068  *  or keyboard grabbed, because Muffin is in a mode itself like moving a
1069  *  window or alt-Tab window selection, or because cinnamon_global_begin_modal()
1070  *  was previouly called.
1071  */
1072 gboolean
cinnamon_global_begin_modal(CinnamonGlobal * global,guint32 timestamp,MetaModalOptions options)1073 cinnamon_global_begin_modal (CinnamonGlobal *global,
1074                           guint32      timestamp,
1075                           MetaModalOptions  options)
1076 {
1077   return meta_plugin_begin_modal (global->plugin, global->stage_xwindow,
1078                                   None, options, timestamp);
1079 }
1080 
1081 /**
1082  * cinnamon_global_end_modal:
1083  * @global: a #CinnamonGlobal
1084  *
1085  * Undoes the effect of cinnamon_global_begin_modal().
1086  */
1087 void
cinnamon_global_end_modal(CinnamonGlobal * global,guint32 timestamp)1088 cinnamon_global_end_modal (CinnamonGlobal *global,
1089                         guint32      timestamp)
1090 {
1091   meta_plugin_end_modal (global->plugin, timestamp);
1092 }
1093 
1094 /**
1095  * cinnamon_global_create_pointer_barrier:
1096  * @global: a #CinnamonGlobal
1097  * @x1: left X coordinate
1098  * @y1: top Y coordinate
1099  * @x2: right X coordinate
1100  * @y2: bottom Y coordinate
1101  * @directions: The directions we're allowed to pass through
1102  *
1103  * If supported by X creates a pointer barrier.
1104  *
1105  * Return value: value you can pass to cinnamon_global_destroy_pointer_barrier()
1106  */
1107 guint32
cinnamon_global_create_pointer_barrier(CinnamonGlobal * global,int x1,int y1,int x2,int y2,int directions)1108 cinnamon_global_create_pointer_barrier (CinnamonGlobal *global,
1109                                      int x1, int y1, int x2, int y2,
1110                                      int directions)
1111 {
1112 #if HAVE_XFIXESCREATEPOINTERBARRIER
1113   return (guint32)
1114     XFixesCreatePointerBarrier (global->xdisplay,
1115                                 DefaultRootWindow (global->xdisplay),
1116                                 x1, y1,
1117                                 x2, y2,
1118                                 directions,
1119                                 0, NULL);
1120 #else
1121   return 0;
1122 #endif
1123 }
1124 
1125 /**
1126  * cinnamon_global_destroy_pointer_barrier:
1127  * @global: a #CinnamonGlobal
1128  * @barrier: a pointer barrier
1129  *
1130  * Destroys the @barrier created by cinnamon_global_create_pointer_barrier().
1131  */
1132 void
cinnamon_global_destroy_pointer_barrier(CinnamonGlobal * global,guint32 barrier)1133 cinnamon_global_destroy_pointer_barrier (CinnamonGlobal *global, guint32 barrier)
1134 {
1135 #if HAVE_XFIXESCREATEPOINTERBARRIER
1136   g_return_if_fail (barrier > 0);
1137 
1138   XFixesDestroyPointerBarrier (global->xdisplay, (PointerBarrier)barrier);
1139 #endif
1140 }
1141 
1142 /**
1143  * cinnamon_global_reexec_self:
1144  * @global: A #CinnamonGlobal
1145  *
1146  * Restart the current process.  Only intended for development purposes.
1147  */
1148 void
cinnamon_global_reexec_self(CinnamonGlobal * global)1149 cinnamon_global_reexec_self (CinnamonGlobal *global)
1150 {
1151   meta_restart ();
1152 }
1153 
1154 void
cinnamon_global_shutdown(void)1155 cinnamon_global_shutdown (void)
1156 {
1157     g_signal_emit_by_name (the_object, "shutdown");
1158 
1159     meta_pre_exec_close_fds ();
1160 
1161     meta_display_unmanage_screen (cinnamon_global_get_display (the_object),
1162                                   cinnamon_global_get_screen (the_object),
1163                                   cinnamon_global_get_current_time (the_object));
1164 }
1165 
1166 /**
1167  * cinnamon_global_notify_error:
1168  * @global: a #CinnamonGlobal
1169  * @msg: Error message
1170  * @details: Error details
1171  *
1172  * Show a system error notification.  Use this function
1173  * when a user-initiated action results in a non-fatal problem
1174  * from causes that may not be under system control.  For
1175  * example, an application crash.
1176  */
1177 void
cinnamon_global_notify_error(CinnamonGlobal * global,const char * msg,const char * details)1178 cinnamon_global_notify_error (CinnamonGlobal  *global,
1179                            const char   *msg,
1180                            const char   *details)
1181 {
1182   g_signal_emit_by_name (global, "notify-error", msg, details);
1183 }
1184 
1185 static void
grab_notify(GtkWidget * widget,gboolean was_grabbed,gpointer user_data)1186 grab_notify (GtkWidget *widget, gboolean was_grabbed, gpointer user_data)
1187 {
1188   CinnamonGlobal *global = CINNAMON_GLOBAL (user_data);
1189 
1190   global->gtk_grab_active = !was_grabbed;
1191 
1192   /* Update for the new setting of gtk_grab_active */
1193   cinnamon_global_set_stage_input_mode (global, global->input_mode);
1194 }
1195 
1196 /**
1197  * cinnamon_global_init_xdnd:
1198  * @global: the #CinnamonGlobal
1199  *
1200  * Enables tracking of Xdnd events
1201  */
cinnamon_global_init_xdnd(CinnamonGlobal * global)1202 void cinnamon_global_init_xdnd (CinnamonGlobal *global)
1203 {
1204   Window output_window = meta_get_overlay_window (global->meta_screen);
1205   long xdnd_version = 5;
1206 
1207   XChangeProperty (global->xdisplay, global->stage_xwindow,
1208                    gdk_x11_get_xatom_by_name ("XdndAware"), XA_ATOM,
1209                    32, PropModeReplace, (const unsigned char *)&xdnd_version, 1);
1210 
1211   XChangeProperty (global->xdisplay, output_window,
1212                    gdk_x11_get_xatom_by_name ("XdndProxy"), XA_WINDOW,
1213                    32, PropModeReplace, (const unsigned char *)&global->stage_xwindow, 1);
1214 
1215   /*
1216    * XdndProxy is additionally set on the proxy window as verification that the
1217    * XdndProxy property on the target window isn't a left-over
1218    */
1219   XChangeProperty (global->xdisplay, global->stage_xwindow,
1220                    gdk_x11_get_xatom_by_name ("XdndProxy"), XA_WINDOW,
1221                    32, PropModeReplace, (const unsigned char *)&global->stage_xwindow, 1);
1222 }
1223 
1224 /**
1225  * cinnamon_global_get_pointer:
1226  * @global: the #CinnamonGlobal
1227  * @x: (out): the X coordinate of the pointer, in global coordinates
1228  * @y: (out): the Y coordinate of the pointer, in global coordinates
1229  * @mods: (out): the current set of modifier keys that are pressed down
1230  *
1231  * Gets the pointer coordinates and current modifier key state.
1232  * This is a wrapper around gdk_display_get_pointer() that strips
1233  * out any un-declared modifier flags, to make gjs happy; see
1234  * https://bugzilla.gnome.org/show_bug.cgi?id=597292.
1235  */
1236 void
cinnamon_global_get_pointer(CinnamonGlobal * global,int * x,int * y,ClutterModifierType * mods)1237 cinnamon_global_get_pointer (CinnamonGlobal         *global,
1238                           int                 *x,
1239                           int                 *y,
1240                           ClutterModifierType *mods)
1241 {
1242   GdkDeviceManager *gmanager;
1243   GdkDevice *gdevice;
1244   GdkScreen *gscreen;
1245   GdkModifierType raw_mods;
1246 
1247   gmanager = gdk_display_get_device_manager (global->gdk_display);
1248   gdevice = gdk_device_manager_get_client_pointer (gmanager);
1249   gdk_device_get_position (gdevice, &gscreen, x, y);
1250   gdk_device_get_state (gdevice,
1251                         gdk_screen_get_root_window (gscreen),
1252                         NULL,
1253                         &raw_mods);
1254   *mods = raw_mods & GDK_MODIFIER_MASK;
1255 }
1256 
1257 /**
1258  * cinnamon_global_set_pointer:
1259  * @global: the #CinnamonGlobal
1260  * @x: (in): the X coordinate of the pointer, in global coordinates
1261  * @y: (in): the Y coordinate of the pointer, in global coordinates
1262  *
1263  * Sets the pointer coordinates.
1264  * This is a wrapper around gdk_device_warp().
1265  */
1266 void
cinnamon_global_set_pointer(CinnamonGlobal * global,int x,int y)1267 cinnamon_global_set_pointer (CinnamonGlobal         *global,
1268                           int                 x,
1269                           int                 y)
1270 {
1271   GdkDeviceManager *gmanager;
1272   GdkDevice *gdevice;
1273   GdkScreen *gscreen;
1274   int x2, y2;
1275 
1276   gmanager = gdk_display_get_device_manager (global->gdk_display);
1277   gdevice = gdk_device_manager_get_client_pointer (gmanager);
1278   gdk_device_get_position (gdevice, &gscreen, &x2, &y2);
1279   gdk_device_warp (gdevice, gscreen, x, y);
1280 }
1281 
1282 /**
1283  * cinnamon_global_sync_pointer:
1284  * @global: the #CinnamonGlobal
1285  *
1286  * Ensures that clutter is aware of the current pointer position,
1287  * causing enter and leave events to be emitted if the pointer moved
1288  * behind our back (ie, during a pointer grab).
1289  */
1290 void
cinnamon_global_sync_pointer(CinnamonGlobal * global)1291 cinnamon_global_sync_pointer (CinnamonGlobal *global)
1292 {
1293   int x, y;
1294   GdkDeviceManager *gmanager;
1295   GdkDevice *gdevice;
1296   GdkScreen *gscreen;
1297   GdkModifierType mods;
1298   ClutterMotionEvent event;
1299 
1300   gmanager = gdk_display_get_device_manager (global->gdk_display);
1301   gdevice = gdk_device_manager_get_client_pointer (gmanager);
1302   gdk_device_get_position (gdevice, &gscreen, &x, &y);
1303   gdk_device_get_state (gdevice,
1304                         gdk_screen_get_root_window (gscreen),
1305                         NULL,
1306                         &mods);
1307 
1308   event.type = CLUTTER_MOTION;
1309   event.time = cinnamon_global_get_current_time (global);
1310   event.flags = 0;
1311   /* This is wrong: we should be setting event.stage to NULL if the
1312    * pointer is not inside the bounds of the stage given the current
1313    * stage_input_mode. For our current purposes however, this works.
1314    */
1315   event.stage = global->stage;
1316   event.x = x;
1317   event.y = y;
1318   event.modifier_state = mods;
1319   event.axes = NULL;
1320   event.device = clutter_device_manager_get_core_device (clutter_device_manager_get_default (),
1321                                                          CLUTTER_POINTER_DEVICE);
1322 
1323   /* Leaving event.source NULL will force clutter to look it up, which
1324    * will generate enter/leave events as a side effect, if they are
1325    * needed. We need a better way to do this though... see
1326    * http://bugzilla.clutter-project.org/show_bug.cgi?id=2615.
1327    */
1328   event.source = NULL;
1329 
1330   clutter_event_put ((ClutterEvent *)&event);
1331 }
1332 
1333 /**
1334  * cinnamon_global_get_settings:
1335  * @global: A #CinnamonGlobal
1336  *
1337  * Get the global GSettings instance.
1338  *
1339  * Return value: (transfer none): The GSettings object
1340  */
1341 GSettings *
cinnamon_global_get_settings(CinnamonGlobal * global)1342 cinnamon_global_get_settings (CinnamonGlobal *global)
1343 {
1344   return global->settings;
1345 }
1346 
1347 /**
1348  * cinnamon_global_get_current_time:
1349  * @global: A #CinnamonGlobal
1350  *
1351  * Returns: the current X server time from the current Clutter, Gdk, or X
1352  * event. If called from outside an event handler, this may return
1353  * %Clutter.CURRENT_TIME (aka 0), or it may return a slightly
1354  * out-of-date timestamp.
1355  */
1356 guint32
cinnamon_global_get_current_time(CinnamonGlobal * global)1357 cinnamon_global_get_current_time (CinnamonGlobal *global)
1358 {
1359   guint32 time;
1360 
1361   /* In case we have a xdnd timestamp use it */
1362   if (global->xdnd_timestamp != 0)
1363     return global->xdnd_timestamp;
1364 
1365   /* meta_display_get_current_time() will return the correct time
1366      when handling an X or Gdk event, but will return CurrentTime
1367      from some Clutter event callbacks.
1368 
1369      clutter_get_current_event_time() will return the correct time
1370      from a Clutter event callback, but may return CLUTTER_CURRENT_TIME
1371      timestamp if called at other times.
1372 
1373      So we try meta_display_get_current_time() first, since we
1374      can recognize a "wrong" answer from that, and then fall back
1375      to clutter_get_current_event_time().
1376    */
1377 
1378   time = meta_display_get_current_time (global->meta_display);
1379   if (time != CLUTTER_CURRENT_TIME)
1380     return time;
1381 
1382   return clutter_get_current_event_time ();
1383 }
1384 
1385 /**
1386  * cinnamon_global_get_pid:
1387  *
1388  * Return value: the pid of the cinnamon process.
1389  */
1390 pid_t
cinnamon_global_get_pid(CinnamonGlobal * global)1391 cinnamon_global_get_pid (CinnamonGlobal *global)
1392 {
1393   return getpid();
1394 }
1395 
1396 /**
1397  * cinnamon_global_get_md5_for_string:
1398  * @string: input string
1399  *
1400  * Return value: (transfer full): the MD5 sum for the given string
1401  */
1402 gchar *
cinnamon_global_get_md5_for_string(CinnamonGlobal * global,const gchar * string)1403 cinnamon_global_get_md5_for_string (CinnamonGlobal *global, const gchar *string)
1404 {
1405     return g_compute_checksum_for_string (G_CHECKSUM_MD5, string, -1);
1406 }
1407 
1408 /**
1409  * cinnamon_global_create_app_launch_context:
1410  * @global: A #CinnamonGlobal
1411  *
1412  * Create a #GAppLaunchContext set up with the correct timestamp, and
1413  * targeted to activate on the current workspace.
1414  *
1415  * Return value: (transfer full): A new #GAppLaunchContext
1416  */
1417 GAppLaunchContext *
cinnamon_global_create_app_launch_context(CinnamonGlobal * global)1418 cinnamon_global_create_app_launch_context (CinnamonGlobal *global)
1419 {
1420   GdkAppLaunchContext *context;
1421 
1422   context = gdk_display_get_app_launch_context (global->gdk_display);
1423 
1424   gdk_app_launch_context_set_timestamp (context, cinnamon_global_get_current_time (global));
1425 
1426   // Make sure that the app is opened on the current workspace even if
1427   // the user switches before it starts
1428   gdk_app_launch_context_set_desktop (context, meta_screen_get_active_workspace_index (global->meta_screen));
1429 
1430   return (GAppLaunchContext *)context;
1431 }
1432 
1433 typedef struct
1434 {
1435   CinnamonLeisureFunction func;
1436   gpointer user_data;
1437   GDestroyNotify notify;
1438 } LeisureClosure;
1439 
1440 static gboolean
run_leisure_functions(gpointer data)1441 run_leisure_functions (gpointer data)
1442 {
1443   CinnamonGlobal *global = data;
1444   GSList *closures;
1445   GSList *iter;
1446 
1447   global->leisure_function_id = 0;
1448 
1449   /* We started more work since we scheduled the idle */
1450   if (global->work_count > 0)
1451     return FALSE;
1452 
1453   /* No leisure closures, so we are done */
1454   if (global->leisure_closures == NULL)
1455     return FALSE;
1456 
1457   closures = global->leisure_closures;
1458   global->leisure_closures = NULL;
1459 
1460   for (iter = closures; iter; iter = iter->next)
1461     {
1462       LeisureClosure *closure = closures->data;
1463       closure->func (closure->user_data);
1464 
1465       if (closure->notify)
1466         closure->notify (closure->user_data);
1467 
1468       g_slice_free (LeisureClosure, closure);
1469     }
1470 
1471   g_slist_free (closures);
1472 
1473   return FALSE;
1474 }
1475 
1476 static void
schedule_leisure_functions(CinnamonGlobal * global)1477 schedule_leisure_functions (CinnamonGlobal *global)
1478 {
1479   /* This is called when we think we are ready to run leisure functions
1480    * by our own accounting. We try to handle other types of business
1481    * (like ClutterAnimation) by adding a low priority idle function.
1482    *
1483    * This won't work properly if the mainloop goes idle waiting for
1484    * the vertical blanking interval or waiting for work being done
1485    * in another thread.
1486    */
1487   if (!global->leisure_function_id)
1488     global->leisure_function_id = g_idle_add_full (G_PRIORITY_LOW,
1489                                                    run_leisure_functions,
1490                                                    global, NULL);
1491 }
1492 
1493 /**
1494  * cinnamon_global_begin_work:
1495  * @global: the #CinnamonGlobal
1496  *
1497  * Marks that we are currently doing work. This is used to to track
1498  * whether we are busy for the purposes of cinnamon_global_run_at_leisure().
1499  * A count is kept and cinnamon_global_end_work() must be called exactly
1500  * as many times as cinnamon_global_begin_work().
1501  */
1502 void
cinnamon_global_begin_work(CinnamonGlobal * global)1503 cinnamon_global_begin_work (CinnamonGlobal *global)
1504 {
1505   global->work_count++;
1506 }
1507 
1508 /**
1509  * cinnamon_global_end_work:
1510  * @global: the #CinnamonGlobal
1511  *
1512  * Marks the end of work that we started with cinnamon_global_begin_work().
1513  * If no other work is ongoing and functions have been added with
1514  * cinnamon_global_run_at_leisure(), they will be run at the next
1515  * opportunity.
1516  */
1517 void
cinnamon_global_end_work(CinnamonGlobal * global)1518 cinnamon_global_end_work (CinnamonGlobal *global)
1519 {
1520   g_return_if_fail (global->work_count > 0);
1521 
1522   global->work_count--;
1523   if (global->work_count == 0)
1524     schedule_leisure_functions (global);
1525 
1526 }
1527 
1528 /**
1529  * cinnamon_global_run_at_leisure:
1530  * @global: the #CinnamonGlobal
1531  * @func: function to call at leisure
1532  * @user_data: data to pass to @func
1533  * @notify: function to call to free @user_data
1534  *
1535  * Schedules a function to be called the next time Cinnamon is idle.
1536  * Idle means here no animations, no redrawing, and no ongoing background
1537  * work. Since there is currently no way to hook into the Clutter master
1538  * clock and know when is running, the implementation here is somewhat
1539  * approximation. Animations done through Cinnamon's Tweener module will
1540  * be handled properly, but other animations may be detected as terminating
1541  * early if they can be drawn fast enough so that the event loop goes idle
1542  * between frames.
1543  *
1544  * The intent of this function is for performance measurement runs
1545  * where a number of actions should be run serially and each action is
1546  * timed individually. Using this function for other purposes will
1547  * interfere with the ability to use it for performance measurement so
1548  * should be avoided.
1549  */
1550 void
cinnamon_global_run_at_leisure(CinnamonGlobal * global,CinnamonLeisureFunction func,gpointer user_data,GDestroyNotify notify)1551 cinnamon_global_run_at_leisure (CinnamonGlobal         *global,
1552                              CinnamonLeisureFunction func,
1553                              gpointer             user_data,
1554                              GDestroyNotify       notify)
1555 {
1556   LeisureClosure *closure = g_slice_new (LeisureClosure);
1557   closure->func = func;
1558   closure->user_data = user_data;
1559   closure->notify = notify;
1560 
1561   global->leisure_closures = g_slist_append (global->leisure_closures,
1562                                              closure);
1563 
1564   if (global->work_count == 0)
1565     schedule_leisure_functions (global);
1566 }
1567 
1568 /*
1569  * Process Xdnd events
1570  *
1571  * We pass the position and leave events to JS via a signal
1572  * where the actual drag & drop handling happens.
1573  *
1574  * http://www.freedesktop.org/wiki/Specifications/XDND
1575  */
_cinnamon_global_check_xdnd_event(CinnamonGlobal * global,XEvent * xev)1576 gboolean _cinnamon_global_check_xdnd_event (CinnamonGlobal  *global,
1577                                          XEvent       *xev)
1578 {
1579   Window output_window = meta_get_overlay_window (global->meta_screen);
1580 
1581   if (xev->xany.window != output_window && xev->xany.window != global->stage_xwindow)
1582     return FALSE;
1583 
1584   if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndPosition"))
1585     {
1586       XEvent xevent;
1587       Window src = xev->xclient.data.l[0];
1588 
1589       memset (&xevent, 0, sizeof(xevent));
1590       xevent.xany.type = ClientMessage;
1591       xevent.xany.display = global->xdisplay;
1592       xevent.xclient.window = src;
1593       xevent.xclient.message_type = gdk_x11_get_xatom_by_name ("XdndStatus");
1594       xevent.xclient.format = 32;
1595       xevent.xclient.data.l[0] = output_window;
1596       /* flags: bit 0: will we accept the drop? bit 1: do we want more position messages */
1597       xevent.xclient.data.l[1] = 2;
1598       xevent.xclient.data.l[4] = None;
1599 
1600       XSendEvent (global->xdisplay, src, False, 0, &xevent);
1601 
1602       /* Store the timestamp of the xdnd position event */
1603       global->xdnd_timestamp = xev->xclient.data.l[3];
1604       g_signal_emit_by_name (G_OBJECT (global), "xdnd-position-changed",
1605                             (int)(xev->xclient.data.l[2] >> 16), (int)(xev->xclient.data.l[2] & 0xFFFF));
1606       global->xdnd_timestamp = 0;
1607 
1608       return TRUE;
1609     }
1610    else if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndLeave"))
1611     {
1612       g_signal_emit_by_name (G_OBJECT (global), "xdnd-leave");
1613 
1614       return TRUE;
1615     }
1616    else if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndEnter"))
1617     {
1618       g_signal_emit_by_name (G_OBJECT (global), "xdnd-enter");
1619 
1620       return TRUE;
1621     }
1622 
1623     return FALSE;
1624 }
1625 
1626 /**
1627  * cinnamon_global_segfault:
1628  * @global: the #CinnamonGlobal
1629  *
1630  * Crashes Cinnamon by causing a segfault
1631  */
1632 void
cinnamon_global_segfault(CinnamonGlobal * global)1633 cinnamon_global_segfault (CinnamonGlobal *global)
1634 {
1635   int *ptr = NULL;
1636   g_strdup_printf ("%d", *ptr);
1637 }
1638