Initial Version
[synfig.git] / synfig-osx / trunk / launcher / X11Application.m
diff --git a/synfig-osx/trunk/launcher/X11Application.m b/synfig-osx/trunk/launcher/X11Application.m
new file mode 100644 (file)
index 0000000..094e064
--- /dev/null
@@ -0,0 +1,1085 @@
+/* X11Application.m -- subclass of NSApplication to multiplex events
+   $Id: X11Application.m,v 1.53 2003/09/13 02:00:46 jharper Exp $
+
+   Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
+
+   Permission is hereby granted, free of charge, to any person
+   obtaining a copy of this software and associated documentation files
+   (the "Software"), to deal in the Software without restriction,
+   including without limitation the rights to use, copy, modify, merge,
+   publish, distribute, sublicense, and/or sell copies of the Software,
+   and to permit persons to whom the Software is furnished to do so,
+   subject to the following conditions:
+
+   The above copyright notice and this permission notice shall be
+   included in all copies or substantial portions of the Software.
+
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+   NONINFRINGEMENT.  IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT
+   HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+   DEALINGS IN THE SOFTWARE.
+
+   Except as contained in this notice, the name(s) of the above
+   copyright holders shall not be used in advertising or otherwise to
+   promote the sale, use or other dealings in this Software without
+   prior written authorization. */
+
+#import "X11Application.h"
+#include <Carbon/Carbon.h>
+
+/* ouch! */
+#define BOOL X_BOOL
+# include "Xproto.h"
+#define WindowPtr X_WindowPtr
+#define Cursor X_Cursor
+# include "quartz.h"
+# define _APPLEWM_SERVER_
+# include "applewm.h"
+# include "X.h"
+#undef Cursor
+#undef WindowPtr
+#undef BOOL
+
+#include "xf86Version.h"
+
+#include <mach/mach.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#define DEFAULTS_FILE "/etc/X11/xserver/Xquartz.plist"
+
+int X11EnableKeyEquivalents = TRUE;
+
+X11Application *X11App;
+
+#define ALL_KEY_MASKS (NSShiftKeyMask | NSControlKeyMask \
+                      | NSAlternateKeyMask | NSCommandKeyMask)
+
+@implementation X11Application
+
+typedef struct message_struct message;
+struct message_struct {
+    mach_msg_header_t hdr;
+    SEL selector;
+    NSObject *arg;
+};
+
+static mach_port_t _port;
+
+static void send_nsevent (NSEventType type, NSEvent *e);
+
+/* avoid header conflict hell */
+extern int RootlessKnowsWindowNumber (int number);
+extern void DarwinEnqueueEvent (const xEvent *e);
+
+static void
+init_ports (void)
+{
+    kern_return_t r;
+    NSPort *p;
+
+    if (_port != MACH_PORT_NULL)
+       return;
+
+    r = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &_port);
+    if (r != KERN_SUCCESS)
+       return;
+
+    p = [NSMachPort portWithMachPort:_port];
+    [p setDelegate:NSApp];
+    [p scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+}
+
+static void
+message_kit_thread (SEL selector, NSObject *arg)
+{
+    message msg;
+    kern_return_t r;
+
+    msg.hdr.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_MAKE_SEND, 0);
+    msg.hdr.msgh_size = sizeof (msg);
+    msg.hdr.msgh_remote_port = _port;
+    msg.hdr.msgh_local_port = MACH_PORT_NULL;
+    msg.hdr.msgh_reserved = 0;
+    msg.hdr.msgh_id = 0;
+
+    msg.selector = selector;
+    msg.arg = [arg retain];
+
+    r = mach_msg (&msg.hdr, MACH_SEND_MSG, msg.hdr.msgh_size,
+                 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
+    if (r != KERN_SUCCESS)
+       fprintf (stderr, "%s: mach_msg failed: %x\n", __FUNCTION__, r);
+}
+
+- (void) handleMachMessage:(void *)_msg
+{
+    message *msg = _msg;
+
+    [self performSelector:msg->selector withObject:msg->arg];
+    [msg->arg release];
+}
+
+- (void) set_controller:obj
+{
+    if (_controller == nil)
+       _controller = [obj retain];
+}
+
+- (void) dealloc
+{
+    if (_controller != nil)
+       [_controller release];
+
+    if (_port != MACH_PORT_NULL)
+       mach_port_deallocate (mach_task_self (), _port);
+
+    [super dealloc];
+}
+
+- (void) orderFrontStandardAboutPanel: (id) sender
+{
+    NSMutableDictionary *dict;
+    NSDictionary *infoDict;
+    NSString *tem;
+
+    dict = [NSMutableDictionary dictionaryWithCapacity:2];
+    infoDict = [[NSBundle mainBundle] infoDictionary];
+
+    [dict setObject: NSLocalizedString (@"The X Window System", @"About panel")
+     forKey:@"ApplicationName"];
+
+    tem = [infoDict objectForKey:@"CFBundleShortVersionString"];
+
+    [dict setObject:[NSString stringWithFormat:@"X11 %@ - XFree86 %d.%d.%d",
+                    tem, XF86_VERSION_MAJOR, XF86_VERSION_MINOR,
+                    XF86_VERSION_PATCH] forKey:@"ApplicationVersion"];
+
+    [self orderFrontStandardAboutPanelWithOptions: dict];
+}
+
+- (void) activateX:(BOOL)state
+{
+    /* Create a TSM document that supports full Unicode input, and
+       have it activated while X is active (unless using the old
+       keymapping files) */
+    static TSMDocumentID x11_document;
+
+    if (state)
+    {
+       QuartzMessageMainThread (kXquartzActivate, 0);
+
+       if (!_x_active)
+       {
+           if (x11_document == 0 && darwinKeymapFile == NULL)
+           {
+               OSType types[1];
+               types[0] = kUnicodeDocument;
+               NewTSMDocument (1, types, &x11_document, 0);
+           }
+
+           if (x11_document != 0)
+               ActivateTSMDocument (x11_document);
+       }
+    }
+    else
+    {
+       QuartzMessageMainThread (kXquartzDeactivate, 0);
+
+       if (_x_active)
+       {
+           if (x11_document != 0)
+               DeactivateTSMDocument (x11_document);
+       }
+    }
+
+    _x_active = state;
+}
+
+- (void) became_key:(NSWindow *)win
+{
+    [self activateX:NO];
+}
+
+- (void) sendEvent:(NSEvent *)e
+{
+    NSEventType type;
+    BOOL for_appkit, for_x;
+
+    type = [e type];
+
+    /* By default pass down the responder chain and to X. */
+    for_appkit = YES;
+    for_x = YES;
+
+    switch (type)
+    {
+    case NSLeftMouseDown: case NSRightMouseDown: case NSOtherMouseDown:
+    case NSLeftMouseUp: case NSRightMouseUp: case NSOtherMouseUp:
+       if ([e window] != nil)
+       {
+           /* Pointer event has a window. Probably something for the kit. */
+
+           for_x = NO;
+
+           if (_x_active)
+               [self activateX:NO];
+       }
+       else if ([self modalWindow] == nil)
+       {
+           /* Must be an X window. Tell appkit it doesn't have focus. */
+
+           for_appkit = NO;
+
+           if ([self isActive])
+           {
+               [self deactivate];
+
+               if (!_x_active && RootlessKnowsWindowNumber ([e windowNumber]))
+                   [self activateX:YES];
+           }
+       }
+       break;
+
+    case NSKeyDown: case NSKeyUp:
+       if (_x_active)
+       {
+           static int swallow_up;
+
+           /* No kit window is focused, so send it to X. */
+
+           for_appkit = NO;
+
+           if (type == NSKeyDown)
+           {
+               /* Before that though, see if there are any global
+                  shortcuts bound to it. */
+
+               if (X11EnableKeyEquivalents
+                   && [[self mainMenu] performKeyEquivalent:e])
+               {
+                   swallow_up = [e keyCode];
+                   for_x = NO;
+               }
+               else if (!quartzEnableRootless
+                        && ([e modifierFlags] & ALL_KEY_MASKS)
+                           == (NSCommandKeyMask | NSAlternateKeyMask)
+                        && ([e keyCode] == 0 /*a*/
+                            || [e keyCode] == 53 /*Esc*/))
+               {
+                   swallow_up = 0;
+                   for_x = NO;
+                   QuartzMessageMainThread (kXquartzToggleFullscreen, 0);
+               }
+           }
+           else
+           {
+               /* If we saw a key equivalent on the down, don't pass
+                  the up through to X. */
+
+               if (swallow_up != 0 && [e keyCode] == swallow_up)
+               {
+                   swallow_up = 0;
+                   for_x = NO;
+               }
+           }
+       }
+       else
+       {
+           for_x = NO;
+       }
+       break;
+
+    case NSFlagsChanged:
+       /* For the l33t X users who remap modifier keys to normal keysyms. */
+       if (!_x_active)
+           for_x = NO;
+       break;
+
+    case NSAppKitDefined:
+       switch ([e subtype])
+       {
+       case NSApplicationActivatedEventType:
+           for_x = NO;
+           if ([self modalWindow] == nil)
+           {
+               for_appkit = NO;
+           
+               /* FIXME: hack to avoid having to pass the event to appkit,
+                  which would cause it to raise one of its windows. */
+               _appFlags._active = YES;
+
+               [self activateX:YES];
+           }
+           break;
+
+       case 18: /* ApplicationDidReactivate */
+           if (quartzHasRoot)
+               for_appkit = NO;
+           break;
+
+       case NSApplicationDeactivatedEventType:
+           for_x = NO;
+           [self activateX:NO];
+           break;
+       }
+       break;
+
+    default: break; /* for gcc */
+    }
+
+    if (for_appkit)
+    {
+       [super sendEvent:e];
+    }
+
+    if (for_x)
+    {
+       send_nsevent (type, e);
+    }
+}
+
+- (void) set_window_menu:(NSArray *)list
+{
+    [_controller set_window_menu:list];
+}
+
+- (void) set_window_menu_check:(NSNumber *)n
+{
+    [_controller set_window_menu_check:n];
+}
+
+- (void) set_apps_menu:(NSArray *)list
+{
+    [_controller set_apps_menu:list];
+}
+
+- (void) set_front_process:unused
+{
+    [NSApp activateIgnoringOtherApps:YES];
+
+    if ([self modalWindow] == nil)
+       [self activateX:YES];
+}
+
+- (void) set_can_quit:(NSNumber *)state
+{
+    [_controller set_can_quit:[state boolValue]];
+}
+
+- (void) server_ready:unused
+{
+    [_controller server_ready];
+}
+
+- (void) show_hide_menubar:(NSNumber *)state
+{
+    if ([state boolValue])
+       ShowMenuBar ();
+    else
+       HideMenuBar ();
+}
+
+\f
+/* user preferences */
+
+/* Note that these functions only work for arrays whose elements
+   can be toll-free-bridged between NS and CF worlds. */
+
+static const void *cfretain (CFAllocatorRef a, const void *b) {
+    return CFRetain (b);
+}
+static void cfrelease (CFAllocatorRef a, const void *b) {
+    CFRelease (b);
+}
+static CFMutableArrayRef
+nsarray_to_cfarray (NSArray *in)
+{
+    CFMutableArrayRef out;
+    CFArrayCallBacks cb;
+    NSObject *ns;
+    const CFTypeRef *cf;
+    int i, count;
+
+    memset (&cb, 0, sizeof (cb));
+    cb.version = 0;
+    cb.retain = cfretain;
+    cb.release = cfrelease;
+
+    count = [in count];
+    out = CFArrayCreateMutable (NULL, count, &cb);
+
+    for (i = 0; i < count; i++)
+    {
+       ns = [in objectAtIndex:i];
+
+       if ([ns isKindOfClass:[NSArray class]])
+           cf = (CFTypeRef) nsarray_to_cfarray ((NSArray *) ns);
+       else
+           cf = CFRetain ((CFTypeRef) ns);
+
+       CFArrayAppendValue (out, cf);
+       CFRelease (cf);
+    }
+
+    return out;
+}
+static NSMutableArray *
+cfarray_to_nsarray (CFArrayRef in)
+{
+    NSMutableArray *out;
+    const CFTypeRef *cf;
+    NSObject *ns;
+    int i, count;
+
+    count = CFArrayGetCount (in);
+    out = [[NSMutableArray alloc] initWithCapacity:count];
+
+    for (i = 0; i < count; i++)
+    {
+       cf = CFArrayGetValueAtIndex (in, i);
+
+       if (CFGetTypeID (cf) == CFArrayGetTypeID ())
+           ns = cfarray_to_nsarray ((CFArrayRef) cf);
+       else
+           ns = [(id)cf retain];
+
+       [out addObject:ns];
+       [ns release];
+    }
+
+    return out;
+}
+
+- (CFPropertyListRef) prefs_get:(NSString *)key
+{
+    CFPropertyListRef value;
+
+    value = CFPreferencesCopyAppValue ((CFStringRef) key, CFSTR (APP_PREFS));
+
+    if (value == NULL)
+    {
+       static CFDictionaryRef defaults;
+
+       if (defaults == NULL)
+       {
+           CFStringRef error = NULL;
+           CFDataRef data;
+           CFURLRef url;
+           SInt32 error_code;
+
+           url = (CFURLCreateFromFileSystemRepresentation
+                  (NULL, DEFAULTS_FILE, strlen (DEFAULTS_FILE), false));
+           if (CFURLCreateDataAndPropertiesFromResource (NULL, url, &data,
+                                                         NULL, NULL,
+                                                         &error_code))
+           {
+               defaults = (CFPropertyListCreateFromXMLData
+                           (NULL, data, kCFPropertyListImmutable, &error));
+               if (error != NULL)
+                   CFRelease (error);
+               CFRelease (data);
+           }
+           CFRelease (url);
+       }
+
+       if (defaults != NULL)
+           value = CFDictionaryGetValue (defaults, key);
+
+       if (value != NULL)
+           CFRetain (value);
+    }
+
+    return value;
+}
+
+- (int) prefs_get_integer:(NSString *)key default:(int)def
+{
+    CFPropertyListRef value;
+    int ret;
+
+    value = [self prefs_get:key];
+
+    if (value != NULL && CFGetTypeID (value) == CFNumberGetTypeID ())
+       CFNumberGetValue (value, kCFNumberIntType, &ret);
+    else if (value != NULL && CFGetTypeID (value) == CFStringGetTypeID ())
+       ret = CFStringGetIntValue (value);
+    else
+       ret = def;
+
+    if (value != NULL)
+       CFRelease (value);
+
+    return ret;
+}
+
+- (const char *) prefs_get_string:(NSString *)key default:(const char *)def
+{
+    CFPropertyListRef value;
+    const char *ret = NULL;
+
+    value = [self prefs_get:key];
+
+    if (value != NULL && CFGetTypeID (value) == CFStringGetTypeID ())
+    {
+       NSString *s = (NSString *) value;
+
+       ret = [s UTF8String];
+    }
+
+    if (value != NULL)
+       CFRelease (value);
+
+    return ret != NULL ? ret : def;
+}
+
+- (float) prefs_get_float:(NSString *)key default:(float)def
+{
+    CFPropertyListRef value;
+    float ret = def;
+
+    value = [self prefs_get:key];
+
+    if (value != NULL
+       && CFGetTypeID (value) == CFNumberGetTypeID ()
+       && CFNumberIsFloatType (value))
+    {
+       CFNumberGetValue (value, kCFNumberFloatType, &ret);
+    }
+    else if (value != NULL && CFGetTypeID (value) == CFStringGetTypeID ())
+    {
+       ret = CFStringGetDoubleValue (value);
+    }
+
+    if (value != NULL)
+       CFRelease (value);
+
+    return ret;
+}
+
+- (int) prefs_get_boolean:(NSString *)key default:(int)def
+{
+    CFPropertyListRef value;
+    int ret = def;
+
+    value = [self prefs_get:key];
+
+    if (value != NULL)
+    {
+       if (CFGetTypeID (value) == CFNumberGetTypeID ())
+           CFNumberGetValue (value, kCFNumberIntType, &ret);
+       else if (CFGetTypeID (value) == CFBooleanGetTypeID ())
+           ret = CFBooleanGetValue (value);
+       else if (CFGetTypeID (value) == CFStringGetTypeID ())
+       {
+           const char *tem = [(NSString *) value lossyCString];
+           if (strcasecmp (tem, "true") == 0 || strcasecmp (tem, "yes") == 0)
+               ret = YES;
+           else
+               ret = NO;
+       }
+
+       CFRelease (value);
+    }
+
+    return ret;
+}
+
+- (NSArray *) prefs_get_array:(NSString *)key
+{
+    NSArray *ret = nil;
+    CFPropertyListRef value;
+
+    value = [self prefs_get:key];
+
+    if (value != NULL)
+    {
+       if (CFGetTypeID (value) == CFArrayGetTypeID ())
+           ret = [cfarray_to_nsarray (value) autorelease];
+
+       CFRelease (value);
+    }
+
+    return ret;
+}
+
+- (void) prefs_set_integer:(NSString *)key value:(int)value
+{
+    CFNumberRef x;
+
+    x = CFNumberCreate (NULL, kCFNumberIntType, &value);
+
+    CFPreferencesSetValue ((CFStringRef) key, (CFTypeRef) x, CFSTR (APP_PREFS),
+                          kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+
+    CFRelease (x);
+}
+
+- (void) prefs_set_float:(NSString *)key value:(float)value
+{
+    CFNumberRef x;
+
+    x = CFNumberCreate (NULL, kCFNumberFloatType, &value);
+
+    CFPreferencesSetValue ((CFStringRef) key, (CFTypeRef) x, CFSTR (APP_PREFS),
+                          kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+
+    CFRelease (x);
+}
+
+- (void) prefs_set_boolean:(NSString *)key value:(int)value
+{
+    CFPreferencesSetValue ((CFStringRef) key,
+                          (CFTypeRef) value ? kCFBooleanTrue
+                          : kCFBooleanFalse, CFSTR (APP_PREFS),
+                          kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+
+}
+
+- (void) prefs_set_array:(NSString *)key value:(NSArray *)value
+{
+    CFArrayRef cfarray;
+
+    cfarray = nsarray_to_cfarray (value);
+    CFPreferencesSetValue ((CFStringRef) key,
+                          (CFTypeRef) cfarray,
+                          CFSTR (APP_PREFS),
+                          kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+    CFRelease (cfarray);
+}
+
+- (void) prefs_set_string:(NSString *)key value:(NSString *)value
+{
+    CFPreferencesSetValue ((CFStringRef) key, (CFTypeRef) value,
+                          CFSTR (APP_PREFS), kCFPreferencesCurrentUser,
+                          kCFPreferencesAnyHost);
+}
+
+- (void) prefs_synchronize
+{
+    CFPreferencesAppSynchronize (kCFPreferencesCurrentApplication);
+}
+
+- (void) read_defaults
+{
+    extern int darwinFakeButtons;
+    const char *tem;
+
+    quartzUseSysBeep = [self prefs_get_boolean:@PREFS_SYSBEEP
+                       default:quartzUseSysBeep];
+    quartzEnableRootless = [self prefs_get_boolean:@PREFS_ROOTLESS
+                           default:quartzEnableRootless];
+    quartzFullscreenDisableHotkeys = ![self prefs_get_boolean:
+                                      @PREFS_FULLSCREEN_HOTKEYS default:
+                                      !quartzFullscreenDisableHotkeys];
+    quartzXpluginOptions = [self prefs_get_integer:@PREFS_XP_OPTIONS
+                           default:quartzXpluginOptions];
+
+    darwinSwapAltMeta = [self prefs_get_boolean:@PREFS_SWAP_ALT_META
+                        default:darwinSwapAltMeta];
+    darwinFakeButtons = [self prefs_get_boolean:@PREFS_FAKEBUTTONS
+                        default:darwinFakeButtons];
+    if (darwinFakeButtons)
+    {
+       const char *fake2, *fake3;
+
+       fake2 = [self prefs_get_string:@PREFS_FAKE_BUTTON2 default:NULL];
+       fake3 = [self prefs_get_string:@PREFS_FAKE_BUTTON3 default:NULL];
+
+       DarwinSetFakeButtons (fake2, fake3);
+    }
+
+    X11EnableKeyEquivalents = [self prefs_get_boolean:@PREFS_KEYEQUIVS
+                              default:X11EnableKeyEquivalents];
+
+    darwinSyncKeymap = [self prefs_get_boolean:@PREFS_SYNC_KEYMAP
+                       default:darwinSyncKeymap];
+
+    tem = [self prefs_get_string:@PREFS_KEYMAP_FILE default:NULL];
+    if (tem != NULL)
+       darwinKeymapFile = strdup (tem);
+
+    quartzDesiredDepth = [self prefs_get_integer:@PREFS_DEPTH
+                         default:quartzDesiredDepth];
+}
+
+/* This will end up at the end of the responder chain. */
+- (void) copy:sender
+{
+    QuartzMessageMainThread (kXquartzPasteboardNotify, 1,
+                            AppleWMCopyToPasteboard);
+}
+
+- (BOOL) x_active
+{
+    return _x_active;
+}
+
+@end
+
+static NSArray *
+array_with_strings_and_numbers (int nitems, const char **items,
+                               const char *numbers)
+{
+    NSMutableArray *array, *subarray;
+    NSString *string;
+    NSString *number;
+    int i;
+
+    /* (Can't autorelease on the X server thread) */
+
+    array = [[NSMutableArray alloc] initWithCapacity:nitems];
+
+    for (i = 0; i < nitems; i++)
+    {
+       subarray = [[NSMutableArray alloc] initWithCapacity:2];
+
+       string = [[NSString alloc] initWithUTF8String:items[i]];
+       [subarray addObject:string];
+       [string release];
+
+       if (numbers[i] != 0)
+       {
+           number = [[NSString alloc] initWithFormat:@"%d", numbers[i]];
+           [subarray addObject:number];
+           [number release];
+       }
+       else
+           [subarray addObject:@""];
+
+       [array addObject:subarray];
+       [subarray release];
+    }
+
+    return array;
+}
+
+void
+X11ApplicationSetWindowMenu (int nitems, const char **items,
+                            const char *shortcuts)
+{
+    NSArray *array;
+
+    array = array_with_strings_and_numbers (nitems, items, shortcuts);
+
+    /* Send the array of strings over to the appkit thread */
+
+    message_kit_thread (@selector (set_window_menu:), array);
+    [array release];
+}
+
+void
+X11ApplicationSetWindowMenuCheck (int idx)
+{
+    NSNumber *n;
+
+    n = [[NSNumber alloc] initWithInt:idx];
+
+    message_kit_thread (@selector (set_window_menu_check:), n);
+
+    [n release];
+}
+
+void
+X11ApplicationSetFrontProcess (void)
+{
+    message_kit_thread (@selector (set_front_process:), nil);
+}
+
+void
+X11ApplicationSetCanQuit (int state)
+{
+    NSNumber *n;
+
+    n = [[NSNumber alloc] initWithBool:state];
+
+    message_kit_thread (@selector (set_can_quit:), n);
+
+    [n release];
+}
+
+void
+X11ApplicationServerReady (void)
+{
+    message_kit_thread (@selector (server_ready:), nil);
+}
+
+void
+X11ApplicationShowHideMenubar (int state)
+{
+    NSNumber *n;
+
+    n = [[NSNumber alloc] initWithBool:state];
+
+    message_kit_thread (@selector (show_hide_menubar:), n);
+
+    [n release];
+}
+
+static void *
+create_thread (void *func, void *arg)
+{
+    pthread_attr_t attr;
+    pthread_t tid;
+
+    pthread_attr_init (&attr);
+
+    pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
+    pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+
+    pthread_create (&tid, &attr, func, arg);
+
+    pthread_attr_destroy (&attr);
+
+    return (void *) tid;
+}
+
+static void
+check_xinitrc (void)
+{
+    char *tem, buf[1024];
+    NSString *msg;
+
+    if ([X11App prefs_get_boolean:@PREFS_DONE_XINIT_CHECK default:NO])
+       return;
+
+    tem = getenv ("HOME");
+    if (tem == NULL)
+       goto done;
+
+    snprintf (buf, sizeof (buf), "%s/.xinitrc", tem);
+    if (access (buf, F_OK) != 0)
+       goto done;
+
+    /* FIXME: put localized strings into Resources/English.lproj */
+
+    msg = NSLocalizedString (
+@"You have an existing ~/.xinitrc file.\n\n\
+Windows displayed by X11 applications may not have titlebars, or may look \
+different to windows displayed by native applications.\n\n\
+Would you like to move aside the existing file and use the standard X11 \
+environment?", @"");
+
+    if (NSRunAlertPanel (nil, msg, NSLocalizedString (@"Yes", @""),
+                        NSLocalizedString (@"No", @""), nil)
+       == NSAlertDefaultReturn)
+    {
+       char buf2[1024];
+       int i = -1;
+
+       snprintf (buf2, sizeof (buf2), "%s.old", buf);
+
+       for (i = 1; access (buf2, F_OK) == 0; i++)
+            snprintf (buf2, sizeof (buf2), "%s.old.%d", buf, i);
+
+       rename (buf, buf2);
+    }
+    
+done:
+    [X11App prefs_set_boolean:@PREFS_DONE_XINIT_CHECK value:YES];
+    [X11App prefs_synchronize];
+}
+
+void
+X11ApplicationMain (int argc, const char *argv[],
+                   void (*server_thread) (void *), void *server_arg)
+{
+    NSAutoreleasePool *pool;
+
+#ifdef DEBUG
+    while (access ("/tmp/x11-block", F_OK) == 0)
+       sleep (1);
+#endif
+
+    pool = [[NSAutoreleasePool alloc] init];
+
+    X11App = (X11Application *) [X11Application sharedApplication];
+
+    init_ports ();
+
+    [NSApp read_defaults];
+
+    [NSBundle loadNibNamed:@"main" owner:NSApp];
+
+    [[NSNotificationCenter defaultCenter] addObserver:NSApp
+     selector:@selector (became_key:)
+     name:NSWindowDidBecomeKeyNotification object:nil];
+
+    check_xinitrc ();
+
+    if (!create_thread (server_thread, server_arg))
+    {
+       fprintf (stderr, "can't create secondary thread\n");
+       exit (1);
+    }
+
+    [NSApp run];
+
+    /* not reached */
+}
+
+\f
+/* event conversion */
+
+static inline unsigned short
+convert_flags (unsigned int nsflags)
+{
+    unsigned int xflags;
+
+    if (nsflags == ~0)
+       return 0xffff;
+
+    xflags = 0;
+
+    if (nsflags & NSAlphaShiftKeyMask)
+       xflags |= LockMask;
+    if (nsflags & NSShiftKeyMask)
+       xflags |= ShiftMask;
+    if (nsflags & NSControlKeyMask)
+       xflags |= ControlMask;
+    if (nsflags & NSAlternateKeyMask)
+       xflags |= Mod1Mask;
+    if (nsflags & NSCommandKeyMask)
+       xflags |= Mod2Mask;
+    /* FIXME: secondaryfn? */
+
+    return xflags;
+}
+
+static void
+send_nsevent (NSEventType type, NSEvent *e)
+{
+    static unsigned int button_state = 0;
+
+    xEvent xe;
+
+    memset (&xe, 0, sizeof (xe));
+
+    switch (type)
+    {
+       NSRect screen;
+       NSPoint location;
+       NSWindow *window;
+       int pointer_x, pointer_y, count;
+
+    case NSLeftMouseDown:
+       xe.u.u.type = ButtonPress;
+       xe.u.u.detail = 1;
+       goto do_press_event;
+
+    case NSRightMouseDown:
+       xe.u.u.type = ButtonPress;
+       xe.u.u.detail = 3;
+       goto do_press_event;
+
+    case NSOtherMouseDown:
+       xe.u.u.type = ButtonPress;
+       xe.u.u.detail = 2; /* FIXME? */
+       goto do_press_event;
+
+    do_press_event:
+       if (RootlessKnowsWindowNumber ([e windowNumber]) == NULL)
+       {
+           /* X server doesn't grok this window, drop the event.
+
+              Note: theoretically this isn't necessary, but if I click
+              on the menubar, we get sent a LeftMouseDown when the
+              release happens, but no LeftMouseUp is ever seen! */
+
+           break;
+       }
+       goto do_event;
+
+    case NSLeftMouseUp:
+       xe.u.u.type = ButtonRelease;
+       xe.u.u.detail = 1;
+       goto do_release_event;
+
+    case NSRightMouseUp:
+       xe.u.u.type = ButtonRelease;
+       xe.u.u.detail = 3;
+       goto do_release_event;
+
+    case NSOtherMouseUp:
+       xe.u.u.type = ButtonRelease;
+       xe.u.u.detail = 2; /* FIXME? */
+       goto do_release_event;
+
+    do_release_event:
+       if ((button_state & (1 << xe.u.u.detail)) == 0)
+       {
+           /* X didn't see the button press for this release, so skip it */
+           break;
+       }
+       goto do_event;
+
+    case NSMouseMoved:
+    case NSLeftMouseDragged:
+    case NSRightMouseDragged:
+    case NSOtherMouseDragged:
+       /* convert location to global top-left coordinates */
+
+       location = [e locationInWindow];
+       window = [e window];
+       screen = [[[NSScreen screens] objectAtIndex:0] frame];
+
+       if (window != nil)
+       {
+           NSRect frame = [window frame];
+           pointer_x = location.x + frame.origin.x;
+           pointer_y = (((screen.origin.y + screen.size.height)
+                         - location.y) - frame.origin.y);
+       }
+       else
+       {
+           pointer_x = location.x;
+           pointer_y = (screen.origin.y + screen.size.height) - location.y;
+       }
+
+       xe.u.keyButtonPointer.rootX = pointer_x;
+       xe.u.keyButtonPointer.rootY = pointer_y;
+       xe.u.u.type = MotionNotify;
+       goto do_event;
+
+    case NSKeyDown:
+       xe.u.u.type = KeyPress;
+       xe.u.u.detail = [e keyCode];
+       goto do_event;
+
+    case NSKeyUp:
+       xe.u.u.type = KeyRelease;
+       xe.u.u.detail = [e keyCode];
+       goto do_event;
+
+    case NSScrollWheel:
+       xe.u.keyButtonPointer.state = convert_flags ([e modifierFlags]);
+       count = [e deltaY];
+       xe.u.u.detail = count > 0 ? 4 : 5;
+       for (count = abs (count); count-- > 0;)
+       {
+           xe.u.u.type = ButtonPress;
+           DarwinEnqueueEvent (&xe);
+           xe.u.u.type = ButtonRelease;
+           DarwinEnqueueEvent (&xe);
+       }
+       xe.u.u.type = 0;
+       break;
+
+    case NSFlagsChanged:
+    do_event:
+       xe.u.keyButtonPointer.state = convert_flags ([e modifierFlags]);
+       DarwinEnqueueEvent (&xe);
+       break;
+
+    default: break; /* for gcc */
+    }
+
+    if (xe.u.u.type == ButtonPress)
+       button_state |= (1 << xe.u.u.detail);
+    else if (xe.u.u.type == ButtonRelease)
+       button_state &= ~(1 << xe.u.u.detail);
+}