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