//
// 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 $ */
#ifdef HAVE_XORG_CONFIG_H
#include <xorg-config.h>
#endif
#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;
}
}
|