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
55int X11EnableKeyEquivalents = TRUE;
56
57X11Application *X11App;
58
59#define ALL_KEY_MASKS (NSShiftKeyMask | NSControlKeyMask \
60		       | NSAlternateKeyMask | NSCommandKeyMask)
61
62@implementation X11Application
63
64typedef struct message_struct message;
65struct message_struct {
66    mach_msg_header_t hdr;
67    SEL selector;
68    NSObject *arg;
69};
70
71static mach_port_t _port;
72
73static void send_nsevent (NSEventType type, NSEvent *e);
74
75/* avoid header conflict hell */
76extern int RootlessKnowsWindowNumber (int number);
77extern void DarwinEnqueueEvent (const xEvent *e);
78
79static void
80init_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
97static void
98message_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
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
393static const void *cfretain (CFAllocatorRef a, const void *b) {
394    return CFRetain (b);
395}
396static void cfrelease (CFAllocatorRef a, const void *b) {
397    CFRelease (b);
398}
399static CFMutableArrayRef
400nsarray_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}
431static NSMutableArray *
432cfarray_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
724static NSArray *
725array_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
761void
762X11ApplicationSetWindowMenu (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
775void
776X11ApplicationSetWindowMenuCheck (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
787void
788X11ApplicationSetFrontProcess (void)
789{
790    message_kit_thread (@selector (set_front_process:), nil);
791}
792
793void
794X11ApplicationSetCanQuit (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
805void
806X11ApplicationServerReady (void)
807{
808    message_kit_thread (@selector (server_ready:), nil);
809}
810
811void
812X11ApplicationShowHideMenubar (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
823static void *
824create_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
841static void
842check_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\
862Windows displayed by X11 applications may not have titlebars, or may look \
863different to windows displayed by native applications.\n\n\
864Would you like to move aside the existing file and use the standard X11 \
865environment?", @"");
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
882done:
883    [X11App prefs_set_boolean:@PREFS_DONE_XINIT_CHECK value:YES];
884    [X11App prefs_synchronize];
885}
886
887void
888X11ApplicationMain (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
926/* event conversion */
927
928static inline unsigned short
929convert_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
953static void
954send_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}
1086