1540 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
			
		
		
	
	
			1540 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
| //
 | |
| //  XServer.m
 | |
| //
 | |
| //  This class handles the interaction between the Cocoa front-end
 | |
| //  and the Darwin X server thread.
 | |
| //
 | |
| //  Created by Andreas Monitzer on January 6, 2001.
 | |
| //
 | |
| /*
 | |
|  * Copyright (c) 2001 Andreas Monitzer. All Rights Reserved.
 | |
|  * Copyright (c) 2002-2005 Torrey T. Lyons. All Rights Reserved.
 | |
|  *
 | |
|  * Permission is hereby granted, free of charge, to any person obtaining a
 | |
|  * copy of this software and associated documentation files (the
 | |
|  * "Software"), to deal in the Software without restriction, including
 | |
|  * without limitation the rights to use, copy, modify, merge, publish,
 | |
|  * distribute, sublicense, and/or sell copies of the Software, and to
 | |
|  * permit persons to whom the Software is furnished to do so, subject to
 | |
|  * the following conditions:
 | |
|  *
 | |
|  * The above copyright notice and this permission notice shall be included
 | |
|  * in all copies or substantial portions of the Software.
 | |
|  *
 | |
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 | |
|  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 | |
|  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 | |
|  * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
 | |
|  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 | |
|  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 | |
|  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | |
|  *
 | |
|  * Except as contained in this notice, the name(s) of the above copyright
 | |
|  * holders shall not be used in advertising or otherwise to promote the
 | |
|  * sale, use or other dealings in this Software without prior written
 | |
|  * authorization.
 | |
|  */
 | |
| /* $XdotOrg: xc/programs/Xserver/hw/darwin/quartz/XServer.m,v 1.3 2004/07/30 19:12:17 torrey Exp $ */
 | |
| 
 | |
| #include <dix-config.h>
 | |
| #include "quartzCommon.h"
 | |
| 
 | |
| #define BOOL xBOOL
 | |
| #include "X11/X.h"
 | |
| #include "X11/Xproto.h"
 | |
| #include "os.h"
 | |
| #include "opaque.h"
 | |
| #include "darwin.h"
 | |
| #include "quartz.h"
 | |
| #define _APPLEWM_SERVER_
 | |
| #include "X11/extensions/applewm.h"
 | |
| #include "applewmExt.h"
 | |
| #undef BOOL
 | |
| 
 | |
| #import "XServer.h"
 | |
| #import "Preferences.h"
 | |
| 
 | |
| #include <unistd.h>
 | |
| #include <stdio.h>
 | |
| #include <sys/syslimits.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/wait.h>
 | |
| #include <pwd.h>
 | |
| #include <signal.h>
 | |
| #include <fcntl.h>
 | |
| 
 | |
| // For power management notifications
 | |
| #import <mach/mach_port.h>
 | |
| #import <mach/mach_interface.h>
 | |
| #import <mach/mach_init.h>
 | |
| #import <IOKit/pwr_mgt/IOPMLib.h>
 | |
| #import <IOKit/IOMessage.h>
 | |
| 
 | |
| // Types of shells
 | |
| enum {
 | |
|     shell_Unknown,
 | |
|     shell_Bourne,
 | |
|     shell_C
 | |
| };
 | |
| 
 | |
| typedef struct {
 | |
|     char *name;
 | |
|     int type;
 | |
| } shellList_t;
 | |
| 
 | |
| static shellList_t const shellList[] = {
 | |
|     { "csh",    shell_C },          // standard C shell
 | |
|     { "tcsh",   shell_C },          // ... needs no introduction
 | |
|     { "sh",     shell_Bourne },     // standard Bourne shell
 | |
|     { "zsh",    shell_Bourne },     // Z shell
 | |
|     { "bash",   shell_Bourne },     // GNU Bourne again shell
 | |
|     { NULL,     shell_Unknown }
 | |
| };
 | |
| 
 | |
| extern int argcGlobal;
 | |
| extern char **argvGlobal;
 | |
| extern char **envpGlobal;
 | |
| extern int main(int argc, char *argv[], char *envp[]);
 | |
| extern void HideMenuBar(void);
 | |
| extern void ShowMenuBar(void);
 | |
| static void childDone(int sig);
 | |
| static void powerDidChange(void *x, io_service_t y, natural_t messageType,
 | |
|                            void *messageArgument);
 | |
| 
 | |
| static NSPort *signalPort;
 | |
| static NSPort *returnPort;
 | |
| static NSPortMessage *signalMessage;
 | |
| static pid_t clientPID;
 | |
| static XServer *oneXServer;
 | |
| static NSRect aquaMenuBarBox;
 | |
| static io_connect_t root_port;
 | |
| 
 | |
| 
 | |
| @implementation XServer
 | |
| 
 | |
| - (id)init
 | |
| {
 | |
|     self = [super init];
 | |
|     oneXServer = self;
 | |
| 
 | |
|     serverState = server_NotStarted;
 | |
|     serverLock = [[NSRecursiveLock alloc] init];
 | |
|     pendingClients = nil;
 | |
|     clientPID = 0;
 | |
|     sendServerEvents = NO;
 | |
|     x11Active = YES;
 | |
|     serverVisible = NO;
 | |
|     rootlessMenuBarVisible = YES;
 | |
|     queueShowServer = YES;
 | |
|     quartzServerQuitting = NO;
 | |
|     pendingAppQuitReply = NO;
 | |
|     mouseState = 0;
 | |
| 
 | |
|     // set up a port to safely send messages to main thread from server thread
 | |
|     signalPort = [[NSPort port] retain];
 | |
|     returnPort = [[NSPort port] retain];
 | |
|     signalMessage = [[NSPortMessage alloc] initWithSendPort:signalPort
 | |
|                     receivePort:returnPort components:nil];
 | |
| 
 | |
|     // set up receiving end
 | |
|     [signalPort setDelegate:self];
 | |
|     [[NSRunLoop currentRunLoop] addPort:signalPort
 | |
|                                 forMode:NSDefaultRunLoopMode];
 | |
|     [[NSRunLoop currentRunLoop] addPort:signalPort
 | |
|                                 forMode:NSModalPanelRunLoopMode];
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| - (NSApplicationTerminateReply)
 | |
|         applicationShouldTerminate:(NSApplication *)sender
 | |
| {
 | |
|     // Quit if the X server is not running
 | |
|     if ([serverLock tryLock]) {
 | |
|         quartzServerQuitting = YES;
 | |
|         serverState = server_Done;
 | |
|         if (clientPID != 0)
 | |
|             kill(clientPID, SIGINT);
 | |
|         return NSTerminateNow;
 | |
|     }
 | |
| 
 | |
|     // Hide the X server and stop sending it events
 | |
|     [self showServer:NO];
 | |
|     sendServerEvents = NO;
 | |
| 
 | |
|     if (!quitWithoutQuery && (clientPID != 0 || !quartzStartClients)) {
 | |
|         int but;
 | |
| 
 | |
|         but = NSRunAlertPanel(NSLocalizedString(@"Quit X server?",@""),
 | |
|                               NSLocalizedString(@"Quitting the X server will terminate any running X Window System programs.",@""),
 | |
|                               NSLocalizedString(@"Quit",@""),
 | |
|                               NSLocalizedString(@"Cancel",@""),
 | |
|                               nil);
 | |
| 
 | |
|         switch (but) {
 | |
|             case NSAlertDefaultReturn:		// quit
 | |
|                 break;
 | |
|             case NSAlertAlternateReturn:	// cancel
 | |
|                 if (serverState == server_Running)
 | |
|                     sendServerEvents = YES;
 | |
|                 return NSTerminateCancel;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     quartzServerQuitting = YES;
 | |
|     if (clientPID != 0)
 | |
|         kill(clientPID, SIGINT);
 | |
| 
 | |
|     // At this point the X server is either running or starting.
 | |
|     if (serverState == server_Starting) {
 | |
|         // Quit will be queued later when server is running
 | |
|         pendingAppQuitReply = YES;
 | |
|         return NSTerminateLater;
 | |
|     } else if (serverState == server_Running) {
 | |
|         [self quitServer];
 | |
|     }
 | |
| 
 | |
|     return NSTerminateNow;
 | |
| }
 | |
| 
 | |
| // Ensure that everything has quit cleanly
 | |
| - (void)applicationWillTerminate:(NSNotification *)aNotification
 | |
| {
 | |
|     // Make sure the client process has finished
 | |
|     if (clientPID != 0) {
 | |
|         NSLog(@"Waiting on client process...");
 | |
|         sleep(2);
 | |
| 
 | |
|         // If the client process hasn't finished yet, kill it off
 | |
|         if (clientPID != 0) {
 | |
|             int clientStatus;
 | |
|             NSLog(@"Killing client process...");
 | |
|             killpg(clientPID, SIGKILL);
 | |
|             waitpid(clientPID, &clientStatus, 0);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Wait until the X server thread quits
 | |
|     [serverLock lock];
 | |
| }
 | |
| 
 | |
| // returns YES when event was handled
 | |
| - (BOOL)translateEvent:(NSEvent *)anEvent
 | |
| {
 | |
|     xEvent xe;
 | |
|     static BOOL mouse1Pressed = NO;
 | |
|     NSEventType type;
 | |
|     unsigned int flags;
 | |
| 
 | |
|     if (!sendServerEvents) {
 | |
|         return NO;
 | |
|     }
 | |
| 
 | |
|     type  = [anEvent type];
 | |
|     flags = [anEvent modifierFlags];
 | |
| 
 | |
|     if (!quartzRootless) {
 | |
|         // Check for switch keypress
 | |
|         if ((type == NSKeyDown) && (![anEvent isARepeat]) &&
 | |
|             ([anEvent keyCode] == [Preferences keyCode]))
 | |
|         {
 | |
|             unsigned int switchFlags = [Preferences modifiers];
 | |
| 
 | |
|             // Switch if all the switch modifiers are pressed, while none are
 | |
|             // pressed that should not be, except for caps lock.
 | |
|             if (((flags & switchFlags) == switchFlags) &&
 | |
|                 ((flags & ~(switchFlags | NSAlphaShiftKeyMask)) == 0))
 | |
|             {
 | |
|                 [self toggle];
 | |
|                 return YES;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!serverVisible)
 | |
|             return NO;
 | |
|     }
 | |
| 
 | |
|     memset(&xe, 0, sizeof(xe));
 | |
| 
 | |
|     switch (type) {
 | |
|         case NSLeftMouseUp:
 | |
|             if (quartzRootless && !mouse1Pressed) {
 | |
|                 // MouseUp after MouseDown in menu - ignore
 | |
|                 return NO;
 | |
|             }
 | |
|             mouse1Pressed = NO;
 | |
|             [self getMousePosition:&xe fromEvent:anEvent];
 | |
|             xe.u.u.type = ButtonRelease;
 | |
|             xe.u.u.detail = 1;
 | |
|             break;
 | |
| 
 | |
|         case NSLeftMouseDown:
 | |
|             if (quartzRootless) {
 | |
|                 // Check that event is in X11 window
 | |
|                 if (!quartzProcs->IsX11Window([anEvent window],
 | |
|                                               [anEvent windowNumber]))
 | |
|                 {
 | |
|                     if (x11Active)
 | |
|                         [self activateX11:NO];
 | |
|                     return NO;
 | |
|                 } else {
 | |
|                     if (!x11Active)
 | |
|                         [self activateX11:YES];
 | |
|                 }
 | |
|             }
 | |
|             mouse1Pressed = YES;
 | |
|             [self getMousePosition:&xe fromEvent:anEvent];
 | |
|             xe.u.u.type = ButtonPress;
 | |
|             xe.u.u.detail = 1;
 | |
|             break;
 | |
| 
 | |
|         case NSRightMouseUp:
 | |
|             [self getMousePosition:&xe fromEvent:anEvent];
 | |
|             xe.u.u.type = ButtonRelease;
 | |
|             xe.u.u.detail = 3;
 | |
|             break;
 | |
| 
 | |
|         case NSRightMouseDown:
 | |
|             [self getMousePosition:&xe fromEvent:anEvent];
 | |
|             xe.u.u.type = ButtonPress;
 | |
|             xe.u.u.detail = 3;
 | |
|             break;
 | |
| 
 | |
|         case NSOtherMouseUp:
 | |
|         {
 | |
|             int hwButton = [anEvent buttonNumber];
 | |
| 
 | |
|             [self getMousePosition:&xe fromEvent:anEvent];
 | |
|             xe.u.u.type = ButtonRelease;
 | |
|             xe.u.u.detail = (hwButton == 2) ? hwButton : hwButton + 1;
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         case NSOtherMouseDown:
 | |
|         {
 | |
|             int hwButton = [anEvent buttonNumber];
 | |
| 
 | |
|             [self getMousePosition:&xe fromEvent:anEvent];
 | |
|             xe.u.u.type = ButtonPress;
 | |
|             xe.u.u.detail = (hwButton == 2) ? hwButton : hwButton + 1;
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         case NSMouseMoved:
 | |
|         case NSLeftMouseDragged:
 | |
|         case NSRightMouseDragged:
 | |
|         case NSOtherMouseDragged:
 | |
|             [self getMousePosition:&xe fromEvent:anEvent];
 | |
|             xe.u.u.type = MotionNotify;
 | |
|             break;
 | |
| 
 | |
|         case NSScrollWheel:
 | |
|             [self getMousePosition:&xe fromEvent:anEvent];
 | |
|             xe.u.u.type = kXDarwinScrollWheel;
 | |
|             xe.u.clientMessage.u.s.shorts0 = [anEvent deltaX] +
 | |
|                                              [anEvent deltaY];
 | |
|             break;
 | |
| 
 | |
|         case NSKeyDown:
 | |
|         case NSKeyUp:
 | |
|             if (!x11Active) {
 | |
|                 swallowedKey = 0;
 | |
|                 return NO;
 | |
|             }
 | |
| 
 | |
|             if (type == NSKeyDown) {
 | |
|                 // If the mouse is not on the valid X display area,
 | |
|                 // don't send the X server key events.
 | |
|                 if (![self getMousePosition:&xe fromEvent:nil]) {
 | |
|                     swallowedKey = [anEvent keyCode];
 | |
|                     return NO;
 | |
|                 }
 | |
| 
 | |
|                 // See if there are any global shortcuts for this key combo.
 | |
|                 if (quartzEnableKeyEquivalents
 | |
|                     && [[NSApp mainMenu] performKeyEquivalent:anEvent])
 | |
|                 {
 | |
|                     swallowedKey = [anEvent keyCode];
 | |
|                     return YES;
 | |
|                 }
 | |
|             } else {
 | |
|                 // If the down key event was a valid key combo,
 | |
|                 // don't pass the up event to X11.
 | |
|                 if (swallowedKey != 0 && [anEvent keyCode] == swallowedKey) {
 | |
|                     swallowedKey = 0;
 | |
|                     return NO;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             xe.u.u.type = (type == NSKeyDown) ? KeyPress : KeyRelease;
 | |
|             xe.u.u.detail = [anEvent keyCode];
 | |
|             break;
 | |
| 
 | |
|         case NSFlagsChanged:
 | |
|             if (!x11Active)
 | |
|                 return NO;
 | |
|             xe.u.u.type = kXDarwinUpdateModifiers;
 | |
|             xe.u.clientMessage.u.l.longs0 = flags;
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             return NO;
 | |
|     }
 | |
| 
 | |
|     [self sendXEvent:&xe];
 | |
| 
 | |
|     // Rootless: Send first NSLeftMouseDown to Cocoa windows and views so
 | |
|     // window ordering can be suppressed.
 | |
|     // Don't pass further events - they (incorrectly?) bring the window
 | |
|     // forward no matter what.
 | |
|     if (quartzRootless  &&
 | |
|         (type == NSLeftMouseDown || type == NSLeftMouseUp) &&
 | |
|         [anEvent clickCount] == 1 && [anEvent window])
 | |
|     {
 | |
|         return NO;
 | |
|     }
 | |
| 
 | |
|     return YES;
 | |
| }
 | |
| 
 | |
| // Return mouse coordinates, inverting y coordinate.
 | |
| // The coordinates are extracted from an event or the current mouse position.
 | |
| // For rootless mode, the menu bar is treated as not part of the usable
 | |
| // X display area and the cursor position is adjusted accordingly.
 | |
| // Returns YES if the cursor is not in the menu bar.
 | |
| - (BOOL)getMousePosition:(xEvent *)xe fromEvent:(NSEvent *)anEvent
 | |
| {
 | |
|     NSPoint pt;
 | |
| 
 | |
|     if (anEvent) {
 | |
|         NSWindow *eventWindow = [anEvent window];
 | |
| 
 | |
|         if (eventWindow) {
 | |
|             pt = [anEvent locationInWindow];
 | |
|             pt.x += [eventWindow frame].origin.x;
 | |
|             pt.y += [eventWindow frame].origin.y;
 | |
|         } else {
 | |
|             pt = [NSEvent mouseLocation];
 | |
|         }
 | |
|     } else {
 | |
|         pt = [NSEvent mouseLocation];
 | |
|     }
 | |
| 
 | |
|     xe->u.keyButtonPointer.rootX = (int)(pt.x);
 | |
| 
 | |
|     if (quartzRootless && NSMouseInRect(pt, aquaMenuBarBox, NO)) {
 | |
|         // mouse in menu bar - tell X11 that it's just below instead
 | |
|         xe->u.keyButtonPointer.rootY = aquaMenuBarHeight;
 | |
|         return NO;
 | |
|     } else {
 | |
|         xe->u.keyButtonPointer.rootY =
 | |
|             NSHeight([[NSScreen mainScreen] frame]) - (int)(pt.y);
 | |
|         return YES;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| // Make a safe path
 | |
| //
 | |
| // Return the path in single quotes in case there are problematic characters in it.
 | |
| // We still have to worry about there being single quotes in the path. So, replace
 | |
| // all instances of the ' character in the path with '\''.
 | |
| - (NSString *)makeSafePath:(NSString *)path
 | |
| {
 | |
|     NSMutableString *safePath = [NSMutableString stringWithString:path];
 | |
|     NSRange aRange = NSMakeRange(0, [safePath length]);
 | |
| 
 | |
|     while (aRange.length) {
 | |
|         aRange = [safePath rangeOfString:@"'" options:0 range:aRange];
 | |
|         if (!aRange.length)
 | |
|             break;
 | |
|         [safePath replaceCharactersInRange:aRange
 | |
|                         withString:@"\'\\'\'"];
 | |
|         aRange.location += 4;
 | |
|         aRange.length = [safePath length] - aRange.location;
 | |
|     }
 | |
| 
 | |
|     safePath = [NSMutableString stringWithFormat:@"'%@'", safePath];
 | |
| 
 | |
|     return safePath;
 | |
| }
 | |
| 
 | |
| 
 | |
| - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
 | |
| {
 | |
|     // Block SIGPIPE
 | |
|     // SIGPIPE repeatably killed the (rootless) server when closing a
 | |
|     // dozen xterms in rapid succession. Those SIGPIPEs should have been
 | |
|     // sent to the X server thread, which ignores them, but somehow they
 | |
|     // ended up in this thread instead.
 | |
|     {
 | |
|         sigset_t set;
 | |
|         sigemptyset(&set);
 | |
|         sigaddset(&set, SIGPIPE);
 | |
|         // pthread_sigmask not implemented yet
 | |
|         // pthread_sigmask(SIG_BLOCK, &set, NULL);
 | |
|         sigprocmask(SIG_BLOCK, &set, NULL);
 | |
|     }
 | |
| 
 | |
|     if (quartzRootless == -1) {
 | |
|         // The display mode was not set from the command line.
 | |
|         // Show mode pick panel?
 | |
|         if ([Preferences modeWindow]) {
 | |
|             if ([Preferences rootless])
 | |
|                 [startRootlessButton setKeyEquivalent:@"\r"];
 | |
|             else
 | |
|                 [startFullScreenButton setKeyEquivalent:@"\r"];
 | |
|             [modeWindow makeKeyAndOrderFront:nil];
 | |
|         } else {
 | |
|             // Otherwise use default mode
 | |
|             quartzRootless = [Preferences rootless];
 | |
|             [self startX];
 | |
|         }
 | |
|     } else {
 | |
|         [self startX];
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| // Load the appropriate display mode bundle
 | |
| - (BOOL)loadDisplayBundle
 | |
| {
 | |
|     if (quartzRootless) {
 | |
|         NSEnumerator *enumerator = [[Preferences displayModeBundles]
 | |
|                                             objectEnumerator];
 | |
|         NSString *bundleName;
 | |
| 
 | |
|         while ((bundleName = [enumerator nextObject])) {
 | |
|             if (QuartzLoadDisplayBundle([bundleName cString]))
 | |
|                 return YES;
 | |
|         }
 | |
| 
 | |
|         return NO;
 | |
|     } else {
 | |
|         return QuartzLoadDisplayBundle("fullscreen.bundle");
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| // Start the X server thread and the client process
 | |
| - (void)startX
 | |
| {
 | |
|     NSDictionary *appDictionary;
 | |
|     NSString *appVersion;
 | |
| 
 | |
|     [modeWindow close];
 | |
| 
 | |
|     // Calculate the height of the menu bar so rootless mode can avoid it
 | |
|     if (quartzRootless) {
 | |
|         aquaMenuBarHeight = NSHeight([[NSScreen mainScreen] frame]) -
 | |
|                             NSMaxY([[NSScreen mainScreen] visibleFrame]) - 1;
 | |
|         aquaMenuBarBox =
 | |
|             NSMakeRect(0, NSMaxY([[NSScreen mainScreen] visibleFrame]) + 1,
 | |
|                        NSWidth([[NSScreen mainScreen] frame]),
 | |
|                        aquaMenuBarHeight);
 | |
|     }
 | |
| 
 | |
|     // Write the XDarwin version to the console log
 | |
|     appDictionary = [[NSBundle mainBundle] infoDictionary];
 | |
|     appVersion = [appDictionary objectForKey:@"CFBundleShortVersionString"];
 | |
|     if (appVersion)
 | |
|         NSLog(@"\n%@", appVersion);
 | |
|     else
 | |
|         NSLog(@"No version");
 | |
| 
 | |
|     if (![self loadDisplayBundle])
 | |
|         [NSApp terminate:nil];
 | |
| 
 | |
|     if (quartzRootless) {
 | |
|         // We need to track whether the key window is an X11 window
 | |
|         [[NSNotificationCenter defaultCenter]
 | |
|                 addObserver:self
 | |
|                 selector:@selector(windowBecameKey:)
 | |
|                 name:NSWindowDidBecomeKeyNotification
 | |
|                 object:nil];
 | |
| 
 | |
|         // Request notification of screen layout changes even when this
 | |
|         // is not the active application
 | |
|         [[NSDistributedNotificationCenter defaultCenter]
 | |
|                 addObserver:self
 | |
|                 selector:@selector(applicationDidChangeScreenParameters:)
 | |
|                 name:NSApplicationDidChangeScreenParametersNotification
 | |
|                 object:nil];
 | |
|     }
 | |
| 
 | |
|     // Start the X server thread
 | |
|     serverState = server_Starting;
 | |
|     [NSThread detachNewThreadSelector:@selector(run) toTarget:self
 | |
|               withObject:nil];
 | |
| 
 | |
|     // Start the X clients if started from GUI
 | |
|     if (quartzStartClients) {
 | |
|         [self startXClients];
 | |
|     }
 | |
| 
 | |
|     if (quartzRootless) {
 | |
|         // There is no help window for rootless; just start
 | |
|         [helpWindow close];
 | |
|         helpWindow = nil;
 | |
|     } else {
 | |
|         IONotificationPortRef notify;
 | |
|         io_object_t anIterator;
 | |
| 
 | |
|         // Register for system power notifications
 | |
|         root_port = IORegisterForSystemPower(0, ¬ify, powerDidChange,
 | |
|                                              &anIterator);
 | |
|         if (root_port) {
 | |
|             CFRunLoopAddSource([[NSRunLoop currentRunLoop] getCFRunLoop],
 | |
|                                IONotificationPortGetRunLoopSource(notify),
 | |
|                                kCFRunLoopDefaultMode);
 | |
|         } else {
 | |
|             NSLog(@"Failed to register for system power notifications.");
 | |
|         }
 | |
|         
 | |
|         // Show the X switch window if not using dock icon switching
 | |
|         if (![Preferences dockSwitch])
 | |
|             [switchWindow orderFront:nil];
 | |
| 
 | |
|         if ([Preferences startupHelp]) {
 | |
|             // display the full screen mode help
 | |
|             [helpWindow makeKeyAndOrderFront:nil];
 | |
|             queueShowServer = NO;
 | |
|         } else {
 | |
|             // start running full screen and make sure X is visible
 | |
|             ShowMenuBar();
 | |
|             [self closeHelpAndShow:nil];
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Finish starting the X server thread
 | |
| // This includes anything that must be done after the X server is
 | |
| // ready to process events after the first or subsequent generations.
 | |
| - (void)finishStartX
 | |
| {
 | |
|     sendServerEvents = YES;
 | |
|     serverState = server_Running;
 | |
| 
 | |
|     if (quartzRootless) {
 | |
|         [self forceShowServer:[NSApp isActive]];
 | |
|     } else {
 | |
|         [self forceShowServer:queueShowServer];
 | |
|     }
 | |
| 
 | |
|     if (quartzServerQuitting) {
 | |
|         [self quitServer];
 | |
|         if (pendingAppQuitReply)
 | |
|             [NSApp replyToApplicationShouldTerminate:YES];
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (pendingClients) {
 | |
|         NSEnumerator *enumerator = [pendingClients objectEnumerator];
 | |
|         NSString *filename;
 | |
| 
 | |
|         while ((filename = [enumerator nextObject])) {
 | |
|             [self runClient:filename];
 | |
|         }
 | |
| 
 | |
|         [pendingClients release];
 | |
|         pendingClients = nil;
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Start the first X clients in a separate process
 | |
| - (BOOL)startXClients
 | |
| {
 | |
|     struct passwd *passwdUser;
 | |
|     NSString *shellPath, *dashShellName, *commandStr, *startXPath;
 | |
|     NSString *safeStartXPath;
 | |
|     NSBundle *thisBundle;
 | |
|     const char *shellPathStr, *newargv[3], *shellNameStr;
 | |
|     int fd[2], outFD, length, shellType, i;
 | |
| 
 | |
|     // Register to catch the signal when the client processs finishes
 | |
|     signal(SIGCHLD, childDone);
 | |
| 
 | |
|     // Get user's password database entry
 | |
|     passwdUser = getpwuid(getuid());
 | |
| 
 | |
|     // Find the shell to use
 | |
|     if ([Preferences useDefaultShell])
 | |
|         shellPath = [NSString stringWithCString:passwdUser->pw_shell];
 | |
|     else
 | |
|         shellPath = [Preferences shellString];
 | |
| 
 | |
|     dashShellName = [NSString stringWithFormat:@"-%@",
 | |
|                             [shellPath lastPathComponent]];
 | |
|     shellPathStr = [shellPath cString];
 | |
|     shellNameStr = [[shellPath lastPathComponent] cString];
 | |
| 
 | |
|     if (access(shellPathStr, X_OK)) {
 | |
|         NSLog(@"Shell %s is not valid!", shellPathStr);
 | |
|         return NO;
 | |
|     }
 | |
| 
 | |
|     // Find the type of shell
 | |
|     for (i = 0; shellList[i].name; i++) {
 | |
|         if (!strcmp(shellNameStr, shellList[i].name))
 | |
|             break;
 | |
|     }
 | |
|     shellType = shellList[i].type;
 | |
| 
 | |
|     newargv[0] = [dashShellName cString];
 | |
|     if (shellType == shell_Bourne) {
 | |
|         // Bourne shells need to be told they are interactive to make
 | |
|         // sure they read all their initialization files.
 | |
|         newargv[1] = "-i";
 | |
|         newargv[2] = NULL;
 | |
|     } else {
 | |
|         newargv[1] = NULL;
 | |
|     }
 | |
| 
 | |
|     // Create a pipe to communicate with the X client process
 | |
|     NSAssert(pipe(fd) == 0, @"Could not create new pipe.");
 | |
| 
 | |
|     // Open a file descriptor for writing to stdout and stderr
 | |
|     outFD = open("/dev/console", O_WRONLY, 0);
 | |
|     if (outFD == -1) {
 | |
|         outFD = open("/dev/null", O_WRONLY, 0);
 | |
|         NSAssert(outFD != -1, @"Could not open shell output.");
 | |
|     }
 | |
| 
 | |
|     // Fork process to start X clients in user's default shell
 | |
|     // Sadly we can't use NSTask because we need to start a login shell.
 | |
|     // Login shells are started by passing "-" as the first character of
 | |
|     // argument 0. NSTask forces argument 0 to be the shell's name.
 | |
|     clientPID = vfork();
 | |
|     if (clientPID == 0) {
 | |
| 
 | |
|         // Inside the new process:
 | |
|         if (fd[0] != STDIN_FILENO) {
 | |
|             dup2(fd[0], STDIN_FILENO);      // Take stdin from pipe
 | |
|             close(fd[0]);
 | |
|         }
 | |
|         close(fd[1]);                       // Close write end of pipe
 | |
|         if (outFD == STDOUT_FILENO) {       // Setup stdout and stderr
 | |
|             dup2(outFD, STDERR_FILENO);
 | |
|         } else if (outFD == STDERR_FILENO) {
 | |
|             dup2(outFD, STDOUT_FILENO);
 | |
|         } else {
 | |
|             dup2(outFD, STDERR_FILENO);
 | |
|             dup2(outFD, STDOUT_FILENO);
 | |
|             close(outFD);
 | |
|         }
 | |
| 
 | |
|         // Setup environment
 | |
|         setenv("HOME", passwdUser->pw_dir, 1);
 | |
|         setenv("SHELL", shellPathStr, 1);
 | |
|         setenv("LOGNAME", passwdUser->pw_name, 1);
 | |
|         setenv("USER", passwdUser->pw_name, 1);
 | |
|         setenv("TERM", "unknown", 1);
 | |
|         if (chdir(passwdUser->pw_dir))	// Change to user's home dir
 | |
|             NSLog(@"Could not change to user's home directory.");
 | |
| 
 | |
|         execv(shellPathStr, (char * const *)newargv);	// Start user's shell
 | |
| 
 | |
|         NSLog(@"Could not start X client process with errno = %i.", errno);
 | |
|         _exit(127);
 | |
|     }
 | |
| 
 | |
|     // In parent process:
 | |
|     close(fd[0]);	// Close read end of pipe
 | |
|     close(outFD);	// Close output file descriptor
 | |
| 
 | |
|     thisBundle = [NSBundle bundleForClass:[self class]];
 | |
|     startXPath = [thisBundle pathForResource:@"startXClients" ofType:nil];
 | |
|     if (!startXPath) {
 | |
|         NSLog(@"Could not find startXClients in application bundle!");
 | |
|         return NO;
 | |
|     }
 | |
| 
 | |
|     safeStartXPath = [self makeSafePath:startXPath];
 | |
| 
 | |
|     if ([Preferences addToPath]) {
 | |
|         commandStr = [NSString stringWithFormat:@"%@ :%d %@\n",
 | |
|                         safeStartXPath, [Preferences display],
 | |
|                         [Preferences addToPathString]];
 | |
|     } else {
 | |
|         commandStr = [NSString stringWithFormat:@"%@ :%d\n",
 | |
|                         safeStartXPath, [Preferences display]];
 | |
|     }
 | |
| 
 | |
|     length = [commandStr cStringLength];
 | |
|     if (write(fd[1], [commandStr cString], length) != length) {
 | |
|         NSLog(@"Write to X client process failed.");
 | |
|         return NO;
 | |
|     }
 | |
| 
 | |
|     // Close the pipe so that shell will terminate when xinit quits
 | |
|     close(fd[1]);
 | |
| 
 | |
|     return YES;
 | |
| }
 | |
| 
 | |
| // Start the specified client in its own task
 | |
| // FIXME: This should be unified with startXClients
 | |
| - (void)runClient:(NSString *)filename
 | |
| {
 | |
|     const char *command = [[self makeSafePath:filename] UTF8String];
 | |
|     const char *shell;
 | |
|     const char *argv[5];
 | |
|     int child1, child2 = 0;
 | |
|     int status;
 | |
| 
 | |
|     shell = getenv("SHELL");
 | |
|     if (shell == NULL)
 | |
|         shell = "/bin/bash";
 | |
| 
 | |
|     /* At least [ba]sh, [t]csh and zsh all work with this syntax. We
 | |
|        need to use an interactive shell to force it to load the user's
 | |
|        environment. */
 | |
| 
 | |
|     argv[0] = shell;
 | |
|     argv[1] = "-i";
 | |
|     argv[2] = "-c";
 | |
|     argv[3] = command;
 | |
|     argv[4] = NULL;
 | |
| 
 | |
|     /* Do the fork-twice trick to avoid having to reap zombies */
 | |
| 
 | |
|     child1 = fork();
 | |
| 
 | |
|     switch (child1) {
 | |
|         case -1:                                /* error */
 | |
|             break;
 | |
| 
 | |
|         case 0:                                 /* child1 */
 | |
|             child2 = fork();
 | |
| 
 | |
|             switch (child2) {
 | |
|                 int max_files, i;
 | |
|                 char buf[1024], *tem;
 | |
| 
 | |
|             case -1:                            /* error */
 | |
|                 _exit(1);
 | |
| 
 | |
|             case 0:                             /* child2 */
 | |
|                 /* close all open files except for standard streams */
 | |
|                 max_files = sysconf(_SC_OPEN_MAX);
 | |
|                 for (i = 3; i < max_files; i++)
 | |
|                     close(i);
 | |
| 
 | |
|                 /* ensure stdin is on /dev/null */
 | |
|                 close(0);
 | |
|                 open("/dev/null", O_RDONLY);
 | |
| 
 | |
|                 /* cd $HOME */
 | |
|                 tem = getenv("HOME");
 | |
|                 if (tem != NULL)
 | |
|                     chdir(tem);
 | |
| 
 | |
|                 /* Setup environment */
 | |
| //              snprintf(buf, sizeof(buf), ":%s", display);
 | |
| //              setenv("DISPLAY", buf, TRUE);
 | |
|                 tem = getenv("PATH");
 | |
|                 if (tem != NULL && tem[0] != NULL)
 | |
|                     snprintf(buf, sizeof(buf), "%s:/usr/X11/bin", tem);
 | |
|                 else
 | |
|                     snprintf(buf, sizeof(buf), "/bin:/usr/bin:/usr/X11/bin");
 | |
|                 setenv("PATH", buf, TRUE);
 | |
| 
 | |
|                 execvp(argv[0], (char **const) argv);
 | |
| 
 | |
|                 _exit(2);
 | |
| 
 | |
|             default:                            /* parent (child1) */
 | |
|                 _exit(0);
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         default:                                /* parent */
 | |
|             waitpid(child1, &status, 0);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Run the X server thread
 | |
| - (void)run
 | |
| {
 | |
|     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 | |
| 
 | |
|     [serverLock lock];
 | |
|     main(argcGlobal, argvGlobal, envpGlobal);
 | |
|     serverVisible = NO;
 | |
|     [pool release];
 | |
|     [serverLock unlock];
 | |
|     QuartzMessageMainThread(kQuartzServerDied, nil, 0);
 | |
| }
 | |
| 
 | |
| // Full screen mode was picked in the mode pick panel
 | |
| - (IBAction)startFullScreen:(id)sender
 | |
| {
 | |
|     [Preferences setModeWindow:[startupModeButton intValue]];
 | |
|     [Preferences saveToDisk];
 | |
|     quartzRootless = FALSE;
 | |
|     [self startX];
 | |
| }
 | |
| 
 | |
| // Rootless mode was picked in the mode pick panel
 | |
| - (IBAction)startRootless:(id)sender
 | |
| {
 | |
|     [Preferences setModeWindow:[startupModeButton intValue]];
 | |
|     [Preferences saveToDisk];
 | |
|     quartzRootless = TRUE;
 | |
|     [self startX];
 | |
| }
 | |
| 
 | |
| // Close the help splash screen and show the X server
 | |
| - (IBAction)closeHelpAndShow:(id)sender
 | |
| {
 | |
|     if (sender) {
 | |
|         int helpVal = [startupHelpButton intValue];
 | |
|         [Preferences setStartupHelp:helpVal];
 | |
|         [Preferences saveToDisk];
 | |
|     }
 | |
|     [helpWindow close];
 | |
|     helpWindow = nil;
 | |
| 
 | |
|     [self forceShowServer:YES];
 | |
|     [NSApp activateIgnoringOtherApps:YES];
 | |
| }
 | |
| 
 | |
| // Show the Aqua-X11 switch panel useful for fullscreen mode
 | |
| - (IBAction)showSwitchPanel:(id)sender
 | |
| {
 | |
|     [switchWindow orderFront:nil];
 | |
| }
 | |
| 
 | |
| // Show the X server when sent message from GUI
 | |
| - (IBAction)showAction:(id)sender
 | |
| {
 | |
|     [self forceShowServer:YES];
 | |
| }
 | |
| 
 | |
| // Show or hide the X server or menu bar in rootless mode
 | |
| - (void)toggle
 | |
| {
 | |
|     if (quartzRootless) {
 | |
| #if 0
 | |
|         // FIXME: Remove or add option to not dodge menubar
 | |
|         if (rootlessMenuBarVisible)
 | |
|             HideMenuBar();
 | |
|         else
 | |
|             ShowMenuBar();
 | |
|         rootlessMenuBarVisible = !rootlessMenuBarVisible;
 | |
| #endif
 | |
|     } else {
 | |
|         [self showServer:!serverVisible];
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Show or hide the X server on screen
 | |
| - (void)showServer:(BOOL)show
 | |
| {
 | |
|     // Do not show or hide multiple times in a row
 | |
|     if (serverVisible == show)
 | |
|         return;
 | |
| 
 | |
|     if (sendServerEvents) {
 | |
|         [self sendShowHide:show];
 | |
|     } else if (serverState == server_Starting) {
 | |
|         queueShowServer = show;
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Show or hide the X server irregardless of the current state
 | |
| - (void)forceShowServer:(BOOL)show
 | |
| {
 | |
|     serverVisible = !show;
 | |
|     [self showServer:show];
 | |
| }
 | |
| 
 | |
| // Tell the X server to show or hide itself.
 | |
| // This ignores the current X server visible state.
 | |
| //
 | |
| // In full screen mode, the order we do things is important and must be
 | |
| // preserved between the threads. X drawing operations have to be performed
 | |
| // in the X server thread. It appears that we have the additional
 | |
| // constraint that we must hide and show the menu bar in the main thread.
 | |
| //
 | |
| // To show the X server:
 | |
| //   1. Capture the displays. (Main thread)
 | |
| //   2. Hide the menu bar. (Must be in main thread)
 | |
| //   3. Send event to X server thread to redraw X screen.
 | |
| //   4. Redraw the X screen. (Must be in X server thread)
 | |
| //
 | |
| // To hide the X server:
 | |
| //   1. Send event to X server thread to stop drawing.
 | |
| //   2. Stop drawing to the X screen. (Must be in X server thread)
 | |
| //   3. Message main thread that drawing is stopped.
 | |
| //   4. If main thread still wants X server hidden:
 | |
| //     a. Release the displays. (Main thread)
 | |
| //     b. Unhide the menu bar. (Must be in main thread)
 | |
| //   Otherwise we have already queued an event to start drawing again.
 | |
| //
 | |
| - (void)sendShowHide:(BOOL)show
 | |
| {
 | |
|     xEvent xe;
 | |
| 
 | |
|     [self getMousePosition:&xe fromEvent:nil];
 | |
| 
 | |
|     if (show) {
 | |
|         if (!quartzRootless) {
 | |
|             quartzProcs->CaptureScreens();
 | |
|             HideMenuBar();
 | |
|         }
 | |
|         [self activateX11:YES];
 | |
| 
 | |
|         // the mouse location will have moved; track it
 | |
|         xe.u.u.type = MotionNotify;
 | |
|         [self sendXEvent:&xe];
 | |
| 
 | |
|         // inform the X server of the current modifier state
 | |
|         xe.u.u.type = kXDarwinUpdateModifiers;
 | |
|         xe.u.clientMessage.u.l.longs0 = [[NSApp currentEvent] modifierFlags];
 | |
|         [self sendXEvent:&xe];
 | |
| 
 | |
|         // If there is no AppleWM-aware cut and paste manager, do what we can.
 | |
|         if ((AppleWMSelectedEvents() & AppleWMPasteboardNotifyMask) == 0) {
 | |
|             // put the pasteboard into the X cut buffer
 | |
|             [self readPasteboard];
 | |
|         }
 | |
|     } else {
 | |
|         // If there is no AppleWM-aware cut and paste manager, do what we can.
 | |
|         if ((AppleWMSelectedEvents() & AppleWMPasteboardNotifyMask) == 0) {
 | |
|             // put the X cut buffer on the pasteboard
 | |
|             [self writePasteboard];
 | |
|         }
 | |
| 
 | |
|         [self activateX11:NO];
 | |
|     }
 | |
| 
 | |
|     serverVisible = show;
 | |
| }
 | |
| 
 | |
| // Enable or disable rendering to the X screen
 | |
| - (void)setRootClip:(BOOL)enable
 | |
| {
 | |
|     xEvent xe;
 | |
| 
 | |
|     xe.u.u.type = kXDarwinSetRootClip;
 | |
|     xe.u.clientMessage.u.l.longs0 = enable;
 | |
|     [self sendXEvent:&xe];
 | |
| }
 | |
| 
 | |
| // Tell the X server to read from the pasteboard into the X cut buffer
 | |
| - (void)readPasteboard
 | |
| {
 | |
|     xEvent xe;
 | |
| 
 | |
|     xe.u.u.type = kXDarwinReadPasteboard;
 | |
|     [self sendXEvent:&xe];
 | |
| }
 | |
| 
 | |
| // Tell the X server to write the X cut buffer into the pasteboard
 | |
| - (void)writePasteboard
 | |
| {
 | |
|     xEvent xe;
 | |
| 
 | |
|     xe.u.u.type = kXDarwinWritePasteboard;
 | |
|     [self sendXEvent:&xe];
 | |
| }
 | |
| 
 | |
| - (void)quitServer
 | |
| {
 | |
|     xEvent xe;
 | |
| 
 | |
|     xe.u.u.type = kXDarwinQuit;
 | |
|     [self sendXEvent:&xe];
 | |
| 
 | |
|     // Revert to the Mac OS X arrow cursor. The main thread sets the cursor
 | |
|     // and it won't be responding to future requests to change it.
 | |
|     [[NSCursor arrowCursor] set];
 | |
| 
 | |
|     serverState = server_Quitting;
 | |
| }
 | |
| 
 | |
| - (void)sendXEvent:(xEvent *)xe
 | |
| {
 | |
|     // This field should be filled in for every event
 | |
|     xe->u.keyButtonPointer.time = GetTimeInMillis();
 | |
| 
 | |
|     DarwinEQEnqueue(xe);
 | |
| }
 | |
| 
 | |
| // Handle messages from the X server thread
 | |
| - (void)handlePortMessage:(NSPortMessage *)portMessage
 | |
| {
 | |
|     unsigned msg = [portMessage msgid];
 | |
| 
 | |
|     switch (msg) {
 | |
|         case kQuartzServerHidden:
 | |
|             // Make sure the X server wasn't queued to be shown again while
 | |
|             // the hide was pending.
 | |
|             if (!quartzRootless && !serverVisible) {
 | |
|                 quartzProcs->ReleaseScreens();
 | |
|                 ShowMenuBar();
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case kQuartzServerStarted:
 | |
|             [self finishStartX];
 | |
|             break;
 | |
| 
 | |
|         case kQuartzServerDied:
 | |
|             sendServerEvents = NO;
 | |
|             serverState = server_Done;
 | |
|             if (!quartzServerQuitting) {
 | |
|                 [NSApp terminate:nil];	// quit if we aren't already
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case kQuartzCursorUpdate:
 | |
|             if (quartzProcs->CursorUpdate)
 | |
|                 quartzProcs->CursorUpdate();
 | |
|             break;
 | |
| 
 | |
|         case kQuartzPostEvent:
 | |
|         {
 | |
|             const xEvent *xe = [[[portMessage components] lastObject] bytes];
 | |
|             DarwinEQEnqueue(xe);
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         case kQuartzSetWindowMenu:
 | |
|         {
 | |
|             NSArray *list;
 | |
|             [[[portMessage components] lastObject] getBytes:&list];
 | |
|             [self setX11WindowList:list];
 | |
|             [list release];
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         case kQuartzSetWindowMenuCheck:
 | |
|         {
 | |
|             int n;
 | |
|             [[[portMessage components] lastObject] getBytes:&n];
 | |
|             [self setX11WindowCheck:[NSNumber numberWithInt:n]];
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         case kQuartzSetFrontProcess:
 | |
|             [NSApp activateIgnoringOtherApps:YES];
 | |
|             break;
 | |
| 
 | |
|         case kQuartzSetCanQuit:
 | |
|         {
 | |
|             int n;
 | |
|             [[[portMessage components] lastObject] getBytes:&n];
 | |
|             quitWithoutQuery = (BOOL) n;
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         default:
 | |
|             NSLog(@"Unknown message from server thread.");
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Quit the X server when the X client process finishes
 | |
| - (void)clientProcessDone:(int)clientStatus
 | |
| {
 | |
|     if (WIFEXITED(clientStatus)) {
 | |
|         int exitStatus = WEXITSTATUS(clientStatus);
 | |
|         if (exitStatus != 0)
 | |
|             NSLog(@"X client process terminated with status %i.", exitStatus);
 | |
|     } else {
 | |
|         NSLog(@"X client process terminated abnormally.");
 | |
|     }
 | |
| 
 | |
|     if (!quartzServerQuitting) {
 | |
|         [NSApp terminate:nil];	// quit if we aren't already
 | |
|     }
 | |
| }
 | |
| 
 | |
| // User selected an X11 window from a menu
 | |
| - (IBAction)itemSelected:(id)sender
 | |
| {
 | |
|     xEvent xe;
 | |
| 
 | |
|     [NSApp activateIgnoringOtherApps:YES];
 | |
| 
 | |
|     // Notify the client of the change through the X server thread
 | |
|     xe.u.u.type = kXDarwinControllerNotify;
 | |
|     xe.u.clientMessage.u.l.longs0 = AppleWMWindowMenuItem;
 | |
|     xe.u.clientMessage.u.l.longs1 = [sender tag];
 | |
|     [self sendXEvent:&xe];
 | |
| }
 | |
| 
 | |
| // User selected Next from window menu
 | |
| - (IBAction)nextWindow:(id)sender
 | |
| {
 | |
|     QuartzMessageServerThread(kXDarwinControllerNotify, 1,
 | |
|                               AppleWMNextWindow);
 | |
| }
 | |
| 
 | |
| // User selected Previous from window menu
 | |
| - (IBAction)previousWindow:(id)sender
 | |
| {
 | |
|     QuartzMessageServerThread(kXDarwinControllerNotify, 1,
 | |
|                               AppleWMPreviousWindow);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * The XPR implementation handles close, minimize, and zoom actions for X11
 | |
|  * windows here, while CR handles these in the NSWindow class.
 | |
|  */
 | |
| 
 | |
| // Handle Close from window menu for X11 window in XPR implementation
 | |
| - (IBAction)performClose:(id)sender
 | |
| {
 | |
|     QuartzMessageServerThread(kXDarwinControllerNotify, 1,
 | |
|                               AppleWMCloseWindow);
 | |
| }
 | |
| 
 | |
| // Handle Minimize from window menu for X11 window in XPR implementation
 | |
| - (IBAction)performMiniaturize:(id)sender
 | |
| {
 | |
|     QuartzMessageServerThread(kXDarwinControllerNotify, 1,
 | |
|                               AppleWMMinimizeWindow);
 | |
| }
 | |
| 
 | |
| // Handle Zoom from window menu for X11 window in XPR implementation
 | |
| - (IBAction)performZoom:(id)sender
 | |
| {
 | |
|     QuartzMessageServerThread(kXDarwinControllerNotify, 1,
 | |
|                               AppleWMZoomWindow);
 | |
| }
 | |
| 
 | |
| // Handle "Bring All to Front" from window menu
 | |
| - (IBAction)bringAllToFront:(id)sender
 | |
| {
 | |
|     if ((AppleWMSelectedEvents() & AppleWMControllerNotifyMask) != 0) {
 | |
|         QuartzMessageServerThread(kXDarwinControllerNotify, 1,
 | |
|                                   AppleWMBringAllToFront);
 | |
|     } else {
 | |
|         [NSApp arrangeInFront:nil];
 | |
|     }
 | |
| }
 | |
| 
 | |
| // This ends up at the end of the responder chain.
 | |
| - (IBAction)copy:(id)sender
 | |
| {
 | |
|     QuartzMessageServerThread(kXDarwinPasteboardNotify, 1,
 | |
|                               AppleWMCopyToPasteboard);
 | |
| }
 | |
| 
 | |
| // Set whether or not X11 is active and should receive all key events
 | |
| - (void)activateX11:(BOOL)state
 | |
| {
 | |
|     if (state) {
 | |
|         QuartzMessageServerThread(kXDarwinActivate, 0);
 | |
|     }
 | |
|     else {
 | |
|         QuartzMessageServerThread(kXDarwinDeactivate, 0);
 | |
|     }
 | |
| 
 | |
|     x11Active = state;
 | |
| }
 | |
| 
 | |
| // Some NSWindow became the key window
 | |
| - (void)windowBecameKey:(NSNotification *)notification
 | |
| {
 | |
|     NSWindow *window = [notification object];
 | |
| 
 | |
|     if (quartzProcs->IsX11Window(window, [window windowNumber])) {
 | |
|         if (!x11Active)
 | |
|             [self activateX11:YES];
 | |
|     } else {
 | |
|         if (x11Active)
 | |
|             [self activateX11:NO];
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Set the Apple-WM specifiable part of the window menu
 | |
| - (void)setX11WindowList:(NSArray *)list
 | |
| {
 | |
|     NSMenuItem *item;
 | |
|     int first, count, i;
 | |
|     xEvent xe;
 | |
| 
 | |
|     /* Work backwards so we don't mess up the indices */
 | |
|     first = [windowMenu indexOfItem:windowSeparator] + 1;
 | |
|     if (first > 0) {
 | |
|         count = [windowMenu numberOfItems];
 | |
|         for (i = count - 1; i >= first; i--)
 | |
|             [windowMenu removeItemAtIndex:i];
 | |
|     } else {
 | |
|         windowSeparator = (NSMenuItem *)[windowMenu addItemWithTitle:@""
 | |
|                                                     action:nil
 | |
|                                                     keyEquivalent:@""];
 | |
|     }
 | |
| 
 | |
|     count = [dockMenu numberOfItems];
 | |
|     for (i = 0; i < count; i++)
 | |
|         [dockMenu removeItemAtIndex:0];
 | |
| 
 | |
|     count = [list count];
 | |
| 
 | |
|     for (i = 0; i < count; i++)
 | |
|     {
 | |
|         NSString *name, *shortcut;
 | |
| 
 | |
|         name = [[list objectAtIndex:i] objectAtIndex:0];
 | |
|         shortcut = [[list objectAtIndex:i] objectAtIndex:1];
 | |
| 
 | |
|         item = (NSMenuItem *)[windowMenu addItemWithTitle:name
 | |
|                                          action:@selector(itemSelected:)
 | |
|                                          keyEquivalent:shortcut];
 | |
|         [item setTarget:self];
 | |
|         [item setTag:i];
 | |
|         [item setEnabled:YES];
 | |
| 
 | |
|         item = (NSMenuItem *)[dockMenu insertItemWithTitle:name
 | |
|                                        action:@selector(itemSelected:)
 | |
|                                        keyEquivalent:shortcut atIndex:i];
 | |
|         [item setTarget:self];
 | |
|         [item setTag:i];
 | |
|         [item setEnabled:YES];
 | |
|     }
 | |
| 
 | |
|     if (checkedWindowItem >= 0 && checkedWindowItem < count)
 | |
|     {
 | |
|         item = (NSMenuItem *)[windowMenu itemAtIndex:first + checkedWindowItem];
 | |
|         [item setState:NSOnState];
 | |
|         item = (NSMenuItem *)[dockMenu itemAtIndex:checkedWindowItem];
 | |
|         [item setState:NSOnState];
 | |
|     }
 | |
| 
 | |
|     // Notify the client of the change through the X server thread
 | |
|     xe.u.u.type = kXDarwinControllerNotify;
 | |
|     xe.u.clientMessage.u.l.longs0 = AppleWMWindowMenuNotify;
 | |
|     [self sendXEvent:&xe];
 | |
| }
 | |
| 
 | |
| // Set the checked item on the Apple-WM specifiable window menu
 | |
| - (void)setX11WindowCheck:(NSNumber *)nn
 | |
| {
 | |
|     NSMenuItem *item;
 | |
|     int first, count;
 | |
|     int n = [nn intValue];
 | |
| 
 | |
|     first = [windowMenu indexOfItem:windowSeparator] + 1;
 | |
|     count = [windowMenu numberOfItems] - first;
 | |
| 
 | |
|     if (checkedWindowItem >= 0 && checkedWindowItem < count)
 | |
|     {
 | |
|         item = (NSMenuItem *)[windowMenu itemAtIndex:first + checkedWindowItem];
 | |
|         [item setState:NSOffState];
 | |
|         item = (NSMenuItem *)[dockMenu itemAtIndex:checkedWindowItem];
 | |
|         [item setState:NSOffState];
 | |
|     }
 | |
|     if (n >= 0 && n < count)
 | |
|     {
 | |
|         item = (NSMenuItem *)[windowMenu itemAtIndex:first + n];
 | |
|         [item setState:NSOnState];
 | |
|         item = (NSMenuItem *)[dockMenu itemAtIndex:n];
 | |
|         [item setState:NSOnState];
 | |
|     }
 | |
|     checkedWindowItem = n;
 | |
| }
 | |
| 
 | |
| // Return whether or not a menu item should be enabled
 | |
| - (BOOL)validateMenuItem:(NSMenuItem *)item
 | |
| {
 | |
|     NSMenu *menu = [item menu];
 | |
| 
 | |
|     if (menu == windowMenu && [item tag] == 30) {
 | |
|         // Mode switch panel is for fullscreen only
 | |
|         return !quartzRootless;
 | |
|     }
 | |
|     else if ((menu == windowMenu && [item tag] != 40) || menu == dockMenu) {
 | |
|         // The special window and dock menu items should not be active unless
 | |
|         // there is an AppleWM-aware window manager running.
 | |
|         return (AppleWMSelectedEvents() & AppleWMControllerNotifyMask) != 0;
 | |
|     }
 | |
|     else {
 | |
|         return TRUE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Application Delegate Methods
 | |
|  */
 | |
| 
 | |
| - (void)applicationDidChangeScreenParameters:(NSNotification *)aNotification
 | |
| {
 | |
|     if (quartzProcs->ScreenChanged)
 | |
|         quartzProcs->ScreenChanged();
 | |
| }
 | |
| 
 | |
| - (void)applicationDidHide:(NSNotification *)aNotification
 | |
| {
 | |
|     if ((AppleWMSelectedEvents() & AppleWMControllerNotifyMask) != 0) {
 | |
|         QuartzMessageServerThread(kXDarwinControllerNotify, 1,
 | |
|                                   AppleWMHideAll);
 | |
|     } else {
 | |
|         if (quartzProcs->HideWindows)
 | |
|             quartzProcs->HideWindows(YES);
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (void)applicationDidUnhide:(NSNotification *)aNotification
 | |
| {
 | |
|     if ((AppleWMSelectedEvents() & AppleWMControllerNotifyMask) != 0) {
 | |
|         QuartzMessageServerThread(kXDarwinControllerNotify, 1,
 | |
|                                   AppleWMShowAll);
 | |
|     } else {
 | |
|         if (quartzProcs->HideWindows)
 | |
|             quartzProcs->HideWindows(NO);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Called when the user clicks the application icon,
 | |
| // but not when Cmd-Tab is used.
 | |
| // Rootless: Don't switch until applicationWillBecomeActive.
 | |
| - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication
 | |
|             hasVisibleWindows:(BOOL)flag
 | |
| {
 | |
|     if ([Preferences dockSwitch] && !quartzRootless) {
 | |
|         [self showServer:YES];
 | |
|     }
 | |
|     return NO;
 | |
| }
 | |
| 
 | |
| - (void)applicationWillResignActive:(NSNotification *)aNotification
 | |
| {
 | |
|     [self showServer:NO];
 | |
| }
 | |
| 
 | |
| - (void)applicationWillBecomeActive:(NSNotification *)aNotification
 | |
| {
 | |
|     if (quartzRootless) {
 | |
|         [self showServer:YES];
 | |
| 
 | |
|         // If there is no AppleWM-aware window manager, we can't allow
 | |
|         // interleaving of Aqua and X11 windows.
 | |
|         if ((AppleWMSelectedEvents() & AppleWMControllerNotifyMask) == 0) {
 | |
|             [NSApp arrangeInFront:nil];
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Called when the user opens a document type that we claim (ie. an X11 executable).
 | |
| - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
 | |
| {
 | |
|     if (serverState == server_Running) {
 | |
|         [self runClient:filename];
 | |
|         return YES;
 | |
|     }
 | |
|     else if (serverState == server_NotStarted || serverState == server_Starting) {
 | |
|         if ([filename UTF8String][0] != ':') {          // Ignore display names
 | |
|             if (!pendingClients) {
 | |
|                 pendingClients = [[NSMutableArray alloc] initWithCapacity:1];
 | |
|             }
 | |
|             [pendingClients addObject:filename];
 | |
|             return YES;                 // Assume it will launch successfully
 | |
|         }
 | |
|         return NO;
 | |
|     }
 | |
| 
 | |
|     // If the server is quitting or done,
 | |
|     // its too late to launch new clients this time.
 | |
|     return NO;
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| 
 | |
| // Send a message to the main thread, which calls handlePortMessage in
 | |
| // response. Must only be called from the X server thread because
 | |
| // NSPort is not thread safe.
 | |
| void QuartzMessageMainThread(unsigned msg, void *data, unsigned length)
 | |
| {
 | |
|     if (length > 0) {
 | |
|         NSData *eventData = [NSData dataWithBytes:data length:length];
 | |
|         NSArray *eventArray = [NSArray arrayWithObject:eventData];
 | |
|         NSPortMessage *newMessage =
 | |
|                 [[NSPortMessage alloc]
 | |
|                         initWithSendPort:signalPort
 | |
|                         receivePort:returnPort components:eventArray];
 | |
|         [newMessage setMsgid:msg];
 | |
|         [newMessage sendBeforeDate:[NSDate distantPast]];
 | |
|         [newMessage release];
 | |
|     } else {
 | |
|         [signalMessage setMsgid:msg];
 | |
|         [signalMessage sendBeforeDate:[NSDate distantPast]];
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| QuartzSetWindowMenu(int nitems, const char **items,
 | |
|                     const char *shortcuts)
 | |
| {
 | |
|     NSMutableArray *array;
 | |
|     int i;
 | |
| 
 | |
|     array = [[NSMutableArray alloc] initWithCapacity:nitems];
 | |
| 
 | |
|     for (i = 0; i < nitems; i++) {
 | |
|         NSMutableArray *subarray = [NSMutableArray arrayWithCapacity:2];
 | |
|         NSString *string = [NSString stringWithUTF8String:items[i]];
 | |
| 
 | |
|         [subarray addObject:string];
 | |
| 
 | |
|         if (shortcuts[i] != 0) {
 | |
|             NSString *number = [NSString stringWithFormat:@"%d",
 | |
|                                          shortcuts[i]];
 | |
|             [subarray addObject:number];
 | |
|         } else
 | |
|             [subarray addObject:@""];
 | |
| 
 | |
|         [array addObject:subarray];
 | |
|     }
 | |
| 
 | |
|     /* Send the array of strings over to the main thread. */
 | |
|     /* Will be released in main thread. */
 | |
|     QuartzMessageMainThread(kQuartzSetWindowMenu, &array, sizeof(NSArray *));
 | |
| }
 | |
| 
 | |
| // Handle SIGCHLD signals
 | |
| static void childDone(int sig)
 | |
| {
 | |
|     int clientStatus;
 | |
| 
 | |
|     if (clientPID == 0)
 | |
|         return;
 | |
| 
 | |
|     // Make sure it was the client task that finished
 | |
|     if (waitpid(clientPID, &clientStatus, WNOHANG) == clientPID) {
 | |
|         if (WIFSTOPPED(clientStatus))
 | |
|             return;
 | |
|         clientPID = 0;
 | |
|         [oneXServer clientProcessDone:clientStatus];
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void powerDidChange(
 | |
|     void *x,
 | |
|     io_service_t y,
 | |
|     natural_t messageType,
 | |
|     void *messageArgument)
 | |
| {
 | |
|     switch (messageType) {
 | |
|         case kIOMessageSystemWillSleep:
 | |
|             if (!quartzRootless) {
 | |
|                 [oneXServer setRootClip:FALSE];
 | |
|             }
 | |
|             IOAllowPowerChange(root_port, (long)messageArgument);
 | |
|             break;
 | |
|         case kIOMessageCanSystemSleep:
 | |
|             IOAllowPowerChange(root_port, (long)messageArgument);
 | |
|             break;
 | |
|         case kIOMessageSystemHasPoweredOn:
 | |
|             if (!quartzRootless) {
 | |
|                 [oneXServer setRootClip:TRUE];
 | |
|             }
 | |
|             break;
 | |
|     }
 | |
| 
 | |
| }
 |