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