1 /* gdkevents-quartz.c
2 *
3 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
4 * Copyright (C) 1998-2002 Tor Lillqvist
5 * Copyright (C) 2005-2008 Imendio AB
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22 #include <sys/types.h>
23 #include <sys/sysctl.h>
24 #include <pthread.h>
25 #include <unistd.h>
26
27 #import <Cocoa/Cocoa.h>
28 #include <Carbon/Carbon.h>
29
30 #include <gdk/gdkdisplayprivate.h>
31
32 #include "gdkscreen.h"
33 #include "gdkkeysyms.h"
34 #include "gdkquartz.h"
35 #include "gdkquartzdisplay.h"
36 #include "gdkprivate-quartz.h"
37 #include "gdkinternal-quartz.h"
38 #include "gdkquartzdevicemanager-core.h"
39 #include "gdkquartzkeys.h"
40 #include "gdkkeys-quartz.h"
41
42 #define GRIP_WIDTH 15
43 #define GRIP_HEIGHT 15
44 #define GDK_LION_RESIZE 5
45 #define TABLET_AXES 5
46
47 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1060
48 #define NSEventTypeRotate 13
49 #define NSEventTypeMagnify 30
50 #endif
51
52 #define WINDOW_IS_TOPLEVEL(window) \
53 (GDK_WINDOW_TYPE (window) != GDK_WINDOW_CHILD && \
54 GDK_WINDOW_TYPE (window) != GDK_WINDOW_FOREIGN && \
55 GDK_WINDOW_TYPE (window) != GDK_WINDOW_OFFSCREEN)
56
57
58 /* This is the window corresponding to the key window */
59 static GdkWindow *current_keyboard_window;
60
61
62 static void append_event (GdkEvent *event,
63 gboolean windowing);
64
65 static GdkWindow *find_toplevel_under_pointer (GdkDisplay *display,
66 NSPoint screen_point,
67 gint *x,
68 gint *y);
69
70
71 static void
gdk_quartz_ns_notification_callback(CFNotificationCenterRef center,void * observer,CFStringRef name,const void * object,CFDictionaryRef userInfo)72 gdk_quartz_ns_notification_callback (CFNotificationCenterRef center,
73 void *observer,
74 CFStringRef name,
75 const void *object,
76 CFDictionaryRef userInfo)
77 {
78 GdkEvent new_event;
79
80 new_event.type = GDK_SETTING;
81 new_event.setting.window = gdk_screen_get_root_window (_gdk_screen);
82 new_event.setting.send_event = FALSE;
83 new_event.setting.action = GDK_SETTING_ACTION_CHANGED;
84 new_event.setting.name = NULL;
85
86 /* Translate name */
87 if (CFStringCompare (name,
88 CFSTR("AppleNoRedisplayAppearancePreferenceChanged"),
89 0) == kCFCompareEqualTo)
90 new_event.setting.name = "gtk-primary-button-warps-slider";
91
92 if (!new_event.setting.name)
93 return;
94
95 gdk_event_put (&new_event);
96 }
97
98 static void
gdk_quartz_events_init_notifications(void)99 gdk_quartz_events_init_notifications (void)
100 {
101 static gboolean notifications_initialized = FALSE;
102
103 if (notifications_initialized)
104 return;
105 notifications_initialized = TRUE;
106
107 /* Initialize any handlers for notifications we want to push to GTK
108 * through GdkEventSettings.
109 */
110
111 /* This is an undocumented *distributed* notification to listen for changes
112 * in scrollbar jump behavior. It is used by LibreOffice and WebKit as well.
113 */
114 CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (),
115 NULL,
116 &gdk_quartz_ns_notification_callback,
117 CFSTR ("AppleNoRedisplayAppearancePreferenceChanged"),
118 NULL,
119 CFNotificationSuspensionBehaviorDeliverImmediately);
120 }
121
122 void
_gdk_quartz_events_init(void)123 _gdk_quartz_events_init (void)
124 {
125 _gdk_quartz_event_loop_init ();
126 gdk_quartz_events_init_notifications ();
127
128 current_keyboard_window = g_object_ref (_gdk_root);
129 }
130
131 gboolean
_gdk_quartz_display_has_pending(GdkDisplay * display)132 _gdk_quartz_display_has_pending (GdkDisplay *display)
133 {
134 return (_gdk_event_queue_find_first (display) ||
135 (_gdk_quartz_event_loop_check_pending ()));
136 }
137
138 void
_gdk_quartz_events_break_all_grabs(guint32 time)139 _gdk_quartz_events_break_all_grabs (guint32 time)
140 {
141 GdkSeat *seat = gdk_display_get_default_seat (_gdk_display);
142 gdk_seat_ungrab (seat);
143 }
144
145 static void
fixup_event(GdkEvent * event)146 fixup_event (GdkEvent *event)
147 {
148 if (event->any.window)
149 g_object_ref (event->any.window);
150 if (((event->any.type == GDK_ENTER_NOTIFY) ||
151 (event->any.type == GDK_LEAVE_NOTIFY)) &&
152 (event->crossing.subwindow != NULL))
153 g_object_ref (event->crossing.subwindow);
154 event->any.send_event = FALSE;
155 }
156
157 static void
append_event(GdkEvent * event,gboolean windowing)158 append_event (GdkEvent *event,
159 gboolean windowing)
160 {
161 GList *node;
162
163 fixup_event (event);
164 node = _gdk_event_queue_append (_gdk_display, event);
165
166 if (windowing)
167 _gdk_windowing_got_event (_gdk_display, node, event, 0);
168 }
169
170 static gint
gdk_event_apply_filters(NSEvent * nsevent,GdkEvent * event,GList ** filters)171 gdk_event_apply_filters (NSEvent *nsevent,
172 GdkEvent *event,
173 GList **filters)
174 {
175 GList *tmp_list;
176 GdkFilterReturn result;
177
178 tmp_list = *filters;
179
180 while (tmp_list)
181 {
182 GdkEventFilter *filter = (GdkEventFilter*) tmp_list->data;
183 GList *node;
184
185 if ((filter->flags & GDK_EVENT_FILTER_REMOVED) != 0)
186 {
187 tmp_list = tmp_list->next;
188 continue;
189 }
190
191 filter->ref_count++;
192 result = filter->function (nsevent, event, filter->data);
193
194 /* get the next node after running the function since the
195 function may add or remove a next node */
196 node = tmp_list;
197 tmp_list = tmp_list->next;
198
199 filter->ref_count--;
200 if (filter->ref_count == 0)
201 {
202 *filters = g_list_remove_link (*filters, node);
203 g_list_free_1 (node);
204 g_free (filter);
205 }
206
207 if (result != GDK_FILTER_CONTINUE)
208 return result;
209 }
210
211 return GDK_FILTER_CONTINUE;
212 }
213
214 static guint32
get_time_from_ns_event(NSEvent * event)215 get_time_from_ns_event (NSEvent *event)
216 {
217 double time = [event timestamp];
218
219 /* cast via double->uint64 conversion to make sure that it is
220 * wrapped on 32-bit machines when it overflows
221 */
222 return (guint32) (guint64) (time * 1000.0);
223 }
224
225 static int
get_mouse_button_from_ns_event(NSEvent * event)226 get_mouse_button_from_ns_event (NSEvent *event)
227 {
228 NSInteger button;
229
230 button = [event buttonNumber];
231
232 switch (button)
233 {
234 case 0:
235 return 1;
236 case 1:
237 return 3;
238 case 2:
239 return 2;
240 default:
241 return button + 1;
242 }
243 }
244
245 static GdkModifierType
get_mouse_button_modifiers_from_ns_buttons(NSUInteger nsbuttons)246 get_mouse_button_modifiers_from_ns_buttons (NSUInteger nsbuttons)
247 {
248 GdkModifierType modifiers = 0;
249
250 if (nsbuttons & (1 << 0))
251 modifiers |= GDK_BUTTON1_MASK;
252 if (nsbuttons & (1 << 1))
253 modifiers |= GDK_BUTTON3_MASK;
254 if (nsbuttons & (1 << 2))
255 modifiers |= GDK_BUTTON2_MASK;
256 if (nsbuttons & (1 << 3))
257 modifiers |= GDK_BUTTON4_MASK;
258 if (nsbuttons & (1 << 4))
259 modifiers |= GDK_BUTTON5_MASK;
260
261 return modifiers;
262 }
263
264 static GdkModifierType
get_mouse_button_modifiers_from_ns_event(NSEvent * event)265 get_mouse_button_modifiers_from_ns_event (NSEvent *event)
266 {
267 int button;
268 GdkModifierType state = 0;
269
270 /* This maps buttons 1 to 5 to GDK_BUTTON[1-5]_MASK */
271 button = get_mouse_button_from_ns_event (event);
272 if (button >= 1 && button <= 5)
273 state = (1 << (button + 7));
274
275 return state;
276 }
277
278 static GdkModifierType
get_keyboard_modifiers_from_ns_flags(NSUInteger nsflags)279 get_keyboard_modifiers_from_ns_flags (NSUInteger nsflags)
280 {
281 GdkModifierType modifiers = 0;
282
283 if (nsflags & GDK_QUARTZ_ALPHA_SHIFT_KEY_MASK)
284 modifiers |= GDK_LOCK_MASK;
285 if (nsflags & GDK_QUARTZ_SHIFT_KEY_MASK)
286 modifiers |= GDK_SHIFT_MASK;
287 if (nsflags & GDK_QUARTZ_CONTROL_KEY_MASK)
288 modifiers |= GDK_CONTROL_MASK;
289 if (nsflags & GDK_QUARTZ_ALTERNATE_KEY_MASK)
290 modifiers |= GDK_MOD1_MASK;
291 if (nsflags & GDK_QUARTZ_COMMAND_KEY_MASK)
292 modifiers |= GDK_MOD2_MASK;
293
294 return modifiers;
295 }
296
297 static GdkModifierType
get_keyboard_modifiers_from_ns_event(NSEvent * nsevent)298 get_keyboard_modifiers_from_ns_event (NSEvent *nsevent)
299 {
300 return get_keyboard_modifiers_from_ns_flags ([nsevent modifierFlags]);
301 }
302
303 /* Return an event mask from an NSEvent */
304 static GdkEventMask
get_event_mask_from_ns_event(NSEvent * nsevent)305 get_event_mask_from_ns_event (NSEvent *nsevent)
306 {
307 switch ([nsevent type])
308 {
309 case GDK_QUARTZ_LEFT_MOUSE_DOWN:
310 case GDK_QUARTZ_RIGHT_MOUSE_DOWN:
311 case GDK_QUARTZ_OTHER_MOUSE_DOWN:
312 return GDK_BUTTON_PRESS_MASK;
313 case GDK_QUARTZ_LEFT_MOUSE_UP:
314 case GDK_QUARTZ_RIGHT_MOUSE_UP:
315 case GDK_QUARTZ_OTHER_MOUSE_UP:
316 return GDK_BUTTON_RELEASE_MASK;
317 case GDK_QUARTZ_MOUSE_MOVED:
318 return GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
319 case GDK_QUARTZ_SCROLL_WHEEL:
320 /* Since applications that want button press events can get
321 * scroll events on X11 (since scroll wheel events are really
322 * button press events there), we need to use GDK_BUTTON_PRESS_MASK too.
323 */
324 return GDK_SCROLL_MASK | GDK_BUTTON_PRESS_MASK;
325 case GDK_QUARTZ_LEFT_MOUSE_DRAGGED:
326 return (GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
327 GDK_BUTTON_MOTION_MASK | GDK_BUTTON1_MOTION_MASK |
328 GDK_BUTTON1_MASK);
329 case GDK_QUARTZ_RIGHT_MOUSE_DRAGGED:
330 return (GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
331 GDK_BUTTON_MOTION_MASK | GDK_BUTTON3_MOTION_MASK |
332 GDK_BUTTON3_MASK);
333 case GDK_QUARTZ_OTHER_MOUSE_DRAGGED:
334 {
335 GdkEventMask mask;
336
337 mask = (GDK_POINTER_MOTION_MASK |
338 GDK_POINTER_MOTION_HINT_MASK |
339 GDK_BUTTON_MOTION_MASK);
340
341 if (get_mouse_button_from_ns_event (nsevent) == 2)
342 mask |= (GDK_BUTTON2_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
343 GDK_BUTTON2_MASK);
344
345 return mask;
346 }
347 case NSEventTypeMagnify:
348 case NSEventTypeRotate:
349 return GDK_TOUCHPAD_GESTURE_MASK;
350 case GDK_QUARTZ_KEY_DOWN:
351 case GDK_QUARTZ_KEY_UP:
352 case GDK_QUARTZ_FLAGS_CHANGED:
353 {
354 switch (_gdk_quartz_keys_event_type (nsevent))
355 {
356 case GDK_KEY_PRESS:
357 return GDK_KEY_PRESS_MASK;
358 case GDK_KEY_RELEASE:
359 return GDK_KEY_RELEASE_MASK;
360 case GDK_NOTHING:
361 return 0;
362 default:
363 g_assert_not_reached ();
364 }
365 }
366 break;
367
368 case GDK_QUARTZ_MOUSE_ENTERED:
369 return GDK_ENTER_NOTIFY_MASK;
370
371 case GDK_QUARTZ_MOUSE_EXITED:
372 return GDK_LEAVE_NOTIFY_MASK;
373
374 default:
375 g_assert_not_reached ();
376 }
377
378 return 0;
379 }
380
381 static void
get_window_point_from_screen_point(GdkWindow * window,NSPoint screen_point,gint * x,gint * y)382 get_window_point_from_screen_point (GdkWindow *window,
383 NSPoint screen_point,
384 gint *x,
385 gint *y)
386 {
387 NSPoint point;
388 GdkQuartzNSWindow *nswindow;
389
390 nswindow = (GdkQuartzNSWindow*)(((GdkWindowImplQuartz *)window->impl)->toplevel);
391 point = [nswindow convertPointFromScreen:screen_point];
392 *x = point.x;
393 *y = window->height - point.y;
394 }
395
396 static gboolean
is_mouse_button_press_event(NSEventType type)397 is_mouse_button_press_event (NSEventType type)
398 {
399 switch (type)
400 {
401 case GDK_QUARTZ_LEFT_MOUSE_DOWN:
402 case GDK_QUARTZ_RIGHT_MOUSE_DOWN:
403 case GDK_QUARTZ_OTHER_MOUSE_DOWN:
404 return TRUE;
405 default:
406 return FALSE;
407 }
408
409 return FALSE;
410 }
411
412 static GdkWindow *
get_toplevel_from_ns_event(NSEvent * nsevent,NSPoint * screen_point,gint * x,gint * y)413 get_toplevel_from_ns_event (NSEvent *nsevent,
414 NSPoint *screen_point,
415 gint *x,
416 gint *y)
417 {
418 GdkWindow *toplevel = NULL;
419
420 if ([nsevent window])
421 {
422 GdkQuartzView *view;
423 NSPoint point, view_point;
424 NSRect view_frame;
425
426 view = (GdkQuartzView *)[[nsevent window] contentView];
427
428 toplevel = [view gdkWindow];
429
430 point = [nsevent locationInWindow];
431 view_point = [view convertPoint:point fromView:nil];
432 view_frame = [view frame];
433
434 /* NSEvents come in with a window set, but with window coordinates
435 * out of window bounds. For e.g. moved events this is fine, we use
436 * this information to properly handle enter/leave notify and motion
437 * events. For mouse button press/release, we want to avoid forwarding
438 * these events however, because the window they relate to is not the
439 * window set in the event. This situation appears to occur when button
440 * presses come in just before (or just after?) a window is resized and
441 * also when a button press occurs on the OS X window titlebar.
442 *
443 * By setting toplevel to NULL, we do another attempt to get the right
444 * toplevel window below.
445 */
446 if (is_mouse_button_press_event ([nsevent type]) &&
447 (view_point.x < view_frame.origin.x ||
448 view_point.x >= view_frame.origin.x + view_frame.size.width ||
449 view_point.y < view_frame.origin.y ||
450 view_point.y >= view_frame.origin.y + view_frame.size.height))
451 {
452 toplevel = NULL;
453
454 /* This is a hack for button presses to break all grabs. E.g. if
455 * a menu is open and one clicks on the title bar (or anywhere
456 * out of window bounds), we really want to pop down the menu (by
457 * breaking the grabs) before OS X handles the action of the title
458 * bar button.
459 *
460 * Because we cannot ingest this event into GDK, we have to do it
461 * here, not very nice.
462 */
463 _gdk_quartz_events_break_all_grabs (get_time_from_ns_event (nsevent));
464
465 /* Check if the event occurred on the titlebar. If it did,
466 * explicitly return NULL to prevent going through the
467 * fallback path, which could match the window that is
468 * directly under the titlebar.
469 */
470 if (view_point.y < 0 &&
471 view_point.x >= view_frame.origin.x &&
472 view_point.x < view_frame.origin.x + view_frame.size.width)
473 {
474 NSView *superview = [view superview];
475 if (superview)
476 {
477 NSRect superview_frame = [superview frame];
478 int titlebar_height = superview_frame.size.height -
479 view_frame.size.height;
480
481 if (titlebar_height > 0 && view_point.y >= -titlebar_height)
482 {
483 return NULL;
484 }
485 }
486 }
487 }
488 else
489 {
490 *screen_point = [(GdkQuartzNSWindow*)[nsevent window] convertPointToScreen:point];
491 *x = point.x;
492 *y = toplevel->height - point.y;
493 }
494 }
495
496 if (!toplevel)
497 {
498 /* Fallback used when no NSWindow set. This happens e.g. when
499 * we allow motion events without a window set in gdk_event_translate()
500 * that occur immediately after the main menu bar was clicked/used.
501 * This fallback will not return coordinates contained in a window's
502 * titlebar.
503 */
504 *screen_point = [NSEvent mouseLocation];
505 toplevel = find_toplevel_under_pointer (_gdk_display,
506 *screen_point,
507 x, y);
508 }
509
510 return toplevel;
511 }
512
513 static GdkEvent *
create_focus_event(GdkWindow * window,gboolean in)514 create_focus_event (GdkWindow *window,
515 gboolean in)
516 {
517 GdkEvent *event;
518 GdkDisplay *display = gdk_window_get_display (window);
519 GdkSeat *seat = gdk_display_get_default_seat (display);
520
521 event = gdk_event_new (GDK_FOCUS_CHANGE);
522 event->focus_change.window = window;
523 event->focus_change.in = in;
524
525 gdk_event_set_device (event, gdk_seat_get_keyboard (seat));
526 gdk_event_set_seat (event, seat);
527
528 return event;
529 }
530
531
532 static void
generate_motion_event(GdkWindow * window)533 generate_motion_event (GdkWindow *window)
534 {
535 NSPoint screen_point;
536 GdkEvent *event;
537 gint x, y, x_root, y_root;
538 GdkDisplay *display = gdk_window_get_display (window);
539 GdkSeat *seat = gdk_display_get_default_seat (display);
540
541 event = gdk_event_new (GDK_MOTION_NOTIFY);
542 event->any.window = NULL;
543 event->any.send_event = TRUE;
544
545 screen_point = [NSEvent mouseLocation];
546
547 _gdk_quartz_window_nspoint_to_gdk_xy (screen_point, &x_root, &y_root);
548 get_window_point_from_screen_point (window, screen_point, &x, &y);
549
550 event->any.type = GDK_MOTION_NOTIFY;
551 event->motion.window = window;
552 event->motion.time = get_time_from_ns_event ([NSApp currentEvent]);
553 event->motion.x = x;
554 event->motion.y = y;
555 event->motion.x_root = x_root;
556 event->motion.y_root = y_root;
557 /* FIXME event->axes */
558 event->motion.state = _gdk_quartz_events_get_current_keyboard_modifiers () |
559 _gdk_quartz_events_get_current_mouse_modifiers ();
560 event->motion.is_hint = FALSE;
561 gdk_event_set_device (event, gdk_seat_get_pointer (seat));
562 gdk_event_set_seat (event, seat);
563
564 append_event (event, TRUE);
565 }
566
567 /* Note: Used to both set a new focus window and to unset the old one. */
568 void
_gdk_quartz_events_update_focus_window(GdkWindow * window,gboolean got_focus)569 _gdk_quartz_events_update_focus_window (GdkWindow *window,
570 gboolean got_focus)
571 {
572 GdkEvent *event;
573
574 if (got_focus && window == current_keyboard_window)
575 return;
576
577 /* FIXME: Don't do this when grabbed? Or make GdkQuartzNSWindow
578 * disallow it in the first place instead?
579 */
580
581 if (!got_focus && window == current_keyboard_window)
582 {
583 event = create_focus_event (current_keyboard_window, FALSE);
584 append_event (event, FALSE);
585 g_object_unref (current_keyboard_window);
586 current_keyboard_window = NULL;
587 }
588
589 if (got_focus)
590 {
591 if (current_keyboard_window)
592 {
593 event = create_focus_event (current_keyboard_window, FALSE);
594 append_event (event, FALSE);
595 g_object_unref (current_keyboard_window);
596 current_keyboard_window = NULL;
597 }
598
599 event = create_focus_event (window, TRUE);
600 append_event (event, FALSE);
601 current_keyboard_window = g_object_ref (window);
602
603 /* We just became the active window. Unlike X11, Mac OS X does
604 * not send us motion events while the window does not have focus
605 * ("is not key"). We send a dummy motion notify event now, so that
606 * everything in the window is set to correct state.
607 */
608 generate_motion_event (window);
609 }
610 }
611
612 void
_gdk_quartz_events_send_map_event(GdkWindow * window)613 _gdk_quartz_events_send_map_event (GdkWindow *window)
614 {
615 GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl);
616
617 if (!impl->toplevel)
618 return;
619
620 if (window->event_mask & GDK_STRUCTURE_MASK)
621 {
622 GdkEvent event;
623
624 event.any.type = GDK_MAP;
625 event.any.window = window;
626
627 gdk_event_put (&event);
628 }
629 }
630
631 static GdkWindow *
find_toplevel_under_pointer(GdkDisplay * display,NSPoint screen_point,gint * x,gint * y)632 find_toplevel_under_pointer (GdkDisplay *display,
633 NSPoint screen_point,
634 gint *x,
635 gint *y)
636 {
637 GdkWindow *toplevel;
638 GdkPointerWindowInfo *info;
639 GdkSeat *seat = gdk_display_get_default_seat (display);
640
641 info = _gdk_display_get_pointer_info (display, gdk_seat_get_pointer (seat));
642 toplevel = info->toplevel_under_pointer;
643
644 if (!(toplevel && WINDOW_IS_TOPLEVEL (toplevel)))
645 {
646 gint gdk_x = 0, gdk_y = 0;
647 GdkDevice *pointer = gdk_seat_get_pointer(seat);
648 _gdk_quartz_window_nspoint_to_gdk_xy (screen_point, &gdk_x, &gdk_y);
649 toplevel = gdk_device_get_window_at_position (pointer, &gdk_x, &gdk_y);
650
651 if (toplevel && ! WINDOW_IS_TOPLEVEL (toplevel))
652 toplevel = gdk_window_get_toplevel (toplevel);
653
654 if (toplevel)
655 info->toplevel_under_pointer = g_object_ref (toplevel);
656 else
657 info->toplevel_under_pointer = NULL;
658
659 }
660
661 if (toplevel)
662 {
663 get_window_point_from_screen_point (toplevel, screen_point, x, y);
664 /* If the coordinates are out of window bounds, this toplevel is not
665 * under the pointer and we thus return NULL. This can occur when
666 * toplevel under pointer has not yet been updated due to a very recent
667 * window resize. Alternatively, we should no longer be relying on
668 * the toplevel_under_pointer value which is maintained in gdkwindow.c.
669 */
670 if (*x < 0 || *y < 0 || *x >= toplevel->width || *y >= toplevel->height)
671 return NULL;
672 }
673
674 return toplevel;
675 }
676
677 static GdkWindow *
find_toplevel_for_keyboard_event(NSEvent * nsevent)678 find_toplevel_for_keyboard_event (NSEvent *nsevent)
679 {
680 GdkQuartzView *view = (GdkQuartzView *)[[nsevent window] contentView];
681 GdkWindow *window = [view gdkWindow];
682 GdkDisplay *display = gdk_window_get_display (window);
683 GdkSeat *seat = gdk_display_get_default_seat (display);
684 GdkDevice *device = gdk_seat_get_keyboard (seat);
685 GdkDeviceGrabInfo *grab = _gdk_display_get_last_device_grab (display, device);
686
687 if (grab && grab->window && !grab->owner_events)
688 window = gdk_window_get_effective_toplevel (grab->window);
689
690 return window;
691 }
692
693 static GdkWindow *
find_toplevel_for_mouse_event(NSEvent * nsevent,gint * x,gint * y)694 find_toplevel_for_mouse_event (NSEvent *nsevent,
695 gint *x,
696 gint *y)
697 {
698 NSPoint screen_point;
699 NSEventType event_type;
700 GdkWindow *toplevel;
701 GdkDisplay *display;
702 GdkDeviceGrabInfo *grab;
703 GdkSeat *seat;
704
705 toplevel = get_toplevel_from_ns_event (nsevent, &screen_point, x, y);
706
707 display = gdk_window_get_display (toplevel);
708 seat = gdk_display_get_default_seat (_gdk_display);
709
710 event_type = [nsevent type];
711
712 /* From the docs for XGrabPointer:
713 *
714 * If owner_events is True and if a generated pointer event
715 * would normally be reported to this client, it is reported
716 * as usual. Otherwise, the event is reported with respect to
717 * the grab_window and is reported only if selected by
718 * event_mask. For either value of owner_events, unreported
719 * events are discarded.
720 */
721 grab = _gdk_display_get_last_device_grab (display,
722 gdk_seat_get_pointer (seat));
723 if (WINDOW_IS_TOPLEVEL (toplevel) && grab)
724 {
725 /* Implicit grabs do not go through XGrabPointer and thus the
726 * event mask should not be checked.
727 */
728 if (!grab->implicit
729 && (grab->event_mask & get_event_mask_from_ns_event (nsevent)) == 0)
730 return NULL;
731
732 if (grab->owner_events)
733 {
734 /* For owner events, we need to use the toplevel under the
735 * pointer, not the window from the NSEvent, since that is
736 * reported with respect to the key window, which could be
737 * wrong.
738 */
739 GdkWindow *toplevel_under_pointer;
740 gint x_tmp, y_tmp;
741
742 toplevel_under_pointer = find_toplevel_under_pointer (display,
743 screen_point,
744 &x_tmp, &y_tmp);
745 if (toplevel_under_pointer)
746 {
747 toplevel = toplevel_under_pointer;
748 *x = x_tmp;
749 *y = y_tmp;
750 }
751
752 return toplevel;
753 }
754 else
755 {
756 /* Finally check the grab window. */
757 GdkWindow *grab_toplevel;
758
759 grab_toplevel = gdk_window_get_effective_toplevel (grab->window);
760 get_window_point_from_screen_point (grab_toplevel, screen_point,
761 x, y);
762
763 return grab_toplevel;
764 }
765
766 return NULL;
767 }
768 else
769 {
770 /* The non-grabbed case. */
771 GdkWindow *toplevel_under_pointer;
772 gint x_tmp, y_tmp;
773
774 /* Ignore all events but mouse moved that might be on the title
775 * bar (above the content view). The reason is that otherwise
776 * gdk gets confused about getting e.g. button presses with no
777 * window (the title bar is not known to it).
778 */
779 if (event_type != GDK_QUARTZ_MOUSE_MOVED)
780 if (*y < 0)
781 return NULL;
782
783 /* As for owner events, we need to use the toplevel under the
784 * pointer, not the window from the NSEvent.
785 */
786 toplevel_under_pointer = find_toplevel_under_pointer (display,
787 screen_point,
788 &x_tmp, &y_tmp);
789 if (toplevel_under_pointer
790 && WINDOW_IS_TOPLEVEL (toplevel_under_pointer))
791 {
792 GdkWindowImplQuartz *toplevel_impl;
793
794 toplevel = toplevel_under_pointer;
795
796 toplevel_impl = (GdkWindowImplQuartz *)toplevel->impl;
797
798 *x = x_tmp;
799 *y = y_tmp;
800 }
801
802 return toplevel;
803 }
804
805 return NULL;
806 }
807
808 /* This function finds the correct window to send an event to, taking
809 * into account grabs, event propagation, and event masks.
810 */
811 static GdkWindow *
find_window_for_ns_event(NSEvent * nsevent,gint * x,gint * y,gint * x_root,gint * y_root)812 find_window_for_ns_event (NSEvent *nsevent,
813 gint *x,
814 gint *y,
815 gint *x_root,
816 gint *y_root)
817 {
818 GdkQuartzView *view;
819 GdkWindow *toplevel;
820 NSPoint screen_point;
821 NSEventType event_type;
822
823 view = (GdkQuartzView *)[[nsevent window] contentView];
824
825 toplevel = get_toplevel_from_ns_event (nsevent, &screen_point, x, y);
826 if (!toplevel)
827 return NULL;
828 _gdk_quartz_window_nspoint_to_gdk_xy (screen_point, x_root, y_root);
829
830 event_type = [nsevent type];
831
832 switch (event_type)
833 {
834 case GDK_QUARTZ_LEFT_MOUSE_DOWN:
835 case GDK_QUARTZ_RIGHT_MOUSE_DOWN:
836 case GDK_QUARTZ_OTHER_MOUSE_DOWN:
837 case GDK_QUARTZ_LEFT_MOUSE_UP:
838 case GDK_QUARTZ_RIGHT_MOUSE_UP:
839 case GDK_QUARTZ_OTHER_MOUSE_UP:
840 case GDK_QUARTZ_MOUSE_MOVED:
841 case GDK_QUARTZ_SCROLL_WHEEL:
842 case GDK_QUARTZ_LEFT_MOUSE_DRAGGED:
843 case GDK_QUARTZ_RIGHT_MOUSE_DRAGGED:
844 case GDK_QUARTZ_OTHER_MOUSE_DRAGGED:
845 case NSEventTypeMagnify:
846 case NSEventTypeRotate:
847 return find_toplevel_for_mouse_event (nsevent, x, y);
848
849 case GDK_QUARTZ_MOUSE_ENTERED:
850 case GDK_QUARTZ_MOUSE_EXITED:
851 /* Only handle our own entered/exited events, not the ones for the
852 * titlebar buttons.
853 */
854 if ([view trackingRect] == [nsevent trackingNumber])
855 return toplevel;
856 else
857 return NULL;
858
859 case GDK_QUARTZ_KEY_DOWN:
860 case GDK_QUARTZ_KEY_UP:
861 case GDK_QUARTZ_FLAGS_CHANGED:
862 return find_toplevel_for_keyboard_event (nsevent);
863
864 default:
865 /* Ignore everything else. */
866 break;
867 }
868
869 return NULL;
870 }
871
872 static void
fill_crossing_event(GdkWindow * toplevel,GdkEvent * event,NSEvent * nsevent,gint x,gint y,gint x_root,gint y_root,GdkEventType event_type,GdkCrossingMode mode,GdkNotifyType detail)873 fill_crossing_event (GdkWindow *toplevel,
874 GdkEvent *event,
875 NSEvent *nsevent,
876 gint x,
877 gint y,
878 gint x_root,
879 gint y_root,
880 GdkEventType event_type,
881 GdkCrossingMode mode,
882 GdkNotifyType detail)
883 {
884 GdkSeat *seat = gdk_display_get_default_seat (_gdk_display);
885
886 event->any.type = event_type;
887 event->crossing.window = toplevel;
888 event->crossing.subwindow = NULL;
889 event->crossing.time = get_time_from_ns_event (nsevent);
890 event->crossing.x = x;
891 event->crossing.y = y;
892 event->crossing.x_root = x_root;
893 event->crossing.y_root = y_root;
894 event->crossing.mode = mode;
895 event->crossing.detail = detail;
896 event->crossing.state = get_keyboard_modifiers_from_ns_event (nsevent) |
897 _gdk_quartz_events_get_current_mouse_modifiers ();
898
899 gdk_event_set_device (event, gdk_seat_get_pointer (seat));
900 gdk_event_set_seat (event, seat);
901
902 /* FIXME: Focus and button state? */
903 }
904
905 /* fill_pinch_event handles the conversion from the two OSX gesture events
906 NSEventTypeMagnfiy and NSEventTypeRotate to the GDK_TOUCHPAD_PINCH event.
907 The normal behavior of the OSX events is that they produce as sequence of
908 1 x NSEventPhaseBegan,
909 n x NSEventPhaseChanged,
910 1 x NSEventPhaseEnded
911 This can happen for both the Magnify and the Rotate events independently.
912 As both events are summarized in one GDK_TOUCHPAD_PINCH event sequence, a
913 little state machine handles the case of two NSEventPhaseBegan events in
914 a sequence, e.g. Magnify(Began), Magnify(Changed)..., Rotate(Began)...
915 such that PINCH(STARTED), PINCH(UPDATE).... will not show a second
916 PINCH(STARTED) event.
917 */
918 #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER
919 static void
fill_pinch_event(GdkWindow * window,GdkEvent * event,NSEvent * nsevent,gint x,gint y,gint x_root,gint y_root)920 fill_pinch_event (GdkWindow *window,
921 GdkEvent *event,
922 NSEvent *nsevent,
923 gint x,
924 gint y,
925 gint x_root,
926 gint y_root)
927 {
928 static double last_scale = 1.0;
929 static enum {
930 FP_STATE_IDLE,
931 FP_STATE_UPDATE
932 } last_state = FP_STATE_IDLE;
933 GdkSeat *seat = gdk_display_get_default_seat (_gdk_display);
934
935 event->any.type = GDK_TOUCHPAD_PINCH;
936 event->touchpad_pinch.window = window;
937 event->touchpad_pinch.time = get_time_from_ns_event (nsevent);
938 event->touchpad_pinch.x = x;
939 event->touchpad_pinch.y = y;
940 event->touchpad_pinch.x_root = x_root;
941 event->touchpad_pinch.y_root = y_root;
942 event->touchpad_pinch.state = get_keyboard_modifiers_from_ns_event (nsevent);
943 event->touchpad_pinch.n_fingers = 2;
944 event->touchpad_pinch.dx = 0.0;
945 event->touchpad_pinch.dy = 0.0;
946 gdk_event_set_device (event, gdk_seat_get_pointer (seat));
947
948 switch ([nsevent phase])
949 {
950 case NSEventPhaseBegan:
951 switch (last_state)
952 {
953 case FP_STATE_IDLE:
954 event->touchpad_pinch.phase = GDK_TOUCHPAD_GESTURE_PHASE_BEGIN;
955 last_state = FP_STATE_UPDATE;
956 last_scale = 1.0;
957 break;
958 case FP_STATE_UPDATE:
959 /* We have already received a PhaseBegan event but no PhaseEnded
960 event. This can happen, e.g. Magnify(Began), Magnify(Change)...
961 Rotate(Began), Rotate (Change),...., Magnify(End) Rotate(End)
962 */
963 event->touchpad_pinch.phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
964 break;
965 }
966 break;
967 case NSEventPhaseChanged:
968 event->touchpad_pinch.phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
969 break;
970 case NSEventPhaseEnded:
971 event->touchpad_pinch.phase = GDK_TOUCHPAD_GESTURE_PHASE_END;
972 switch (last_state)
973 {
974 case FP_STATE_IDLE:
975 /* We are idle but have received a second PhaseEnded event.
976 This can happen because we have Magnify and Rotate OSX
977 event sequences. We just send a second end GDK_PHASE_END.
978 */
979 break;
980 case FP_STATE_UPDATE:
981 last_state = FP_STATE_IDLE;
982 break;
983 }
984 break;
985 case NSEventPhaseCancelled:
986 event->touchpad_pinch.phase = GDK_TOUCHPAD_GESTURE_PHASE_CANCEL;
987 last_state = FP_STATE_IDLE;
988 break;
989 case NSEventPhaseMayBegin:
990 case NSEventPhaseStationary:
991 event->touchpad_pinch.phase = GDK_TOUCHPAD_GESTURE_PHASE_CANCEL;
992 break;
993 default:
994 g_assert_not_reached ();
995 break;
996 }
997
998 switch ([nsevent type])
999 {
1000 case NSEventTypeMagnify:
1001 last_scale *= [nsevent magnification] + 1.0;
1002 event->touchpad_pinch.angle_delta = 0.0;
1003 break;
1004 case NSEventTypeRotate:
1005 event->touchpad_pinch.angle_delta = - [nsevent rotation] * G_PI / 180.0;
1006 break;
1007 default:
1008 g_assert_not_reached ();
1009 }
1010 event->touchpad_pinch.scale = last_scale;
1011 }
1012 #endif /* OSX Version >= 10.8 */
1013
1014 static void
fill_button_event(GdkWindow * window,GdkEvent * event,NSEvent * nsevent,gint x,gint y,gint x_root,gint y_root)1015 fill_button_event (GdkWindow *window,
1016 GdkEvent *event,
1017 NSEvent *nsevent,
1018 gint x,
1019 gint y,
1020 gint x_root,
1021 gint y_root)
1022 {
1023 GdkEventType type;
1024 GdkDevice *event_device = NULL;
1025 gdouble *axes = NULL;
1026 gint state;
1027 GdkSeat *seat = gdk_display_get_default_seat (_gdk_display);
1028
1029 state = get_keyboard_modifiers_from_ns_event (nsevent) |
1030 _gdk_quartz_events_get_current_mouse_modifiers ();
1031
1032 switch ([nsevent type])
1033 {
1034 case GDK_QUARTZ_LEFT_MOUSE_DOWN:
1035 case GDK_QUARTZ_RIGHT_MOUSE_DOWN:
1036 case GDK_QUARTZ_OTHER_MOUSE_DOWN:
1037 type = GDK_BUTTON_PRESS;
1038 state &= ~get_mouse_button_modifiers_from_ns_event (nsevent);
1039 break;
1040
1041 case GDK_QUARTZ_LEFT_MOUSE_UP:
1042 case GDK_QUARTZ_RIGHT_MOUSE_UP:
1043 case GDK_QUARTZ_OTHER_MOUSE_UP:
1044 type = GDK_BUTTON_RELEASE;
1045 state |= get_mouse_button_modifiers_from_ns_event (nsevent);
1046 break;
1047
1048 default:
1049 g_assert_not_reached ();
1050 }
1051
1052 event_device = _gdk_quartz_device_manager_core_device_for_ns_event (gdk_display_get_device_manager (_gdk_display),
1053 nsevent);
1054
1055 if ([nsevent subtype] == GDK_QUARTZ_EVENT_SUBTYPE_TABLET_POINT)
1056 {
1057 axes = g_new (gdouble, TABLET_AXES);
1058
1059 axes[0] = x;
1060 axes[1] = y;
1061 axes[2] = [nsevent pressure];
1062 axes[3] = [nsevent tilt].x;
1063 axes[4] = [nsevent tilt].y;
1064 }
1065
1066 event->any.type = type;
1067 event->button.window = window;
1068 event->button.time = get_time_from_ns_event (nsevent);
1069 event->button.x = x;
1070 event->button.y = y;
1071 event->button.x_root = x_root;
1072 event->button.y_root = y_root;
1073 event->button.axes = axes;
1074 event->button.state = state;
1075 event->button.button = get_mouse_button_from_ns_event (nsevent);
1076
1077 gdk_event_set_device (event, gdk_seat_get_pointer (seat));
1078 gdk_event_set_source_device (event, event_device);
1079 gdk_event_set_seat (event, seat);
1080 }
1081
1082 static void
fill_motion_event(GdkWindow * window,GdkEvent * event,NSEvent * nsevent,gint x,gint y,gint x_root,gint y_root)1083 fill_motion_event (GdkWindow *window,
1084 GdkEvent *event,
1085 NSEvent *nsevent,
1086 gint x,
1087 gint y,
1088 gint x_root,
1089 gint y_root)
1090 {
1091 GdkSeat *seat = gdk_display_get_default_seat (_gdk_display);
1092 GdkDevice *event_device = NULL;
1093 gdouble *axes = NULL;
1094
1095 event_device = _gdk_quartz_device_manager_core_device_for_ns_event (gdk_display_get_device_manager (_gdk_display),
1096 nsevent);
1097
1098 if ([nsevent subtype] == GDK_QUARTZ_EVENT_SUBTYPE_TABLET_POINT)
1099 {
1100 axes = g_new (gdouble, TABLET_AXES);
1101
1102 axes[0] = x;
1103 axes[1] = y;
1104 axes[2] = [nsevent pressure];
1105 axes[3] = [nsevent tilt].x;
1106 axes[4] = [nsevent tilt].y;
1107 }
1108
1109 event->any.type = GDK_MOTION_NOTIFY;
1110 event->motion.window = window;
1111 event->motion.time = get_time_from_ns_event (nsevent);
1112 event->motion.x = x;
1113 event->motion.y = y;
1114 event->motion.x_root = x_root;
1115 event->motion.y_root = y_root;
1116 event->motion.axes = axes;
1117 event->motion.state = get_keyboard_modifiers_from_ns_event (nsevent) |
1118 _gdk_quartz_events_get_current_mouse_modifiers ();
1119 event->motion.is_hint = FALSE;
1120 gdk_event_set_device (event, gdk_seat_get_pointer (seat));
1121 gdk_event_set_source_device (event, event_device);
1122
1123 gdk_event_set_seat (event, seat);
1124 }
1125
1126 static void
fill_scroll_event(GdkWindow * window,GdkEvent * event,NSEvent * nsevent,gint x,gint y,gint x_root,gint y_root,gdouble delta_x,gdouble delta_y,GdkScrollDirection direction)1127 fill_scroll_event (GdkWindow *window,
1128 GdkEvent *event,
1129 NSEvent *nsevent,
1130 gint x,
1131 gint y,
1132 gint x_root,
1133 gint y_root,
1134 gdouble delta_x,
1135 gdouble delta_y,
1136 GdkScrollDirection direction)
1137 {
1138 GdkSeat *seat = gdk_display_get_default_seat (_gdk_display);
1139 NSPoint point;
1140
1141 point = [nsevent locationInWindow];
1142
1143 event->any.type = GDK_SCROLL;
1144 event->scroll.window = window;
1145 event->scroll.time = get_time_from_ns_event (nsevent);
1146 event->scroll.x = x;
1147 event->scroll.y = y;
1148 event->scroll.x_root = x_root;
1149 event->scroll.y_root = y_root;
1150 event->scroll.state = get_keyboard_modifiers_from_ns_event (nsevent);
1151 event->scroll.direction = direction;
1152 event->scroll.delta_x = delta_x;
1153 event->scroll.delta_y = delta_y;
1154 gdk_event_set_device (event, gdk_seat_get_pointer (seat));
1155 gdk_event_set_seat (event, seat);
1156 }
1157
1158 static void
fill_key_event(GdkWindow * window,GdkEvent * event,NSEvent * nsevent,GdkEventType type)1159 fill_key_event (GdkWindow *window,
1160 GdkEvent *event,
1161 NSEvent *nsevent,
1162 GdkEventType type)
1163 {
1164 GdkEventPrivate *priv;
1165 GdkSeat *seat = gdk_display_get_default_seat (_gdk_display);
1166 gchar buf[7];
1167 gunichar c = 0;
1168
1169 priv = (GdkEventPrivate *) event;
1170 priv->windowing_data = [nsevent retain];
1171
1172 event->any.type = type;
1173 event->key.window = window;
1174 event->key.time = get_time_from_ns_event (nsevent);
1175 event->key.state = get_keyboard_modifiers_from_ns_event (nsevent);
1176 event->key.hardware_keycode = [nsevent keyCode];
1177 gdk_event_set_scancode (event, [nsevent keyCode]);
1178 event->key.group = ([nsevent modifierFlags] & GDK_QUARTZ_ALTERNATE_KEY_MASK) ? 1 : 0;
1179 event->key.keyval = GDK_KEY_VoidSymbol;
1180
1181 gdk_event_set_device (event, gdk_seat_get_keyboard (seat));
1182 gdk_event_set_seat (event, seat);
1183
1184 gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (_gdk_display),
1185 event->key.hardware_keycode,
1186 event->key.state,
1187 event->key.group,
1188 &event->key.keyval,
1189 NULL, NULL, NULL);
1190
1191 event->key.is_modifier = _gdk_quartz_keys_is_modifier (event->key.hardware_keycode);
1192
1193 /* If the key press is a modifier, the state should include the mask
1194 * for that modifier but only for releases, not presses. This
1195 * matches the X11 backend behavior.
1196 */
1197 if (event->key.is_modifier)
1198 {
1199 int mask = 0;
1200
1201 switch (event->key.keyval)
1202 {
1203 case GDK_KEY_Meta_R:
1204 case GDK_KEY_Meta_L:
1205 mask = GDK_MOD2_MASK;
1206 break;
1207 case GDK_KEY_Shift_R:
1208 case GDK_KEY_Shift_L:
1209 mask = GDK_SHIFT_MASK;
1210 break;
1211 case GDK_KEY_Caps_Lock:
1212 mask = GDK_LOCK_MASK;
1213 break;
1214 case GDK_KEY_Alt_R:
1215 case GDK_KEY_Alt_L:
1216 mask = GDK_MOD1_MASK;
1217 break;
1218 case GDK_KEY_Control_R:
1219 case GDK_KEY_Control_L:
1220 mask = GDK_CONTROL_MASK;
1221 break;
1222 default:
1223 mask = 0;
1224 }
1225
1226 if (type == GDK_KEY_PRESS)
1227 event->key.state &= ~mask;
1228 else if (type == GDK_KEY_RELEASE)
1229 event->key.state |= mask;
1230 }
1231
1232 event->key.state |= _gdk_quartz_events_get_current_mouse_modifiers ();
1233
1234 /* The X11 backend adds the first virtual modifier MOD2..MOD5 are
1235 * mapped to. Since we only have one virtual modifier in the quartz
1236 * backend, calling the standard function will do.
1237 */
1238 gdk_keymap_add_virtual_modifiers (gdk_keymap_get_for_display (_gdk_display),
1239 &event->key.state);
1240
1241 event->key.string = NULL;
1242
1243 /* Fill in ->string since apps depend on it, taken from the x11 backend. */
1244 if (event->key.keyval != GDK_KEY_VoidSymbol)
1245 c = gdk_keyval_to_unicode (event->key.keyval);
1246
1247 if (c)
1248 {
1249 gsize bytes_written;
1250 gint len;
1251
1252 len = g_unichar_to_utf8 (c, buf);
1253 buf[len] = '\0';
1254
1255 event->key.string = g_locale_from_utf8 (buf, len,
1256 NULL, &bytes_written,
1257 NULL);
1258 if (event->key.string)
1259 event->key.length = bytes_written;
1260 }
1261 else if (event->key.keyval == GDK_KEY_Escape)
1262 {
1263 event->key.length = 1;
1264 event->key.string = g_strdup ("\033");
1265 }
1266 else if (event->key.keyval == GDK_KEY_Return ||
1267 event->key.keyval == GDK_KEY_KP_Enter)
1268 {
1269 event->key.length = 1;
1270 event->key.string = g_strdup ("\r");
1271 }
1272
1273 if (!event->key.string)
1274 {
1275 event->key.length = 0;
1276 event->key.string = g_strdup ("");
1277 }
1278
1279 GDK_NOTE(EVENTS,
1280 g_message ("key %s:\t\twindow: %p key: %12s %d",
1281 type == GDK_KEY_PRESS ? "press" : "release",
1282 event->key.window,
1283 event->key.keyval ? gdk_keyval_name (event->key.keyval) : "(none)",
1284 event->key.keyval));
1285 }
1286
1287 static gboolean
synthesize_crossing_event(GdkWindow * window,GdkEvent * event,NSEvent * nsevent,gint x,gint y,gint x_root,gint y_root)1288 synthesize_crossing_event (GdkWindow *window,
1289 GdkEvent *event,
1290 NSEvent *nsevent,
1291 gint x,
1292 gint y,
1293 gint x_root,
1294 gint y_root)
1295 {
1296 switch ([nsevent type])
1297 {
1298 case GDK_QUARTZ_MOUSE_ENTERED:
1299 /* Enter events are considered always to be from another toplevel
1300 * window, this shouldn't negatively affect any app or gtk code,
1301 * and is the only way to make GtkMenu work. EEK EEK EEK.
1302 */
1303 if (!(window->event_mask & GDK_ENTER_NOTIFY_MASK))
1304 return FALSE;
1305
1306 fill_crossing_event (window, event, nsevent,
1307 x, y,
1308 x_root, y_root,
1309 GDK_ENTER_NOTIFY,
1310 GDK_CROSSING_NORMAL,
1311 GDK_NOTIFY_NONLINEAR);
1312 return TRUE;
1313
1314 case GDK_QUARTZ_MOUSE_EXITED:
1315 /* See above */
1316 if (!(window->event_mask & GDK_LEAVE_NOTIFY_MASK))
1317 return FALSE;
1318
1319 fill_crossing_event (window, event, nsevent,
1320 x, y,
1321 x_root, y_root,
1322 GDK_LEAVE_NOTIFY,
1323 GDK_CROSSING_NORMAL,
1324 GDK_NOTIFY_NONLINEAR);
1325 return TRUE;
1326
1327 default:
1328 break;
1329 }
1330
1331 return FALSE;
1332 }
1333
1334 void
_gdk_quartz_synthesize_null_key_event(GdkWindow * window)1335 _gdk_quartz_synthesize_null_key_event (GdkWindow *window)
1336 {
1337 GdkEvent *event;
1338 GdkSeat *seat = gdk_display_get_default_seat (_gdk_display);
1339
1340 event = gdk_event_new (GDK_KEY_PRESS);
1341 event->any.type = GDK_KEY_PRESS;
1342 event->key.window = window;
1343 event->key.state = 0;
1344 event->key.hardware_keycode = 0;
1345 event->key.group = 0;
1346 event->key.keyval = GDK_KEY_VoidSymbol;
1347
1348 gdk_event_set_device (event, gdk_seat_get_keyboard (seat));
1349 gdk_event_set_seat (event, seat);
1350 append_event(event, FALSE);
1351 }
1352
1353 GdkModifierType
_gdk_quartz_events_get_current_keyboard_modifiers(void)1354 _gdk_quartz_events_get_current_keyboard_modifiers (void)
1355 {
1356 if (gdk_quartz_osx_version () >= GDK_OSX_SNOW_LEOPARD)
1357 {
1358 return get_keyboard_modifiers_from_ns_flags ([NSClassFromString(@"NSEvent") modifierFlags]);
1359 }
1360 else
1361 {
1362 guint carbon_modifiers = GetCurrentKeyModifiers ();
1363 GdkModifierType modifiers = 0;
1364
1365 if (carbon_modifiers & alphaLock)
1366 modifiers |= GDK_LOCK_MASK;
1367 if (carbon_modifiers & shiftKey)
1368 modifiers |= GDK_SHIFT_MASK;
1369 if (carbon_modifiers & controlKey)
1370 modifiers |= GDK_CONTROL_MASK;
1371 if (carbon_modifiers & optionKey)
1372 modifiers |= GDK_MOD1_MASK;
1373 if (carbon_modifiers & cmdKey)
1374 modifiers |= GDK_MOD2_MASK;
1375
1376 return modifiers;
1377 }
1378 }
1379
1380 GdkModifierType
_gdk_quartz_events_get_current_mouse_modifiers(void)1381 _gdk_quartz_events_get_current_mouse_modifiers (void)
1382 {
1383 NSUInteger buttons = 0;
1384 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
1385 if (gdk_quartz_osx_version () >= GDK_OSX_SNOW_LEOPARD)
1386 buttons = [NSClassFromString(@"NSEvent") pressedMouseButtons];
1387 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
1388 else
1389 #endif
1390 #endif
1391 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
1392 buttons = GetCurrentButtonState ();
1393 #endif
1394 return get_mouse_button_modifiers_from_ns_buttons (buttons);
1395 }
1396
1397 /* Detect window resizing */
1398
1399 static gboolean
test_resize(NSEvent * event,GdkWindow * toplevel,gint x,gint y)1400 test_resize (NSEvent *event, GdkWindow *toplevel, gint x, gint y)
1401 {
1402 GdkWindowImplQuartz *toplevel_impl;
1403 gboolean lion;
1404
1405 /* Resizing from the resize indicator only begins if an GDK_QUARTZ_LEFT_MOUSE_BUTTON
1406 * event is received in the resizing area.
1407 */
1408 toplevel_impl = (GdkWindowImplQuartz *)toplevel->impl;
1409 if ([toplevel_impl->toplevel showsResizeIndicator])
1410 if ([event type] == GDK_QUARTZ_LEFT_MOUSE_DOWN &&
1411 [toplevel_impl->toplevel showsResizeIndicator])
1412 {
1413 NSRect frame;
1414
1415 /* If the resize indicator is visible and the event
1416 * is in the lower right 15x15 corner, we leave these
1417 * events to Cocoa as to be handled as resize events.
1418 * Applications may have widgets in this area. These
1419 * will most likely be larger than 15x15 and for
1420 * scroll bars there are also other means to move
1421 * the scroll bar. Since the resize indicator is
1422 * the only way of resizing windows on Mac OS, it
1423 * is too important to not make functional.
1424 */
1425 frame = [toplevel_impl->view bounds];
1426 if (x > frame.size.width - GRIP_WIDTH &&
1427 x < frame.size.width &&
1428 y > frame.size.height - GRIP_HEIGHT &&
1429 y < frame.size.height)
1430 return TRUE;
1431 }
1432
1433 /* If we're on Lion and within 5 pixels of an edge,
1434 * then assume that the user wants to resize, and
1435 * return NULL to let Quartz get on with it. We check
1436 * the selector isRestorable to see if we're on 10.7.
1437 * This extra check is in case the user starts
1438 * dragging before GDK recognizes the grab.
1439 *
1440 * We perform this check for a button press of all buttons, because we
1441 * do receive, for instance, a right mouse down event for a GDK window
1442 * for x-coordinate range [-3, 0], but we do not want to forward this
1443 * into GDK. Forwarding such events into GDK will confuse the pointer
1444 * window finding code, because there are no GdkWindows present in
1445 * the range [-3, 0].
1446 */
1447 lion = gdk_quartz_osx_version () >= GDK_OSX_LION;
1448 if (lion &&
1449 ([event type] == GDK_QUARTZ_LEFT_MOUSE_DOWN ||
1450 [event type] == GDK_QUARTZ_RIGHT_MOUSE_DOWN ||
1451 [event type] == GDK_QUARTZ_OTHER_MOUSE_DOWN))
1452 {
1453 if (x < GDK_LION_RESIZE ||
1454 x > toplevel->width - GDK_LION_RESIZE ||
1455 y > toplevel->height - GDK_LION_RESIZE)
1456 return TRUE;
1457 }
1458
1459 return FALSE;
1460 }
1461
1462 #if MAC_OS_X_VERSION_MIN_REQUIRED < 101200
1463 #define GDK_QUARTZ_APP_KIT_DEFINED NSAppKitDefined
1464 #define GDK_QUARTZ_APPLICATION_DEACTIVATED NSApplicationDeactivatedEventType
1465 #else
1466 #define GDK_QUARTZ_APP_KIT_DEFINED NSEventTypeAppKitDefined
1467 #define GDK_QUARTZ_APPLICATION_DEACTIVATED NSEventSubtypeApplicationDeactivated
1468 #endif
1469
1470 static gboolean
gdk_event_translate(GdkEvent * event,NSEvent * nsevent)1471 gdk_event_translate (GdkEvent *event,
1472 NSEvent *nsevent)
1473 {
1474 NSEventType event_type;
1475 NSWindow *nswindow;
1476 GdkWindow *window;
1477 int x, y;
1478 int x_root, y_root;
1479 gboolean return_val;
1480
1481 /* There is no support for real desktop wide grabs, so we break
1482 * grabs when the application loses focus (gets deactivated).
1483 */
1484 event_type = [nsevent type];
1485 if (event_type == GDK_QUARTZ_APP_KIT_DEFINED)
1486 {
1487 if ([nsevent subtype] == GDK_QUARTZ_APPLICATION_DEACTIVATED)
1488 _gdk_quartz_events_break_all_grabs (get_time_from_ns_event (nsevent));
1489
1490 /* This could potentially be used to break grabs when clicking
1491 * on the title. The subtype 20 is undocumented so it's probably
1492 * not a good idea: else if (subtype == 20) break_all_grabs ();
1493 */
1494
1495 /* Leave all AppKit events to AppKit. */
1496 return FALSE;
1497 }
1498
1499 if (_gdk_default_filters)
1500 {
1501 /* Apply global filters */
1502 GdkFilterReturn result;
1503
1504 result = gdk_event_apply_filters (nsevent, event, &_gdk_default_filters);
1505 if (result != GDK_FILTER_CONTINUE)
1506 {
1507 return_val = (result == GDK_FILTER_TRANSLATE) ? TRUE : FALSE;
1508 goto done;
1509 }
1510 }
1511
1512 /* We need to register the proximity event from any point on the screen
1513 * to properly register the devices
1514 */
1515 if (event_type == GDK_QUARTZ_EVENT_TABLET_PROXIMITY)
1516 {
1517 _gdk_quartz_device_manager_register_device_for_ns_event (gdk_display_get_device_manager (_gdk_display),
1518 nsevent);
1519 }
1520
1521 nswindow = [nsevent window];
1522
1523 /* Ignore events for windows not created by GDK. */
1524 if (nswindow && ![[nswindow contentView] isKindOfClass:[GdkQuartzView class]])
1525 return FALSE;
1526
1527 /* Ignore events for ones with no windows */
1528 if (!nswindow)
1529 {
1530 GdkWindow *toplevel = NULL;
1531
1532 if (event_type == GDK_QUARTZ_MOUSE_MOVED)
1533 {
1534 /* Motion events received after clicking the menu bar do not have the
1535 * window field set. Instead of giving up on the event immediately,
1536 * we first check whether this event is within our window bounds.
1537 */
1538 NSPoint screen_point = [NSEvent mouseLocation];
1539 gint x_tmp, y_tmp;
1540
1541 toplevel = find_toplevel_under_pointer (_gdk_display,
1542 screen_point,
1543 &x_tmp, &y_tmp);
1544 }
1545
1546 if (!toplevel)
1547 return FALSE;
1548 }
1549
1550 /* Ignore events and break grabs while the window is being
1551 * dragged. This is a workaround for the window getting events for
1552 * the window title.
1553 */
1554 if ([(GdkQuartzNSWindow *)nswindow isInMove])
1555 {
1556 _gdk_quartz_events_break_all_grabs (get_time_from_ns_event (nsevent));
1557 return FALSE;
1558 }
1559
1560 /* Also when in a manual resize or move , we ignore events so that
1561 * these are pushed to GdkQuartzNSWindow's sendEvent handler.
1562 */
1563 if ([(GdkQuartzNSWindow *)nswindow isInManualResizeOrMove])
1564 return FALSE;
1565
1566 /* Find the right GDK window to send the event to, taking grabs and
1567 * event masks into consideration.
1568 */
1569 window = find_window_for_ns_event (nsevent, &x, &y, &x_root, &y_root);
1570 if (!window)
1571 return FALSE;
1572
1573 /* Quartz handles resizing on its own, so we want to stay out of the way. */
1574 if (test_resize (nsevent, window, x, y))
1575 return FALSE;
1576
1577 /* Apply any window filters. */
1578 if (GDK_IS_WINDOW (window))
1579 {
1580 GdkFilterReturn result;
1581
1582 if (window->filters)
1583 {
1584 g_object_ref (window);
1585
1586 result = gdk_event_apply_filters (nsevent, event, &window->filters);
1587
1588 g_object_unref (window);
1589
1590 if (result != GDK_FILTER_CONTINUE)
1591 {
1592 return_val = (result == GDK_FILTER_TRANSLATE) ? TRUE : FALSE;
1593 goto done;
1594 }
1595 }
1596 }
1597
1598 /* If the app is not active leave the event to AppKit so the window gets
1599 * focused correctly and don't do click-through (so we behave like most
1600 * native apps). If the app is active, we focus the window and then handle
1601 * the event, also to match native apps.
1602 */
1603 if ((event_type == GDK_QUARTZ_RIGHT_MOUSE_DOWN ||
1604 event_type == GDK_QUARTZ_OTHER_MOUSE_DOWN ||
1605 event_type == GDK_QUARTZ_LEFT_MOUSE_DOWN))
1606 {
1607 GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl);
1608
1609 if (![NSApp isActive])
1610 {
1611 [NSApp activateIgnoringOtherApps:YES];
1612 return FALSE;
1613 }
1614 else if (![impl->toplevel isKeyWindow])
1615 {
1616 GdkDeviceGrabInfo *grab;
1617 GdkSeat *seat = gdk_display_get_default_seat (_gdk_display);
1618
1619 grab = _gdk_display_get_last_device_grab (_gdk_display,
1620 gdk_seat_get_pointer (seat));
1621 if (!grab)
1622 [impl->toplevel makeKeyWindow];
1623
1624 }
1625 }
1626
1627 return_val = TRUE;
1628
1629 switch (event_type)
1630 {
1631 case GDK_QUARTZ_LEFT_MOUSE_DOWN:
1632 case GDK_QUARTZ_RIGHT_MOUSE_DOWN:
1633 case GDK_QUARTZ_OTHER_MOUSE_DOWN:
1634 case GDK_QUARTZ_LEFT_MOUSE_UP:
1635 case GDK_QUARTZ_RIGHT_MOUSE_UP:
1636 case GDK_QUARTZ_OTHER_MOUSE_UP:
1637 fill_button_event (window, event, nsevent, x, y, x_root, y_root);
1638 break;
1639
1640 case GDK_QUARTZ_LEFT_MOUSE_DRAGGED:
1641 case GDK_QUARTZ_RIGHT_MOUSE_DRAGGED:
1642 case GDK_QUARTZ_OTHER_MOUSE_DRAGGED:
1643 case GDK_QUARTZ_MOUSE_MOVED:
1644 fill_motion_event (window, event, nsevent, x, y, x_root, y_root);
1645 break;
1646
1647 case GDK_QUARTZ_SCROLL_WHEEL:
1648 {
1649 GdkScrollDirection direction;
1650 float dx;
1651 float dy;
1652 #ifdef AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER
1653 if (gdk_quartz_osx_version() >= GDK_OSX_LION &&
1654 [nsevent hasPreciseScrollingDeltas])
1655 {
1656 dx = [nsevent scrollingDeltaX];
1657 dy = [nsevent scrollingDeltaY];
1658 direction = GDK_SCROLL_SMOOTH;
1659
1660 fill_scroll_event (window, event, nsevent, x, y, x_root, y_root,
1661 -dx, -dy, direction);
1662
1663 /* Fall through for scroll buttons emulation */
1664 }
1665 #endif
1666 dx = [nsevent deltaX];
1667 dy = [nsevent deltaY];
1668
1669 if (dy != 0.0)
1670 {
1671 if (dy < 0.0)
1672 direction = GDK_SCROLL_DOWN;
1673 else
1674 direction = GDK_SCROLL_UP;
1675
1676 dy = fabs (dy);
1677 dx = 0.0;
1678 }
1679 else if (dx != 0.0)
1680 {
1681 if (dx < 0.0)
1682 direction = GDK_SCROLL_RIGHT;
1683 else
1684 direction = GDK_SCROLL_LEFT;
1685
1686 dx = fabs (dx);
1687 dy = 0.0;
1688 }
1689
1690 if (dx != 0.0 || dy != 0.0)
1691 {
1692 #ifdef AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER
1693 if (gdk_quartz_osx_version() >= GDK_OSX_LION &&
1694 [nsevent hasPreciseScrollingDeltas])
1695 {
1696 GdkEvent *emulated_event;
1697
1698 emulated_event = gdk_event_new (GDK_SCROLL);
1699 gdk_event_set_pointer_emulated (emulated_event, TRUE);
1700 fill_scroll_event (window, emulated_event, nsevent,
1701 x, y, x_root, y_root,
1702 dx, dy, direction);
1703 append_event (emulated_event, TRUE);
1704 }
1705 else
1706 #endif
1707 fill_scroll_event (window, event, nsevent,
1708 x, y, x_root, y_root,
1709 dx, dy, direction);
1710 }
1711 }
1712 break;
1713 #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER
1714 case NSEventTypeMagnify:
1715 case NSEventTypeRotate:
1716 /* Event handling requires [NSEvent phase] which was introduced in 10.7 */
1717 /* However - Tests on 10.7 showed that phase property does not work */
1718 if (gdk_quartz_osx_version () >= GDK_OSX_MOUNTAIN_LION)
1719 fill_pinch_event (window, event, nsevent, x, y, x_root, y_root);
1720 else
1721 return_val = FALSE;
1722 break;
1723 #endif
1724 case GDK_QUARTZ_MOUSE_EXITED:
1725 if (WINDOW_IS_TOPLEVEL (window))
1726 [[NSCursor arrowCursor] set];
1727 /* fall through */
1728 case GDK_QUARTZ_MOUSE_ENTERED:
1729 return_val = synthesize_crossing_event (window, event, nsevent, x, y, x_root, y_root);
1730 break;
1731
1732 case GDK_QUARTZ_KEY_DOWN:
1733 case GDK_QUARTZ_KEY_UP:
1734 case GDK_QUARTZ_FLAGS_CHANGED:
1735 {
1736 GdkEventType type;
1737
1738 type = _gdk_quartz_keys_event_type (nsevent);
1739 if (type == GDK_NOTHING)
1740 return_val = FALSE;
1741 else
1742 fill_key_event (window, event, nsevent, type);
1743 }
1744 break;
1745
1746 default:
1747 /* Ignore everything elsee. */
1748 return_val = FALSE;
1749 break;
1750 }
1751
1752 done:
1753 if (return_val)
1754 {
1755 if (event->any.window)
1756 g_object_ref (event->any.window);
1757 if (((event->any.type == GDK_ENTER_NOTIFY) ||
1758 (event->any.type == GDK_LEAVE_NOTIFY)) &&
1759 (event->crossing.subwindow != NULL))
1760 g_object_ref (event->crossing.subwindow);
1761 }
1762 else
1763 {
1764 /* Mark this event as having no resources to be freed */
1765 event->any.window = NULL;
1766 event->any.type = GDK_NOTHING;
1767 }
1768
1769 return return_val;
1770 }
1771
1772 void
_gdk_quartz_display_queue_events(GdkDisplay * display)1773 _gdk_quartz_display_queue_events (GdkDisplay *display)
1774 {
1775 NSEvent *nsevent;
1776
1777 nsevent = _gdk_quartz_event_loop_get_pending ();
1778 if (nsevent)
1779 {
1780 GdkEvent *event;
1781 GList *node;
1782
1783 event = gdk_event_new (GDK_NOTHING);
1784
1785 event->any.window = NULL;
1786 event->any.send_event = FALSE;
1787
1788 ((GdkEventPrivate *)event)->flags |= GDK_EVENT_PENDING;
1789
1790 node = _gdk_event_queue_append (display, event);
1791
1792 if (gdk_event_translate (event, nsevent))
1793 {
1794 ((GdkEventPrivate *)event)->flags &= ~GDK_EVENT_PENDING;
1795 _gdk_windowing_got_event (display, node, event, 0);
1796 }
1797 else
1798 {
1799 _gdk_event_queue_remove_link (display, node);
1800 g_list_free_1 (node);
1801 gdk_event_free (event);
1802
1803 gdk_threads_leave ();
1804 [NSApp sendEvent:nsevent];
1805 gdk_threads_enter ();
1806 }
1807
1808 _gdk_quartz_event_loop_release_event (nsevent);
1809 }
1810 }
1811
1812 void
_gdk_quartz_screen_broadcast_client_message(GdkScreen * screen,GdkEvent * event)1813 _gdk_quartz_screen_broadcast_client_message (GdkScreen *screen,
1814 GdkEvent *event)
1815 {
1816 /* Not supported. */
1817 }
1818
1819 gboolean
_gdk_quartz_screen_get_setting(GdkScreen * screen,const gchar * name,GValue * value)1820 _gdk_quartz_screen_get_setting (GdkScreen *screen,
1821 const gchar *name,
1822 GValue *value)
1823 {
1824 if (strcmp (name, "gtk-double-click-time") == 0)
1825 {
1826 NSUserDefaults *defaults;
1827 float t;
1828
1829 GDK_QUARTZ_ALLOC_POOL;
1830
1831 defaults = [NSUserDefaults standardUserDefaults];
1832
1833 t = [defaults floatForKey:@"com.apple.mouse.doubleClickThreshold"];
1834 if (t == 0.0)
1835 {
1836 /* No user setting, use the default in OS X. */
1837 t = 0.5;
1838 }
1839
1840 GDK_QUARTZ_RELEASE_POOL;
1841
1842 g_value_set_int (value, t * 1000);
1843
1844 return TRUE;
1845 }
1846 else if (strcmp (name, "gtk-font-name") == 0)
1847 {
1848 NSString *name;
1849 char *str;
1850 gint size;
1851
1852 GDK_QUARTZ_ALLOC_POOL;
1853
1854 name = [[NSFont systemFontOfSize:0] familyName];
1855 size = (gint)[[NSFont userFontOfSize:0] pointSize];
1856
1857 /* Let's try to use the "views" font size (12pt) by default. This is
1858 * used for lists/text/other "content" which is the largest parts of
1859 * apps, using the "regular control" size (13pt) looks a bit out of
1860 * place. We might have to tweak this.
1861 */
1862
1863 /* The size has to be hardcoded as there doesn't seem to be a way to
1864 * get the views font size programmatically.
1865 */
1866 str = g_strdup_printf ("%s %d", [name UTF8String], size);
1867 g_value_set_string (value, str);
1868 g_free (str);
1869
1870 GDK_QUARTZ_RELEASE_POOL;
1871
1872 return TRUE;
1873 }
1874 else if (strcmp (name, "gtk-primary-button-warps-slider") == 0)
1875 {
1876 GDK_QUARTZ_ALLOC_POOL;
1877
1878 BOOL setting = [[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollerPagingBehavior"];
1879
1880 /* If the Apple property is YES, it means "warp" */
1881 g_value_set_boolean (value, setting == YES);
1882
1883 GDK_QUARTZ_RELEASE_POOL;
1884
1885 return TRUE;
1886 }
1887 else if (strcmp (name, "gtk-shell-shows-desktop") == 0)
1888 {
1889 GDK_QUARTZ_ALLOC_POOL;
1890
1891 g_value_set_boolean (value, TRUE);
1892
1893 GDK_QUARTZ_RELEASE_POOL;
1894
1895 return TRUE;
1896 }
1897
1898 /* FIXME: Add more settings */
1899
1900 return FALSE;
1901 }
1902
1903 void
_gdk_quartz_display_event_data_copy(GdkDisplay * display,const GdkEvent * src,GdkEvent * dst)1904 _gdk_quartz_display_event_data_copy (GdkDisplay *display,
1905 const GdkEvent *src,
1906 GdkEvent *dst)
1907 {
1908 GdkEventPrivate *priv_src = (GdkEventPrivate *) src;
1909 GdkEventPrivate *priv_dst = (GdkEventPrivate *) dst;
1910
1911 if (priv_src->windowing_data)
1912 {
1913 priv_dst->windowing_data = priv_src->windowing_data;
1914 [(NSEvent *)priv_dst->windowing_data retain];
1915 }
1916 }
1917
1918 void
_gdk_quartz_display_event_data_free(GdkDisplay * display,GdkEvent * event)1919 _gdk_quartz_display_event_data_free (GdkDisplay *display,
1920 GdkEvent *event)
1921 {
1922 GdkEventPrivate *priv = (GdkEventPrivate *) event;
1923
1924 if (priv->windowing_data)
1925 {
1926 [(NSEvent *)priv->windowing_data release];
1927 priv->windowing_data = NULL;
1928 }
1929 }
1930