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 $ */
|
|
/* $XFree86: xc/programs/Xserver/hw/darwin/quartz/XServer.m,v 1.19 2003/11/24 05:39:01 torrey Exp $ */
|
|
|
|
#include "quartzCommon.h"
|
|
|
|
#define BOOL xBOOL
|
|
#include "X.h"
|
|
#include "Xproto.h"
|
|
#include "os.h"
|
|
#include "opaque.h"
|
|
#include "darwin.h"
|
|
#include "quartz.h"
|
|
#define _APPLEWM_SERVER_
|
|
#include "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/X11R6/bin", tem);
|
|
else
|
|
snprintf(buf, sizeof(buf), "/bin:/usr/bin:/usr/X11R6/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;
|
|
}
|
|
|
|
}
|