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