X11Controller.m revision 35c4bbdf
1/* X11Controller.m -- connect the IB ui, also the NSApp delegate
2 *
3 * Copyright (c) 2002-2012 Apple Inc. All rights reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person
6 * obtaining a copy of this software and associated documentation files
7 * (the "Software"), to deal in the Software without restriction,
8 * including without limitation the rights to use, copy, modify, merge,
9 * publish, distribute, sublicense, and/or sell copies of the Software,
10 * and to permit persons to whom the Software is furnished to do so,
11 * subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT.  IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT
20 * HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
24 *
25 * Except as contained in this notice, the name(s) of the above
26 * copyright holders shall not be used in advertising or otherwise to
27 * promote the sale, use or other dealings in this Software without
28 * prior written authorization.
29 */
30
31#include "sanitizedCarbon.h"
32#include <AvailabilityMacros.h>
33
34#ifdef HAVE_DIX_CONFIG_H
35#include <dix-config.h>
36#endif
37
38#include "quartzCommon.h"
39
40#import "X11Controller.h"
41#import "X11Application.h"
42
43#include "opaque.h"
44#include "darwin.h"
45#include "darwinEvents.h"
46#include "quartz.h"
47#include "quartzKeyboard.h"
48#include <X11/extensions/applewmconst.h>
49#include "applewmExt.h"
50
51#include <stdio.h>
52#include <unistd.h>
53#include <fcntl.h>
54#include <sys/types.h>
55#include <sys/wait.h>
56#include <asl.h>
57#include <stdlib.h>
58
59extern aslclient aslc;
60extern char *bundle_id_prefix;
61
62@implementation X11Controller
63
64- (void) awakeFromNib
65{
66    X11Application *xapp = NSApp;
67    NSArray *array;
68
69    /* Point X11Application at ourself. */
70    [xapp set_controller:self];
71
72    array = [xapp prefs_get_array:@PREFS_APPSMENU];
73    if (array != nil) {
74        int count;
75
76        /* convert from [TITLE1 COMMAND1 TITLE2 COMMAND2 ...]
77           to [[TITLE1 COMMAND1] [TITLE2 COMMAND2] ...] format. */
78
79        count = [array count];
80        if (count > 0
81            && ![[array objectAtIndex:0] isKindOfClass:[NSArray class]]) {
82            int i;
83            NSMutableArray *copy, *sub;
84
85            copy = [NSMutableArray arrayWithCapacity:(count / 2)];
86
87            for (i = 0; i < count / 2; i++) {
88                sub = [[NSMutableArray alloc] initWithCapacity:3];
89                [sub addObject:[array objectAtIndex:i * 2]];
90                [sub addObject:[array objectAtIndex:i * 2 + 1]];
91                [sub addObject:@""];
92                [copy addObject:sub];
93                [sub release];
94            }
95
96            array = copy;
97        }
98
99        [self set_apps_menu:array];
100    }
101
102    [[NSNotificationCenter defaultCenter]
103     addObserver: self
104        selector: @selector(apps_table_done:)
105            name: NSWindowWillCloseNotification
106          object: [apps_table window]];
107
108    // Setup data about our Windows menu
109    if (window_separator) {
110        [[window_separator menu] removeItem:window_separator];
111        window_separator = nil;
112    }
113
114    windows_menu_start = [[X11App windowsMenu] numberOfItems];
115}
116
117- (void) item_selected:sender
118{
119    [NSApp activateIgnoringOtherApps:YES];
120
121    DarwinSendDDXEvent(kXquartzControllerNotify, 2,
122                       AppleWMWindowMenuItem, [sender tag]);
123}
124
125- (void) remove_window_menu
126{
127    NSMenu *menu;
128    int count, i;
129
130    /* Work backwards so we don't mess up the indices */
131    menu = [X11App windowsMenu];
132    count = [menu numberOfItems];
133    for (i = count - 1; i >= windows_menu_start; i--)
134        [menu removeItemAtIndex:i];
135
136    count = [dock_menu indexOfItem:dock_window_separator];
137    for (i = 0; i < count; i++)
138        [dock_menu removeItemAtIndex:0];
139}
140
141- (void) install_window_menu:(NSArray *)list
142{
143    NSMenu *menu;
144    NSMenuItem *item;
145    int first, count, i;
146
147    menu = [X11App windowsMenu];
148    first = windows_menu_start + 1;
149    count = [list count];
150
151    // Push a Separator
152    if (count) {
153        [menu addItem:[NSMenuItem separatorItem]];
154    }
155
156    for (i = 0; i < count; i++) {
157        NSString *name, *shortcut;
158
159        name = [[list objectAtIndex:i] objectAtIndex:0];
160        shortcut = [[list objectAtIndex:i] objectAtIndex:1];
161
162        if (windowItemModMask == 0 || windowItemModMask == -1)
163            shortcut = @"";
164
165        item =
166            (NSMenuItem *)[menu addItemWithTitle:name action:
167                           @selector
168                           (item_selected:) keyEquivalent:shortcut];
169        [item setKeyEquivalentModifierMask:(NSUInteger)windowItemModMask];
170        [item setTarget:self];
171        [item setTag:i];
172        [item setEnabled:YES];
173
174        item = (NSMenuItem *)[dock_menu  insertItemWithTitle:name
175                                                      action:@selector
176                              (item_selected:) keyEquivalent:shortcut
177                                                     atIndex:i];
178        [item setKeyEquivalentModifierMask:(NSUInteger)windowItemModMask];
179        [item setTarget:self];
180        [item setTag:i];
181        [item setEnabled:YES];
182    }
183
184    if (checked_window_item >= 0 && checked_window_item < count) {
185        item = (NSMenuItem *)[menu itemAtIndex:first + checked_window_item];
186        [item setState:NSOnState];
187        item = (NSMenuItem *)[dock_menu itemAtIndex:checked_window_item];
188        [item setState:NSOnState];
189    }
190}
191
192- (void) remove_apps_menu
193{
194    NSMenu *menu;
195    NSMenuItem *item;
196    int i;
197
198    if (apps == nil || apps_separator == nil) return;
199
200    menu = [apps_separator menu];
201
202    if (menu != nil) {
203        for (i = [menu numberOfItems] - 1; i >= 0; i--) {
204            item = (NSMenuItem *)[menu itemAtIndex:i];
205            if ([item tag] != 0)
206                [menu removeItemAtIndex:i];
207        }
208    }
209
210    if (dock_apps_menu != nil) {
211        for (i = [dock_apps_menu numberOfItems] - 1; i >= 0; i--) {
212            item = (NSMenuItem *)[dock_apps_menu itemAtIndex:i];
213            if ([item tag] != 0)
214                [dock_apps_menu removeItemAtIndex:i];
215        }
216    }
217
218    [apps release];
219    apps = nil;
220}
221
222- (void) prepend_apps_item:(NSArray *)list index:(int)i menu:(NSMenu *)menu
223{
224    NSString *title, *shortcut = @"";
225    NSArray *group;
226    NSMenuItem *item;
227
228    group = [list objectAtIndex:i];
229    title = [group objectAtIndex:0];
230    if ([group count] >= 3)
231        shortcut = [group objectAtIndex:2];
232
233    if ([title length] != 0) {
234        item = (NSMenuItem *)[menu insertItemWithTitle:title
235                                                action:@selector (
236                                  app_selected:)
237                              keyEquivalent:shortcut atIndex:0];
238        [item setTarget:self];
239        [item setEnabled:YES];
240    }
241    else {
242        item = (NSMenuItem *)[NSMenuItem separatorItem];
243        [menu insertItem:item atIndex:0];
244    }
245
246    [item setTag:i + 1];                  /* can't be zero, so add one */
247}
248
249- (void) install_apps_menu:(NSArray *)list
250{
251    NSMenu *menu;
252    int i, count;
253
254    count = [list count];
255
256    if (count == 0 || apps_separator == nil) return;
257
258    menu = [apps_separator menu];
259
260    for (i = count - 1; i >= 0; i--) {
261        if (menu != nil)
262            [self prepend_apps_item:list index:i menu:menu];
263        if (dock_apps_menu != nil)
264            [self prepend_apps_item:list index:i menu:dock_apps_menu];
265    }
266
267    apps = [list retain];
268}
269
270- (void) set_window_menu:(NSArray *)list
271{
272    [self remove_window_menu];
273    [self install_window_menu:list];
274
275    DarwinSendDDXEvent(kXquartzControllerNotify, 1,
276                       AppleWMWindowMenuNotify);
277}
278
279- (void) set_window_menu_check:(NSNumber *)nn
280{
281    NSMenu *menu;
282    NSMenuItem *item;
283    int first, count;
284    int n = [nn intValue];
285
286    menu = [X11App windowsMenu];
287    first = windows_menu_start + 1;
288    count = [menu numberOfItems] - first;
289
290    if (checked_window_item >= 0 && checked_window_item < count) {
291        item = (NSMenuItem *)[menu itemAtIndex:first + checked_window_item];
292        [item setState:NSOffState];
293        item = (NSMenuItem *)[dock_menu itemAtIndex:checked_window_item];
294        [item setState:NSOffState];
295    }
296    if (n >= 0 && n < count) {
297        item = (NSMenuItem *)[menu itemAtIndex:first + n];
298        [item setState:NSOnState];
299        item = (NSMenuItem *)[dock_menu itemAtIndex:n];
300        [item setState:NSOnState];
301    }
302    checked_window_item = n;
303}
304
305- (void) set_apps_menu:(NSArray *)list
306{
307    [self remove_apps_menu];
308    [self install_apps_menu:list];
309}
310
311#ifdef XQUARTZ_SPARKLE
312- (void) setup_sparkle
313{
314    if (check_for_updates_item)
315        return;  // already did it...
316
317    NSMenu *menu = [x11_about_item menu];
318
319    check_for_updates_item =
320        [menu insertItemWithTitle:NSLocalizedString(
321             @"Check for X11 Updates...",
322             @"Check for X11 Updates...")
323         action:@selector (
324             checkForUpdates:)
325         keyEquivalent:@""
326         atIndex:1];
327    [check_for_updates_item setTarget:[SUUpdater sharedUpdater]];
328    [check_for_updates_item setEnabled:YES];
329
330    // Set X11Controller as the delegate for the updater.
331    [[SUUpdater sharedUpdater] setDelegate:self];
332}
333
334// Sent immediately before installing the specified update.
335- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)
336   update
337{
338    //[self set_can_quit:YES];
339}
340
341#endif
342
343- (void) launch_client:(NSString *)filename
344{
345    int child1, child2 = 0;
346    int status;
347    const char *newargv[4];
348    char buf[128];
349    char *s;
350#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
351    int stdout_pipe[2];
352    int stderr_pipe[2];
353#endif
354
355    newargv[0] = [X11App prefs_get_string:@PREFS_LOGIN_SHELL default:"/bin/sh"];
356    newargv[1] = "-c";
357    newargv[2] = [filename UTF8String];
358    newargv[3] = NULL;
359
360    s = getenv("DISPLAY");
361    if (s == NULL || s[0] == 0) {
362        snprintf(buf, sizeof(buf), ":%s", display);
363        setenv("DISPLAY", buf, TRUE);
364    }
365
366#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
367    if (&asl_log_descriptor) {
368        char *asl_sender;
369        aslmsg amsg = asl_new(ASL_TYPE_MSG);
370        assert(amsg);
371
372        asprintf(&asl_sender, "%s.%s", bundle_id_prefix, newargv[2]);
373        assert(asl_sender);
374        for(s = asl_sender + strlen(bundle_id_prefix) + 1; *s; s++) {
375            if(! ((*s >= 'a' && *s <= 'z') ||
376                  (*s >= 'A' && *s <= 'Z') ||
377                  (*s >= '0' && *s <= '9'))) {
378                *s = '_';
379            }
380        }
381
382        (void)asl_set(amsg, ASL_KEY_SENDER, asl_sender);
383        free(asl_sender);
384
385        assert(0 == pipe(stdout_pipe));
386        fcntl(stdout_pipe[0], F_SETFD, FD_CLOEXEC);
387        fcntl(stdout_pipe[0], F_SETFL, O_NONBLOCK);
388
389        assert(0 == pipe(stderr_pipe));
390        fcntl(stderr_pipe[0], F_SETFD, FD_CLOEXEC);
391        fcntl(stderr_pipe[0], F_SETFL, O_NONBLOCK);
392
393        asl_log_descriptor(aslc, amsg, ASL_LEVEL_INFO, stdout_pipe[0], ASL_LOG_DESCRIPTOR_READ);
394        asl_log_descriptor(aslc, amsg, ASL_LEVEL_NOTICE, stderr_pipe[0], ASL_LOG_DESCRIPTOR_READ);
395
396        asl_free(amsg);
397    }
398#endif
399
400    /* Do the fork-twice trick to avoid having to reap zombies */
401    child1 = fork();
402    switch (child1) {
403    case -1:                                    /* error */
404        break;
405
406    case 0:                                     /* child1 */
407        child2 = fork();
408
409        switch (child2) {
410            int max_files, i;
411
412        case -1:                                    /* error */
413            _exit(1);
414
415        case 0:                                     /* child2 */
416#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
417            if (&asl_log_descriptor) {
418                /* Replace our stdout/stderr */
419                dup2(stdout_pipe[1], STDOUT_FILENO);
420                dup2(stderr_pipe[1], STDERR_FILENO);
421            }
422#endif
423
424            /* close all open files except for standard streams */
425            max_files = sysconf(_SC_OPEN_MAX);
426            for (i = 3; i < max_files; i++)
427                close(i);
428
429            /* ensure stdin is on /dev/null */
430            close(0);
431            open("/dev/null", O_RDONLY);
432
433            execvp(newargv[0], (char * *const)newargv);
434            _exit(2);
435
436        default:                                    /* parent (child1) */
437            _exit(0);
438        }
439        break;
440
441    default:                                    /* parent */
442        waitpid(child1, &status, 0);
443    }
444
445#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
446    if (&asl_log_descriptor) {
447        /* Close the write ends of the pipe */
448        close(stdout_pipe[1]);
449        close(stderr_pipe[1]);
450    }
451#endif
452}
453
454- (void) app_selected:sender
455{
456    int tag;
457    NSString *item;
458
459    tag = [sender tag] - 1;
460    if (apps == nil || tag < 0 || tag >= [apps count])
461        return;
462
463    item = [[apps objectAtIndex:tag] objectAtIndex:1];
464
465    [self launch_client:item];
466}
467
468- (IBAction) apps_table_show:sender
469{
470    NSArray *columns;
471    NSMutableArray *oldapps = nil;
472
473    if (table_apps != nil)
474        oldapps = table_apps;
475
476    table_apps = [[NSMutableArray alloc] initWithCapacity:1];
477    if (apps != nil)
478        [table_apps addObjectsFromArray:apps];
479
480    columns = [apps_table tableColumns];
481    [[columns objectAtIndex:0] setIdentifier:@"0"];
482    [[columns objectAtIndex:1] setIdentifier:@"1"];
483    [[columns objectAtIndex:2] setIdentifier:@"2"];
484
485    [apps_table setDataSource:self];
486    [apps_table selectRowIndexes:[NSIndexSet indexSetWithIndex:0]
487     byExtendingSelection:NO];
488
489    [[apps_table window] makeKeyAndOrderFront:sender];
490    [apps_table reloadData];
491    if (oldapps != nil)
492        [oldapps release];
493}
494
495- (IBAction) apps_table_done:sender
496{
497    [apps_table deselectAll:sender];    /* flush edits? */
498
499    [self remove_apps_menu];
500    [self install_apps_menu:table_apps];
501
502    [NSApp prefs_set_array:@PREFS_APPSMENU value:table_apps];
503    [NSApp prefs_synchronize];
504
505    [[apps_table window] orderOut:sender];
506
507    [table_apps release];
508    table_apps = nil;
509}
510
511- (IBAction) apps_table_new:sender
512{
513    NSMutableArray *item;
514
515    int row = [apps_table selectedRow], i;
516
517    if (row < 0) row = 0;
518    else row = row + 1;
519
520    i = row;
521    if (i > [table_apps count])
522        return;                         /* avoid exceptions */
523
524    [apps_table deselectAll:sender];
525
526    item = [[NSMutableArray alloc] initWithCapacity:3];
527    [item addObject:@""];
528    [item addObject:@""];
529    [item addObject:@""];
530
531    [table_apps insertObject:item atIndex:i];
532    [item release];
533
534    [apps_table reloadData];
535    [apps_table selectRowIndexes:[NSIndexSet indexSetWithIndex:row]
536     byExtendingSelection:NO];
537}
538
539- (IBAction) apps_table_duplicate:sender
540{
541    int row = [apps_table selectedRow], i;
542    NSObject *item;
543
544    if (row < 0) {
545        [self apps_table_new:sender];
546        return;
547    }
548
549    i = row;
550    if (i > [table_apps count] - 1) return;                             /* avoid exceptions */
551
552    [apps_table deselectAll:sender];
553
554    item = [[table_apps objectAtIndex:i] mutableCopy];
555    [table_apps insertObject:item atIndex:i];
556    [item release];
557
558    [apps_table reloadData];
559    [apps_table selectRowIndexes:[NSIndexSet indexSetWithIndex:row +
560                                  1] byExtendingSelection:NO];
561}
562
563- (IBAction) apps_table_delete:sender
564{
565    int row = [apps_table selectedRow];
566
567    if (row >= 0) {
568        int i = row;
569
570        if (i > [table_apps count] - 1) return;                 /* avoid exceptions */
571
572        [apps_table deselectAll:sender];
573
574        [table_apps removeObjectAtIndex:i];
575    }
576
577    [apps_table reloadData];
578
579    row = MIN(row, [table_apps count] - 1);
580    if (row >= 0)
581        [apps_table selectRowIndexes:[NSIndexSet indexSetWithIndex:row]
582         byExtendingSelection:NO];
583}
584
585- (NSInteger) numberOfRowsInTableView:(NSTableView *)tableView
586{
587    if (table_apps == nil) return 0;
588
589    return [table_apps count];
590}
591
592- (id)             tableView:(NSTableView *)tableView
593   objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
594{
595    NSArray *item;
596    int col;
597
598    if (table_apps == nil) return nil;
599
600    col = [[tableColumn identifier] intValue];
601
602    item = [table_apps objectAtIndex:row];
603    if ([item count] > col)
604        return [item objectAtIndex:col];
605    else
606        return @"";
607}
608
609- (void) tableView:(NSTableView *)tableView setObjectValue:(id)object
610    forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
611{
612    NSMutableArray *item;
613    int col;
614
615    if (table_apps == nil) return;
616
617    col = [[tableColumn identifier] intValue];
618
619    item = [table_apps objectAtIndex:row];
620    [item replaceObjectAtIndex:col withObject:object];
621}
622
623- (void) hide_window:sender
624{
625    if ([X11App x_active])
626        DarwinSendDDXEvent(kXquartzControllerNotify, 1, AppleWMHideWindow);
627    else
628        NSBeep();                       /* FIXME: something here */
629}
630
631- (IBAction)bring_to_front:sender
632{
633    DarwinSendDDXEvent(kXquartzControllerNotify, 1, AppleWMBringAllToFront);
634}
635
636- (IBAction)close_window:sender
637{
638    if ([X11App x_active])
639        DarwinSendDDXEvent(kXquartzControllerNotify, 1, AppleWMCloseWindow);
640    else
641        [[NSApp keyWindow] performClose:sender];
642}
643
644- (IBAction)minimize_window:sender
645{
646    if ([X11App x_active])
647        DarwinSendDDXEvent(kXquartzControllerNotify, 1, AppleWMMinimizeWindow);
648    else
649        [[NSApp keyWindow] performMiniaturize:sender];
650}
651
652- (IBAction)zoom_window:sender
653{
654    if ([X11App x_active])
655        DarwinSendDDXEvent(kXquartzControllerNotify, 1, AppleWMZoomWindow);
656    else
657        [[NSApp keyWindow] performZoom:sender];
658}
659
660- (IBAction) next_window:sender
661{
662    DarwinSendDDXEvent(kXquartzControllerNotify, 1, AppleWMNextWindow);
663}
664
665- (IBAction) previous_window:sender
666{
667    DarwinSendDDXEvent(kXquartzControllerNotify, 1, AppleWMPreviousWindow);
668}
669
670- (IBAction) enable_fullscreen_changed:sender
671{
672    XQuartzRootlessDefault = ![enable_fullscreen intValue];
673
674    [enable_fullscreen_menu setEnabled:!XQuartzRootlessDefault];
675    [enable_fullscreen_menu_text setTextColor:XQuartzRootlessDefault ?[
676         NSColor disabledControlTextColor] : [NSColor controlTextColor]];
677
678    DarwinSendDDXEvent(kXquartzSetRootless, 1, XQuartzRootlessDefault);
679
680    [NSApp prefs_set_boolean:@PREFS_ROOTLESS value:XQuartzRootlessDefault];
681    [NSApp prefs_synchronize];
682}
683
684- (IBAction) toggle_fullscreen:sender
685{
686    DarwinSendDDXEvent(kXquartzToggleFullscreen, 0);
687}
688
689- (void) set_can_quit:(OSX_BOOL)state
690{
691    can_quit = state;
692}
693
694- (IBAction)prefs_changed:sender
695{
696    if (!sender)
697        return;
698
699    if (sender == fake_buttons) {
700        darwinFakeButtons = [fake_buttons intValue];
701        [NSApp prefs_set_boolean:@PREFS_FAKEBUTTONS value:darwinFakeButtons];
702    }
703    else if (sender == enable_keyequivs) {
704        XQuartzEnableKeyEquivalents = [enable_keyequivs intValue];
705        [NSApp prefs_set_boolean:@PREFS_KEYEQUIVS value:
706         XQuartzEnableKeyEquivalents];
707    }
708    else if (sender == sync_keymap) {
709        darwinSyncKeymap = [sync_keymap intValue];
710        [NSApp prefs_set_boolean:@PREFS_SYNC_KEYMAP value:darwinSyncKeymap];
711    }
712    else if (sender == enable_fullscreen_menu) {
713        XQuartzFullscreenMenu = [enable_fullscreen_menu intValue];
714        [NSApp prefs_set_boolean:@PREFS_FULLSCREEN_MENU value:
715         XQuartzFullscreenMenu];
716    }
717    else if (sender == option_sends_alt) {
718        BOOL prev_opt_sends_alt = XQuartzOptionSendsAlt;
719
720        XQuartzOptionSendsAlt = [option_sends_alt intValue];
721        [NSApp prefs_set_boolean:@PREFS_OPTION_SENDS_ALT value:
722         XQuartzOptionSendsAlt];
723
724        if (prev_opt_sends_alt != XQuartzOptionSendsAlt)
725            QuartsResyncKeymap(TRUE);
726    }
727    else if (sender == click_through) {
728        [NSApp prefs_set_boolean:@PREFS_CLICK_THROUGH value:[click_through
729                                                             intValue]];
730    }
731    else if (sender == focus_follows_mouse) {
732        [NSApp prefs_set_boolean:@PREFS_FFM value:[focus_follows_mouse
733                                                   intValue]];
734    }
735    else if (sender == focus_on_new_window) {
736        [NSApp prefs_set_boolean:@PREFS_FOCUS_ON_NEW_WINDOW value:[
737             focus_on_new_window intValue]];
738    }
739    else if (sender == enable_auth) {
740        [NSApp prefs_set_boolean:@PREFS_NO_AUTH value:![enable_auth intValue]
741        ];
742    }
743    else if (sender == enable_tcp) {
744        [NSApp prefs_set_boolean:@PREFS_NO_TCP value:![enable_tcp intValue]];
745    }
746    else if (sender == depth) {
747        [NSApp prefs_set_integer:@PREFS_DEPTH value:[depth selectedTag]];
748    }
749    else if (sender == sync_pasteboard) {
750        BOOL pbproxy_active = [sync_pasteboard intValue];
751        [NSApp prefs_set_boolean:@PREFS_SYNC_PB value:pbproxy_active];
752
753        [sync_pasteboard_to_clipboard setEnabled:pbproxy_active];
754        [sync_pasteboard_to_primary setEnabled:pbproxy_active];
755        [sync_clipboard_to_pasteboard setEnabled:pbproxy_active];
756        [sync_primary_immediately setEnabled:pbproxy_active];
757
758        // setEnabled doesn't do this...
759        [sync_text1 setTextColor:pbproxy_active ?[NSColor controlTextColor] :
760         [NSColor disabledControlTextColor]];
761        [sync_text2 setTextColor:pbproxy_active ?[NSColor controlTextColor] :
762         [NSColor disabledControlTextColor]];
763    }
764    else if (sender == sync_pasteboard_to_clipboard) {
765        [NSApp prefs_set_boolean:@PREFS_SYNC_PB_TO_CLIPBOARD value:[
766             sync_pasteboard_to_clipboard intValue]];
767    }
768    else if (sender == sync_pasteboard_to_primary) {
769        [NSApp prefs_set_boolean:@PREFS_SYNC_PB_TO_PRIMARY value:[
770             sync_pasteboard_to_primary intValue]];
771    }
772    else if (sender == sync_clipboard_to_pasteboard) {
773        [NSApp prefs_set_boolean:@PREFS_SYNC_CLIPBOARD_TO_PB value:[
774             sync_clipboard_to_pasteboard intValue]];
775    }
776    else if (sender == sync_primary_immediately) {
777        [NSApp prefs_set_boolean:@PREFS_SYNC_PRIMARY_ON_SELECT value:[
778             sync_primary_immediately intValue]];
779    }
780    else if (sender == scroll_in_device_direction) {
781        XQuartzScrollInDeviceDirection =
782            [scroll_in_device_direction intValue];
783        [NSApp prefs_set_boolean:@PREFS_SCROLL_IN_DEV_DIRECTION value:
784         XQuartzScrollInDeviceDirection];
785    }
786
787    [NSApp prefs_synchronize];
788
789    DarwinSendDDXEvent(kXquartzReloadPreferences, 0);
790}
791
792- (IBAction) prefs_show:sender
793{
794    BOOL pbproxy_active =
795        [NSApp prefs_get_boolean:@PREFS_SYNC_PB default:YES];
796
797    // Remove preferences from the GUI which are not supported
798    // TODO: Change 1117 to NSAppKitVersionNumber10_7 when it is defined
799    if (scroll_in_device_direction && NSAppKitVersionNumber < 1117) {
800        [scroll_in_device_direction removeFromSuperview];
801        scroll_in_device_direction = nil;
802    }
803    else {
804        [scroll_in_device_direction setIntValue:
805         XQuartzScrollInDeviceDirection];
806    }
807
808    [fake_buttons setIntValue:darwinFakeButtons];
809    [enable_keyequivs setIntValue:XQuartzEnableKeyEquivalents];
810    [sync_keymap setIntValue:darwinSyncKeymap];
811    [option_sends_alt setIntValue:XQuartzOptionSendsAlt];
812    [click_through setIntValue:[NSApp prefs_get_boolean:@PREFS_CLICK_THROUGH
813                                default:NO]];
814    [focus_follows_mouse setIntValue:[NSApp prefs_get_boolean:@PREFS_FFM
815                                      default:NO]];
816    [focus_on_new_window setIntValue:[NSApp prefs_get_boolean:
817                                      @PREFS_FOCUS_ON_NEW_WINDOW default:YES]
818    ];
819
820    [enable_auth setIntValue:![NSApp prefs_get_boolean:@PREFS_NO_AUTH default
821                               :NO]];
822    [enable_tcp setIntValue:![NSApp prefs_get_boolean:@PREFS_NO_TCP default:
823                              NO]];
824
825    [depth selectItemAtIndex:[depth indexOfItemWithTag:[NSApp
826                                                        prefs_get_integer:
827                                                        @PREFS_DEPTH default:
828                                                        -1]]];
829
830    [sync_pasteboard setIntValue:pbproxy_active];
831    [sync_pasteboard_to_clipboard setIntValue:[NSApp prefs_get_boolean:
832                                               @PREFS_SYNC_PB_TO_CLIPBOARD
833                                               default:YES]];
834    [sync_pasteboard_to_primary setIntValue:[NSApp prefs_get_boolean:
835                                             @PREFS_SYNC_PB_TO_PRIMARY
836                                             default:YES]];
837    [sync_clipboard_to_pasteboard setIntValue:[NSApp prefs_get_boolean:
838                                               @PREFS_SYNC_CLIPBOARD_TO_PB
839                                               default:YES]];
840    [sync_primary_immediately setIntValue:[NSApp prefs_get_boolean:
841                                           @PREFS_SYNC_PRIMARY_ON_SELECT
842                                           default:NO]];
843
844    [sync_pasteboard_to_clipboard setEnabled:pbproxy_active];
845    [sync_pasteboard_to_primary setEnabled:pbproxy_active];
846    [sync_clipboard_to_pasteboard setEnabled:pbproxy_active];
847    [sync_primary_immediately setEnabled:pbproxy_active];
848
849    // setEnabled doesn't do this...
850    [sync_text1 setTextColor:pbproxy_active ?[NSColor controlTextColor] : [
851         NSColor disabledControlTextColor]];
852    [sync_text2 setTextColor:pbproxy_active ?[NSColor controlTextColor] : [
853         NSColor disabledControlTextColor]];
854
855    [enable_fullscreen setIntValue:!XQuartzRootlessDefault];
856    [enable_fullscreen_menu setIntValue:XQuartzFullscreenMenu];
857    [enable_fullscreen_menu setEnabled:!XQuartzRootlessDefault];
858    [enable_fullscreen_menu_text setTextColor:XQuartzRootlessDefault ?[
859         NSColor disabledControlTextColor] : [NSColor controlTextColor]];
860
861    [prefs_panel makeKeyAndOrderFront:sender];
862}
863
864- (IBAction) quit:sender
865{
866    DarwinSendDDXEvent(kXquartzQuit, 0);
867}
868
869- (IBAction) x11_help:sender
870{
871#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
872    AHLookupAnchor((CFStringRef)NSLocalizedString(@"Mac Help",
873                                                  no comment),
874                   CFSTR("mchlp2276"));
875#else
876    AHLookupAnchor(CFSTR("com.apple.machelp"), CFSTR("mchlp2276"));
877#endif
878}
879
880- (OSX_BOOL) validateMenuItem:(NSMenuItem *)item
881{
882    NSMenu *menu = [item menu];
883
884    if (item == toggle_fullscreen_item)
885        return !XQuartzIsRootless;
886    else if (menu == [X11App windowsMenu] || menu == dock_menu
887             || (menu == [x11_about_item menu] && [item tag] == 42))
888        return (AppleWMSelectedEvents() & AppleWMControllerNotifyMask) != 0;
889    else
890        return TRUE;
891}
892
893- (void) applicationDidHide:(NSNotification *)notify
894{
895    DarwinSendDDXEvent(kXquartzControllerNotify, 1, AppleWMHideAll);
896
897    /* Toggle off fullscreen mode to leave our non-default video
898     * mode and hide our guard window.
899     */
900    if (!XQuartzIsRootless && XQuartzFullscreenVisible) {
901        DarwinSendDDXEvent(kXquartzToggleFullscreen, 0);
902    }
903}
904
905- (void) applicationDidUnhide:(NSNotification *)notify
906{
907    DarwinSendDDXEvent(kXquartzControllerNotify, 1, AppleWMShowAll);
908}
909
910- (NSApplicationTerminateReply) applicationShouldTerminate:sender
911{
912    NSString *msg;
913    NSString *title;
914
915    if (can_quit ||
916        [X11App prefs_get_boolean:@PREFS_NO_QUIT_ALERT default:NO])
917        return NSTerminateNow;
918
919    /* Make sure we're frontmost. */
920    [NSApp activateIgnoringOtherApps:YES];
921
922    title = NSLocalizedString(@"Do you really want to quit X11?",
923                              @"Dialog title when quitting");
924    msg = NSLocalizedString(
925        @"Any open X11 applications will stop immediately, and you will lose any unsaved changes.",
926        @"Dialog when quitting");
927
928    /* FIXME: safe to run the alert in here? Or should we return Later
929     *        and then run the alert on a timer? It seems to work here, so..
930     */
931
932    NSInteger result = NSRunAlertPanel(title, @"%@", NSLocalizedString(@"Quit", @""),
933                                       NSLocalizedString(@"Cancel", @""), nil, msg);
934    return (result == NSAlertDefaultReturn) ? NSTerminateNow : NSTerminateCancel;
935}
936
937- (void) applicationWillTerminate:(NSNotification *)aNotification _X_NORETURN
938{
939    [X11App prefs_synchronize];
940
941    /* shutdown the X server, it will exit () for us. */
942    DarwinSendDDXEvent(kXquartzQuit, 0);
943
944    /* In case it doesn't, exit anyway after 5s. */
945    [NSThread sleepForTimeInterval:5.0];
946
947    exit(1);
948}
949
950- (void) server_ready
951{
952    x_list *node;
953
954    finished_launching = YES;
955
956    for (node = pending_apps; node != NULL; node = node->next) {
957        NSString *filename = node->data;
958        [self launch_client:filename];
959        [filename release];
960    }
961
962    x_list_free(pending_apps);
963    pending_apps = NULL;
964}
965
966- (OSX_BOOL) application:(NSApplication *)app openFile:(NSString *)filename
967{
968    const char *name = [filename UTF8String];
969
970    if (finished_launching)
971        [self launch_client:filename];
972    else if (name[0] != ':')            /* ignore display names */
973        pending_apps = x_list_prepend(pending_apps, [filename retain]);
974
975    /* FIXME: report failures. */
976    return YES;
977}
978
979@end
980
981void
982X11ControllerMain(int argc, char **argv, char **envp)
983{
984    X11ApplicationMain(argc, argv, envp);
985}
986