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