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