1 /* X11Controller.m -- connect the IB ui, also the NSApp delegate
2 $Id: X11Controller.m,v 1.40 2006/09/06 21:19:32 jharper Exp $
4 Copyright (c) 2002-2007 Apple Inc. All rights reserved.
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:
14 The above copyright notice and this permission notice shall be
15 included in all copies or substantial portions of the Software.
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.
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. */
31 #define DEFAULT_PATH "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/X11/bin"
33 #include "../quartz/quartzCommon.h"
35 #import "X11Controller.h"
36 #import "X11Application.h"
37 #import <Carbon/Carbon.h>
41 //# include "Xproto.h"
44 # include "../quartz/quartz.h"
45 # define _APPLEWM_SERVER_
46 # include "X11/extensions/applewm.h"
47 # include "../quartz/applewmExt.h"
54 #include <sys/types.h>
57 #define TRACE() fprintf (stderr, "%s\n", __FUNCTION__)
59 @implementation X11Controller
63 X11Application *xapp = NSApp;
66 /* Point X11Application at ourself. */
67 [xapp set_controller:self];
69 array = [xapp prefs_get_array:@PREFS_APPSMENU];
74 /* convert from [TITLE1 COMMAND1 TITLE2 COMMAND2 ...]
75 to [[TITLE1 COMMAND1] [TITLE2 COMMAND2] ...] format. */
77 count = [array count];
79 && ![[array objectAtIndex:0] isKindOfClass:[NSArray class]])
82 NSMutableArray *copy, *sub;
84 copy = [NSMutableArray arrayWithCapacity:(count / 2)];
86 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]];
99 [self set_apps_menu:array];
103 - (void) item_selected:sender
105 [NSApp activateIgnoringOtherApps:YES];
107 QuartzMessageServerThread (kXDarwinControllerNotify, 2,
108 AppleWMWindowMenuItem, [sender tag]);
111 - (void) remove_window_menu
116 /* Work backwards so we don't mess up the indices */
117 menu = [window_separator menu];
118 first = [menu indexOfItem:window_separator] + 1;
119 count = [menu numberOfItems];
120 for (i = count - 1; i >= first; i--)
121 [menu removeItemAtIndex:i];
123 menu = [dock_window_separator menu];
124 count = [menu indexOfItem:dock_window_separator];
125 for (i = 0; i < count; i++)
126 [dock_menu removeItemAtIndex:0];
129 - (void) install_window_menu:(NSArray *)list
135 menu = [window_separator menu];
136 first = [menu indexOfItem:window_separator] + 1;
137 count = [list count];
138 for (i = 0; i < count; i++)
140 NSString *name, *shortcut;
142 name = [[list objectAtIndex:i] objectAtIndex:0];
143 shortcut = [[list objectAtIndex:i] objectAtIndex:1];
145 item = (NSMenuItem *) [menu addItemWithTitle:name action:@selector
146 (item_selected:) keyEquivalent:shortcut];
147 [item setTarget:self];
149 [item setEnabled:YES];
151 item = (NSMenuItem *) [dock_menu insertItemWithTitle:name
153 (item_selected:) keyEquivalent:shortcut
155 [item setTarget:self];
157 [item setEnabled:YES];
160 if (checked_window_item >= 0 && checked_window_item < count)
162 item = (NSMenuItem *) [menu itemAtIndex:first + checked_window_item];
163 [item setState:NSOnState];
164 item = (NSMenuItem *) [dock_menu itemAtIndex:checked_window_item];
165 [item setState:NSOnState];
169 - (void) remove_apps_menu
175 if (apps == nil || apps_separator == nil) return;
177 menu = [apps_separator menu];
181 for (i = [menu numberOfItems] - 1; i >= 0; i--)
183 item = (NSMenuItem *) [menu itemAtIndex:i];
185 [menu removeItemAtIndex:i];
189 if (dock_apps_menu != nil)
191 for (i = [dock_apps_menu numberOfItems] - 1; i >= 0; i--)
193 item = (NSMenuItem *) [dock_apps_menu itemAtIndex:i];
195 [dock_apps_menu removeItemAtIndex:i];
203 - (void) prepend_apps_item:(NSArray *)list index:(int)i menu:(NSMenu *)menu
205 NSString *title, *shortcut = @"";
209 group = [list objectAtIndex:i];
210 title = [group objectAtIndex:0];
211 if ([group count] >= 3)
212 shortcut = [group objectAtIndex:2];
214 if ([title length] != 0)
216 item = (NSMenuItem *) [menu insertItemWithTitle:title
217 action:@selector (app_selected:)
218 keyEquivalent:shortcut atIndex:0];
219 [item setTarget:self];
220 [item setEnabled:YES];
224 item = (NSMenuItem *) [NSMenuItem separatorItem];
225 [menu insertItem:item atIndex:0];
228 [item setTag:i+1]; /* can't be zero, so add one */
231 - (void) install_apps_menu:(NSArray *)list
236 count = [list count];
238 if (count == 0 || apps_separator == nil) return;
240 menu = [apps_separator menu];
242 for (i = count - 1; i >= 0; i--)
245 [self prepend_apps_item:list index:i menu:menu];
246 if (dock_apps_menu != nil)
247 [self prepend_apps_item:list index:i menu:dock_apps_menu];
250 apps = [list retain];
253 - (void) set_window_menu:(NSArray *)list
255 [self remove_window_menu];
256 [self install_window_menu:list];
258 QuartzMessageServerThread (kXDarwinControllerNotify, 1,
259 AppleWMWindowMenuNotify);
262 - (void) set_window_menu_check:(NSNumber *)nn
267 int n = [nn intValue];
269 menu = [window_separator menu];
270 first = [menu indexOfItem:window_separator] + 1;
271 count = [menu numberOfItems] - first;
273 if (checked_window_item >= 0 && checked_window_item < count)
275 item = (NSMenuItem *) [menu itemAtIndex:first + checked_window_item];
276 [item setState:NSOffState];
277 item = (NSMenuItem *) [dock_menu itemAtIndex:checked_window_item];
278 [item setState:NSOffState];
280 if (n >= 0 && n < count)
282 item = (NSMenuItem *) [menu itemAtIndex:first + n];
283 [item setState:NSOnState];
284 item = (NSMenuItem *) [dock_menu itemAtIndex:n];
285 [item setState:NSOnState];
287 checked_window_item = n;
290 - (void) set_apps_menu:(NSArray *)list
292 [self remove_apps_menu];
293 [self install_apps_menu:list];
296 - (void) launch_client:(NSString *)filename
298 const char *command = [filename UTF8String];
301 int child1, child2 = 0;
304 shell = getenv("SHELL");
305 if (shell == NULL) shell = "/bin/bash";
313 /* Do the fork-twice trick to avoid having to reap zombies */
326 char buf[1024], *temp;
332 /* close all open files except for standard streams */
333 max_files = sysconf(_SC_OPEN_MAX);
334 for (i = 3; i < max_files; i++) close(i);
336 /* ensure stdin is on /dev/null */
338 open("/dev/null", O_RDONLY);
340 /* Setup environment */
341 temp = getenv("DISPLAY");
342 if (temp == NULL || temp[0] == 0) {
343 snprintf(buf, sizeof(buf), ":%s", display);
344 setenv("DISPLAY", buf, TRUE);
347 temp = getenv("PATH");
348 if (temp == NULL || temp[0] == 0)
349 setenv ("PATH", DEFAULT_PATH, TRUE);
350 else if (strnstr(temp, "/usr/X11/bin", sizeof(temp)) == NULL) {
351 snprintf(buf, sizeof(buf), "%s:/usr/X11/bin", temp);
352 setenv("PATH", buf, TRUE);
355 temp = getenv("HOME");
356 if (temp != NULL && temp[0]!=0) chdir(temp);
358 execvp(argv[0], (char **const) argv);
362 default: /* parent (child1) */
367 default: /* parent */
368 waitpid(child1, &status, 0);
372 - (void) app_selected:sender
377 tag = [sender tag] - 1;
378 if (apps == nil || tag < 0 || tag >= [apps count])
381 item = [[apps objectAtIndex:tag] objectAtIndex:1];
383 [self launch_client:item];
386 - (IBAction) apps_table_show:sender
390 if (table_apps == nil) {
391 table_apps = [[NSMutableArray alloc] initWithCapacity:1];
393 if (apps != nil)[table_apps addObjectsFromArray:apps];
396 columns = [apps_table tableColumns];
397 [[columns objectAtIndex:0] setIdentifier:@"0"];
398 [[columns objectAtIndex:1] setIdentifier:@"1"];
399 [[columns objectAtIndex:2] setIdentifier:@"2"];
401 [apps_table setDataSource:self];
402 [apps_table selectRow:0 byExtendingSelection:NO];
404 [[apps_table window] makeKeyAndOrderFront:sender];
407 - (IBAction) apps_table_cancel:sender
409 [[apps_table window] orderOut:sender];
410 [apps_table reloadData];
412 [table_apps release];
416 - (IBAction) apps_table_done:sender
418 [apps_table deselectAll:sender]; /* flush edits? */
420 [self remove_apps_menu];
421 [self install_apps_menu:table_apps];
423 [NSApp prefs_set_array:@PREFS_APPSMENU value:table_apps];
424 [NSApp prefs_synchronize];
426 [[apps_table window] orderOut:sender];
428 [table_apps release];
432 - (IBAction) apps_table_new:sender
434 NSMutableArray *item;
436 int row = [apps_table selectedRow], i;
438 if (row < 0) row = 0;
442 if (i > [table_apps count])
443 return; /* avoid exceptions */
445 [apps_table deselectAll:sender];
447 item = [[NSMutableArray alloc] initWithCapacity:3];
448 [item addObject:@""];
449 [item addObject:@""];
450 [item addObject:@""];
452 [table_apps insertObject:item atIndex:i];
455 [apps_table reloadData];
456 [apps_table selectRow:row byExtendingSelection:NO];
459 - (IBAction) apps_table_duplicate:sender
461 int row = [apps_table selectedRow], i;
465 [self apps_table_new:sender];
470 if (i > [table_apps count] - 1) return; /* avoid exceptions */
472 [apps_table deselectAll:sender];
474 item = [[table_apps objectAtIndex:i] mutableCopy];
475 [table_apps insertObject:item atIndex:i];
478 [apps_table reloadData];
479 [apps_table selectRow:row+1 byExtendingSelection:NO];
482 - (IBAction) apps_table_delete:sender
484 int row = [apps_table selectedRow];
490 if (i > [table_apps count] - 1) return; /* avoid exceptions */
492 [apps_table deselectAll:sender];
494 [table_apps removeObjectAtIndex:i];
497 [apps_table reloadData];
499 row = MIN (row, [table_apps count] - 1);
501 [apps_table selectRow:row byExtendingSelection:NO];
504 - (int) numberOfRowsInTableView:(NSTableView *)tableView
506 if (table_apps == nil) return 0;
508 return [table_apps count];
511 - (id) tableView:(NSTableView *)tableView
512 objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
517 if (table_apps == nil) return nil;
519 col = [[tableColumn identifier] intValue];
521 item = [table_apps objectAtIndex:row];
522 if ([item count] > col)
523 return [item objectAtIndex:col];
528 - (void) tableView:(NSTableView *)tableView setObjectValue:(id)object
529 forTableColumn:(NSTableColumn *)tableColumn row:(int)row
531 NSMutableArray *item;
534 if (table_apps == nil) return;
536 col = [[tableColumn identifier] intValue];
538 item = [table_apps objectAtIndex:row];
539 [item replaceObjectAtIndex:col withObject:object];
542 - (void) hide_window:sender
544 if ([X11App x_active])
545 QuartzMessageServerThread (kXDarwinControllerNotify, 1, AppleWMHideWindow);
547 NSBeep (); /* FIXME: something here */
550 - (IBAction)bring_to_front:sender
552 QuartzMessageServerThread(kXDarwinControllerNotify, 1, AppleWMBringAllToFront);
555 - (IBAction)close_window:sender
557 if ([X11App x_active])
558 QuartzMessageServerThread (kXDarwinControllerNotify, 1, AppleWMCloseWindow);
560 [[NSApp keyWindow] performClose:sender];
563 - (IBAction)minimize_window:sender
565 if ([X11App x_active])
566 QuartzMessageServerThread (kXDarwinControllerNotify, 1, AppleWMMinimizeWindow);
568 [[NSApp keyWindow] performMiniaturize:sender];
571 - (IBAction)zoom_window:sender
573 if ([X11App x_active])
574 QuartzMessageServerThread (kXDarwinControllerNotify, 1, AppleWMZoomWindow);
576 [[NSApp keyWindow] performZoom:sender];
579 - (IBAction) next_window:sender
581 QuartzMessageServerThread (kXDarwinControllerNotify, 1, AppleWMNextWindow);
584 - (IBAction) previous_window:sender
586 QuartzMessageServerThread (kXDarwinControllerNotify, 1, AppleWMPreviousWindow);
589 - (IBAction) enable_fullscreen_changed:sender
591 int value = ![enable_fullscreen intValue];
593 #ifdef DARWIN_DDX_MISSING
594 QuartzMessageServerThread (kXDarwinSetRootless, 1, value);
597 [NSApp prefs_set_boolean:@PREFS_ROOTLESS value:value];
598 [NSApp prefs_synchronize];
601 - (IBAction) toggle_fullscreen:sender
603 #ifdef DARWIN_DDX_MISSING
604 QuartzMessageServerThread (kXDarwinToggleFullscreen, 0);
608 - (void) set_can_quit:(BOOL)state
613 - (IBAction)prefs_changed:sender
615 darwinFakeButtons = [fake_buttons intValue];
616 quartzUseSysBeep = [use_sysbeep intValue];
617 X11EnableKeyEquivalents = [enable_keyequivs intValue];
618 darwinSyncKeymap = [sync_keymap intValue];
620 /* after adding prefs here, also add to [X11Application read_defaults]
623 [NSApp prefs_set_boolean:@PREFS_FAKEBUTTONS value:darwinFakeButtons];
624 [NSApp prefs_set_boolean:@PREFS_SYSBEEP value:quartzUseSysBeep];
625 [NSApp prefs_set_boolean:@PREFS_KEYEQUIVS value:X11EnableKeyEquivalents];
626 [NSApp prefs_set_boolean:@PREFS_SYNC_KEYMAP value:darwinSyncKeymap];
627 [NSApp prefs_set_boolean:@PREFS_NO_AUTH value:![enable_auth intValue]];
628 [NSApp prefs_set_boolean:@PREFS_NO_TCP value:![enable_tcp intValue]];
629 [NSApp prefs_set_integer:@PREFS_DEPTH value:[depth selectedTag]];
631 [NSApp prefs_synchronize];
634 - (IBAction) prefs_show:sender
636 [fake_buttons setIntValue:darwinFakeButtons];
637 [use_sysbeep setIntValue:quartzUseSysBeep];
638 [enable_keyequivs setIntValue:X11EnableKeyEquivalents];
639 [sync_keymap setIntValue:darwinSyncKeymap];
640 [sync_keymap setEnabled:darwinKeymapFile == NULL];
642 [enable_auth setIntValue:![NSApp prefs_get_boolean:@PREFS_NO_AUTH default:NO]];
643 [enable_tcp setIntValue:![NSApp prefs_get_boolean:@PREFS_NO_TCP default:NO]];
644 [depth selectItemAtIndex:[depth indexOfItemWithTag:[NSApp prefs_get_integer:@PREFS_DEPTH default:-1]]];
646 [enable_fullscreen setIntValue:!quartzEnableRootless];
648 [prefs_panel makeKeyAndOrderFront:sender];
651 - (IBAction) quit:sender
653 QuartzMessageServerThread (kXDarwinQuit, 0);
656 - (IBAction) x11_help:sender
658 AHLookupAnchor (CFSTR ("Mac Help"), CFSTR ("mchlp2276"));
661 - (BOOL) validateMenuItem:(NSMenuItem *)item
663 NSMenu *menu = [item menu];
665 if (item == toggle_fullscreen_item)
666 return !quartzEnableRootless;
667 else if (menu == [window_separator menu] || menu == dock_menu
668 || (menu == [x11_about_item menu] && [item tag] == 42))
669 return (AppleWMSelectedEvents () & AppleWMControllerNotifyMask) != 0;
674 - (void) applicationDidHide:(NSNotification *)notify
676 QuartzMessageServerThread (kXDarwinControllerNotify, 1, AppleWMHideAll);
679 - (void) applicationDidUnhide:(NSNotification *)notify
681 QuartzMessageServerThread (kXDarwinControllerNotify, 1, AppleWMShowAll);
684 - (NSApplicationTerminateReply) applicationShouldTerminate:sender
688 if (can_quit || [X11App prefs_get_boolean:@PREFS_NO_QUIT_ALERT default:NO])
689 return NSTerminateNow;
691 /* Make sure we're frontmost. */
692 [NSApp activateIgnoringOtherApps:YES];
694 msg = NSLocalizedString (@"Are you sure you want to quit X11?\n\nIf you quit X11, any X11 applications you are running will stop immediately and you will lose any changes you have not saved.", @"Dialog when quitting");
696 /* FIXME: safe to run the alert in here? Or should we return Later
697 and then run the alert on a timer? It seems to work here, so.. */
699 return (NSRunAlertPanel (nil, msg, NSLocalizedString (@"Quit", @""),
700 NSLocalizedString (@"Cancel", @""), nil)
701 == NSAlertDefaultReturn) ? NSTerminateNow : NSTerminateCancel;
704 - (void) applicationWillTerminate:(NSNotification *)aNotification
706 [X11App prefs_synchronize];
708 /* shutdown the X server, it will exit () for us. */
709 QuartzMessageServerThread (kXDarwinQuit, 0);
711 /* In case it doesn't, exit anyway after a while. */
712 while (sleep (10) != 0) ;
716 - (void) server_ready
720 finished_launching = YES;
722 for (node = pending_apps; node != NULL; node = node->next)
724 NSString *filename = node->data;
725 [self launch_client:filename];
729 x_list_free (pending_apps);
733 - (BOOL) application:(NSApplication *)app openFile:(NSString *)filename
735 const char *name = [filename UTF8String];
737 if (finished_launching)
738 [self launch_client:filename];
739 else if (name[0] != ':') /* ignore display names */
740 pending_apps = x_list_prepend (pending_apps, [filename retain]);
742 /* FIXME: report failures. */
748 void X11ControllerMain (int argc, const char *argv[],
749 void (*server_thread) (void *), void *server_arg)
751 X11ApplicationMain (argc, argv, server_thread, server_arg);