Separated several classes into header files from tool main.cpp
[synfig.git] / synfig-osx / launcher / X11Application.m
1 /* X11Application.m -- subclass of NSApplication to multiplex events
2    $Id: X11Application.m,v 1.53 2003/09/13 02:00:46 jharper Exp $
3
4    Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
5
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:
13
14    The above copyright notice and this permission notice shall be
15    included in all copies or substantial portions of the Software.
16
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.
25
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. */
30
31 #import "X11Application.h"
32 #include <Carbon/Carbon.h>
33
34 /* ouch! */
35 #define BOOL X_BOOL
36 # include "Xproto.h"
37 #define WindowPtr X_WindowPtr
38 #define Cursor X_Cursor
39 # include "quartz.h"
40 # define _APPLEWM_SERVER_
41 # include "applewm.h"
42 # include "X.h"
43 #undef Cursor
44 #undef WindowPtr
45 #undef BOOL
46
47 #include "xf86Version.h"
48
49 #include <mach/mach.h>
50 #include <unistd.h>
51 #include <pthread.h>
52
53 #define DEFAULTS_FILE "/etc/X11/xserver/Xquartz.plist"
54
55 int X11EnableKeyEquivalents = TRUE;
56
57 X11Application *X11App;
58
59 #define ALL_KEY_MASKS (NSShiftKeyMask | NSControlKeyMask \
60                        | NSAlternateKeyMask | NSCommandKeyMask)
61
62 @implementation X11Application
63
64 typedef struct message_struct message;
65 struct message_struct {
66     mach_msg_header_t hdr;
67     SEL selector;
68     NSObject *arg;
69 };
70
71 static mach_port_t _port;
72
73 static void send_nsevent (NSEventType type, NSEvent *e);
74
75 /* avoid header conflict hell */
76 extern int RootlessKnowsWindowNumber (int number);
77 extern void DarwinEnqueueEvent (const xEvent *e);
78
79 static void
80 init_ports (void)
81 {
82     kern_return_t r;
83     NSPort *p;
84
85     if (_port != MACH_PORT_NULL)
86         return;
87
88     r = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &_port);
89     if (r != KERN_SUCCESS)
90         return;
91
92     p = [NSMachPort portWithMachPort:_port];
93     [p setDelegate:NSApp];
94     [p scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
95 }
96
97 static void
98 message_kit_thread (SEL selector, NSObject *arg)
99 {
100     message msg;
101     kern_return_t r;
102
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;
108     msg.hdr.msgh_id = 0;
109
110     msg.selector = selector;
111     msg.arg = [arg retain];
112
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);
117 }
118
119 - (void) handleMachMessage:(void *)_msg
120 {
121     message *msg = _msg;
122
123     [self performSelector:msg->selector withObject:msg->arg];
124     [msg->arg release];
125 }
126
127 - (void) set_controller:obj
128 {
129     if (_controller == nil)
130         _controller = [obj retain];
131 }
132
133 - (void) dealloc
134 {
135     if (_controller != nil)
136         [_controller release];
137
138     if (_port != MACH_PORT_NULL)
139         mach_port_deallocate (mach_task_self (), _port);
140
141     [super dealloc];
142 }
143
144 - (void) orderFrontStandardAboutPanel: (id) sender
145 {
146     NSMutableDictionary *dict;
147     NSDictionary *infoDict;
148     NSString *tem;
149
150     dict = [NSMutableDictionary dictionaryWithCapacity:2];
151     infoDict = [[NSBundle mainBundle] infoDictionary];
152
153     [dict setObject: NSLocalizedString (@"The X Window System", @"About panel")
154      forKey:@"ApplicationName"];
155
156     tem = [infoDict objectForKey:@"CFBundleShortVersionString"];
157
158     [dict setObject:[NSString stringWithFormat:@"X11 %@ - XFree86 %d.%d.%d",
159                      tem, XF86_VERSION_MAJOR, XF86_VERSION_MINOR,
160                      XF86_VERSION_PATCH] forKey:@"ApplicationVersion"];
161
162     [self orderFrontStandardAboutPanelWithOptions: dict];
163 }
164
165 - (void) activateX:(BOOL)state
166 {
167     /* Create a TSM document that supports full Unicode input, and
168        have it activated while X is active (unless using the old
169        keymapping files) */
170     static TSMDocumentID x11_document;
171
172     if (state)
173     {
174         QuartzMessageMainThread (kXquartzActivate, 0);
175
176         if (!_x_active)
177         {
178             if (x11_document == 0 && darwinKeymapFile == NULL)
179             {
180                 OSType types[1];
181                 types[0] = kUnicodeDocument;
182                 NewTSMDocument (1, types, &x11_document, 0);
183             }
184
185             if (x11_document != 0)
186                 ActivateTSMDocument (x11_document);
187         }
188     }
189     else
190     {
191         QuartzMessageMainThread (kXquartzDeactivate, 0);
192
193         if (_x_active)
194         {
195             if (x11_document != 0)
196                 DeactivateTSMDocument (x11_document);
197         }
198     }
199
200     _x_active = state;
201 }
202
203 - (void) became_key:(NSWindow *)win
204 {
205     [self activateX:NO];
206 }
207
208 - (void) sendEvent:(NSEvent *)e
209 {
210     NSEventType type;
211     BOOL for_appkit, for_x;
212
213     type = [e type];
214
215     /* By default pass down the responder chain and to X. */
216     for_appkit = YES;
217     for_x = YES;
218
219     switch (type)
220     {
221     case NSLeftMouseDown: case NSRightMouseDown: case NSOtherMouseDown:
222     case NSLeftMouseUp: case NSRightMouseUp: case NSOtherMouseUp:
223         if ([e window] != nil)
224         {
225             /* Pointer event has a window. Probably something for the kit. */
226
227             for_x = NO;
228
229             if (_x_active)
230                 [self activateX:NO];
231         }
232         else if ([self modalWindow] == nil)
233         {
234             /* Must be an X window. Tell appkit it doesn't have focus. */
235
236             for_appkit = NO;
237
238             if ([self isActive])
239             {
240                 [self deactivate];
241
242                 if (!_x_active && RootlessKnowsWindowNumber ([e windowNumber]))
243                     [self activateX:YES];
244             }
245         }
246         break;
247
248     case NSKeyDown: case NSKeyUp:
249         if (_x_active)
250         {
251             static int swallow_up;
252
253             /* No kit window is focused, so send it to X. */
254
255             for_appkit = NO;
256
257             if (type == NSKeyDown)
258             {
259                 /* Before that though, see if there are any global
260                    shortcuts bound to it. */
261
262                 if (X11EnableKeyEquivalents
263                     && [[self mainMenu] performKeyEquivalent:e])
264                 {
265                     swallow_up = [e keyCode];
266                     for_x = NO;
267                 }
268                 else if (!quartzEnableRootless
269                          && ([e modifierFlags] & ALL_KEY_MASKS)
270                             == (NSCommandKeyMask | NSAlternateKeyMask)
271                          && ([e keyCode] == 0 /*a*/
272                              || [e keyCode] == 53 /*Esc*/))
273                 {
274                     swallow_up = 0;
275                     for_x = NO;
276                     QuartzMessageMainThread (kXquartzToggleFullscreen, 0);
277                 }
278             }
279             else
280             {
281                 /* If we saw a key equivalent on the down, don't pass
282                    the up through to X. */
283
284                 if (swallow_up != 0 && [e keyCode] == swallow_up)
285                 {
286                     swallow_up = 0;
287                     for_x = NO;
288                 }
289             }
290         }
291         else
292         {
293             for_x = NO;
294         }
295         break;
296
297     case NSFlagsChanged:
298         /* For the l33t X users who remap modifier keys to normal keysyms. */
299         if (!_x_active)
300             for_x = NO;
301         break;
302
303     case NSAppKitDefined:
304         switch ([e subtype])
305         {
306         case NSApplicationActivatedEventType:
307             for_x = NO;
308             if ([self modalWindow] == nil)
309             {
310                 for_appkit = NO;
311             
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;
315
316                 [self activateX:YES];
317             }
318             break;
319
320         case 18: /* ApplicationDidReactivate */
321             if (quartzHasRoot)
322                 for_appkit = NO;
323             break;
324
325         case NSApplicationDeactivatedEventType:
326             for_x = NO;
327             [self activateX:NO];
328             break;
329         }
330         break;
331
332     default: break; /* for gcc */
333     }
334
335     if (for_appkit)
336     {
337         [super sendEvent:e];
338     }
339
340     if (for_x)
341     {
342         send_nsevent (type, e);
343     }
344 }
345
346 - (void) set_window_menu:(NSArray *)list
347 {
348     [_controller set_window_menu:list];
349 }
350
351 - (void) set_window_menu_check:(NSNumber *)n
352 {
353     [_controller set_window_menu_check:n];
354 }
355
356 - (void) set_apps_menu:(NSArray *)list
357 {
358     [_controller set_apps_menu:list];
359 }
360
361 - (void) set_front_process:unused
362 {
363     [NSApp activateIgnoringOtherApps:YES];
364
365     if ([self modalWindow] == nil)
366         [self activateX:YES];
367 }
368
369 - (void) set_can_quit:(NSNumber *)state
370 {
371     [_controller set_can_quit:[state boolValue]];
372 }
373
374 - (void) server_ready:unused
375 {
376     [_controller server_ready];
377 }
378
379 - (void) show_hide_menubar:(NSNumber *)state
380 {
381     if ([state boolValue])
382         ShowMenuBar ();
383     else
384         HideMenuBar ();
385 }
386
387 \f
388 /* user preferences */
389
390 /* Note that these functions only work for arrays whose elements
391    can be toll-free-bridged between NS and CF worlds. */
392
393 static const void *cfretain (CFAllocatorRef a, const void *b) {
394     return CFRetain (b);
395 }
396 static void cfrelease (CFAllocatorRef a, const void *b) {
397     CFRelease (b);
398 }
399 static CFMutableArrayRef
400 nsarray_to_cfarray (NSArray *in)
401 {
402     CFMutableArrayRef out;
403     CFArrayCallBacks cb;
404     NSObject *ns;
405     const CFTypeRef *cf;
406     int i, count;
407
408     memset (&cb, 0, sizeof (cb));
409     cb.version = 0;
410     cb.retain = cfretain;
411     cb.release = cfrelease;
412
413     count = [in count];
414     out = CFArrayCreateMutable (NULL, count, &cb);
415
416     for (i = 0; i < count; i++)
417     {
418         ns = [in objectAtIndex:i];
419
420         if ([ns isKindOfClass:[NSArray class]])
421             cf = (CFTypeRef) nsarray_to_cfarray ((NSArray *) ns);
422         else
423             cf = CFRetain ((CFTypeRef) ns);
424
425         CFArrayAppendValue (out, cf);
426         CFRelease (cf);
427     }
428
429     return out;
430 }
431 static NSMutableArray *
432 cfarray_to_nsarray (CFArrayRef in)
433 {
434     NSMutableArray *out;
435     const CFTypeRef *cf;
436     NSObject *ns;
437     int i, count;
438
439     count = CFArrayGetCount (in);
440     out = [[NSMutableArray alloc] initWithCapacity:count];
441
442     for (i = 0; i < count; i++)
443     {
444         cf = CFArrayGetValueAtIndex (in, i);
445
446         if (CFGetTypeID (cf) == CFArrayGetTypeID ())
447             ns = cfarray_to_nsarray ((CFArrayRef) cf);
448         else
449             ns = [(id)cf retain];
450
451         [out addObject:ns];
452         [ns release];
453     }
454
455     return out;
456 }
457
458 - (CFPropertyListRef) prefs_get:(NSString *)key
459 {
460     CFPropertyListRef value;
461
462     value = CFPreferencesCopyAppValue ((CFStringRef) key, CFSTR (APP_PREFS));
463
464     if (value == NULL)
465     {
466         static CFDictionaryRef defaults;
467
468         if (defaults == NULL)
469         {
470             CFStringRef error = NULL;
471             CFDataRef data;
472             CFURLRef url;
473             SInt32 error_code;
474
475             url = (CFURLCreateFromFileSystemRepresentation
476                    (NULL, DEFAULTS_FILE, strlen (DEFAULTS_FILE), false));
477             if (CFURLCreateDataAndPropertiesFromResource (NULL, url, &data,
478                                                           NULL, NULL,
479                                                           &error_code))
480             {
481                 defaults = (CFPropertyListCreateFromXMLData
482                             (NULL, data, kCFPropertyListImmutable, &error));
483                 if (error != NULL)
484                     CFRelease (error);
485                 CFRelease (data);
486             }
487             CFRelease (url);
488         }
489
490         if (defaults != NULL)
491             value = CFDictionaryGetValue (defaults, key);
492
493         if (value != NULL)
494             CFRetain (value);
495     }
496
497     return value;
498 }
499
500 - (int) prefs_get_integer:(NSString *)key default:(int)def
501 {
502     CFPropertyListRef value;
503     int ret;
504
505     value = [self prefs_get:key];
506
507     if (value != NULL && CFGetTypeID (value) == CFNumberGetTypeID ())
508         CFNumberGetValue (value, kCFNumberIntType, &ret);
509     else if (value != NULL && CFGetTypeID (value) == CFStringGetTypeID ())
510         ret = CFStringGetIntValue (value);
511     else
512         ret = def;
513
514     if (value != NULL)
515         CFRelease (value);
516
517     return ret;
518 }
519
520 - (const char *) prefs_get_string:(NSString *)key default:(const char *)def
521 {
522     CFPropertyListRef value;
523     const char *ret = NULL;
524
525     value = [self prefs_get:key];
526
527     if (value != NULL && CFGetTypeID (value) == CFStringGetTypeID ())
528     {
529         NSString *s = (NSString *) value;
530
531         ret = [s UTF8String];
532     }
533
534     if (value != NULL)
535         CFRelease (value);
536
537     return ret != NULL ? ret : def;
538 }
539
540 - (float) prefs_get_float:(NSString *)key default:(float)def
541 {
542     CFPropertyListRef value;
543     float ret = def;
544
545     value = [self prefs_get:key];
546
547     if (value != NULL
548         && CFGetTypeID (value) == CFNumberGetTypeID ()
549         && CFNumberIsFloatType (value))
550     {
551         CFNumberGetValue (value, kCFNumberFloatType, &ret);
552     }
553     else if (value != NULL && CFGetTypeID (value) == CFStringGetTypeID ())
554     {
555         ret = CFStringGetDoubleValue (value);
556     }
557
558     if (value != NULL)
559         CFRelease (value);
560
561     return ret;
562 }
563
564 - (int) prefs_get_boolean:(NSString *)key default:(int)def
565 {
566     CFPropertyListRef value;
567     int ret = def;
568
569     value = [self prefs_get:key];
570
571     if (value != NULL)
572     {
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 ())
578         {
579             const char *tem = [(NSString *) value lossyCString];
580             if (strcasecmp (tem, "true") == 0 || strcasecmp (tem, "yes") == 0)
581                 ret = YES;
582             else
583                 ret = NO;
584         }
585
586         CFRelease (value);
587     }
588
589     return ret;
590 }
591
592 - (NSArray *) prefs_get_array:(NSString *)key
593 {
594     NSArray *ret = nil;
595     CFPropertyListRef value;
596
597     value = [self prefs_get:key];
598
599     if (value != NULL)
600     {
601         if (CFGetTypeID (value) == CFArrayGetTypeID ())
602             ret = [cfarray_to_nsarray (value) autorelease];
603
604         CFRelease (value);
605     }
606
607     return ret;
608 }
609
610 - (void) prefs_set_integer:(NSString *)key value:(int)value
611 {
612     CFNumberRef x;
613
614     x = CFNumberCreate (NULL, kCFNumberIntType, &value);
615
616     CFPreferencesSetValue ((CFStringRef) key, (CFTypeRef) x, CFSTR (APP_PREFS),
617                            kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
618
619     CFRelease (x);
620 }
621
622 - (void) prefs_set_float:(NSString *)key value:(float)value
623 {
624     CFNumberRef x;
625
626     x = CFNumberCreate (NULL, kCFNumberFloatType, &value);
627
628     CFPreferencesSetValue ((CFStringRef) key, (CFTypeRef) x, CFSTR (APP_PREFS),
629                            kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
630
631     CFRelease (x);
632 }
633
634 - (void) prefs_set_boolean:(NSString *)key value:(int)value
635 {
636     CFPreferencesSetValue ((CFStringRef) key,
637                            (CFTypeRef) value ? kCFBooleanTrue
638                            : kCFBooleanFalse, CFSTR (APP_PREFS),
639                            kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
640
641 }
642
643 - (void) prefs_set_array:(NSString *)key value:(NSArray *)value
644 {
645     CFArrayRef cfarray;
646
647     cfarray = nsarray_to_cfarray (value);
648     CFPreferencesSetValue ((CFStringRef) key,
649                            (CFTypeRef) cfarray,
650                            CFSTR (APP_PREFS),
651                            kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
652     CFRelease (cfarray);
653 }
654
655 - (void) prefs_set_string:(NSString *)key value:(NSString *)value
656 {
657     CFPreferencesSetValue ((CFStringRef) key, (CFTypeRef) value,
658                            CFSTR (APP_PREFS), kCFPreferencesCurrentUser,
659                            kCFPreferencesAnyHost);
660 }
661
662 - (void) prefs_synchronize
663 {
664     CFPreferencesAppSynchronize (kCFPreferencesCurrentApplication);
665 }
666
667 - (void) read_defaults
668 {
669     extern int darwinFakeButtons;
670     const char *tem;
671
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];
681
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)
687     {
688         const char *fake2, *fake3;
689
690         fake2 = [self prefs_get_string:@PREFS_FAKE_BUTTON2 default:NULL];
691         fake3 = [self prefs_get_string:@PREFS_FAKE_BUTTON3 default:NULL];
692
693         DarwinSetFakeButtons (fake2, fake3);
694     }
695
696     X11EnableKeyEquivalents = [self prefs_get_boolean:@PREFS_KEYEQUIVS
697                                default:X11EnableKeyEquivalents];
698
699     darwinSyncKeymap = [self prefs_get_boolean:@PREFS_SYNC_KEYMAP
700                         default:darwinSyncKeymap];
701
702     tem = [self prefs_get_string:@PREFS_KEYMAP_FILE default:NULL];
703     if (tem != NULL)
704         darwinKeymapFile = strdup (tem);
705
706     quartzDesiredDepth = [self prefs_get_integer:@PREFS_DEPTH
707                           default:quartzDesiredDepth];
708 }
709
710 /* This will end up at the end of the responder chain. */
711 - (void) copy:sender
712 {
713     QuartzMessageMainThread (kXquartzPasteboardNotify, 1,
714                              AppleWMCopyToPasteboard);
715 }
716
717 - (BOOL) x_active
718 {
719     return _x_active;
720 }
721
722 @end
723
724 static NSArray *
725 array_with_strings_and_numbers (int nitems, const char **items,
726                                 const char *numbers)
727 {
728     NSMutableArray *array, *subarray;
729     NSString *string;
730     NSString *number;
731     int i;
732
733     /* (Can't autorelease on the X server thread) */
734
735     array = [[NSMutableArray alloc] initWithCapacity:nitems];
736
737     for (i = 0; i < nitems; i++)
738     {
739         subarray = [[NSMutableArray alloc] initWithCapacity:2];
740
741         string = [[NSString alloc] initWithUTF8String:items[i]];
742         [subarray addObject:string];
743         [string release];
744
745         if (numbers[i] != 0)
746         {
747             number = [[NSString alloc] initWithFormat:@"%d", numbers[i]];
748             [subarray addObject:number];
749             [number release];
750         }
751         else
752             [subarray addObject:@""];
753
754         [array addObject:subarray];
755         [subarray release];
756     }
757
758     return array;
759 }
760
761 void
762 X11ApplicationSetWindowMenu (int nitems, const char **items,
763                              const char *shortcuts)
764 {
765     NSArray *array;
766
767     array = array_with_strings_and_numbers (nitems, items, shortcuts);
768
769     /* Send the array of strings over to the appkit thread */
770
771     message_kit_thread (@selector (set_window_menu:), array);
772     [array release];
773 }
774
775 void
776 X11ApplicationSetWindowMenuCheck (int idx)
777 {
778     NSNumber *n;
779
780     n = [[NSNumber alloc] initWithInt:idx];
781
782     message_kit_thread (@selector (set_window_menu_check:), n);
783
784     [n release];
785 }
786
787 void
788 X11ApplicationSetFrontProcess (void)
789 {
790     message_kit_thread (@selector (set_front_process:), nil);
791 }
792
793 void
794 X11ApplicationSetCanQuit (int state)
795 {
796     NSNumber *n;
797
798     n = [[NSNumber alloc] initWithBool:state];
799
800     message_kit_thread (@selector (set_can_quit:), n);
801
802     [n release];
803 }
804
805 void
806 X11ApplicationServerReady (void)
807 {
808     message_kit_thread (@selector (server_ready:), nil);
809 }
810
811 void
812 X11ApplicationShowHideMenubar (int state)
813 {
814     NSNumber *n;
815
816     n = [[NSNumber alloc] initWithBool:state];
817
818     message_kit_thread (@selector (show_hide_menubar:), n);
819
820     [n release];
821 }
822
823 static void *
824 create_thread (void *func, void *arg)
825 {
826     pthread_attr_t attr;
827     pthread_t tid;
828
829     pthread_attr_init (&attr);
830
831     pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
832     pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
833
834     pthread_create (&tid, &attr, func, arg);
835
836     pthread_attr_destroy (&attr);
837
838     return (void *) tid;
839 }
840
841 static void
842 check_xinitrc (void)
843 {
844     char *tem, buf[1024];
845     NSString *msg;
846
847     if ([X11App prefs_get_boolean:@PREFS_DONE_XINIT_CHECK default:NO])
848         return;
849
850     tem = getenv ("HOME");
851     if (tem == NULL)
852         goto done;
853
854     snprintf (buf, sizeof (buf), "%s/.xinitrc", tem);
855     if (access (buf, F_OK) != 0)
856         goto done;
857
858     /* FIXME: put localized strings into Resources/English.lproj */
859
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 \
865 environment?", @"");
866
867     if (NSRunAlertPanel (nil, msg, NSLocalizedString (@"Yes", @""),
868                          NSLocalizedString (@"No", @""), nil)
869         == NSAlertDefaultReturn)
870     {
871         char buf2[1024];
872         int i = -1;
873
874         snprintf (buf2, sizeof (buf2), "%s.old", buf);
875
876         for (i = 1; access (buf2, F_OK) == 0; i++)
877              snprintf (buf2, sizeof (buf2), "%s.old.%d", buf, i);
878
879         rename (buf, buf2);
880     }
881     
882 done:
883     [X11App prefs_set_boolean:@PREFS_DONE_XINIT_CHECK value:YES];
884     [X11App prefs_synchronize];
885 }
886
887 void
888 X11ApplicationMain (int argc, const char *argv[],
889                     void (*server_thread) (void *), void *server_arg)
890 {
891     NSAutoreleasePool *pool;
892
893 #ifdef DEBUG
894     while (access ("/tmp/x11-block", F_OK) == 0)
895         sleep (1);
896 #endif
897
898     pool = [[NSAutoreleasePool alloc] init];
899
900     X11App = (X11Application *) [X11Application sharedApplication];
901
902     init_ports ();
903
904     [NSApp read_defaults];
905
906     [NSBundle loadNibNamed:@"main" owner:NSApp];
907
908     [[NSNotificationCenter defaultCenter] addObserver:NSApp
909      selector:@selector (became_key:)
910      name:NSWindowDidBecomeKeyNotification object:nil];
911
912     check_xinitrc ();
913
914     if (!create_thread (server_thread, server_arg))
915     {
916         fprintf (stderr, "can't create secondary thread\n");
917         exit (1);
918     }
919
920     [NSApp run];
921
922     /* not reached */
923 }
924
925 \f
926 /* event conversion */
927
928 static inline unsigned short
929 convert_flags (unsigned int nsflags)
930 {
931     unsigned int xflags;
932
933     if (nsflags == ~0)
934         return 0xffff;
935
936     xflags = 0;
937
938     if (nsflags & NSAlphaShiftKeyMask)
939         xflags |= LockMask;
940     if (nsflags & NSShiftKeyMask)
941         xflags |= ShiftMask;
942     if (nsflags & NSControlKeyMask)
943         xflags |= ControlMask;
944     if (nsflags & NSAlternateKeyMask)
945         xflags |= Mod1Mask;
946     if (nsflags & NSCommandKeyMask)
947         xflags |= Mod2Mask;
948     /* FIXME: secondaryfn? */
949
950     return xflags;
951 }
952
953 static void
954 send_nsevent (NSEventType type, NSEvent *e)
955 {
956     static unsigned int button_state = 0;
957
958     xEvent xe;
959
960     memset (&xe, 0, sizeof (xe));
961
962     switch (type)
963     {
964         NSRect screen;
965         NSPoint location;
966         NSWindow *window;
967         int pointer_x, pointer_y, count;
968
969     case NSLeftMouseDown:
970         xe.u.u.type = ButtonPress;
971         xe.u.u.detail = 1;
972         goto do_press_event;
973
974     case NSRightMouseDown:
975         xe.u.u.type = ButtonPress;
976         xe.u.u.detail = 3;
977         goto do_press_event;
978
979     case NSOtherMouseDown:
980         xe.u.u.type = ButtonPress;
981         xe.u.u.detail = 2; /* FIXME? */
982         goto do_press_event;
983
984     do_press_event:
985         if (RootlessKnowsWindowNumber ([e windowNumber]) == NULL)
986         {
987             /* X server doesn't grok this window, drop the event.
988
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! */
992
993             break;
994         }
995         goto do_event;
996
997     case NSLeftMouseUp:
998         xe.u.u.type = ButtonRelease;
999         xe.u.u.detail = 1;
1000         goto do_release_event;
1001
1002     case NSRightMouseUp:
1003         xe.u.u.type = ButtonRelease;
1004         xe.u.u.detail = 3;
1005         goto do_release_event;
1006
1007     case NSOtherMouseUp:
1008         xe.u.u.type = ButtonRelease;
1009         xe.u.u.detail = 2; /* FIXME? */
1010         goto do_release_event;
1011
1012     do_release_event:
1013         if ((button_state & (1 << xe.u.u.detail)) == 0)
1014         {
1015             /* X didn't see the button press for this release, so skip it */
1016             break;
1017         }
1018         goto do_event;
1019
1020     case NSMouseMoved:
1021     case NSLeftMouseDragged:
1022     case NSRightMouseDragged:
1023     case NSOtherMouseDragged:
1024         /* convert location to global top-left coordinates */
1025
1026         location = [e locationInWindow];
1027         window = [e window];
1028         screen = [[[NSScreen screens] objectAtIndex:0] frame];
1029
1030         if (window != nil)
1031         {
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);
1036         }
1037         else
1038         {
1039             pointer_x = location.x;
1040             pointer_y = (screen.origin.y + screen.size.height) - location.y;
1041         }
1042
1043         xe.u.keyButtonPointer.rootX = pointer_x;
1044         xe.u.keyButtonPointer.rootY = pointer_y;
1045         xe.u.u.type = MotionNotify;
1046         goto do_event;
1047
1048     case NSKeyDown:
1049         xe.u.u.type = KeyPress;
1050         xe.u.u.detail = [e keyCode];
1051         goto do_event;
1052
1053     case NSKeyUp:
1054         xe.u.u.type = KeyRelease;
1055         xe.u.u.detail = [e keyCode];
1056         goto do_event;
1057
1058     case NSScrollWheel:
1059         xe.u.keyButtonPointer.state = convert_flags ([e modifierFlags]);
1060         count = [e deltaY];
1061         xe.u.u.detail = count > 0 ? 4 : 5;
1062         for (count = abs (count); count-- > 0;)
1063         {
1064             xe.u.u.type = ButtonPress;
1065             DarwinEnqueueEvent (&xe);
1066             xe.u.u.type = ButtonRelease;
1067             DarwinEnqueueEvent (&xe);
1068         }
1069         xe.u.u.type = 0;
1070         break;
1071
1072     case NSFlagsChanged:
1073     do_event:
1074         xe.u.keyButtonPointer.state = convert_flags ([e modifierFlags]);
1075         DarwinEnqueueEvent (&xe);
1076         break;
1077
1078     default: break; /* for gcc */
1079     }
1080
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);
1085 }