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