X11Application.m revision dc61d50d
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#import "NSUserDefaults+XQuartzDefaults.h"
39
40#include "darwin.h"
41#include "quartz.h"
42#include "darwinEvents.h"
43#include "quartzKeyboard.h"
44#include <X11/extensions/applewmconst.h>
45#include "micmap.h"
46#include "exglobals.h"
47
48#include <mach/mach.h>
49#include <unistd.h>
50
51#include <pthread.h>
52
53#include <Xplugin.h>
54
55// pbproxy/pbproxy.h
56extern int
57xpbproxy_run(void);
58
59#ifndef XSERVER_VERSION
60#define XSERVER_VERSION "?"
61#endif
62
63#include <dispatch/dispatch.h>
64
65static dispatch_queue_t eventTranslationQueue;
66
67#ifndef __has_feature
68#define __has_feature(x) 0
69#endif
70
71#ifndef CF_RETURNS_RETAINED
72#if __has_feature(attribute_cf_returns_retained)
73#define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
74#else
75#define CF_RETURNS_RETAINED
76#endif
77#endif
78
79#ifndef APPKIT_APPFLAGS_HACK
80#define APPKIT_APPFLAGS_HACK 1
81#endif
82
83extern Bool noTestExtensions;
84extern Bool noRenderExtension;
85
86static TISInputSourceRef last_key_layout;
87
88/* This preference is only tested on Lion or later as it's not relevant to
89 * earlier OS versions.
90 */
91Bool XQuartzScrollInDeviceDirection = FALSE;
92
93extern int darwinFakeButtons;
94
95/* Store the mouse location while in the background, and update X11's pointer
96 * location when we become the foreground application
97 */
98static NSPoint bgMouseLocation;
99static BOOL bgMouseLocationUpdated = FALSE;
100
101X11Application *X11App;
102
103#define ALL_KEY_MASKS (NSShiftKeyMask | NSControlKeyMask | \
104                       NSAlternateKeyMask | NSCommandKeyMask)
105
106#if APPKIT_APPFLAGS_HACK && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500
107// This was removed from the SDK in 10.15
108@interface NSApplication () {
109@protected
110    /* All instance variables are private */
111    short               _running;
112    struct __appFlags {
113        unsigned int        _hidden:1;
114        unsigned int        _appleEventActivationInProgress:1;
115        unsigned int        _active:1;
116        unsigned int        _hasBeenRun:1;
117        unsigned int        _doingUnhide:1;
118        unsigned int        _delegateReturnsValidRequestor:1;
119        unsigned int        _deactPending:1;
120        unsigned int        _invalidState:1;
121        unsigned int        _invalidEvent:1;
122        unsigned int        _postedWindowsNeedUpdateNote:1;
123        unsigned int        _wantsToActivate:1;
124        unsigned int        _doingHide:1;
125        unsigned int        _dontSendShouldTerminate:1;
126        unsigned int        _ignoresFullScreen:1;
127        unsigned int        _finishedLaunching:1;
128        unsigned int        _hasEventDelegate:1;
129        unsigned int        _appTerminating:1;
130        unsigned int        _didNSOpenOrPrint:1;
131        unsigned int        _inDealloc:1;
132        unsigned int        _pendingDidFinish:1;
133        unsigned int        _hasKeyFocus:1;
134        unsigned int        _panelsNonactivating:1;
135        unsigned int        _hiddenOnLaunch:1;
136        unsigned int        _openStatus:2;
137        unsigned int        _batchOrdering:1;
138        unsigned int        _waitingForTerminationReply:1;
139        unsigned int        _unused:1;
140        unsigned int        _enumeratingMemoryPressureHandlers:1;
141        unsigned int        _didTryRestoringPersistentState:1;
142        unsigned int        _windowDragging:1;
143        unsigned int        _mightBeSwitching:1;
144    }                   _appFlags;
145    id                  _mainMenu;
146}
147@end
148#endif
149
150@interface NSApplication (Internal)
151- (void)_setKeyWindow:(id)window;
152- (void)_setMainWindow:(id)window;
153@end
154
155@interface X11Application (Private)
156- (void) sendX11NSEvent:(NSEvent *)e;
157@end
158
159@interface X11Application ()
160@property (nonatomic, readwrite, assign) OSX_BOOL x_active;
161@end
162
163@implementation X11Application
164
165typedef struct message_struct message;
166struct message_struct {
167    mach_msg_header_t hdr;
168    SEL selector;
169    NSObject *arg;
170};
171
172/* Quartz mode initialization routine. This is often dynamically loaded
173   but is statically linked into this X server. */
174Bool
175QuartzModeBundleInit(void);
176
177- (void) dealloc
178{
179    self.controller = nil;
180    [super dealloc];
181}
182
183- (void) orderFrontStandardAboutPanel: (id) sender
184{
185    NSMutableDictionary *dict;
186    NSDictionary *infoDict;
187    NSString *tem;
188
189    dict = [NSMutableDictionary dictionaryWithCapacity:3];
190    infoDict = [[NSBundle mainBundle] infoDictionary];
191
192    [dict setObject: NSLocalizedString(@"The X Window System", @"About panel")
193             forKey:@"ApplicationName"];
194
195    tem = [infoDict objectForKey:@"CFBundleShortVersionString"];
196
197    [dict setObject:[NSString stringWithFormat:@"XQuartz %@", tem]
198             forKey:@"ApplicationVersion"];
199
200    [dict setObject:[NSString stringWithFormat:@"xorg-server %s",
201                     XSERVER_VERSION]
202     forKey:@"Version"];
203
204    [self orderFrontStandardAboutPanelWithOptions: dict];
205}
206
207- (void) activateX:(OSX_BOOL)state
208{
209    OSX_BOOL const x_active = self.x_active;
210
211    if (x_active == state)
212        return;
213
214    DEBUG_LOG("state=%d, x_active=%d, \n", state, x_active);
215    if (state) {
216        if (bgMouseLocationUpdated) {
217            DarwinSendPointerEvents(darwinPointer, MotionNotify, 0,
218                                    bgMouseLocation.x, bgMouseLocation.y,
219                                    0.0, 0.0);
220            bgMouseLocationUpdated = FALSE;
221        }
222        DarwinSendDDXEvent(kXquartzActivate, 0);
223    }
224    else {
225
226        if (darwin_all_modifier_flags)
227            DarwinUpdateModKeys(0);
228
229        DarwinInputReleaseButtonsAndKeys(darwinKeyboard);
230        DarwinInputReleaseButtonsAndKeys(darwinPointer);
231        DarwinInputReleaseButtonsAndKeys(darwinTabletCursor);
232        DarwinInputReleaseButtonsAndKeys(darwinTabletStylus);
233        DarwinInputReleaseButtonsAndKeys(darwinTabletEraser);
234
235        DarwinSendDDXEvent(kXquartzDeactivate, 0);
236    }
237
238    self.x_active = state;
239}
240
241- (void) became_key:(NSWindow *)win
242{
243    [self activateX:NO];
244}
245
246- (void) sendEvent:(NSEvent *)e
247{
248    /* Don't try sending to X if we haven't initialized.  This can happen if AppKit takes over
249     * (eg: uncaught exception) early in launch.
250     */
251    if (!eventTranslationQueue) {
252        [super sendEvent:e];
253        return;
254    }
255
256    OSX_BOOL for_appkit, for_x;
257    OSX_BOOL const x_active = self.x_active;
258
259    /* By default pass down the responder chain and to X. */
260    for_appkit = YES;
261    for_x = YES;
262
263    switch ([e type]) {
264    case NSLeftMouseDown:
265    case NSRightMouseDown:
266    case NSOtherMouseDown:
267    case NSLeftMouseUp:
268    case NSRightMouseUp:
269    case NSOtherMouseUp:
270    case NSScrollWheel:
271
272        if ([e window] != nil) {
273            /* Pointer event has an (AppKit) window. Probably something for the kit. */
274            for_x = NO;
275            if (x_active) [self activateX:NO];
276        }
277        else if ([self modalWindow] == nil) {
278            /* Must be an X window. Tell appkit windows to resign main/key */
279            for_appkit = NO;
280
281            if (!x_active && quartzProcs->IsX11Window([e windowNumber])) {
282                if ([self respondsToSelector:@selector(_setKeyWindow:)] && [self respondsToSelector:@selector(_setMainWindow:)]) {
283                    NSWindow *keyWindow = [self keyWindow];
284                    if (keyWindow) {
285                        [self _setKeyWindow:nil];
286                        [keyWindow resignKeyWindow];
287                    }
288
289                    NSWindow *mainWindow = [self mainWindow];
290                    if (mainWindow) {
291                        [self _setMainWindow:nil];
292                        [mainWindow resignMainWindow];
293                   }
294                 } else {
295                    /* This has a side effect of causing background apps to steal focus from XQuartz.
296                     * Unfortunately, there is no public and stable API to do what we want, but this
297                     * is a decent fallback in the off chance that the above selectors get dropped
298                     * in the future.
299                     */
300                    [self deactivate];
301                }
302
303                [self activateX:YES];
304            }
305        }
306
307        /* We want to force sending to appkit if we're over the menu bar */
308        if (!for_appkit) {
309            NSPoint NSlocation = [e locationInWindow];
310            NSWindow *window = [e window];
311            NSRect NSframe, NSvisibleFrame;
312            CGRect CGframe, CGvisibleFrame;
313            CGPoint CGlocation;
314
315            if (window != nil) {
316                NSRect frame = [window frame];
317                NSlocation.x += frame.origin.x;
318                NSlocation.y += frame.origin.y;
319            }
320
321            NSframe = [[NSScreen mainScreen] frame];
322            NSvisibleFrame = [[NSScreen mainScreen] visibleFrame];
323
324            CGframe = CGRectMake(NSframe.origin.x, NSframe.origin.y,
325                                 NSframe.size.width, NSframe.size.height);
326            CGvisibleFrame = CGRectMake(NSvisibleFrame.origin.x,
327                                        NSvisibleFrame.origin.y,
328                                        NSvisibleFrame.size.width,
329                                        NSvisibleFrame.size.height);
330            CGlocation = CGPointMake(NSlocation.x, NSlocation.y);
331
332            if (CGRectContainsPoint(CGframe, CGlocation) &&
333                !CGRectContainsPoint(CGvisibleFrame, CGlocation))
334                for_appkit = YES;
335        }
336
337        break;
338
339    case NSKeyDown:
340    case NSKeyUp:
341
342        if (_x_active) {
343            static BOOL do_swallow = NO;
344            static int swallow_keycode;
345
346            if ([e type] == NSKeyDown) {
347                /* Before that though, see if there are any global
348                 * shortcuts bound to it. */
349
350                if (darwinAppKitModMask &[e modifierFlags]) {
351                    /* Override to force sending to Appkit */
352                    swallow_keycode = [e keyCode];
353                    do_swallow = YES;
354                    for_x = NO;
355                } else if (XQuartzEnableKeyEquivalents &&
356                         xp_is_symbolic_hotkey_event([e eventRef])) {
357                    swallow_keycode = [e keyCode];
358                    do_swallow = YES;
359                    for_x = NO;
360                }
361                else if (XQuartzEnableKeyEquivalents &&
362                         [[self mainMenu] performKeyEquivalent:e]) {
363                    swallow_keycode = [e keyCode];
364                    do_swallow = YES;
365                    for_appkit = NO;
366                    for_x = NO;
367                }
368                else if (!XQuartzIsRootless
369                         && ([e modifierFlags] & ALL_KEY_MASKS) ==
370                         (NSCommandKeyMask | NSAlternateKeyMask)
371                         && ([e keyCode] == 0 /*a*/ || [e keyCode] ==
372                             53 /*Esc*/)) {
373                    /* We have this here to force processing fullscreen
374                     * toggle even if XQuartzEnableKeyEquivalents is disabled */
375                    swallow_keycode = [e keyCode];
376                    do_swallow = YES;
377                    for_x = NO;
378                    for_appkit = NO;
379                    DarwinSendDDXEvent(kXquartzToggleFullscreen, 0);
380                }
381                else {
382                    /* No kit window is focused, so send it to X. */
383                    for_appkit = NO;
384
385                    /* Reset our swallow state if we're seeing the same keyCode again.
386                     * This can happen if we become !_x_active when the keyCode we
387                     * intended to swallow is delivered.  See:
388                     * https://bugs.freedesktop.org/show_bug.cgi?id=92648
389                     */
390                    if ([e keyCode] == swallow_keycode) {
391                        do_swallow = NO;
392                    }
393                }
394            }
395            else {       /* KeyUp */
396                /* If we saw a key equivalent on the down, don't pass
397                 * the up through to X. */
398                if (do_swallow && [e keyCode] == swallow_keycode) {
399                    do_swallow = NO;
400                    for_x = NO;
401                }
402            }
403        }
404        else {       /* !_x_active */
405            for_x = NO;
406        }
407        break;
408
409    case NSFlagsChanged:
410        /* Don't tell X11 about modifiers changing while it's not active */
411        if (!_x_active)
412            for_x = NO;
413        break;
414
415    case NSAppKitDefined:
416        switch ([e subtype]) {
417            static BOOL x_was_active = NO;
418
419        case NSApplicationActivatedEventType:
420            for_x = NO;
421            if ([e window] == nil && x_was_active) {
422                BOOL order_all_windows = YES;
423                for_appkit = NO;
424
425#if APPKIT_APPFLAGS_HACK
426                /* FIXME: This is a hack to avoid passing the event to AppKit which
427                 *        would result in it raising one of its windows.
428                 */
429                _appFlags._active = YES;
430#endif
431
432                [self set_front_process:nil];
433
434                /* Get the Spaces preference for SwitchOnActivate */
435                BOOL const workspaces = [NSUserDefaults.dockDefaults boolForKey:@"workspaces"];
436                if (workspaces) {
437                    order_all_windows = [NSUserDefaults.globalDefaults boolForKey:@"AppleSpacesSwitchOnActivate"];
438                }
439
440                /* TODO: In the workspaces && !AppleSpacesSwitchOnActivate case, the windows are ordered
441                 *       correctly, but we need to activate the top window on this space if there is
442                 *       none active.
443                 *
444                 *       If there are no active windows, and there are minimized windows, we should
445                 *       be restoring one of them.
446                 */
447                if ([e data2] & 0x10) {         // 0x10 (bfCPSOrderAllWindowsForward) is set when we use cmd-tab or the dock icon
448                    DarwinSendDDXEvent(kXquartzBringAllToFront, 1, order_all_windows);
449                }
450            }
451            break;
452
453        case 18:         /* ApplicationDidReactivate */
454            if (XQuartzFullscreenVisible) for_appkit = NO;
455            break;
456
457        case NSApplicationDeactivatedEventType:
458            for_x = NO;
459
460            x_was_active = _x_active;
461            if (_x_active)
462                [self activateX:NO];
463            break;
464        }
465        break;
466
467    default:
468        break;          /* for gcc */
469    }
470
471    if (for_appkit) {
472        [super sendEvent:e];
473    }
474
475    if (for_x) {
476        dispatch_async(eventTranslationQueue, ^{
477            [self sendX11NSEvent:e];
478        });
479    }
480}
481
482- (void) set_apps_menu:(NSArray *)list
483{
484    [self.controller set_apps_menu:list];
485}
486
487- (void) set_front_process:unused
488{
489    [NSApp activateIgnoringOtherApps:YES];
490
491    if ([self modalWindow] == nil)
492        [self activateX:YES];
493}
494
495- (void) show_hide_menubar:(NSNumber *)state
496{
497    /* Also shows/hides the dock */
498    if ([state boolValue])
499        SetSystemUIMode(kUIModeNormal, 0);
500    else
501        SetSystemUIMode(kUIModeAllHidden,
502                        XQuartzFullscreenMenu ? kUIOptionAutoShowMenuBar : 0);                   // kUIModeAllSuppressed or kUIOptionAutoShowMenuBar can be used to allow "mouse-activation"
503}
504
505- (void) launch_client:(NSString *)cmd
506{
507    (void)[self.controller application:self openFile:cmd];
508}
509
510
511- (void) read_defaults
512{
513    NSUserDefaults * const defaults = NSUserDefaults.xquartzDefaults;
514
515    XQuartzRootlessDefault = [defaults boolForKey:XQuartzPrefKeyRootless];
516    XQuartzFullscreenMenu = [defaults boolForKey:XQuartzPrefKeyFullscreenMenu];
517    XQuartzFullscreenDisableHotkeys = ![defaults boolForKey:XQuartzPrefKeyFullscreenHotkeys];
518
519    darwinFakeButtons = [defaults boolForKey:XQuartzPrefKeyFakeButtons];
520    XQuartzOptionSendsAlt = [defaults boolForKey:XQuartzPrefKeyOptionSendsAlt];
521
522    if (darwinFakeButtons) {
523        NSString * const fake2 = [defaults stringForKey:XQuartzPrefKeyFakeButton2];
524        if (fake2) {
525            darwinFakeMouse2Mask = DarwinParseModifierList(fake2.UTF8String, TRUE);
526        }
527
528        NSString * const fake3 = [defaults stringForKey:XQuartzPrefKeyFakeButton3];
529        if (fake3) {
530            darwinFakeMouse3Mask = DarwinParseModifierList(fake3.UTF8String, TRUE);
531        }
532    }
533
534    NSString * const appKitModifiers = [defaults stringForKey:XQuartzPrefKeyAppKitModifiers];
535    if (appKitModifiers) {
536        darwinAppKitModMask = DarwinParseModifierList(appKitModifiers.UTF8String, TRUE);
537    }
538
539    NSString * const windowItemModifiers = [defaults stringForKey:XQuartzPrefKeyWindowItemModifiers];
540    if (windowItemModifiers) {
541        windowItemModMask = DarwinParseModifierList(windowItemModifiers.UTF8String, FALSE);
542    }
543
544    XQuartzEnableKeyEquivalents = [defaults boolForKey:XQuartzPrefKeyKeyEquivs];
545
546    darwinSyncKeymap = [defaults boolForKey:XQuartzPrefKeySyncKeymap];
547
548    darwinDesiredDepth = [defaults integerForKey:XQuartzPrefKeyDepth];
549
550    noTestExtensions = ![defaults boolForKey:XQuartzPrefKeyTESTExtension];
551    noRenderExtension = ![defaults boolForKey:XQuartzPrefKeyRENDERExtension];
552
553    XQuartzScrollInDeviceDirection = [defaults boolForKey:XQuartzPrefKeyScrollInDeviceDirection];
554}
555
556/* This will end up at the end of the responder chain. */
557- (void) copy:sender
558{
559    DarwinSendDDXEvent(kXquartzPasteboardNotify, 1,
560                       AppleWMCopyToPasteboard);
561}
562
563@end
564
565void
566X11ApplicationSetWindowMenu(int nitems, const char **items,
567                            const char *shortcuts)
568{
569    @autoreleasepool {
570        NSMutableArray <NSArray <NSString *> *> * const allMenuItems = [NSMutableArray array];
571
572        for (int i = 0; i < nitems; i++) {
573            NSMutableArray <NSString *> * const menuItem = [NSMutableArray array];
574            [menuItem addObject:@(items[i])];
575
576            if (shortcuts[i] == 0) {
577                [menuItem addObject:@""];
578            } else {
579                [menuItem addObject:[NSString stringWithFormat:@"%d", shortcuts[i]]];
580            }
581
582            [allMenuItems addObject:menuItem];
583        }
584
585        dispatch_async(dispatch_get_main_queue(), ^{
586            [X11App.controller set_window_menu:allMenuItems];
587        });
588    }
589}
590
591void
592X11ApplicationSetWindowMenuCheck(int idx)
593{
594    dispatch_async(dispatch_get_main_queue(), ^{
595        [X11App.controller set_window_menu_check:@(idx)];
596    });
597}
598
599void
600X11ApplicationSetFrontProcess(void)
601{
602    dispatch_async(dispatch_get_main_queue(), ^{
603        [X11App set_front_process:nil];
604    });
605}
606
607void
608X11ApplicationSetCanQuit(int state)
609{
610    dispatch_async(dispatch_get_main_queue(), ^{
611        X11App.controller.can_quit = !!state;
612    });
613}
614
615void
616X11ApplicationServerReady(void)
617{
618    dispatch_async(dispatch_get_main_queue(), ^{
619        [X11App.controller server_ready];
620    });
621}
622
623void
624X11ApplicationShowHideMenubar(int state)
625{
626    dispatch_async(dispatch_get_main_queue(), ^{
627        [X11App show_hide_menubar:@(state)];
628    });
629}
630
631void
632X11ApplicationLaunchClient(const char *cmd)
633{
634    @autoreleasepool {
635        NSString *string = @(cmd);
636        dispatch_async(dispatch_get_main_queue(), ^{
637            [X11App launch_client:string];
638        });
639    }
640}
641
642/* This is a special function in that it is run from the *SERVER* thread and
643 * not the AppKit thread.  We want to block entering a screen-capturing RandR
644 * mode until we notify the user about how to get out if the X11 client crashes.
645 */
646Bool
647X11ApplicationCanEnterRandR(void)
648{
649    NSString *title, *msg;
650    NSUserDefaults * const defaults = NSUserDefaults.xquartzDefaults;
651
652    if ([defaults boolForKey:XQuartzPrefKeyNoRANDRAlert] ||
653        XQuartzShieldingWindowLevel != 0)
654        return TRUE;
655
656    title = NSLocalizedString(@"Enter RandR mode?",
657                              @"Dialog title when switching to RandR");
658    msg = NSLocalizedString(
659        @"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.",
660        @"Dialog when switching to RandR");
661
662    if (!XQuartzIsRootless)
663        QuartzShowFullscreen(FALSE);
664
665    NSInteger __block alert_result;
666    dispatch_sync(dispatch_get_main_queue(), ^{
667        alert_result = NSRunAlertPanel(title, @"%@",
668                                       NSLocalizedString(@"Allow", @""),
669                                       NSLocalizedString(@"Cancel", @""),
670                                       NSLocalizedString(@"Always Allow", @""), msg);
671    });
672
673    switch (alert_result) {
674    case NSAlertOtherReturn:
675        [defaults setBool:YES forKey:XQuartzPrefKeyNoRANDRAlert];
676
677    case NSAlertDefaultReturn:
678        return YES;
679
680    default:
681        return NO;
682    }
683}
684
685static void
686check_xinitrc(void)
687{
688    char *tem, buf[1024];
689    NSString *msg;
690    NSUserDefaults * const defaults = NSUserDefaults.xquartzDefaults;
691
692    if ([defaults boolForKey:XQuartzPrefKeyDoneXinitCheck])
693        return;
694
695    tem = getenv("HOME");
696    if (tem == NULL) goto done;
697
698    snprintf(buf, sizeof(buf), "%s/.xinitrc", tem);
699    if (access(buf, F_OK) != 0)
700        goto done;
701
702    msg =
703        NSLocalizedString(
704            @"You have an existing ~/.xinitrc file.\n\n\
705                             Windows displayed by X11 applications may not have titlebars, or may look \
706                             different to windows displayed by native applications.\n\n\
707                             Would you like to move aside the existing file and use the standard X11 \
708                             environment the next time you start X11?"                                                                                                                                                                                                                                                                                                                                                                  ,
709            @"Startup xinitrc dialog");
710
711    if (NSAlertDefaultReturn ==
712        NSRunAlertPanel(nil, @"%@", NSLocalizedString(@"Yes", @""),
713                        NSLocalizedString(@"No", @""), nil, msg)) {
714        char buf2[1024];
715        int i = -1;
716
717        snprintf(buf2, sizeof(buf2), "%s.old", buf);
718
719        for (i = 1; access(buf2, F_OK) == 0; i++)
720            snprintf(buf2, sizeof(buf2), "%s.old.%d", buf, i);
721
722        rename(buf, buf2);
723    }
724
725done:
726    [defaults setBool:YES forKey:XQuartzPrefKeyDoneXinitCheck];
727}
728
729static inline pthread_t
730create_thread(void *(*func)(void *), void *arg)
731{
732    pthread_attr_t attr;
733    pthread_t tid;
734
735    pthread_attr_init(&attr);
736    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
737    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
738    pthread_create(&tid, &attr, func, arg);
739    pthread_attr_destroy(&attr);
740
741    return tid;
742}
743
744static void *
745xpbproxy_x_thread(void *args)
746{
747    xpbproxy_run();
748
749    ErrorF("xpbproxy thread is terminating unexpectedly.\n");
750    return NULL;
751}
752
753void
754X11ApplicationMain(int argc, char **argv, char **envp)
755{
756#ifdef DEBUG
757    while (access("/tmp/x11-block", F_OK) == 0) sleep(1);
758#endif
759
760    @autoreleasepool {
761        X11App = (X11Application *)[X11Application sharedApplication];
762        [X11App read_defaults];
763
764        [NSBundle loadNibNamed:@"main" owner:NSApp];
765        [NSNotificationCenter.defaultCenter addObserver:NSApp
766                                               selector:@selector (became_key:)
767                                                   name:NSWindowDidBecomeKeyNotification
768                                                 object:nil];
769
770        /*
771         * The xpr Quartz mode is statically linked into this server.
772         * Initialize all the Quartz functions.
773         */
774        QuartzModeBundleInit();
775
776        /* Calculate the height of the menubar so we can avoid it. */
777        aquaMenuBarHeight = NSApp.mainMenu.menuBarHeight;
778        if (!aquaMenuBarHeight) {
779            NSScreen* primaryScreen = NSScreen.screens[0];
780            aquaMenuBarHeight = NSHeight(primaryScreen.frame) - NSMaxY(primaryScreen.visibleFrame);
781        }
782
783        eventTranslationQueue = dispatch_queue_create(BUNDLE_ID_PREFIX ".X11.NSEventsToX11EventsQueue", NULL);
784        assert(eventTranslationQueue != NULL);
785
786        /* Set the key layout seed before we start the server */
787        last_key_layout = TISCopyCurrentKeyboardLayoutInputSource();
788
789        if (!last_key_layout) {
790            ErrorF("X11ApplicationMain: Unable to determine TISCopyCurrentKeyboardLayoutInputSource() at startup.\n");
791        }
792
793        if (!QuartsResyncKeymap(FALSE)) {
794            ErrorF("X11ApplicationMain: Could not build a valid keymap.\n");
795        }
796
797        /* Tell the server thread that it can proceed */
798        QuartzInitServer(argc, argv, envp);
799
800        /* This must be done after QuartzInitServer because it can result in
801         * an mieqEnqueue() - <rdar://problem/6300249>
802         */
803        check_xinitrc();
804
805        create_thread(xpbproxy_x_thread, NULL);
806
807#if XQUARTZ_SPARKLE
808        [[X11App controller] setup_sparkle];
809        [[SUUpdater sharedUpdater] resetUpdateCycle];
810        //    [[SUUpdater sharedUpdater] checkForUpdates:X11App];
811#endif
812    }
813
814    [NSApp run];
815    /* not reached */
816}
817
818@implementation X11Application (Private)
819
820#ifdef NX_DEVICELCMDKEYMASK
821/* This is to workaround a bug in the VNC server where we sometimes see the L
822 * modifier and sometimes see no "side"
823 */
824static inline int
825ensure_flag(int flags, int device_independent, int device_dependents,
826            int device_dependent_default)
827{
828    if ((flags & device_independent) &&
829        !(flags & device_dependents))
830        flags |= device_dependent_default;
831    return flags;
832}
833#endif
834
835#ifdef DEBUG_UNTRUSTED_POINTER_DELTA
836static const char *
837untrusted_str(NSEvent *e)
838{
839    switch ([e type]) {
840    case NSScrollWheel:
841        return "NSScrollWheel";
842
843    case NSTabletPoint:
844        return "NSTabletPoint";
845
846    case NSOtherMouseDown:
847        return "NSOtherMouseDown";
848
849    case NSOtherMouseUp:
850        return "NSOtherMouseUp";
851
852    case NSLeftMouseDown:
853        return "NSLeftMouseDown";
854
855    case NSLeftMouseUp:
856        return "NSLeftMouseUp";
857
858    default:
859        switch ([e subtype]) {
860        case NSTabletPointEventSubtype:
861            return "NSTabletPointEventSubtype";
862
863        case NSTabletProximityEventSubtype:
864            return "NSTabletProximityEventSubtype";
865
866        default:
867            return "Other";
868        }
869    }
870}
871#endif
872
873extern void
874wait_for_mieq_init(void);
875
876- (void) sendX11NSEvent:(NSEvent *)e
877{
878    NSPoint location = NSZeroPoint;
879    int ev_button, ev_type;
880    static float pressure = 0.0;       // static so ProximityOut will have the value from the previous tablet event
881    static NSPoint tilt;               // static so ProximityOut will have the value from the previous tablet event
882    static DeviceIntPtr darwinTabletCurrent = NULL;
883    static BOOL needsProximityIn = NO; // Do we do need to handle a pending ProximityIn once we have pressure/tilt?
884    DeviceIntPtr pDev;
885    int modifierFlags;
886    BOOL isMouseOrTabletEvent, isTabletEvent;
887
888    if (!darwinTabletCurrent) {
889        /* Ensure that the event system is initialized */
890        wait_for_mieq_init();
891        assert(darwinTabletStylus);
892
893        tilt = NSZeroPoint;
894        darwinTabletCurrent = darwinTabletStylus;
895    }
896
897    isMouseOrTabletEvent = [e type] == NSLeftMouseDown ||
898                           [e type] == NSOtherMouseDown ||
899                           [e type] == NSRightMouseDown ||
900                           [e type] == NSLeftMouseUp ||
901                           [e type] == NSOtherMouseUp ||
902                           [e type] == NSRightMouseUp ||
903                           [e type] == NSLeftMouseDragged ||
904                           [e type] == NSOtherMouseDragged ||
905                           [e type] == NSRightMouseDragged ||
906                           [e type] == NSMouseMoved ||
907                           [e type] == NSTabletPoint || 
908                           [e type] == NSScrollWheel;
909
910    isTabletEvent = ([e type] == NSTabletPoint) ||
911                    (isMouseOrTabletEvent &&
912                     ([e subtype] == NSTabletPointEventSubtype ||
913                      [e subtype] == NSTabletProximityEventSubtype));
914
915    if (isMouseOrTabletEvent) {
916        static NSPoint lastpt;
917        NSWindow *window = [e window];
918        NSRect screen = [[[NSScreen screens] objectAtIndex:0] frame];
919        BOOL hasUntrustedPointerDelta;
920
921        // NSEvents for tablets are not consistent wrt deltaXY between events, so we cannot rely on that
922        // Thus tablets will be subject to the warp-pointer bug worked around by the delta, but tablets
923        // are not normally used in cases where that bug would present itself, so this is a fair tradeoff
924        // <rdar://problem/7111003> deltaX and deltaY are incorrect for NSMouseMoved, NSTabletPointEventSubtype
925        // http://xquartz.macosforge.org/trac/ticket/288
926        hasUntrustedPointerDelta = isTabletEvent;
927
928        // The deltaXY for middle click events also appear erroneous after fast user switching
929        // <rdar://problem/7979468> deltaX and deltaY are incorrect for NSOtherMouseDown and NSOtherMouseUp after FUS
930        // http://xquartz.macosforge.org/trac/ticket/389
931        hasUntrustedPointerDelta |= [e type] == NSOtherMouseDown ||
932                                    [e type] == NSOtherMouseUp;
933
934        // The deltaXY for scroll events correspond to the scroll delta, not the pointer delta
935        // <rdar://problem/7989690> deltaXY for wheel events are being sent as mouse movement
936        hasUntrustedPointerDelta |= [e type] == NSScrollWheel;
937
938#ifdef DEBUG_UNTRUSTED_POINTER_DELTA
939        hasUntrustedPointerDelta |= [e type] == NSLeftMouseDown ||
940                                    [e type] == NSLeftMouseUp;
941#endif
942
943        if (window != nil) {
944            NSRect frame = [window frame];
945            location = [e locationInWindow];
946            location.x += frame.origin.x;
947            location.y += frame.origin.y;
948            lastpt = location;
949        }
950        else if (hasUntrustedPointerDelta) {
951#ifdef DEBUG_UNTRUSTED_POINTER_DELTA
952            ErrorF("--- Begin Event Debug ---\n");
953            ErrorF("Event type: %s\n", untrusted_str(e));
954            ErrorF("old lastpt: (%0.2f, %0.2f)\n", lastpt.x, lastpt.y);
955            ErrorF("     delta: (%0.2f, %0.2f)\n", [e deltaX], -[e deltaY]);
956            ErrorF("  location: (%0.2f, %0.2f)\n", lastpt.x + [e deltaX],
957                   lastpt.y - [e deltaY]);
958            ErrorF("workaround: (%0.2f, %0.2f)\n", [e locationInWindow].x,
959                   [e locationInWindow].y);
960            ErrorF("--- End Event Debug ---\n");
961
962            location.x = lastpt.x + [e deltaX];
963            location.y = lastpt.y - [e deltaY];
964            lastpt = [e locationInWindow];
965#else
966            location = [e locationInWindow];
967            lastpt = location;
968#endif
969        }
970        else {
971            location.x = lastpt.x + [e deltaX];
972            location.y = lastpt.y - [e deltaY];
973            lastpt = [e locationInWindow];
974        }
975
976        /* Convert coordinate system */
977        location.y = (screen.origin.y + screen.size.height) - location.y;
978    }
979
980    modifierFlags = [e modifierFlags];
981
982#ifdef NX_DEVICELCMDKEYMASK
983    /* This is to workaround a bug in the VNC server where we sometimes see the L
984     * modifier and sometimes see no "side"
985     */
986    modifierFlags = ensure_flag(modifierFlags, NX_CONTROLMASK,
987                                NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK,
988                                NX_DEVICELCTLKEYMASK);
989    modifierFlags = ensure_flag(modifierFlags, NX_SHIFTMASK,
990                                NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK, 
991                                NX_DEVICELSHIFTKEYMASK);
992    modifierFlags = ensure_flag(modifierFlags, NX_COMMANDMASK,
993                                NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK,
994                                NX_DEVICELCMDKEYMASK);
995    modifierFlags = ensure_flag(modifierFlags, NX_ALTERNATEMASK,
996                                NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK,
997                                NX_DEVICELALTKEYMASK);
998#endif
999
1000    modifierFlags &= darwin_all_modifier_mask;
1001
1002    /* We don't receive modifier key events while out of focus, and 3button
1003     * emulation mucks this up, so we need to check our modifier flag state
1004     * on every event... ugg
1005     */
1006
1007    if (darwin_all_modifier_flags != modifierFlags)
1008        DarwinUpdateModKeys(modifierFlags);
1009
1010    switch ([e type]) {
1011    case NSLeftMouseDown:
1012        ev_button = 1;
1013        ev_type = ButtonPress;
1014        goto handle_mouse;
1015
1016    case NSOtherMouseDown:
1017        // Get the AppKit button number, and convert it from 0-based to 1-based
1018        ev_button = [e buttonNumber] + 1;
1019
1020        /* Translate middle mouse button (3 in AppKit) to button 2 in X11,
1021         * and translate additional mouse buttons (4 and higher in AppKit)
1022         * to buttons 8 and higher in X11, to match default behavior of X11
1023         * on other platforms
1024         */
1025        ev_button = (ev_button == 3) ? 2 : (ev_button + 4);
1026
1027        ev_type = ButtonPress;
1028        goto handle_mouse;
1029
1030    case NSRightMouseDown:
1031        ev_button = 3;
1032        ev_type = ButtonPress;
1033        goto handle_mouse;
1034
1035    case NSLeftMouseUp:
1036        ev_button = 1;
1037        ev_type = ButtonRelease;
1038        goto handle_mouse;
1039
1040    case NSOtherMouseUp:
1041        // See above comments for NSOtherMouseDown
1042        ev_button = [e buttonNumber] + 1;
1043        ev_button = (ev_button == 3) ? 2 : (ev_button + 4);
1044        ev_type = ButtonRelease;
1045        goto handle_mouse;
1046
1047    case NSRightMouseUp:
1048        ev_button = 3;
1049        ev_type = ButtonRelease;
1050        goto handle_mouse;
1051
1052    case NSLeftMouseDragged:
1053        ev_button = 1;
1054        ev_type = MotionNotify;
1055        goto handle_mouse;
1056
1057    case NSOtherMouseDragged:
1058        // See above comments for NSOtherMouseDown
1059        ev_button = [e buttonNumber] + 1;
1060        ev_button = (ev_button == 3) ? 2 : (ev_button + 4);
1061        ev_type = MotionNotify;
1062        goto handle_mouse;
1063
1064    case NSRightMouseDragged:
1065        ev_button = 3;
1066        ev_type = MotionNotify;
1067        goto handle_mouse;
1068
1069    case NSMouseMoved:
1070        ev_button = 0;
1071        ev_type = MotionNotify;
1072        goto handle_mouse;
1073
1074    case NSTabletPoint:
1075        ev_button = 0;
1076        ev_type = MotionNotify;
1077        goto handle_mouse;
1078
1079handle_mouse:
1080        pDev = darwinPointer;
1081
1082        /* NSTabletPoint can have no subtype */
1083        if ([e type] != NSTabletPoint &&
1084            [e subtype] == NSTabletProximityEventSubtype) {
1085            switch ([e pointingDeviceType]) {
1086            case NSEraserPointingDevice:
1087                darwinTabletCurrent = darwinTabletEraser;
1088                break;
1089
1090            case NSPenPointingDevice:
1091                darwinTabletCurrent = darwinTabletStylus;
1092                break;
1093
1094            case NSCursorPointingDevice:
1095            case NSUnknownPointingDevice:
1096            default:
1097                darwinTabletCurrent = darwinTabletCursor;
1098                break;
1099            }
1100
1101            if ([e isEnteringProximity])
1102                needsProximityIn = YES;
1103            else
1104                DarwinSendTabletEvents(darwinTabletCurrent, ProximityOut, 0,
1105                                       location.x, location.y, pressure,
1106                                       tilt.x, tilt.y);
1107            return;
1108        }
1109
1110        if ([e type] == NSTabletPoint ||
1111            [e subtype] == NSTabletPointEventSubtype) {
1112            pressure = [e pressure];
1113            tilt = [e tilt];
1114
1115            pDev = darwinTabletCurrent;
1116
1117            if (needsProximityIn) {
1118                DarwinSendTabletEvents(darwinTabletCurrent, ProximityIn, 0,
1119                                       location.x, location.y, pressure,
1120                                       tilt.x, tilt.y);
1121
1122                needsProximityIn = NO;
1123            }
1124        }
1125
1126        if (!XQuartzServerVisible && noTestExtensions) {
1127            xp_window_id wid = 0;
1128            xp_error err;
1129
1130            /* Sigh. Need to check that we're really over one of
1131             * our windows. (We need to receive pointer events while
1132             * not in the foreground, but we don't want to receive them
1133             * when another window is over us or we might show a tooltip)
1134             */
1135
1136            err = xp_find_window(location.x, location.y, 0, &wid);
1137
1138            if (err != XP_Success || (err == XP_Success && wid == 0))
1139            {
1140                bgMouseLocation = location;
1141                bgMouseLocationUpdated = TRUE;
1142                return;
1143            }
1144        }
1145
1146        if (bgMouseLocationUpdated) {
1147            if (!(ev_type == MotionNotify && ev_button == 0)) {
1148                DarwinSendPointerEvents(darwinPointer, MotionNotify, 0,
1149                                        location.x, location.y,
1150                                        0.0, 0.0);
1151            }
1152            bgMouseLocationUpdated = FALSE;
1153        }
1154
1155        if (pDev == darwinPointer) {
1156            DarwinSendPointerEvents(pDev, ev_type, ev_button,
1157                                    location.x, location.y,
1158                                    [e deltaX], [e deltaY]);
1159        } else {
1160            DarwinSendTabletEvents(pDev, ev_type, ev_button,
1161                                   location.x, location.y, pressure,
1162                                   tilt.x, tilt.y);
1163        }
1164
1165        break;
1166
1167    case NSTabletProximity:
1168        switch ([e pointingDeviceType]) {
1169        case NSEraserPointingDevice:
1170            darwinTabletCurrent = darwinTabletEraser;
1171            break;
1172
1173        case NSPenPointingDevice:
1174            darwinTabletCurrent = darwinTabletStylus;
1175            break;
1176
1177        case NSCursorPointingDevice:
1178        case NSUnknownPointingDevice:
1179        default:
1180            darwinTabletCurrent = darwinTabletCursor;
1181            break;
1182        }
1183
1184        if ([e isEnteringProximity])
1185            needsProximityIn = YES;
1186        else
1187            DarwinSendTabletEvents(darwinTabletCurrent, ProximityOut, 0,
1188                                   location.x, location.y, pressure,
1189                                   tilt.x, tilt.y);
1190        break;
1191
1192    case NSScrollWheel:
1193    {
1194        CGFloat deltaX = [e deltaX];
1195        CGFloat deltaY = [e deltaY];
1196        CGEventRef cge = [e CGEvent];
1197        BOOL isContinuous =
1198            CGEventGetIntegerValueField(cge, kCGScrollWheelEventIsContinuous);
1199
1200#if 0
1201        /* Scale the scroll value by line height */
1202        CGEventSourceRef source = CGEventCreateSourceFromEvent(cge);
1203        if (source) {
1204            double lineHeight = CGEventSourceGetPixelsPerLine(source);
1205            CFRelease(source);
1206            
1207            /* There's no real reason for the 1/5 ratio here other than that
1208             * it feels like a good ratio after some testing.
1209             */
1210            
1211            deltaX *= lineHeight / 5.0;
1212            deltaY *= lineHeight / 5.0;
1213        }
1214#endif
1215        
1216        if (XQuartzScrollInDeviceDirection &&
1217            [e isDirectionInvertedFromDevice]) {
1218            deltaX *= -1;
1219            deltaY *= -1;
1220        }
1221        /* This hack is in place to better deal with "clicky" scroll wheels:
1222         * http://xquartz.macosforge.org/trac/ticket/562
1223         */
1224        if (!isContinuous) {
1225            static NSTimeInterval lastScrollTime = 0.0;
1226
1227            /* These store how much extra we have already scrolled.
1228             * ie, this is how much we ignore on the next event.
1229             */
1230            static double deficit_x = 0.0;
1231            static double deficit_y = 0.0;
1232
1233            /* If we have past a second since the last scroll, wipe the slate
1234             * clean
1235             */
1236            if ([e timestamp] - lastScrollTime > 1.0) {
1237                deficit_x = deficit_y = 0.0;
1238            }
1239            lastScrollTime = [e timestamp];
1240
1241            if (deltaX != 0.0) {
1242                /* If we changed directions, wipe the slate clean */
1243                if ((deficit_x < 0.0 && deltaX > 0.0) ||
1244                    (deficit_x > 0.0 && deltaX < 0.0)) {
1245                    deficit_x = 0.0;
1246                }
1247
1248                /* Eat up the deficit, but ensure that something is
1249                 * always sent 
1250                 */
1251                if (fabs(deltaX) > fabs(deficit_x)) {
1252                    deltaX -= deficit_x;
1253
1254                    if (deltaX > 0.0) {
1255                        deficit_x = ceil(deltaX) - deltaX;
1256                        deltaX = ceil(deltaX);
1257                    } else {
1258                        deficit_x = floor(deltaX) - deltaX;
1259                        deltaX = floor(deltaX);
1260                    }
1261                } else {
1262                    deficit_x -= deltaX;
1263
1264                    if (deltaX > 0.0) {
1265                        deltaX = 1.0;
1266                    } else {
1267                        deltaX = -1.0;
1268                    }
1269
1270                    deficit_x += deltaX;
1271                }
1272            }
1273
1274            if (deltaY != 0.0) {
1275                /* If we changed directions, wipe the slate clean */
1276                if ((deficit_y < 0.0 && deltaY > 0.0) ||
1277                    (deficit_y > 0.0 && deltaY < 0.0)) {
1278                    deficit_y = 0.0;
1279                }
1280
1281                /* Eat up the deficit, but ensure that something is
1282                 * always sent 
1283                 */
1284                if (fabs(deltaY) > fabs(deficit_y)) {
1285                    deltaY -= deficit_y;
1286
1287                    if (deltaY > 0.0) {
1288                        deficit_y = ceil(deltaY) - deltaY;
1289                        deltaY = ceil(deltaY);
1290                    } else {
1291                        deficit_y = floor(deltaY) - deltaY;
1292                        deltaY = floor(deltaY);
1293                    }
1294                } else {
1295                    deficit_y -= deltaY;
1296
1297                    if (deltaY > 0.0) {
1298                        deltaY = 1.0;
1299                    } else {
1300                        deltaY = -1.0;
1301                    }
1302
1303                    deficit_y += deltaY;
1304                }
1305            }
1306        }
1307
1308        DarwinSendScrollEvents(deltaX, deltaY);
1309        break;
1310    }
1311
1312    case NSKeyDown:
1313    case NSKeyUp:
1314    {
1315        /* XKB clobbers our keymap at startup, so we need to force it on the first keypress.
1316         * TODO: Make this less of a kludge.
1317         */
1318        static int force_resync_keymap = YES;
1319        if (force_resync_keymap) {
1320            DarwinSendDDXEvent(kXquartzReloadKeymap, 0);
1321            force_resync_keymap = NO;
1322        }
1323    }
1324
1325        if (darwinSyncKeymap) {
1326            __block TISInputSourceRef key_layout;
1327            dispatch_block_t copyCurrentKeyboardLayoutInputSource = ^{
1328                key_layout = TISCopyCurrentKeyboardLayoutInputSource();
1329            };
1330            /* This is an ugly ant-pattern, but it is more expedient to address the problem right now. */
1331            if (pthread_main_np()) {
1332                copyCurrentKeyboardLayoutInputSource();
1333            } else {
1334                dispatch_sync(dispatch_get_main_queue(), copyCurrentKeyboardLayoutInputSource);
1335            }
1336
1337            TISInputSourceRef clear;
1338            if (CFEqual(key_layout, last_key_layout)) {
1339                CFRelease(key_layout);
1340            }
1341            else {
1342                /* Swap/free thread-safely */
1343                clear = last_key_layout;
1344                last_key_layout = key_layout;
1345                CFRelease(clear);
1346
1347                /* Update keyInfo */
1348                if (!QuartsResyncKeymap(TRUE)) {
1349                    ErrorF(
1350                        "sendX11NSEvent: Could not build a valid keymap.\n");
1351                }
1352            }
1353        }
1354
1355        ev_type = ([e type] == NSKeyDown) ? KeyPress : KeyRelease;
1356        DarwinSendKeyboardEvents(ev_type, [e keyCode]);
1357        break;
1358
1359    default:
1360        break;              /* for gcc */
1361    }
1362}
1363@end
1364