1/* X11Application.m -- subclass of NSApplication to multiplex events
2 *
3 * Copyright (c) 2002-2012 Apple Inc. All rights reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person
6 * obtaining a copy of this software and associated documentation files
7 * (the "Software"), to deal in the Software without restriction,
8 * including without limitation the rights to use, copy, modify, merge,
9 * publish, distribute, sublicense, and/or sell copies of the Software,
10 * and to permit persons to whom the Software is furnished to do so,
11 * subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT.  IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT
20 * HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
24 *
25 * Except as contained in this notice, the name(s) of the above
26 * copyright holders shall not be used in advertising or otherwise to
27 * promote the sale, use or other dealings in this Software without
28 * prior written authorization.
29 */
30
31#include "sanitizedCarbon.h"
32
33#ifdef HAVE_DIX_CONFIG_H
34#include <dix-config.h>
35#endif
36
37#include "quartzCommon.h"
38
39#import "X11Application.h"
40
41#include "darwin.h"
42#include "quartz.h"
43#include "darwinEvents.h"
44#include "quartzKeyboard.h"
45#include <X11/extensions/applewmconst.h>
46#include "micmap.h"
47#include "exglobals.h"
48
49#include <mach/mach.h>
50#include <unistd.h>
51#include <AvailabilityMacros.h>
52
53#include <pthread.h>
54
55#include <Xplugin.h>
56
57// pbproxy/pbproxy.h
58extern int
59xpbproxy_run(void);
60
61#define DEFAULTS_FILE X11LIBDIR "/X11/xserver/Xquartz.plist"
62
63#ifndef XSERVER_VERSION
64#define XSERVER_VERSION "?"
65#endif
66
67#ifdef HAVE_LIBDISPATCH
68#include <dispatch/dispatch.h>
69
70static dispatch_queue_t eventTranslationQueue;
71#endif
72
73#ifndef __has_feature
74#define __has_feature(x) 0
75#endif
76
77#ifndef CF_RETURNS_RETAINED
78#if __has_feature(attribute_cf_returns_retained)
79#define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
80#else
81#define CF_RETURNS_RETAINED
82#endif
83#endif
84
85extern Bool noTestExtensions;
86extern Bool noRenderExtension;
87
88#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
89static TISInputSourceRef last_key_layout;
90#else
91static KeyboardLayoutRef last_key_layout;
92#endif
93
94/* This preference is only tested on Lion or later as it's not relevant to
95 * earlier OS versions.
96 */
97Bool XQuartzScrollInDeviceDirection = FALSE;
98
99extern int darwinFakeButtons;
100
101/* Store the mouse location while in the background, and update X11's pointer
102 * location when we become the foreground application
103 */
104static NSPoint bgMouseLocation;
105static BOOL bgMouseLocationUpdated = FALSE;
106
107X11Application *X11App;
108
109CFStringRef app_prefs_domain_cfstr = NULL;
110
111#define ALL_KEY_MASKS (NSShiftKeyMask | NSControlKeyMask | \
112                       NSAlternateKeyMask | NSCommandKeyMask)
113
114@interface X11Application (Private)
115- (void) sendX11NSEvent:(NSEvent *)e;
116@end
117
118@implementation X11Application
119
120typedef struct message_struct message;
121struct message_struct {
122    mach_msg_header_t hdr;
123    SEL selector;
124    NSObject *arg;
125};
126
127static mach_port_t _port;
128
129/* Quartz mode initialization routine. This is often dynamically loaded
130   but is statically linked into this X server. */
131Bool
132QuartzModeBundleInit(void);
133
134static void
135init_ports(void)
136{
137    kern_return_t r;
138    NSPort *p;
139
140    if (_port != MACH_PORT_NULL) return;
141
142    r = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &_port);
143    if (r != KERN_SUCCESS) return;
144
145    p = [NSMachPort portWithMachPort:_port];
146    [p setDelegate:NSApp];
147    [p scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:
148     NSDefaultRunLoopMode];
149}
150
151static void
152message_kit_thread(SEL selector, NSObject *arg)
153{
154    message msg;
155    kern_return_t r;
156
157    msg.hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
158    msg.hdr.msgh_size = sizeof(msg);
159    msg.hdr.msgh_remote_port = _port;
160    msg.hdr.msgh_local_port = MACH_PORT_NULL;
161    msg.hdr.msgh_reserved = 0;
162    msg.hdr.msgh_id = 0;
163
164    msg.selector = selector;
165    msg.arg = [arg retain];
166
167    r = mach_msg(&msg.hdr, MACH_SEND_MSG, msg.hdr.msgh_size,
168                 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
169    if (r != KERN_SUCCESS)
170        ErrorF("%s: mach_msg failed: %x\n", __FUNCTION__, r);
171}
172
173- (void) handleMachMessage:(void *)_msg
174{
175    message *msg = _msg;
176
177    [self performSelector:msg->selector withObject:msg->arg];
178    [msg->arg release];
179}
180
181- (void) set_controller:obj
182{
183    if (_controller == nil) _controller = [obj retain];
184}
185
186- (void) dealloc
187{
188    if (_controller != nil) [_controller release];
189
190    if (_port != MACH_PORT_NULL)
191        mach_port_deallocate(mach_task_self(), _port);
192
193    [super dealloc];
194}
195
196- (void) orderFrontStandardAboutPanel: (id) sender
197{
198    NSMutableDictionary *dict;
199    NSDictionary *infoDict;
200    NSString *tem;
201
202    dict = [NSMutableDictionary dictionaryWithCapacity:3];
203    infoDict = [[NSBundle mainBundle] infoDictionary];
204
205    [dict setObject: NSLocalizedString(@"The X Window System", @"About panel")
206             forKey:@"ApplicationName"];
207
208    tem = [infoDict objectForKey:@"CFBundleShortVersionString"];
209
210    [dict setObject:[NSString stringWithFormat:@"XQuartz %@", tem]
211             forKey:@"ApplicationVersion"];
212
213    [dict setObject:[NSString stringWithFormat:@"xorg-server %s",
214                     XSERVER_VERSION]
215     forKey:@"Version"];
216
217    [self orderFrontStandardAboutPanelWithOptions: dict];
218}
219
220- (void) activateX:(OSX_BOOL)state
221{
222    if (_x_active == state)
223        return;
224
225    DEBUG_LOG("state=%d, _x_active=%d, \n", state, _x_active);
226    if (state) {
227        if (bgMouseLocationUpdated) {
228            DarwinSendPointerEvents(darwinPointer, MotionNotify, 0,
229                                    bgMouseLocation.x, bgMouseLocation.y,
230                                    0.0, 0.0);
231            bgMouseLocationUpdated = FALSE;
232        }
233        DarwinSendDDXEvent(kXquartzActivate, 0);
234    }
235    else {
236
237        if (darwin_all_modifier_flags)
238            DarwinUpdateModKeys(0);
239
240        DarwinInputReleaseButtonsAndKeys(darwinKeyboard);
241        DarwinInputReleaseButtonsAndKeys(darwinPointer);
242        DarwinInputReleaseButtonsAndKeys(darwinTabletCursor);
243        DarwinInputReleaseButtonsAndKeys(darwinTabletStylus);
244        DarwinInputReleaseButtonsAndKeys(darwinTabletEraser);
245
246        DarwinSendDDXEvent(kXquartzDeactivate, 0);
247    }
248
249    _x_active = state;
250}
251
252- (void) became_key:(NSWindow *)win
253{
254    [self activateX:NO];
255}
256
257- (void) sendEvent:(NSEvent *)e
258{
259    OSX_BOOL for_appkit, for_x;
260
261    /* By default pass down the responder chain and to X. */
262    for_appkit = YES;
263    for_x = YES;
264
265    switch ([e type]) {
266    case NSLeftMouseDown:
267    case NSRightMouseDown:
268    case NSOtherMouseDown:
269    case NSLeftMouseUp:
270    case NSRightMouseUp:
271    case NSOtherMouseUp:
272        if ([e window] != nil) {
273            /* Pointer event has an (AppKit) window. Probably something for the kit. */
274            for_x = NO;
275            if (_x_active) [self activateX:NO];
276        }
277        else if ([self modalWindow] == nil) {
278            /* Must be an X window. Tell appkit windows to resign main/key */
279            for_appkit = NO;
280
281            if (!_x_active && quartzProcs->IsX11Window([e windowNumber])) {
282                if ([self respondsToSelector:@selector(_setKeyWindow:)] && [self respondsToSelector:@selector(_setMainWindow:)]) {
283                    NSWindow *keyWindow = [self keyWindow];
284                    if (keyWindow) {
285                        [self _setKeyWindow:nil];
286                        [keyWindow resignKeyWindow];
287                    }
288
289                    NSWindow *mainWindow = [self mainWindow];
290                    if (mainWindow) {
291                        [self _setMainWindow:nil];
292                        [mainWindow resignMainWindow];
293                   }
294                 } else {
295                    /* This has a side effect of causing background apps to steal focus from XQuartz.
296                     * Unfortunately, there is no public and stable API to do what we want, but this
297                     * is a decent fallback in the off chance that the above selectors get dropped
298                     * in the future.
299                     */
300                    [self deactivate];
301                }
302
303                [self activateX:YES];
304            }
305        }
306
307        /* We want to force sending to appkit if we're over the menu bar */
308        if (!for_appkit) {
309            NSPoint NSlocation = [e locationInWindow];
310            NSWindow *window = [e window];
311            NSRect NSframe, NSvisibleFrame;
312            CGRect CGframe, CGvisibleFrame;
313            CGPoint CGlocation;
314
315            if (window != nil) {
316                NSRect frame = [window frame];
317                NSlocation.x += frame.origin.x;
318                NSlocation.y += frame.origin.y;
319            }
320
321            NSframe = [[NSScreen mainScreen] frame];
322            NSvisibleFrame = [[NSScreen mainScreen] visibleFrame];
323
324            CGframe = CGRectMake(NSframe.origin.x, NSframe.origin.y,
325                                 NSframe.size.width, NSframe.size.height);
326            CGvisibleFrame = CGRectMake(NSvisibleFrame.origin.x,
327                                        NSvisibleFrame.origin.y,
328                                        NSvisibleFrame.size.width,
329                                        NSvisibleFrame.size.height);
330            CGlocation = CGPointMake(NSlocation.x, NSlocation.y);
331
332            if (CGRectContainsPoint(CGframe, CGlocation) &&
333                !CGRectContainsPoint(CGvisibleFrame, CGlocation))
334                for_appkit = YES;
335        }
336
337        break;
338
339    case NSKeyDown:
340    case NSKeyUp:
341
342        if (_x_active) {
343            static BOOL do_swallow = NO;
344            static int swallow_keycode;
345
346            if ([e type] == NSKeyDown) {
347                /* Before that though, see if there are any global
348                 * shortcuts bound to it. */
349
350                if (darwinAppKitModMask &[e modifierFlags]) {
351                    /* Override to force sending to Appkit */
352                    swallow_keycode = [e keyCode];
353                    do_swallow = YES;
354                    for_x = NO;
355#if XPLUGIN_VERSION >= 1
356                }
357                else if (XQuartzEnableKeyEquivalents &&
358                         xp_is_symbolic_hotkey_event([e eventRef])) {
359                    swallow_keycode = [e keyCode];
360                    do_swallow = YES;
361                    for_x = NO;
362#endif
363                }
364                else if (XQuartzEnableKeyEquivalents &&
365                         [[self mainMenu] performKeyEquivalent:e]) {
366                    swallow_keycode = [e keyCode];
367                    do_swallow = YES;
368                    for_appkit = NO;
369                    for_x = NO;
370                }
371                else if (!XQuartzIsRootless
372                         && ([e modifierFlags] & ALL_KEY_MASKS) ==
373                         (NSCommandKeyMask | NSAlternateKeyMask)
374                         && ([e keyCode] == 0 /*a*/ || [e keyCode] ==
375                             53 /*Esc*/)) {
376                    /* We have this here to force processing fullscreen
377                     * toggle even if XQuartzEnableKeyEquivalents is disabled */
378                    swallow_keycode = [e keyCode];
379                    do_swallow = YES;
380                    for_x = NO;
381                    for_appkit = NO;
382                    DarwinSendDDXEvent(kXquartzToggleFullscreen, 0);
383                }
384                else {
385                    /* No kit window is focused, so send it to X. */
386                    for_appkit = NO;
387
388                    /* Reset our swallow state if we're seeing the same keyCode again.
389                     * This can happen if we become !_x_active when the keyCode we
390                     * intended to swallow is delivered.  See:
391                     * https://bugs.freedesktop.org/show_bug.cgi?id=92648
392                     */
393                    if ([e keyCode] == swallow_keycode) {
394                        do_swallow = NO;
395                    }
396                }
397            }
398            else {       /* KeyUp */
399                /* If we saw a key equivalent on the down, don't pass
400                 * the up through to X. */
401                if (do_swallow && [e keyCode] == swallow_keycode) {
402                    do_swallow = NO;
403                    for_x = NO;
404                }
405            }
406        }
407        else {       /* !_x_active */
408            for_x = NO;
409        }
410        break;
411
412    case NSFlagsChanged:
413        /* Don't tell X11 about modifiers changing while it's not active */
414        if (!_x_active)
415            for_x = NO;
416        break;
417
418    case NSAppKitDefined:
419        switch ([e subtype]) {
420            static BOOL x_was_active = NO;
421
422        case NSApplicationActivatedEventType:
423            for_x = NO;
424            if ([e window] == nil && x_was_active) {
425                BOOL order_all_windows = YES, workspaces, ok;
426                for_appkit = NO;
427
428                /* FIXME: This is a hack to avoid passing the event to AppKit which
429                 *        would result in it raising one of its windows.
430                 */
431                _appFlags._active = YES;
432
433                [self set_front_process:nil];
434
435                /* Get the Spaces preference for SwitchOnActivate */
436                (void)CFPreferencesAppSynchronize(CFSTR("com.apple.dock"));
437                workspaces =
438                    CFPreferencesGetAppBooleanValue(CFSTR("workspaces"),
439                                                    CFSTR(
440                                                        "com.apple.dock"),
441                                                    &ok);
442                if (!ok)
443                    workspaces = NO;
444
445                if (workspaces) {
446                    (void)CFPreferencesAppSynchronize(CFSTR(
447                                                          ".GlobalPreferences"));
448                    order_all_windows =
449                        CFPreferencesGetAppBooleanValue(CFSTR(
450                                                            "AppleSpacesSwitchOnActivate"),
451                                                        CFSTR(
452                                                            ".GlobalPreferences"),
453                                                        &ok);
454                    if (!ok)
455                        order_all_windows = YES;
456                }
457
458                /* TODO: In the workspaces && !AppleSpacesSwitchOnActivate case, the windows are ordered
459                 *       correctly, but we need to activate the top window on this space if there is
460                 *       none active.
461                 *
462                 *       If there are no active windows, and there are minimized windows, we should
463                 *       be restoring one of them.
464                 */
465                if ([e data2] & 0x10) {         // 0x10 (bfCPSOrderAllWindowsForward) is set when we use cmd-tab or the dock icon
466                    DarwinSendDDXEvent(kXquartzBringAllToFront, 1,
467                                       order_all_windows);
468                }
469            }
470            break;
471
472        case 18:         /* ApplicationDidReactivate */
473            if (XQuartzFullscreenVisible) for_appkit = NO;
474            break;
475
476        case NSApplicationDeactivatedEventType:
477            for_x = NO;
478
479            x_was_active = _x_active;
480            if (_x_active)
481                [self activateX:NO];
482            break;
483        }
484        break;
485
486    default:
487        break;          /* for gcc */
488    }
489
490    if (for_appkit) [super sendEvent:e];
491
492    if (for_x) {
493#ifdef HAVE_LIBDISPATCH
494        dispatch_async(eventTranslationQueue, ^{
495                           [self sendX11NSEvent:e];
496                       });
497#else
498        [self sendX11NSEvent:e];
499#endif
500    }
501}
502
503- (void) set_window_menu:(NSArray *)list
504{
505    [_controller set_window_menu:list];
506}
507
508- (void) set_window_menu_check:(NSNumber *)n
509{
510    [_controller set_window_menu_check:n];
511}
512
513- (void) set_apps_menu:(NSArray *)list
514{
515    [_controller set_apps_menu:list];
516}
517
518- (void) set_front_process:unused
519{
520    [NSApp activateIgnoringOtherApps:YES];
521
522    if ([self modalWindow] == nil)
523        [self activateX:YES];
524}
525
526- (void) set_can_quit:(NSNumber *)state
527{
528    [_controller set_can_quit:[state boolValue]];
529}
530
531- (void) server_ready:unused
532{
533    [_controller server_ready];
534}
535
536- (void) show_hide_menubar:(NSNumber *)state
537{
538    /* Also shows/hides the dock */
539    if ([state boolValue])
540        SetSystemUIMode(kUIModeNormal, 0);
541    else
542        SetSystemUIMode(kUIModeAllHidden,
543                        XQuartzFullscreenMenu ? kUIOptionAutoShowMenuBar : 0);                   // kUIModeAllSuppressed or kUIOptionAutoShowMenuBar can be used to allow "mouse-activation"
544}
545
546- (void) launch_client:(NSString *)cmd
547{
548    (void)[_controller application:self openFile:cmd];
549}
550
551/* user preferences */
552
553/* Note that these functions only work for arrays whose elements
554   can be toll-free-bridged between NS and CF worlds. */
555
556static const void *
557cfretain(CFAllocatorRef a, const void *b)
558{
559    return CFRetain(b);
560}
561
562static void
563cfrelease(CFAllocatorRef a, const void *b)
564{
565    CFRelease(b);
566}
567
568CF_RETURNS_RETAINED
569static CFMutableArrayRef
570nsarray_to_cfarray(NSArray *in)
571{
572    CFMutableArrayRef out;
573    CFArrayCallBacks cb;
574    NSObject *ns;
575    const CFTypeRef *cf;
576    int i, count;
577
578    memset(&cb, 0, sizeof(cb));
579    cb.version = 0;
580    cb.retain = cfretain;
581    cb.release = cfrelease;
582
583    count = [in count];
584    out = CFArrayCreateMutable(NULL, count, &cb);
585
586    for (i = 0; i < count; i++) {
587        ns = [in objectAtIndex:i];
588
589        if ([ns isKindOfClass:[NSArray class]])
590            cf = (CFTypeRef)nsarray_to_cfarray((NSArray *)ns);
591        else
592            cf = CFRetain((CFTypeRef)ns);
593
594        CFArrayAppendValue(out, cf);
595        CFRelease(cf);
596    }
597
598    return out;
599}
600
601static NSMutableArray *
602cfarray_to_nsarray(CFArrayRef in)
603{
604    NSMutableArray *out;
605    const CFTypeRef *cf;
606    NSObject *ns;
607    int i, count;
608
609    count = CFArrayGetCount(in);
610    out = [[NSMutableArray alloc] initWithCapacity:count];
611
612    for (i = 0; i < count; i++) {
613        cf = CFArrayGetValueAtIndex(in, i);
614
615        if (CFGetTypeID(cf) == CFArrayGetTypeID())
616            ns = cfarray_to_nsarray((CFArrayRef)cf);
617        else
618            ns = [(id) cf retain];
619
620        [out addObject:ns];
621        [ns release];
622    }
623
624    return out;
625}
626
627- (CFPropertyListRef) prefs_get_copy:(NSString *)key
628{
629    CFPropertyListRef value;
630
631    value = CFPreferencesCopyAppValue((CFStringRef)key,
632                                      app_prefs_domain_cfstr);
633
634    if (value == NULL) {
635        static CFDictionaryRef defaults;
636
637        if (defaults == NULL) {
638            CFStringRef error = NULL;
639            CFDataRef data;
640            CFURLRef url;
641            SInt32 error_code;
642
643            url = (CFURLCreateFromFileSystemRepresentation
644                       (NULL, (unsigned char *)DEFAULTS_FILE,
645                       strlen(DEFAULTS_FILE), false));
646            if (CFURLCreateDataAndPropertiesFromResource(NULL, url, &data,
647                                                         NULL, NULL,
648                                                         &error_code)) {
649                defaults = (CFPropertyListCreateFromXMLData
650                                (NULL, data,
651                                kCFPropertyListMutableContainersAndLeaves,
652                                &error));
653                if (error != NULL) CFRelease(error);
654                CFRelease(data);
655            }
656            CFRelease(url);
657
658            if (defaults != NULL) {
659                NSMutableArray *apps, *elt;
660                int count, i;
661                NSString *name, *nname;
662
663                /* Localize the names in the default apps menu. */
664
665                apps =
666                    [(NSDictionary *) defaults objectForKey:@PREFS_APPSMENU];
667                if (apps != nil) {
668                    count = [apps count];
669                    for (i = 0; i < count; i++) {
670                        elt = [apps objectAtIndex:i];
671                        if (elt != nil &&
672                            [elt isKindOfClass:[NSArray class]]) {
673                            name = [elt objectAtIndex:0];
674                            if (name != nil) {
675                                nname = NSLocalizedString(name, nil);
676                                if (nname != nil && nname != name)
677                                    [elt replaceObjectAtIndex:0 withObject:
678                                     nname];
679                            }
680                        }
681                    }
682                }
683            }
684        }
685
686        if (defaults != NULL) value = CFDictionaryGetValue(defaults, key);
687        if (value != NULL) CFRetain(value);
688    }
689
690    return value;
691}
692
693- (int) prefs_get_integer:(NSString *)key default:(int)def
694{
695    CFPropertyListRef value;
696    int ret;
697
698    value = [self prefs_get_copy:key];
699
700    if (value != NULL && CFGetTypeID(value) == CFNumberGetTypeID())
701        CFNumberGetValue(value, kCFNumberIntType, &ret);
702    else if (value != NULL && CFGetTypeID(value) == CFStringGetTypeID())
703        ret = CFStringGetIntValue(value);
704    else
705        ret = def;
706
707    if (value != NULL) CFRelease(value);
708
709    return ret;
710}
711
712- (const char *) prefs_get_string:(NSString *)key default:(const char *)def
713{
714    CFPropertyListRef value;
715    const char *ret = NULL;
716
717    value = [self prefs_get_copy:key];
718
719    if (value != NULL && CFGetTypeID(value) == CFStringGetTypeID()) {
720        NSString *s = (NSString *)value;
721
722        ret = [s UTF8String];
723    }
724
725    if (value != NULL) CFRelease(value);
726
727    return ret != NULL ? ret : def;
728}
729
730- (NSURL *) prefs_copy_url:(NSString *)key default:(NSURL *)def
731{
732    CFPropertyListRef value;
733    NSURL *ret = NULL;
734
735    value = [self prefs_get_copy:key];
736
737    if (value != NULL && CFGetTypeID(value) == CFStringGetTypeID()) {
738        NSString *s = (NSString *)value;
739
740        ret = [NSURL URLWithString:s];
741        [ret retain];
742    }
743
744    if (value != NULL) CFRelease(value);
745
746    return ret != NULL ? ret : def;
747}
748
749- (float) prefs_get_float:(NSString *)key default:(float)def
750{
751    CFPropertyListRef value;
752    float ret = def;
753
754    value = [self prefs_get_copy:key];
755
756    if (value != NULL
757        && CFGetTypeID(value) == CFNumberGetTypeID()
758        && CFNumberIsFloatType(value))
759        CFNumberGetValue(value, kCFNumberFloatType, &ret);
760    else if (value != NULL && CFGetTypeID(value) == CFStringGetTypeID())
761        ret = CFStringGetDoubleValue(value);
762
763    if (value != NULL) CFRelease(value);
764
765    return ret;
766}
767
768- (int) prefs_get_boolean:(NSString *)key default:(int)def
769{
770    CFPropertyListRef value;
771    int ret = def;
772
773    value = [self prefs_get_copy:key];
774
775    if (value != NULL) {
776        if (CFGetTypeID(value) == CFNumberGetTypeID())
777            CFNumberGetValue(value, kCFNumberIntType, &ret);
778        else if (CFGetTypeID(value) == CFBooleanGetTypeID())
779            ret = CFBooleanGetValue(value);
780        else if (CFGetTypeID(value) == CFStringGetTypeID()) {
781            const char *tem = [(NSString *) value UTF8String];
782            if (strcasecmp(tem, "true") == 0 || strcasecmp(tem, "yes") == 0)
783                ret = YES;
784            else
785                ret = NO;
786        }
787
788        CFRelease(value);
789    }
790    return ret;
791}
792
793- (NSArray *) prefs_get_array:(NSString *)key
794{
795    NSArray *ret = nil;
796    CFPropertyListRef value;
797
798    value = [self prefs_get_copy:key];
799
800    if (value != NULL) {
801        if (CFGetTypeID(value) == CFArrayGetTypeID())
802            ret = [cfarray_to_nsarray (value)autorelease];
803
804        CFRelease(value);
805    }
806
807    return ret;
808}
809
810- (void) prefs_set_integer:(NSString *)key value:(int)value
811{
812    CFNumberRef x;
813
814    x = CFNumberCreate(NULL, kCFNumberIntType, &value);
815
816    CFPreferencesSetValue((CFStringRef)key, (CFTypeRef)x,
817                          app_prefs_domain_cfstr,
818                          kCFPreferencesCurrentUser,
819                          kCFPreferencesAnyHost);
820
821    CFRelease(x);
822}
823
824- (void) prefs_set_float:(NSString *)key value:(float)value
825{
826    CFNumberRef x;
827
828    x = CFNumberCreate(NULL, kCFNumberFloatType, &value);
829
830    CFPreferencesSetValue((CFStringRef)key, (CFTypeRef)x,
831                          app_prefs_domain_cfstr,
832                          kCFPreferencesCurrentUser,
833                          kCFPreferencesAnyHost);
834
835    CFRelease(x);
836}
837
838- (void) prefs_set_boolean:(NSString *)key value:(int)value
839{
840    CFPreferencesSetValue(
841        (CFStringRef)key,
842        (CFTypeRef)(value ? kCFBooleanTrue
843                    : kCFBooleanFalse),
844        app_prefs_domain_cfstr,
845        kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
846
847}
848
849- (void) prefs_set_array:(NSString *)key value:(NSArray *)value
850{
851    CFArrayRef cfarray;
852
853    cfarray = nsarray_to_cfarray(value);
854    CFPreferencesSetValue((CFStringRef)key,
855                          (CFTypeRef)cfarray,
856                          app_prefs_domain_cfstr,
857                          kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
858    CFRelease(cfarray);
859}
860
861- (void) prefs_set_string:(NSString *)key value:(NSString *)value
862{
863    CFPreferencesSetValue((CFStringRef)key, (CFTypeRef)value,
864                          app_prefs_domain_cfstr, kCFPreferencesCurrentUser,
865                          kCFPreferencesAnyHost);
866}
867
868- (void) prefs_synchronize
869{
870    CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
871}
872
873- (void) read_defaults
874{
875    NSString *nsstr;
876    const char *tem;
877
878    XQuartzRootlessDefault = [self prefs_get_boolean:@PREFS_ROOTLESS
879                              default               :XQuartzRootlessDefault];
880    XQuartzFullscreenMenu = [self prefs_get_boolean:@PREFS_FULLSCREEN_MENU
881                             default               :XQuartzFullscreenMenu];
882    XQuartzFullscreenDisableHotkeys =
883        ![self prefs_get_boolean:@PREFS_FULLSCREEN_HOTKEYS
884          default               :!
885          XQuartzFullscreenDisableHotkeys];
886    darwinFakeButtons = [self prefs_get_boolean:@PREFS_FAKEBUTTONS
887                         default               :darwinFakeButtons];
888    XQuartzOptionSendsAlt = [self prefs_get_boolean:@PREFS_OPTION_SENDS_ALT
889                             default               :XQuartzOptionSendsAlt];
890
891    if (darwinFakeButtons) {
892        const char *fake2, *fake3;
893
894        fake2 = [self prefs_get_string:@PREFS_FAKE_BUTTON2 default:NULL];
895        fake3 = [self prefs_get_string:@PREFS_FAKE_BUTTON3 default:NULL];
896
897        if (fake2 != NULL) darwinFakeMouse2Mask = DarwinParseModifierList(
898                fake2, TRUE);
899        if (fake3 != NULL) darwinFakeMouse3Mask = DarwinParseModifierList(
900                fake3, TRUE);
901    }
902
903    tem = [self prefs_get_string:@PREFS_APPKIT_MODIFIERS default:NULL];
904    if (tem != NULL) darwinAppKitModMask = DarwinParseModifierList(tem, TRUE);
905
906    tem = [self prefs_get_string:@PREFS_WINDOW_ITEM_MODIFIERS default:NULL];
907    if (tem != NULL) {
908        windowItemModMask = DarwinParseModifierList(tem, FALSE);
909    }
910    else {
911        nsstr = NSLocalizedString(@"window item modifiers",
912                                  @"window item modifiers");
913        if (nsstr != NULL) {
914            tem = [nsstr UTF8String];
915            if ((tem != NULL) && strcmp(tem, "window item modifiers")) {
916                windowItemModMask = DarwinParseModifierList(tem, FALSE);
917            }
918        }
919    }
920
921    XQuartzEnableKeyEquivalents = [self prefs_get_boolean:@PREFS_KEYEQUIVS
922                                   default               :
923                                   XQuartzEnableKeyEquivalents];
924
925    darwinSyncKeymap = [self prefs_get_boolean:@PREFS_SYNC_KEYMAP
926                        default               :darwinSyncKeymap];
927
928    darwinDesiredDepth = [self prefs_get_integer:@PREFS_DEPTH
929                          default               :darwinDesiredDepth];
930
931    noTestExtensions = ![self prefs_get_boolean:@PREFS_TEST_EXTENSIONS
932                         default               :FALSE];
933
934    noRenderExtension = ![self prefs_get_boolean:@PREFS_RENDER_EXTENSION
935                          default               :TRUE];
936
937    XQuartzScrollInDeviceDirection =
938        [self prefs_get_boolean:@PREFS_SCROLL_IN_DEV_DIRECTION
939         default               :
940         XQuartzScrollInDeviceDirection];
941
942#if XQUARTZ_SPARKLE
943    NSURL *url = [self prefs_copy_url:@PREFS_UPDATE_FEED default:nil];
944    if (url) {
945        [[SUUpdater sharedUpdater] setFeedURL:url];
946        [url release];
947    }
948#endif
949}
950
951/* This will end up at the end of the responder chain. */
952- (void) copy:sender
953{
954    DarwinSendDDXEvent(kXquartzPasteboardNotify, 1,
955                       AppleWMCopyToPasteboard);
956}
957
958- (X11Controller *) controller
959{
960    return _controller;
961}
962
963- (OSX_BOOL) x_active
964{
965    return _x_active;
966}
967
968@end
969
970static NSArray *
971array_with_strings_and_numbers(int nitems, const char **items,
972                               const char *numbers)
973{
974    NSMutableArray *array, *subarray;
975    NSString *string, *number;
976    int i;
977
978    /* (Can't autorelease on the X server thread) */
979
980    array = [[NSMutableArray alloc] initWithCapacity:nitems];
981
982    for (i = 0; i < nitems; i++) {
983        subarray = [[NSMutableArray alloc] initWithCapacity:2];
984
985        string = [[NSString alloc] initWithUTF8String:items[i]];
986        [subarray addObject:string];
987        [string release];
988
989        if (numbers[i] != 0) {
990            number = [[NSString alloc] initWithFormat:@"%d", numbers[i]];
991            [subarray addObject:number];
992            [number release];
993        }
994        else
995            [subarray addObject:@""];
996
997        [array addObject:subarray];
998        [subarray release];
999    }
1000
1001    return array;
1002}
1003
1004void
1005X11ApplicationSetWindowMenu(int nitems, const char **items,
1006                            const char *shortcuts)
1007{
1008    NSArray *array;
1009    array = array_with_strings_and_numbers(nitems, items, shortcuts);
1010
1011    /* Send the array of strings over to the appkit thread */
1012
1013    message_kit_thread(@selector (set_window_menu:), array);
1014    [array release];
1015}
1016
1017void
1018X11ApplicationSetWindowMenuCheck(int idx)
1019{
1020    NSNumber *n;
1021
1022    n = [[NSNumber alloc] initWithInt:idx];
1023
1024    message_kit_thread(@selector (set_window_menu_check:), n);
1025
1026    [n release];
1027}
1028
1029void
1030X11ApplicationSetFrontProcess(void)
1031{
1032    message_kit_thread(@selector (set_front_process:), nil);
1033}
1034
1035void
1036X11ApplicationSetCanQuit(int state)
1037{
1038    NSNumber *n;
1039
1040    n = [[NSNumber alloc] initWithBool:state];
1041
1042    message_kit_thread(@selector (set_can_quit:), n);
1043
1044    [n release];
1045}
1046
1047void
1048X11ApplicationServerReady(void)
1049{
1050    message_kit_thread(@selector (server_ready:), nil);
1051}
1052
1053void
1054X11ApplicationShowHideMenubar(int state)
1055{
1056    NSNumber *n;
1057
1058    n = [[NSNumber alloc] initWithBool:state];
1059
1060    message_kit_thread(@selector (show_hide_menubar:), n);
1061
1062    [n release];
1063}
1064
1065void
1066X11ApplicationLaunchClient(const char *cmd)
1067{
1068    NSString *string;
1069
1070    string = [[NSString alloc] initWithUTF8String:cmd];
1071
1072    message_kit_thread(@selector (launch_client:), string);
1073
1074    [string release];
1075}
1076
1077/* This is a special function in that it is run from the *SERVER* thread and
1078 * not the AppKit thread.  We want to block entering a screen-capturing RandR
1079 * mode until we notify the user about how to get out if the X11 client crashes.
1080 */
1081Bool
1082X11ApplicationCanEnterRandR(void)
1083{
1084    NSString *title, *msg;
1085
1086    if ([X11App prefs_get_boolean:@PREFS_NO_RANDR_ALERT default:NO] ||
1087        XQuartzShieldingWindowLevel != 0)
1088        return TRUE;
1089
1090    title = NSLocalizedString(@"Enter RandR mode?",
1091                              @"Dialog title when switching to RandR");
1092    msg = NSLocalizedString(
1093        @"An application has requested X11 to change the resolution of your display.  X11 will restore the display to its previous state when the requesting application requests to return to the previous state.  Alternatively, you can use the ⌥⌘A key sequence to force X11 to return to the previous state.",
1094        @"Dialog when switching to RandR");
1095
1096    if (!XQuartzIsRootless)
1097        QuartzShowFullscreen(FALSE);
1098
1099    switch (NSRunAlertPanel(title, @"%@",
1100                            NSLocalizedString(@"Allow",
1101                                              @""),
1102                            NSLocalizedString(@"Cancel",
1103                                              @""),
1104                            NSLocalizedString(@"Always Allow", @""), msg)) {
1105    case NSAlertOtherReturn:
1106        [X11App prefs_set_boolean:@PREFS_NO_RANDR_ALERT value:YES];
1107        [X11App prefs_synchronize];
1108
1109    case NSAlertDefaultReturn:
1110        return YES;
1111
1112    default:
1113        return NO;
1114    }
1115}
1116
1117static void
1118check_xinitrc(void)
1119{
1120    char *tem, buf[1024];
1121    NSString *msg;
1122
1123    if ([X11App prefs_get_boolean:@PREFS_DONE_XINIT_CHECK default:NO])
1124        return;
1125
1126    tem = getenv("HOME");
1127    if (tem == NULL) goto done;
1128
1129    snprintf(buf, sizeof(buf), "%s/.xinitrc", tem);
1130    if (access(buf, F_OK) != 0)
1131        goto done;
1132
1133    msg =
1134        NSLocalizedString(
1135            @"You have an existing ~/.xinitrc file.\n\n\
1136                             Windows displayed by X11 applications may not have titlebars, or may look \
1137                             different to windows displayed by native applications.\n\n\
1138                             Would you like to move aside the existing file and use the standard X11 \
1139                             environment the next time you start X11?"                                                                                                                                                                                                                                                                                                                                                                  ,
1140            @"Startup xinitrc dialog");
1141
1142    if (NSAlertDefaultReturn ==
1143        NSRunAlertPanel(nil, @"%@", NSLocalizedString(@"Yes", @""),
1144                        NSLocalizedString(@"No", @""), nil, msg)) {
1145        char buf2[1024];
1146        int i = -1;
1147
1148        snprintf(buf2, sizeof(buf2), "%s.old", buf);
1149
1150        for (i = 1; access(buf2, F_OK) == 0; i++)
1151            snprintf(buf2, sizeof(buf2), "%s.old.%d", buf, i);
1152
1153        rename(buf, buf2);
1154    }
1155
1156done:
1157    [X11App prefs_set_boolean:@PREFS_DONE_XINIT_CHECK value:YES];
1158    [X11App prefs_synchronize];
1159}
1160
1161static inline pthread_t
1162create_thread(void *(*func)(void *), void *arg)
1163{
1164    pthread_attr_t attr;
1165    pthread_t tid;
1166
1167    pthread_attr_init(&attr);
1168    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
1169    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
1170    pthread_create(&tid, &attr, func, arg);
1171    pthread_attr_destroy(&attr);
1172
1173    return tid;
1174}
1175
1176static void *
1177xpbproxy_x_thread(void *args)
1178{
1179    xpbproxy_run();
1180
1181    ErrorF("xpbproxy thread is terminating unexpectedly.\n");
1182    return NULL;
1183}
1184
1185void
1186X11ApplicationMain(int argc, char **argv, char **envp)
1187{
1188    NSAutoreleasePool *pool;
1189
1190#ifdef DEBUG
1191    while (access("/tmp/x11-block", F_OK) == 0) sleep(1);
1192#endif
1193
1194    pool = [[NSAutoreleasePool alloc] init];
1195    X11App = (X11Application *)[X11Application sharedApplication];
1196    init_ports();
1197
1198    app_prefs_domain_cfstr =
1199        (CFStringRef)[[NSBundle mainBundle] bundleIdentifier];
1200
1201    if (app_prefs_domain_cfstr == NULL) {
1202        ErrorF(
1203            "X11ApplicationMain: Unable to determine bundle identifier.  Your installation of XQuartz may be broken.\n");
1204        app_prefs_domain_cfstr = CFSTR(BUNDLE_ID_PREFIX ".X11");
1205    }
1206
1207    [NSApp read_defaults];
1208    [NSBundle loadNibNamed:@"main" owner:NSApp];
1209    [[NSNotificationCenter defaultCenter] addObserver:NSApp
1210                                             selector:@selector (became_key:)
1211                                                 name:
1212     NSWindowDidBecomeKeyNotification object:nil];
1213
1214    /*
1215     * The xpr Quartz mode is statically linked into this server.
1216     * Initialize all the Quartz functions.
1217     */
1218    QuartzModeBundleInit();
1219
1220    /* Calculate the height of the menubar so we can avoid it. */
1221    aquaMenuBarHeight = [[NSApp mainMenu] menuBarHeight];
1222#if ! __LP64__
1223    if (!aquaMenuBarHeight) {
1224        aquaMenuBarHeight = [NSMenuView menuBarHeight];
1225    }
1226#endif
1227    if (!aquaMenuBarHeight) {
1228        NSScreen* primaryScreen = [[NSScreen screens] objectAtIndex:0];
1229        aquaMenuBarHeight = NSHeight([primaryScreen frame]) - NSMaxY([primaryScreen visibleFrame]);
1230    }
1231
1232#ifdef HAVE_LIBDISPATCH
1233    eventTranslationQueue = dispatch_queue_create(
1234        BUNDLE_ID_PREFIX ".X11.NSEventsToX11EventsQueue", NULL);
1235    assert(eventTranslationQueue != NULL);
1236#endif
1237
1238    /* Set the key layout seed before we start the server */
1239#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
1240    last_key_layout = TISCopyCurrentKeyboardLayoutInputSource();
1241
1242    if (!last_key_layout)
1243        ErrorF(
1244            "X11ApplicationMain: Unable to determine TISCopyCurrentKeyboardLayoutInputSource() at startup.\n");
1245#else
1246    KLGetCurrentKeyboardLayout(&last_key_layout);
1247    if (!last_key_layout)
1248        ErrorF(
1249            "X11ApplicationMain: Unable to determine KLGetCurrentKeyboardLayout() at startup.\n");
1250#endif
1251
1252    if (!QuartsResyncKeymap(FALSE)) {
1253        ErrorF("X11ApplicationMain: Could not build a valid keymap.\n");
1254    }
1255
1256    /* Tell the server thread that it can proceed */
1257    QuartzInitServer(argc, argv, envp);
1258
1259    /* This must be done after QuartzInitServer because it can result in
1260     * an mieqEnqueue() - <rdar://problem/6300249>
1261     */
1262    check_xinitrc();
1263
1264    create_thread(xpbproxy_x_thread, NULL);
1265
1266#if XQUARTZ_SPARKLE
1267    [[X11App controller] setup_sparkle];
1268    [[SUUpdater sharedUpdater] resetUpdateCycle];
1269    //    [[SUUpdater sharedUpdater] checkForUpdates:X11App];
1270#endif
1271
1272    [pool release];
1273    [NSApp run];
1274    /* not reached */
1275}
1276
1277@implementation X11Application (Private)
1278
1279#ifdef NX_DEVICELCMDKEYMASK
1280/* This is to workaround a bug in the VNC server where we sometimes see the L
1281 * modifier and sometimes see no "side"
1282 */
1283static inline int
1284ensure_flag(int flags, int device_independent, int device_dependents,
1285            int device_dependent_default)
1286{
1287    if ((flags & device_independent) &&
1288        !(flags & device_dependents))
1289        flags |= device_dependent_default;
1290    return flags;
1291}
1292#endif
1293
1294#ifdef DEBUG_UNTRUSTED_POINTER_DELTA
1295static const char *
1296untrusted_str(NSEvent *e)
1297{
1298    switch ([e type]) {
1299    case NSScrollWheel:
1300        return "NSScrollWheel";
1301
1302    case NSTabletPoint:
1303        return "NSTabletPoint";
1304
1305    case NSOtherMouseDown:
1306        return "NSOtherMouseDown";
1307
1308    case NSOtherMouseUp:
1309        return "NSOtherMouseUp";
1310
1311    case NSLeftMouseDown:
1312        return "NSLeftMouseDown";
1313
1314    case NSLeftMouseUp:
1315        return "NSLeftMouseUp";
1316
1317    default:
1318        switch ([e subtype]) {
1319        case NSTabletPointEventSubtype:
1320            return "NSTabletPointEventSubtype";
1321
1322        case NSTabletProximityEventSubtype:
1323            return "NSTabletProximityEventSubtype";
1324
1325        default:
1326            return "Other";
1327        }
1328    }
1329}
1330#endif
1331
1332extern void
1333wait_for_mieq_init(void);
1334
1335- (void) sendX11NSEvent:(NSEvent *)e
1336{
1337    NSPoint location = NSZeroPoint;
1338    int ev_button, ev_type;
1339    static float pressure = 0.0;       // static so ProximityOut will have the value from the previous tablet event
1340    static NSPoint tilt;               // static so ProximityOut will have the value from the previous tablet event
1341    static DeviceIntPtr darwinTabletCurrent = NULL;
1342    static BOOL needsProximityIn = NO; // Do we do need to handle a pending ProximityIn once we have pressure/tilt?
1343    DeviceIntPtr pDev;
1344    int modifierFlags;
1345    BOOL isMouseOrTabletEvent, isTabletEvent;
1346
1347    if (!darwinTabletCurrent) {
1348        /* Ensure that the event system is initialized */
1349        wait_for_mieq_init();
1350        assert(darwinTabletStylus);
1351
1352        tilt = NSZeroPoint;
1353        darwinTabletCurrent = darwinTabletStylus;
1354    }
1355
1356    isMouseOrTabletEvent = [e type] == NSLeftMouseDown ||
1357                           [e type] == NSOtherMouseDown ||
1358                           [e type] == NSRightMouseDown ||
1359                           [e type] == NSLeftMouseUp ||
1360                           [e type] == NSOtherMouseUp ||
1361                           [e type] == NSRightMouseUp ||
1362                           [e type] == NSLeftMouseDragged ||
1363                           [e type] == NSOtherMouseDragged ||
1364                           [e type] == NSRightMouseDragged ||
1365                           [e type] == NSMouseMoved ||
1366                           [e type] == NSTabletPoint ||
1367                           [e type] == NSScrollWheel;
1368
1369    isTabletEvent = ([e type] == NSTabletPoint) ||
1370                    (isMouseOrTabletEvent &&
1371                     ([e subtype] == NSTabletPointEventSubtype ||
1372                      [e subtype] == NSTabletProximityEventSubtype));
1373
1374    if (isMouseOrTabletEvent) {
1375        static NSPoint lastpt;
1376        NSWindow *window = [e window];
1377        NSRect screen = [[[NSScreen screens] objectAtIndex:0] frame];
1378        BOOL hasUntrustedPointerDelta;
1379
1380        // NSEvents for tablets are not consistent wrt deltaXY between events, so we cannot rely on that
1381        // Thus tablets will be subject to the warp-pointer bug worked around by the delta, but tablets
1382        // are not normally used in cases where that bug would present itself, so this is a fair tradeoff
1383        // <rdar://problem/7111003> deltaX and deltaY are incorrect for NSMouseMoved, NSTabletPointEventSubtype
1384        // http://xquartz.macosforge.org/trac/ticket/288
1385        hasUntrustedPointerDelta = isTabletEvent;
1386
1387        // The deltaXY for middle click events also appear erroneous after fast user switching
1388        // <rdar://problem/7979468> deltaX and deltaY are incorrect for NSOtherMouseDown and NSOtherMouseUp after FUS
1389        // http://xquartz.macosforge.org/trac/ticket/389
1390        hasUntrustedPointerDelta |= [e type] == NSOtherMouseDown ||
1391                                    [e type] == NSOtherMouseUp;
1392
1393        // The deltaXY for scroll events correspond to the scroll delta, not the pointer delta
1394        // <rdar://problem/7989690> deltaXY for wheel events are being sent as mouse movement
1395        hasUntrustedPointerDelta |= [e type] == NSScrollWheel;
1396
1397#ifdef DEBUG_UNTRUSTED_POINTER_DELTA
1398        hasUntrustedPointerDelta |= [e type] == NSLeftMouseDown ||
1399                                    [e type] == NSLeftMouseUp;
1400#endif
1401
1402        if (window != nil) {
1403            NSRect frame = [window frame];
1404            location = [e locationInWindow];
1405            location.x += frame.origin.x;
1406            location.y += frame.origin.y;
1407            lastpt = location;
1408        }
1409        else if (hasUntrustedPointerDelta) {
1410#ifdef DEBUG_UNTRUSTED_POINTER_DELTA
1411            ErrorF("--- Begin Event Debug ---\n");
1412            ErrorF("Event type: %s\n", untrusted_str(e));
1413            ErrorF("old lastpt: (%0.2f, %0.2f)\n", lastpt.x, lastpt.y);
1414            ErrorF("     delta: (%0.2f, %0.2f)\n", [e deltaX], -[e deltaY]);
1415            ErrorF("  location: (%0.2f, %0.2f)\n", lastpt.x + [e deltaX],
1416                   lastpt.y - [e deltaY]);
1417            ErrorF("workaround: (%0.2f, %0.2f)\n", [e locationInWindow].x,
1418                   [e locationInWindow].y);
1419            ErrorF("--- End Event Debug ---\n");
1420
1421            location.x = lastpt.x + [e deltaX];
1422            location.y = lastpt.y - [e deltaY];
1423            lastpt = [e locationInWindow];
1424#else
1425            location = [e locationInWindow];
1426            lastpt = location;
1427#endif
1428        }
1429        else {
1430            location.x = lastpt.x + [e deltaX];
1431            location.y = lastpt.y - [e deltaY];
1432            lastpt = [e locationInWindow];
1433        }
1434
1435        /* Convert coordinate system */
1436        location.y = (screen.origin.y + screen.size.height) - location.y;
1437    }
1438
1439    modifierFlags = [e modifierFlags];
1440
1441#ifdef NX_DEVICELCMDKEYMASK
1442    /* This is to workaround a bug in the VNC server where we sometimes see the L
1443     * modifier and sometimes see no "side"
1444     */
1445    modifierFlags = ensure_flag(modifierFlags, NX_CONTROLMASK,
1446                                NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK,
1447                                NX_DEVICELCTLKEYMASK);
1448    modifierFlags = ensure_flag(modifierFlags, NX_SHIFTMASK,
1449                                NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK,
1450                                NX_DEVICELSHIFTKEYMASK);
1451    modifierFlags = ensure_flag(modifierFlags, NX_COMMANDMASK,
1452                                NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK,
1453                                NX_DEVICELCMDKEYMASK);
1454    modifierFlags = ensure_flag(modifierFlags, NX_ALTERNATEMASK,
1455                                NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK,
1456                                NX_DEVICELALTKEYMASK);
1457#endif
1458
1459    modifierFlags &= darwin_all_modifier_mask;
1460
1461    /* We don't receive modifier key events while out of focus, and 3button
1462     * emulation mucks this up, so we need to check our modifier flag state
1463     * on every event... ugg
1464     */
1465
1466    if (darwin_all_modifier_flags != modifierFlags)
1467        DarwinUpdateModKeys(modifierFlags);
1468
1469    switch ([e type]) {
1470    case NSLeftMouseDown:
1471        ev_button = 1;
1472        ev_type = ButtonPress;
1473        goto handle_mouse;
1474
1475    case NSOtherMouseDown:
1476        // Get the AppKit button number, and convert it from 0-based to 1-based
1477        ev_button = [e buttonNumber] + 1;
1478
1479        /* Translate middle mouse button (3 in AppKit) to button 2 in X11,
1480         * and translate additional mouse buttons (4 and higher in AppKit)
1481         * to buttons 8 and higher in X11, to match default behavior of X11
1482         * on other platforms
1483         */
1484        ev_button = (ev_button == 3) ? 2 : (ev_button + 4);
1485
1486        ev_type = ButtonPress;
1487        goto handle_mouse;
1488
1489    case NSRightMouseDown:
1490        ev_button = 3;
1491        ev_type = ButtonPress;
1492        goto handle_mouse;
1493
1494    case NSLeftMouseUp:
1495        ev_button = 1;
1496        ev_type = ButtonRelease;
1497        goto handle_mouse;
1498
1499    case NSOtherMouseUp:
1500        // See above comments for NSOtherMouseDown
1501        ev_button = [e buttonNumber] + 1;
1502        ev_button = (ev_button == 3) ? 2 : (ev_button + 4);
1503        ev_type = ButtonRelease;
1504        goto handle_mouse;
1505
1506    case NSRightMouseUp:
1507        ev_button = 3;
1508        ev_type = ButtonRelease;
1509        goto handle_mouse;
1510
1511    case NSLeftMouseDragged:
1512        ev_button = 1;
1513        ev_type = MotionNotify;
1514        goto handle_mouse;
1515
1516    case NSOtherMouseDragged:
1517        // See above comments for NSOtherMouseDown
1518        ev_button = [e buttonNumber] + 1;
1519        ev_button = (ev_button == 3) ? 2 : (ev_button + 4);
1520        ev_type = MotionNotify;
1521        goto handle_mouse;
1522
1523    case NSRightMouseDragged:
1524        ev_button = 3;
1525        ev_type = MotionNotify;
1526        goto handle_mouse;
1527
1528    case NSMouseMoved:
1529        ev_button = 0;
1530        ev_type = MotionNotify;
1531        goto handle_mouse;
1532
1533    case NSTabletPoint:
1534        ev_button = 0;
1535        ev_type = MotionNotify;
1536        goto handle_mouse;
1537
1538handle_mouse:
1539        pDev = darwinPointer;
1540
1541        /* NSTabletPoint can have no subtype */
1542        if ([e type] != NSTabletPoint &&
1543            [e subtype] == NSTabletProximityEventSubtype) {
1544            switch ([e pointingDeviceType]) {
1545            case NSEraserPointingDevice:
1546                darwinTabletCurrent = darwinTabletEraser;
1547                break;
1548
1549            case NSPenPointingDevice:
1550                darwinTabletCurrent = darwinTabletStylus;
1551                break;
1552
1553            case NSCursorPointingDevice:
1554            case NSUnknownPointingDevice:
1555            default:
1556                darwinTabletCurrent = darwinTabletCursor;
1557                break;
1558            }
1559
1560            if ([e isEnteringProximity])
1561                needsProximityIn = YES;
1562            else
1563                DarwinSendTabletEvents(darwinTabletCurrent, ProximityOut, 0,
1564                                       location.x, location.y, pressure,
1565                                       tilt.x, tilt.y);
1566            return;
1567        }
1568
1569        if ([e type] == NSTabletPoint ||
1570            [e subtype] == NSTabletPointEventSubtype) {
1571            pressure = [e pressure];
1572            tilt = [e tilt];
1573
1574            pDev = darwinTabletCurrent;
1575
1576            if (needsProximityIn) {
1577                DarwinSendTabletEvents(darwinTabletCurrent, ProximityIn, 0,
1578                                       location.x, location.y, pressure,
1579                                       tilt.x, tilt.y);
1580
1581                needsProximityIn = NO;
1582            }
1583        }
1584
1585        if (!XQuartzServerVisible && noTestExtensions) {
1586#if defined(XPLUGIN_VERSION) && XPLUGIN_VERSION > 0
1587            /* Older libXplugin (Tiger/"Stock" Leopard) aren't thread safe, so we can't call xp_find_window from the Appkit thread */
1588            xp_window_id wid = 0;
1589            xp_error err;
1590
1591            /* Sigh. Need to check that we're really over one of
1592             * our windows. (We need to receive pointer events while
1593             * not in the foreground, but we don't want to receive them
1594             * when another window is over us or we might show a tooltip)
1595             */
1596
1597            err = xp_find_window(location.x, location.y, 0, &wid);
1598
1599            if (err != XP_Success || (err == XP_Success && wid == 0))
1600#endif
1601            {
1602                bgMouseLocation = location;
1603                bgMouseLocationUpdated = TRUE;
1604                return;
1605            }
1606        }
1607
1608        if (bgMouseLocationUpdated) {
1609            if (!(ev_type == MotionNotify && ev_button == 0)) {
1610                DarwinSendPointerEvents(darwinPointer, MotionNotify, 0,
1611                                        location.x, location.y,
1612                                        0.0, 0.0);
1613            }
1614            bgMouseLocationUpdated = FALSE;
1615        }
1616
1617        if (pDev == darwinPointer) {
1618            DarwinSendPointerEvents(pDev, ev_type, ev_button,
1619                                    location.x, location.y,
1620                                    [e deltaX], [e deltaY]);
1621        } else {
1622            DarwinSendTabletEvents(pDev, ev_type, ev_button,
1623                                   location.x, location.y, pressure,
1624                                   tilt.x, tilt.y);
1625        }
1626
1627        break;
1628
1629    case NSTabletProximity:
1630        switch ([e pointingDeviceType]) {
1631        case NSEraserPointingDevice:
1632            darwinTabletCurrent = darwinTabletEraser;
1633            break;
1634
1635        case NSPenPointingDevice:
1636            darwinTabletCurrent = darwinTabletStylus;
1637            break;
1638
1639        case NSCursorPointingDevice:
1640        case NSUnknownPointingDevice:
1641        default:
1642            darwinTabletCurrent = darwinTabletCursor;
1643            break;
1644        }
1645
1646        if ([e isEnteringProximity])
1647            needsProximityIn = YES;
1648        else
1649            DarwinSendTabletEvents(darwinTabletCurrent, ProximityOut, 0,
1650                                   location.x, location.y, pressure,
1651                                   tilt.x, tilt.y);
1652        break;
1653
1654    case NSScrollWheel:
1655    {
1656#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050
1657        float deltaX = [e deltaX];
1658        float deltaY = [e deltaY];
1659        BOOL isContinuous = NO;
1660#else
1661        CGFloat deltaX = [e deltaX];
1662        CGFloat deltaY = [e deltaY];
1663        CGEventRef cge = [e CGEvent];
1664        BOOL isContinuous =
1665            CGEventGetIntegerValueField(cge, kCGScrollWheelEventIsContinuous);
1666
1667#if 0
1668        /* Scale the scroll value by line height */
1669        CGEventSourceRef source = CGEventCreateSourceFromEvent(cge);
1670        if (source) {
1671            double lineHeight = CGEventSourceGetPixelsPerLine(source);
1672            CFRelease(source);
1673
1674            /* There's no real reason for the 1/5 ratio here other than that
1675             * it feels like a good ratio after some testing.
1676             */
1677
1678            deltaX *= lineHeight / 5.0;
1679            deltaY *= lineHeight / 5.0;
1680        }
1681#endif
1682#endif
1683
1684#if !defined(XPLUGIN_VERSION) || XPLUGIN_VERSION == 0
1685        /* If we're in the background, we need to send a MotionNotify event
1686         * first, since we aren't getting them on background mouse motion
1687         */
1688        if (!XQuartzServerVisible && noTestExtensions) {
1689            bgMouseLocationUpdated = FALSE;
1690            DarwinSendPointerEvents(darwinPointer, MotionNotify, 0,
1691                                    location.x, location.y,
1692                                    0.0, 0.0);
1693        }
1694#endif
1695#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
1696        // TODO: Change 1117 to NSAppKitVersionNumber10_7 when it is defined
1697        if (NSAppKitVersionNumber >= 1117 &&
1698            XQuartzScrollInDeviceDirection &&
1699            [e isDirectionInvertedFromDevice]) {
1700            deltaX *= -1;
1701            deltaY *= -1;
1702        }
1703#endif
1704        /* This hack is in place to better deal with "clicky" scroll wheels:
1705         * http://xquartz.macosforge.org/trac/ticket/562
1706         */
1707        if (!isContinuous) {
1708            static NSTimeInterval lastScrollTime = 0.0;
1709
1710            /* These store how much extra we have already scrolled.
1711             * ie, this is how much we ignore on the next event.
1712             */
1713            static double deficit_x = 0.0;
1714            static double deficit_y = 0.0;
1715
1716            /* If we have past a second since the last scroll, wipe the slate
1717             * clean
1718             */
1719            if ([e timestamp] - lastScrollTime > 1.0) {
1720                deficit_x = deficit_y = 0.0;
1721            }
1722            lastScrollTime = [e timestamp];
1723
1724            if (deltaX != 0.0) {
1725                /* If we changed directions, wipe the slate clean */
1726                if ((deficit_x < 0.0 && deltaX > 0.0) ||
1727                    (deficit_x > 0.0 && deltaX < 0.0)) {
1728                    deficit_x = 0.0;
1729                }
1730
1731                /* Eat up the deficit, but ensure that something is
1732                 * always sent
1733                 */
1734                if (fabs(deltaX) > fabs(deficit_x)) {
1735                    deltaX -= deficit_x;
1736
1737                    if (deltaX > 0.0) {
1738                        deficit_x = ceil(deltaX) - deltaX;
1739                        deltaX = ceil(deltaX);
1740                    } else {
1741                        deficit_x = floor(deltaX) - deltaX;
1742                        deltaX = floor(deltaX);
1743                    }
1744                } else {
1745                    deficit_x -= deltaX;
1746
1747                    if (deltaX > 0.0) {
1748                        deltaX = 1.0;
1749                    } else {
1750                        deltaX = -1.0;
1751                    }
1752
1753                    deficit_x += deltaX;
1754                }
1755            }
1756
1757            if (deltaY != 0.0) {
1758                /* If we changed directions, wipe the slate clean */
1759                if ((deficit_y < 0.0 && deltaY > 0.0) ||
1760                    (deficit_y > 0.0 && deltaY < 0.0)) {
1761                    deficit_y = 0.0;
1762                }
1763
1764                /* Eat up the deficit, but ensure that something is
1765                 * always sent
1766                 */
1767                if (fabs(deltaY) > fabs(deficit_y)) {
1768                    deltaY -= deficit_y;
1769
1770                    if (deltaY > 0.0) {
1771                        deficit_y = ceil(deltaY) - deltaY;
1772                        deltaY = ceil(deltaY);
1773                    } else {
1774                        deficit_y = floor(deltaY) - deltaY;
1775                        deltaY = floor(deltaY);
1776                    }
1777                } else {
1778                    deficit_y -= deltaY;
1779
1780                    if (deltaY > 0.0) {
1781                        deltaY = 1.0;
1782                    } else {
1783                        deltaY = -1.0;
1784                    }
1785
1786                    deficit_y += deltaY;
1787                }
1788            }
1789        }
1790
1791        DarwinSendScrollEvents(deltaX, deltaY);
1792        break;
1793    }
1794
1795    case NSKeyDown:
1796    case NSKeyUp:
1797    {
1798        /* XKB clobbers our keymap at startup, so we need to force it on the first keypress.
1799         * TODO: Make this less of a kludge.
1800         */
1801        static int force_resync_keymap = YES;
1802        if (force_resync_keymap) {
1803            DarwinSendDDXEvent(kXquartzReloadKeymap, 0);
1804            force_resync_keymap = NO;
1805        }
1806    }
1807
1808        if (darwinSyncKeymap) {
1809#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
1810            TISInputSourceRef key_layout =
1811                TISCopyCurrentKeyboardLayoutInputSource();
1812            TISInputSourceRef clear;
1813            if (CFEqual(key_layout, last_key_layout)) {
1814                CFRelease(key_layout);
1815            }
1816            else {
1817                /* Swap/free thread-safely */
1818                clear = last_key_layout;
1819                last_key_layout = key_layout;
1820                CFRelease(clear);
1821#else
1822            KeyboardLayoutRef key_layout;
1823            KLGetCurrentKeyboardLayout(&key_layout);
1824            if (key_layout != last_key_layout) {
1825                last_key_layout = key_layout;
1826#endif
1827                /* Update keyInfo */
1828                if (!QuartsResyncKeymap(TRUE)) {
1829                    ErrorF(
1830                        "sendX11NSEvent: Could not build a valid keymap.\n");
1831                }
1832            }
1833        }
1834
1835        ev_type = ([e type] == NSKeyDown) ? KeyPress : KeyRelease;
1836        DarwinSendKeyboardEvents(ev_type, [e keyCode]);
1837        break;
1838
1839    default:
1840        break;              /* for gcc */
1841    }
1842}
1843@end
1844