1 /* X11Application.m -- subclass of NSApplication to multiplex events
2 $Id: X11Application.m,v 1.53 2003/09/13 02:00:46 jharper Exp $
4 Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
6 Permission is hereby granted, free of charge, to any person
7 obtaining a copy of this software and associated documentation files
8 (the "Software"), to deal in the Software without restriction,
9 including without limitation the rights to use, copy, modify, merge,
10 publish, distribute, sublicense, and/or sell copies of the Software,
11 and to permit persons to whom the Software is furnished to do so,
12 subject to the following conditions:
14 The above copyright notice and this permission notice shall be
15 included in all copies or substantial portions of the Software.
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 NONINFRINGEMENT. IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT
21 HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 DEALINGS IN THE SOFTWARE.
26 Except as contained in this notice, the name(s) of the above
27 copyright holders shall not be used in advertising or otherwise to
28 promote the sale, use or other dealings in this Software without
29 prior written authorization. */
31 #import "X11Application.h"
32 #include <Carbon/Carbon.h>
37 #define WindowPtr X_WindowPtr
38 #define Cursor X_Cursor
40 # define _APPLEWM_SERVER_
47 #include "xf86Version.h"
49 #include <mach/mach.h>
53 #define DEFAULTS_FILE "/etc/X11/xserver/Xquartz.plist"
55 int X11EnableKeyEquivalents = TRUE;
57 X11Application *X11App;
59 #define ALL_KEY_MASKS (NSShiftKeyMask | NSControlKeyMask \
60 | NSAlternateKeyMask | NSCommandKeyMask)
62 @implementation X11Application
64 typedef struct message_struct message;
65 struct message_struct {
66 mach_msg_header_t hdr;
71 static mach_port_t _port;
73 static void send_nsevent (NSEventType type, NSEvent *e);
75 /* avoid header conflict hell */
76 extern int RootlessKnowsWindowNumber (int number);
77 extern void DarwinEnqueueEvent (const xEvent *e);
85 if (_port != MACH_PORT_NULL)
88 r = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &_port);
89 if (r != KERN_SUCCESS)
92 p = [NSMachPort portWithMachPort:_port];
93 [p setDelegate:NSApp];
94 [p scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
98 message_kit_thread (SEL selector, NSObject *arg)
103 msg.hdr.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_MAKE_SEND, 0);
104 msg.hdr.msgh_size = sizeof (msg);
105 msg.hdr.msgh_remote_port = _port;
106 msg.hdr.msgh_local_port = MACH_PORT_NULL;
107 msg.hdr.msgh_reserved = 0;
110 msg.selector = selector;
111 msg.arg = [arg retain];
113 r = mach_msg (&msg.hdr, MACH_SEND_MSG, msg.hdr.msgh_size,
114 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
115 if (r != KERN_SUCCESS)
116 fprintf (stderr, "%s: mach_msg failed: %x\n", __FUNCTION__, r);
119 - (void) handleMachMessage:(void *)_msg
123 [self performSelector:msg->selector withObject:msg->arg];
127 - (void) set_controller:obj
129 if (_controller == nil)
130 _controller = [obj retain];
135 if (_controller != nil)
136 [_controller release];
138 if (_port != MACH_PORT_NULL)
139 mach_port_deallocate (mach_task_self (), _port);
144 - (void) orderFrontStandardAboutPanel: (id) sender
146 NSMutableDictionary *dict;
147 NSDictionary *infoDict;
150 dict = [NSMutableDictionary dictionaryWithCapacity:2];
151 infoDict = [[NSBundle mainBundle] infoDictionary];
153 [dict setObject: NSLocalizedString (@"The X Window System", @"About panel")
154 forKey:@"ApplicationName"];
156 tem = [infoDict objectForKey:@"CFBundleShortVersionString"];
158 [dict setObject:[NSString stringWithFormat:@"X11 %@ - XFree86 %d.%d.%d",
159 tem, XF86_VERSION_MAJOR, XF86_VERSION_MINOR,
160 XF86_VERSION_PATCH] forKey:@"ApplicationVersion"];
162 [self orderFrontStandardAboutPanelWithOptions: dict];
165 - (void) activateX:(BOOL)state
167 /* Create a TSM document that supports full Unicode input, and
168 have it activated while X is active (unless using the old
170 static TSMDocumentID x11_document;
174 QuartzMessageMainThread (kXquartzActivate, 0);
178 if (x11_document == 0 && darwinKeymapFile == NULL)
181 types[0] = kUnicodeDocument;
182 NewTSMDocument (1, types, &x11_document, 0);
185 if (x11_document != 0)
186 ActivateTSMDocument (x11_document);
191 QuartzMessageMainThread (kXquartzDeactivate, 0);
195 if (x11_document != 0)
196 DeactivateTSMDocument (x11_document);
203 - (void) became_key:(NSWindow *)win
208 - (void) sendEvent:(NSEvent *)e
211 BOOL for_appkit, for_x;
215 /* By default pass down the responder chain and to X. */
221 case NSLeftMouseDown: case NSRightMouseDown: case NSOtherMouseDown:
222 case NSLeftMouseUp: case NSRightMouseUp: case NSOtherMouseUp:
223 if ([e window] != nil)
225 /* Pointer event has a window. Probably something for the kit. */
232 else if ([self modalWindow] == nil)
234 /* Must be an X window. Tell appkit it doesn't have focus. */
242 if (!_x_active && RootlessKnowsWindowNumber ([e windowNumber]))
243 [self activateX:YES];
248 case NSKeyDown: case NSKeyUp:
251 static int swallow_up;
253 /* No kit window is focused, so send it to X. */
257 if (type == NSKeyDown)
259 /* Before that though, see if there are any global
260 shortcuts bound to it. */
262 if (X11EnableKeyEquivalents
263 && [[self mainMenu] performKeyEquivalent:e])
265 swallow_up = [e keyCode];
268 else if (!quartzEnableRootless
269 && ([e modifierFlags] & ALL_KEY_MASKS)
270 == (NSCommandKeyMask | NSAlternateKeyMask)
271 && ([e keyCode] == 0 /*a*/
272 || [e keyCode] == 53 /*Esc*/))
276 QuartzMessageMainThread (kXquartzToggleFullscreen, 0);
281 /* If we saw a key equivalent on the down, don't pass
282 the up through to X. */
284 if (swallow_up != 0 && [e keyCode] == swallow_up)
298 /* For the l33t X users who remap modifier keys to normal keysyms. */
303 case NSAppKitDefined:
306 case NSApplicationActivatedEventType:
308 if ([self modalWindow] == nil)
312 /* FIXME: hack to avoid having to pass the event to appkit,
313 which would cause it to raise one of its windows. */
314 _appFlags._active = YES;
316 [self activateX:YES];
320 case 18: /* ApplicationDidReactivate */
325 case NSApplicationDeactivatedEventType:
332 default: break; /* for gcc */
342 send_nsevent (type, e);
346 - (void) set_window_menu:(NSArray *)list
348 [_controller set_window_menu:list];
351 - (void) set_window_menu_check:(NSNumber *)n
353 [_controller set_window_menu_check:n];
356 - (void) set_apps_menu:(NSArray *)list
358 [_controller set_apps_menu:list];
361 - (void) set_front_process:unused
363 [NSApp activateIgnoringOtherApps:YES];
365 if ([self modalWindow] == nil)
366 [self activateX:YES];
369 - (void) set_can_quit:(NSNumber *)state
371 [_controller set_can_quit:[state boolValue]];
374 - (void) server_ready:unused
376 [_controller server_ready];
379 - (void) show_hide_menubar:(NSNumber *)state
381 if ([state boolValue])
388 /* user preferences */
390 /* Note that these functions only work for arrays whose elements
391 can be toll-free-bridged between NS and CF worlds. */
393 static const void *cfretain (CFAllocatorRef a, const void *b) {
396 static void cfrelease (CFAllocatorRef a, const void *b) {
399 static CFMutableArrayRef
400 nsarray_to_cfarray (NSArray *in)
402 CFMutableArrayRef out;
408 memset (&cb, 0, sizeof (cb));
410 cb.retain = cfretain;
411 cb.release = cfrelease;
414 out = CFArrayCreateMutable (NULL, count, &cb);
416 for (i = 0; i < count; i++)
418 ns = [in objectAtIndex:i];
420 if ([ns isKindOfClass:[NSArray class]])
421 cf = (CFTypeRef) nsarray_to_cfarray ((NSArray *) ns);
423 cf = CFRetain ((CFTypeRef) ns);
425 CFArrayAppendValue (out, cf);
431 static NSMutableArray *
432 cfarray_to_nsarray (CFArrayRef in)
439 count = CFArrayGetCount (in);
440 out = [[NSMutableArray alloc] initWithCapacity:count];
442 for (i = 0; i < count; i++)
444 cf = CFArrayGetValueAtIndex (in, i);
446 if (CFGetTypeID (cf) == CFArrayGetTypeID ())
447 ns = cfarray_to_nsarray ((CFArrayRef) cf);
449 ns = [(id)cf retain];
458 - (CFPropertyListRef) prefs_get:(NSString *)key
460 CFPropertyListRef value;
462 value = CFPreferencesCopyAppValue ((CFStringRef) key, CFSTR (APP_PREFS));
466 static CFDictionaryRef defaults;
468 if (defaults == NULL)
470 CFStringRef error = NULL;
475 url = (CFURLCreateFromFileSystemRepresentation
476 (NULL, DEFAULTS_FILE, strlen (DEFAULTS_FILE), false));
477 if (CFURLCreateDataAndPropertiesFromResource (NULL, url, &data,
481 defaults = (CFPropertyListCreateFromXMLData
482 (NULL, data, kCFPropertyListImmutable, &error));
490 if (defaults != NULL)
491 value = CFDictionaryGetValue (defaults, key);
500 - (int) prefs_get_integer:(NSString *)key default:(int)def
502 CFPropertyListRef value;
505 value = [self prefs_get:key];
507 if (value != NULL && CFGetTypeID (value) == CFNumberGetTypeID ())
508 CFNumberGetValue (value, kCFNumberIntType, &ret);
509 else if (value != NULL && CFGetTypeID (value) == CFStringGetTypeID ())
510 ret = CFStringGetIntValue (value);
520 - (const char *) prefs_get_string:(NSString *)key default:(const char *)def
522 CFPropertyListRef value;
523 const char *ret = NULL;
525 value = [self prefs_get:key];
527 if (value != NULL && CFGetTypeID (value) == CFStringGetTypeID ())
529 NSString *s = (NSString *) value;
531 ret = [s UTF8String];
537 return ret != NULL ? ret : def;
540 - (float) prefs_get_float:(NSString *)key default:(float)def
542 CFPropertyListRef value;
545 value = [self prefs_get:key];
548 && CFGetTypeID (value) == CFNumberGetTypeID ()
549 && CFNumberIsFloatType (value))
551 CFNumberGetValue (value, kCFNumberFloatType, &ret);
553 else if (value != NULL && CFGetTypeID (value) == CFStringGetTypeID ())
555 ret = CFStringGetDoubleValue (value);
564 - (int) prefs_get_boolean:(NSString *)key default:(int)def
566 CFPropertyListRef value;
569 value = [self prefs_get:key];
573 if (CFGetTypeID (value) == CFNumberGetTypeID ())
574 CFNumberGetValue (value, kCFNumberIntType, &ret);
575 else if (CFGetTypeID (value) == CFBooleanGetTypeID ())
576 ret = CFBooleanGetValue (value);
577 else if (CFGetTypeID (value) == CFStringGetTypeID ())
579 const char *tem = [(NSString *) value lossyCString];
580 if (strcasecmp (tem, "true") == 0 || strcasecmp (tem, "yes") == 0)
592 - (NSArray *) prefs_get_array:(NSString *)key
595 CFPropertyListRef value;
597 value = [self prefs_get:key];
601 if (CFGetTypeID (value) == CFArrayGetTypeID ())
602 ret = [cfarray_to_nsarray (value) autorelease];
610 - (void) prefs_set_integer:(NSString *)key value:(int)value
614 x = CFNumberCreate (NULL, kCFNumberIntType, &value);
616 CFPreferencesSetValue ((CFStringRef) key, (CFTypeRef) x, CFSTR (APP_PREFS),
617 kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
622 - (void) prefs_set_float:(NSString *)key value:(float)value
626 x = CFNumberCreate (NULL, kCFNumberFloatType, &value);
628 CFPreferencesSetValue ((CFStringRef) key, (CFTypeRef) x, CFSTR (APP_PREFS),
629 kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
634 - (void) prefs_set_boolean:(NSString *)key value:(int)value
636 CFPreferencesSetValue ((CFStringRef) key,
637 (CFTypeRef) value ? kCFBooleanTrue
638 : kCFBooleanFalse, CFSTR (APP_PREFS),
639 kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
643 - (void) prefs_set_array:(NSString *)key value:(NSArray *)value
647 cfarray = nsarray_to_cfarray (value);
648 CFPreferencesSetValue ((CFStringRef) key,
651 kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
655 - (void) prefs_set_string:(NSString *)key value:(NSString *)value
657 CFPreferencesSetValue ((CFStringRef) key, (CFTypeRef) value,
658 CFSTR (APP_PREFS), kCFPreferencesCurrentUser,
659 kCFPreferencesAnyHost);
662 - (void) prefs_synchronize
664 CFPreferencesAppSynchronize (kCFPreferencesCurrentApplication);
667 - (void) read_defaults
669 extern int darwinFakeButtons;
672 quartzUseSysBeep = [self prefs_get_boolean:@PREFS_SYSBEEP
673 default:quartzUseSysBeep];
674 quartzEnableRootless = [self prefs_get_boolean:@PREFS_ROOTLESS
675 default:quartzEnableRootless];
676 quartzFullscreenDisableHotkeys = ![self prefs_get_boolean:
677 @PREFS_FULLSCREEN_HOTKEYS default:
678 !quartzFullscreenDisableHotkeys];
679 quartzXpluginOptions = [self prefs_get_integer:@PREFS_XP_OPTIONS
680 default:quartzXpluginOptions];
682 darwinSwapAltMeta = [self prefs_get_boolean:@PREFS_SWAP_ALT_META
683 default:darwinSwapAltMeta];
684 darwinFakeButtons = [self prefs_get_boolean:@PREFS_FAKEBUTTONS
685 default:darwinFakeButtons];
686 if (darwinFakeButtons)
688 const char *fake2, *fake3;
690 fake2 = [self prefs_get_string:@PREFS_FAKE_BUTTON2 default:NULL];
691 fake3 = [self prefs_get_string:@PREFS_FAKE_BUTTON3 default:NULL];
693 DarwinSetFakeButtons (fake2, fake3);
696 X11EnableKeyEquivalents = [self prefs_get_boolean:@PREFS_KEYEQUIVS
697 default:X11EnableKeyEquivalents];
699 darwinSyncKeymap = [self prefs_get_boolean:@PREFS_SYNC_KEYMAP
700 default:darwinSyncKeymap];
702 tem = [self prefs_get_string:@PREFS_KEYMAP_FILE default:NULL];
704 darwinKeymapFile = strdup (tem);
706 quartzDesiredDepth = [self prefs_get_integer:@PREFS_DEPTH
707 default:quartzDesiredDepth];
710 /* This will end up at the end of the responder chain. */
713 QuartzMessageMainThread (kXquartzPasteboardNotify, 1,
714 AppleWMCopyToPasteboard);
725 array_with_strings_and_numbers (int nitems, const char **items,
728 NSMutableArray *array, *subarray;
733 /* (Can't autorelease on the X server thread) */
735 array = [[NSMutableArray alloc] initWithCapacity:nitems];
737 for (i = 0; i < nitems; i++)
739 subarray = [[NSMutableArray alloc] initWithCapacity:2];
741 string = [[NSString alloc] initWithUTF8String:items[i]];
742 [subarray addObject:string];
747 number = [[NSString alloc] initWithFormat:@"%d", numbers[i]];
748 [subarray addObject:number];
752 [subarray addObject:@""];
754 [array addObject:subarray];
762 X11ApplicationSetWindowMenu (int nitems, const char **items,
763 const char *shortcuts)
767 array = array_with_strings_and_numbers (nitems, items, shortcuts);
769 /* Send the array of strings over to the appkit thread */
771 message_kit_thread (@selector (set_window_menu:), array);
776 X11ApplicationSetWindowMenuCheck (int idx)
780 n = [[NSNumber alloc] initWithInt:idx];
782 message_kit_thread (@selector (set_window_menu_check:), n);
788 X11ApplicationSetFrontProcess (void)
790 message_kit_thread (@selector (set_front_process:), nil);
794 X11ApplicationSetCanQuit (int state)
798 n = [[NSNumber alloc] initWithBool:state];
800 message_kit_thread (@selector (set_can_quit:), n);
806 X11ApplicationServerReady (void)
808 message_kit_thread (@selector (server_ready:), nil);
812 X11ApplicationShowHideMenubar (int state)
816 n = [[NSNumber alloc] initWithBool:state];
818 message_kit_thread (@selector (show_hide_menubar:), n);
824 create_thread (void *func, void *arg)
829 pthread_attr_init (&attr);
831 pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
832 pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
834 pthread_create (&tid, &attr, func, arg);
836 pthread_attr_destroy (&attr);
844 char *tem, buf[1024];
847 if ([X11App prefs_get_boolean:@PREFS_DONE_XINIT_CHECK default:NO])
850 tem = getenv ("HOME");
854 snprintf (buf, sizeof (buf), "%s/.xinitrc", tem);
855 if (access (buf, F_OK) != 0)
858 /* FIXME: put localized strings into Resources/English.lproj */
860 msg = NSLocalizedString (
861 @"You have an existing ~/.xinitrc file.\n\n\
862 Windows displayed by X11 applications may not have titlebars, or may look \
863 different to windows displayed by native applications.\n\n\
864 Would you like to move aside the existing file and use the standard X11 \
867 if (NSRunAlertPanel (nil, msg, NSLocalizedString (@"Yes", @""),
868 NSLocalizedString (@"No", @""), nil)
869 == NSAlertDefaultReturn)
874 snprintf (buf2, sizeof (buf2), "%s.old", buf);
876 for (i = 1; access (buf2, F_OK) == 0; i++)
877 snprintf (buf2, sizeof (buf2), "%s.old.%d", buf, i);
883 [X11App prefs_set_boolean:@PREFS_DONE_XINIT_CHECK value:YES];
884 [X11App prefs_synchronize];
888 X11ApplicationMain (int argc, const char *argv[],
889 void (*server_thread) (void *), void *server_arg)
891 NSAutoreleasePool *pool;
894 while (access ("/tmp/x11-block", F_OK) == 0)
898 pool = [[NSAutoreleasePool alloc] init];
900 X11App = (X11Application *) [X11Application sharedApplication];
904 [NSApp read_defaults];
906 [NSBundle loadNibNamed:@"main" owner:NSApp];
908 [[NSNotificationCenter defaultCenter] addObserver:NSApp
909 selector:@selector (became_key:)
910 name:NSWindowDidBecomeKeyNotification object:nil];
914 if (!create_thread (server_thread, server_arg))
916 fprintf (stderr, "can't create secondary thread\n");
926 /* event conversion */
928 static inline unsigned short
929 convert_flags (unsigned int nsflags)
938 if (nsflags & NSAlphaShiftKeyMask)
940 if (nsflags & NSShiftKeyMask)
942 if (nsflags & NSControlKeyMask)
943 xflags |= ControlMask;
944 if (nsflags & NSAlternateKeyMask)
946 if (nsflags & NSCommandKeyMask)
948 /* FIXME: secondaryfn? */
954 send_nsevent (NSEventType type, NSEvent *e)
956 static unsigned int button_state = 0;
960 memset (&xe, 0, sizeof (xe));
967 int pointer_x, pointer_y, count;
969 case NSLeftMouseDown:
970 xe.u.u.type = ButtonPress;
974 case NSRightMouseDown:
975 xe.u.u.type = ButtonPress;
979 case NSOtherMouseDown:
980 xe.u.u.type = ButtonPress;
981 xe.u.u.detail = 2; /* FIXME? */
985 if (RootlessKnowsWindowNumber ([e windowNumber]) == NULL)
987 /* X server doesn't grok this window, drop the event.
989 Note: theoretically this isn't necessary, but if I click
990 on the menubar, we get sent a LeftMouseDown when the
991 release happens, but no LeftMouseUp is ever seen! */
998 xe.u.u.type = ButtonRelease;
1000 goto do_release_event;
1002 case NSRightMouseUp:
1003 xe.u.u.type = ButtonRelease;
1005 goto do_release_event;
1007 case NSOtherMouseUp:
1008 xe.u.u.type = ButtonRelease;
1009 xe.u.u.detail = 2; /* FIXME? */
1010 goto do_release_event;
1013 if ((button_state & (1 << xe.u.u.detail)) == 0)
1015 /* X didn't see the button press for this release, so skip it */
1021 case NSLeftMouseDragged:
1022 case NSRightMouseDragged:
1023 case NSOtherMouseDragged:
1024 /* convert location to global top-left coordinates */
1026 location = [e locationInWindow];
1027 window = [e window];
1028 screen = [[[NSScreen screens] objectAtIndex:0] frame];
1032 NSRect frame = [window frame];
1033 pointer_x = location.x + frame.origin.x;
1034 pointer_y = (((screen.origin.y + screen.size.height)
1035 - location.y) - frame.origin.y);
1039 pointer_x = location.x;
1040 pointer_y = (screen.origin.y + screen.size.height) - location.y;
1043 xe.u.keyButtonPointer.rootX = pointer_x;
1044 xe.u.keyButtonPointer.rootY = pointer_y;
1045 xe.u.u.type = MotionNotify;
1049 xe.u.u.type = KeyPress;
1050 xe.u.u.detail = [e keyCode];
1054 xe.u.u.type = KeyRelease;
1055 xe.u.u.detail = [e keyCode];
1059 xe.u.keyButtonPointer.state = convert_flags ([e modifierFlags]);
1061 xe.u.u.detail = count > 0 ? 4 : 5;
1062 for (count = abs (count); count-- > 0;)
1064 xe.u.u.type = ButtonPress;
1065 DarwinEnqueueEvent (&xe);
1066 xe.u.u.type = ButtonRelease;
1067 DarwinEnqueueEvent (&xe);
1072 case NSFlagsChanged:
1074 xe.u.keyButtonPointer.state = convert_flags ([e modifierFlags]);
1075 DarwinEnqueueEvent (&xe);
1078 default: break; /* for gcc */
1081 if (xe.u.u.type == ButtonPress)
1082 button_state |= (1 << xe.u.u.detail);
1083 else if (xe.u.u.type == ButtonRelease)
1084 button_state &= ~(1 << xe.u.u.detail);