diff -uNr synergy-1.3.1/lib/platform/COSXScreen.cpp synergy-1.3.1-broadcast/lib/platform/COSXScreen.cpp
--- synergy-1.3.1/lib/platform/COSXScreen.cpp	2006-03-21 21:40:27.000000000 -0800
+++ synergy-1.3.1-broadcast/lib/platform/COSXScreen.cpp	2007-08-15 20:38:40.000000000 -0700
@@ -120,6 +120,9 @@
 							&inputBounds, &m_userInputWindow);
 
 			SetWindowAlpha(m_userInputWindow, 0);
+
+			// Install global event handlers, so we can broadcast.
+			InstallMyEventHandlers();
 		}
 		
 		// install display manager notification handler
@@ -233,6 +236,28 @@
 	delete m_screensaver;
 }
 
+static OSStatus MonitorHandler( EventHandlerCallRef inCaller, EventRef inEvent, void* inRefcon )
+{
+	// nothing
+	return noErr;
+}
+
+void
+COSXScreen::InstallMyEventHandlers(void)
+{
+	EventTypeSpec	kEvents[] =
+		{
+			{ kEventClassKeyboard, kEventRawKeyDown },
+			{ kEventClassKeyboard, kEventRawKeyUp },
+			{ kEventClassKeyboard, kEventRawKeyRepeat },
+			{ kEventClassKeyboard, kEventRawKeyModifiersChanged }
+		};
+
+	InstallEventHandler( GetEventMonitorTarget(), MonitorHandler, GetEventTypeCount( kEvents ),
+						 kEvents, 0, NULL );
+	
+}
+
 void*
 COSXScreen::getEventTarget() const
 {
@@ -803,6 +828,8 @@
 
 	UInt32 eventClass = GetEventClass(*carbonEvent);
 
+	LOG((CLOG_DEBUG1 "OSX system event."));
+
 	switch (eventClass) {
 	case kEventClassMouse:
 		switch (GetEventKind(*carbonEvent)) {
diff -uNr synergy-1.3.1/lib/platform/COSXScreen.h synergy-1.3.1-broadcast/lib/platform/COSXScreen.h
--- synergy-1.3.1/lib/platform/COSXScreen.h	2006-03-08 20:07:17.000000000 -0800
+++ synergy-1.3.1-broadcast/lib/platform/COSXScreen.h	2007-08-15 20:40:02.000000000 -0700
@@ -25,6 +25,7 @@
 #include <mach/mach_init.h>
 #include <IOKit/pwr_mgt/IOPMLib.h>
 #include <IOKit/IOMessage.h>
+#include <ApplicationServices/ApplicationServices.h>
 
 template <class T>
 class CCondVar;
@@ -147,6 +148,8 @@
 	void				handlePowerChangeRequest(natural_t messageType,
 							 void* messageArgument);
 
+	void				InstallMyEventHandlers(void);
+
 	static CEvent::Type	getConfirmSleepEvent();
 	void				handleConfirmSleep(const CEvent& event, void*);
 	
@@ -227,9 +230,9 @@
 	EventHandlerRef			m_switchEventHandlerRef;
 
 	// sleep / wakeup
-	CMutex*					m_pmMutex;
-	CThread*				m_pmWatchThread;
-    CCondVar<bool>*			m_pmThreadReady;
+	CMutex*				m_pmMutex;
+	CThread*			m_pmWatchThread;
+    	CCondVar<bool>*			m_pmThreadReady;
 	CFRunLoopRef			m_pmRunloop;
 	io_connect_t			m_pmRootPort;
 
diff -uNr synergy-1.3.1/lib/server/CPrimaryClient.cpp synergy-1.3.1-broadcast/lib/server/CPrimaryClient.cpp
--- synergy-1.3.1/lib/server/CPrimaryClient.cpp	2006-04-01 17:47:03.000000000 -0800
+++ synergy-1.3.1-broadcast/lib/server/CPrimaryClient.cpp	2007-08-15 20:13:00.000000000 -0700
@@ -181,6 +181,8 @@
 void
 CPrimaryClient::keyDown(KeyID key, KeyModifierMask mask, KeyButton button)
 {
+	LOG((CLOG_DEBUG1 "KeyDownMain id=%d mask=0x%04x button=0x%04x", key, mask, button));
+
 	if (m_fakeInputCount > 0) {
 // XXX -- don't forward keystrokes to primary screen for now
 		(void)key;
@@ -191,14 +193,16 @@
 }
 
 void
-CPrimaryClient::keyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton)
+CPrimaryClient::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button)
 {
 	// ignore
+	LOG((CLOG_DEBUG1 "KeyRepeatMain id=%d mask=0x%04x count=%d button=0x%04x", key, mask, count, button));
 }
 
 void
 CPrimaryClient::keyUp(KeyID key, KeyModifierMask mask, KeyButton button)
 {
+	LOG((CLOG_DEBUG1 "KeyUpMain id=%d mask=0x%04x button=0x%04x", key, mask, button));
 	if (m_fakeInputCount > 0) {
 // XXX -- don't forward keystrokes to primary screen for now
 		(void)key;
diff -uNr synergy-1.3.1/lib/server/CServer.cpp synergy-1.3.1-broadcast/lib/server/CServer.cpp
--- synergy-1.3.1/lib/server/CServer.cpp	2006-04-01 17:47:04.000000000 -0800
+++ synergy-1.3.1-broadcast/lib/server/CServer.cpp	2007-08-31 16:44:08.000000000 -0700
@@ -61,7 +61,8 @@
 	m_switchTwoTapArmed(false),
 	m_switchTwoTapZone(3),
 	m_relativeMoves(false),
-	m_lockedToScreen(false)
+	m_lockedToScreen(false),
+	m_broadcast(false)
 {
 	// must have a primary client and it must have a canonical name
 	assert(m_primaryClient != NULL);
@@ -1388,12 +1389,14 @@
 
 	// enter new state
 	if (newState != m_lockedToScreen) {
+		SInt32 x, y;
 		m_lockedToScreen = newState;
+
 		LOG((CLOG_DEBUG "cursor %s current screen", m_lockedToScreen ? "locked to" : "unlocked from"));
 
 		m_primaryClient->reconfigure(getActivePrimarySides());
 		if (!isLockedToScreenServer()) {
-			stopRelativeMoves();
+			//stopRelativeMoves();
 		}
 	}
 }
@@ -1514,7 +1517,31 @@
 
 	// relay
 	if (IKeyState::CKeyInfo::isDefault(screens)) {
-		m_active->keyDown(id, mask, button);
+		if (m_active != m_primaryClient)
+		{
+			m_active->keyDown(id, mask, button);
+		}
+		else
+		{ 
+			if (id == 96 && mask == 0 && button == 0x0033)
+			{
+				if (m_broadcast)
+				{
+					m_broadcast = false;
+				}
+				else
+				{
+					m_broadcast = true;
+				}
+			}
+			if (m_broadcast)
+			{
+				for (CClientList::const_iterator index = m_clients.begin();
+						 index != m_clients.end(); ++index) {
+					index->second->keyDown(id, mask, button);
+				}
+			}
+		}
 	}
 	else {
 		for (CClientList::const_iterator index = m_clients.begin();
@@ -1535,7 +1562,16 @@
 
 	// relay
 	if (IKeyState::CKeyInfo::isDefault(screens)) {
-		m_active->keyUp(id, mask, button);
+		if (m_active != m_primaryClient)
+		{
+			m_active->keyUp(id, mask, button);
+		}
+		else if (m_broadcast)
+		{
+			for (CClientList::const_iterator index = m_clients.begin(); index != m_clients.end(); ++index) {
+				index->second->keyUp(id, mask, button);
+			}
+		}
 	}
 	else {
 		for (CClientList::const_iterator index = m_clients.begin();
@@ -1555,7 +1591,16 @@
 	assert(m_active != NULL);
 
 	// relay
-	m_active->keyRepeat(id, mask, count, button);
+	if (m_active != m_primaryClient)
+	{
+		m_active->keyRepeat(id, mask, count, button);
+	}
+	else if (m_broadcast)
+	{
+		for (CClientList::const_iterator index = m_clients.begin(); index != m_clients.end(); ++index) {
+			index->second->keyRepeat(id, mask, count, button);
+		}
+	}
 }
 
 void
diff -uNr synergy-1.3.1/lib/server/CServer.cpp.hack synergy-1.3.1-broadcast/lib/server/CServer.cpp.hack
--- synergy-1.3.1/lib/server/CServer.cpp.hack	1969-12-31 16:00:00.000000000 -0800
+++ synergy-1.3.1-broadcast/lib/server/CServer.cpp.hack	2007-08-17 01:08:28.000000000 -0700
@@ -0,0 +1,2168 @@
+/*
+ * synergy -- mouse and keyboard sharing utility
+ * Copyright (C) 2002 Chris Schoeneman
+ * 
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file COPYING that should have accompanied this file.
+ * 
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "CServer.h"
+#include "CClientProxy.h"
+#include "CClientProxyUnknown.h"
+#include "CPrimaryClient.h"
+#include "IPlatformScreen.h"
+#include "OptionTypes.h"
+#include "ProtocolTypes.h"
+#include "XScreen.h"
+#include "XSynergy.h"
+#include "IDataSocket.h"
+#include "IListenSocket.h"
+#include "XSocket.h"
+#include "IEventQueue.h"
+#include "CLog.h"
+#include "TMethodEventJob.h"
+#include "CArch.h"
+#include <string.h>
+
+//
+// CServer
+//
+
+CEvent::Type			CServer::s_errorEvent         = CEvent::kUnknown;
+CEvent::Type			CServer::s_connectedEvent     = CEvent::kUnknown;
+CEvent::Type			CServer::s_disconnectedEvent  = CEvent::kUnknown;
+CEvent::Type			CServer::s_switchToScreen     = CEvent::kUnknown;
+CEvent::Type			CServer::s_switchInDirection  = CEvent::kUnknown;
+CEvent::Type			CServer::s_lockCursorToScreen = CEvent::kUnknown;
+
+CServer::CServer(const CConfig& config, CPrimaryClient* primaryClient) :
+	m_primaryClient(primaryClient),
+	m_active(primaryClient),
+	m_seqNum(0),
+	m_xDelta(0),
+	m_yDelta(0),
+	m_xDelta2(0),
+	m_yDelta2(0),
+	m_config(),
+	m_inputFilter(m_config.getInputFilter()),
+	m_activeSaver(NULL),
+	m_switchDir(kNoDirection),
+	m_switchScreen(NULL),
+	m_switchWaitDelay(0.0),
+	m_switchWaitTimer(NULL),
+	m_switchTwoTapDelay(0.0),
+	m_switchTwoTapEngaged(false),
+	m_switchTwoTapArmed(false),
+	m_switchTwoTapZone(3),
+	m_relativeMoves(false),
+	m_lockedToScreen(false),
+	m_broadcast(false),
+	m_broadcastOnID(NULL),
+	m_broadcastOffID(NULL)
+{
+	const char *soundOnFile = "/System/Library/Sounds/Blow.aiff";
+	FSRef soundOnFileRef;
+	const char *soundOffFile = "/System/Library/Sounds/Submarine.aiff";
+	FSRef soundOffFileRef;
+	OSStatus soundErr;
+
+	soundErr = FSPathMakeRef(soundOnFile, &soundOnFileRef, NULL);
+	if (noErr == soundErr)
+	{
+		soundErr = SystemSoundGetActionID(&soundOnFileRef, &m_broadcastOnID);
+	}
+
+	soundErr = FSPathMakeRef(soundOffFile, &soundOffFileRef, NULL);
+	if (noErr == soundErr)
+	{
+		soundErr = SystemSoundGetActionID(&soundOffFileRef, &m_broadcastOffID);
+	}
+
+	// must have a primary client and it must have a canonical name
+	assert(m_primaryClient != NULL);
+	assert(config.isScreen(primaryClient->getName()));
+
+	CString primaryName = getName(primaryClient);
+
+	// clear clipboards
+	for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+		CClipboardInfo& clipboard   = m_clipboards[id];
+		clipboard.m_clipboardOwner  = primaryName;
+		clipboard.m_clipboardSeqNum = m_seqNum;
+		if (clipboard.m_clipboard.open(0)) {
+			clipboard.m_clipboard.empty();
+			clipboard.m_clipboard.close();
+		}
+		clipboard.m_clipboardData   = clipboard.m_clipboard.marshall();
+	}
+
+	// install event handlers
+	EVENTQUEUE->adoptHandler(CEvent::kTimer, this,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleSwitchWaitTimeout));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyDownEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleKeyDownEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyUpEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleKeyUpEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyRepeatEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleKeyRepeatEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonDownEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleButtonDownEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonUpEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleButtonUpEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnPrimaryEvent(),
+							m_primaryClient->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleMotionPrimaryEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnSecondaryEvent(),
+							m_primaryClient->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleMotionSecondaryEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getWheelEvent(),
+							m_primaryClient->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleWheelEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverActivatedEvent(),
+							m_primaryClient->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleScreensaverActivatedEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverDeactivatedEvent(),
+							m_primaryClient->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleScreensaverDeactivatedEvent));
+	EVENTQUEUE->adoptHandler(getSwitchToScreenEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleSwitchToScreenEvent));
+	EVENTQUEUE->adoptHandler(getSwitchInDirectionEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleSwitchInDirectionEvent));
+	EVENTQUEUE->adoptHandler(getLockCursorToScreenEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleLockCursorToScreenEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getFakeInputBeginEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleFakeInputBeginEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getFakeInputEndEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleFakeInputEndEvent));
+
+	// add connection
+	addClient(m_primaryClient);
+
+	// set initial configuration
+	setConfig(config);
+
+	// enable primary client
+	m_primaryClient->enable();
+	m_inputFilter->setPrimaryClient(m_primaryClient);
+}
+
+CServer::~CServer()
+{
+	if (m_broadcastOnId)
+	{
+		SystemSoundRemoveActionID(m_broadcastOnID);
+	}
+	if (m_broadcastOffId)
+	{
+		SystemSoundRemoveActionID(m_broadcastOffID);
+	}
+
+	// remove event handlers and timers
+	EVENTQUEUE->removeHandler(IPlatformScreen::getKeyDownEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getKeyUpEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getKeyRepeatEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getButtonDownEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getButtonUpEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnPrimaryEvent(),
+							m_primaryClient->getEventTarget());
+	EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnSecondaryEvent(),
+							m_primaryClient->getEventTarget());
+	EVENTQUEUE->removeHandler(IPlatformScreen::getWheelEvent(),
+							m_primaryClient->getEventTarget());
+	EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverActivatedEvent(),
+							m_primaryClient->getEventTarget());
+	EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverDeactivatedEvent(),
+							m_primaryClient->getEventTarget());
+	EVENTQUEUE->removeHandler(IPlatformScreen::getFakeInputBeginEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getFakeInputEndEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(CEvent::kTimer, this);
+	stopSwitch();
+
+	// force immediate disconnection of secondary clients
+	disconnect();
+	for (COldClients::iterator index = m_oldClients.begin();
+							index != m_oldClients.begin(); ++index) {
+		CBaseClientProxy* client = index->first;
+		EVENTQUEUE->deleteTimer(index->second);
+		EVENTQUEUE->removeHandler(CEvent::kTimer, client);
+		EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client);
+		delete client;
+	}
+
+	// remove input filter
+	m_inputFilter->setPrimaryClient(NULL);
+
+	// disable and disconnect primary client
+	m_primaryClient->disable();
+	removeClient(m_primaryClient);
+}
+
+bool
+CServer::setConfig(const CConfig& config)
+{
+	// refuse configuration if it doesn't include the primary screen
+	if (!config.isScreen(m_primaryClient->getName())) {
+		return false;
+	}
+
+	// close clients that are connected but being dropped from the
+	// configuration.
+	closeClients(config);
+
+	// cut over
+	m_config = config;
+	processOptions();
+
+	// add ScrollLock as a hotkey to lock to the screen.  this was a
+	// built-in feature in earlier releases and is now supported via
+	// the user configurable hotkey mechanism.  if the user has already
+	// registered ScrollLock for something else then that will win but
+	// we will unfortunately generate a warning.  if the user has
+	// configured a CLockCursorToScreenAction then we don't add
+	// ScrollLock as a hotkey.
+	if (!m_config.hasLockToScreenAction()) {
+		IPlatformScreen::CKeyInfo* key =
+			IPlatformScreen::CKeyInfo::alloc(kKeyScrollLock, 0, 0, 0);
+		CInputFilter::CRule rule(new CInputFilter::CKeystrokeCondition(key));
+		rule.adoptAction(new CInputFilter::CLockCursorToScreenAction, true);
+		m_inputFilter->addFilterRule(rule);
+	}
+
+	// tell primary screen about reconfiguration
+	m_primaryClient->reconfigure(getActivePrimarySides());
+
+	// tell all (connected) clients about current options
+	for (CClientList::const_iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+		CBaseClientProxy* client = index->second;
+		sendOptions(client);
+	}
+
+	return true;
+}
+
+void
+CServer::adoptClient(CBaseClientProxy* client)
+{
+	assert(client != NULL);
+
+	// watch for client disconnection
+	EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), client,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleClientDisconnected, client));
+
+	// name must be in our configuration
+	if (!m_config.isScreen(client->getName())) {
+		LOG((CLOG_WARN "a client with name \"%s\" is not in the map", client->getName().c_str()));
+		closeClient(client, kMsgEUnknown);
+		return;
+	}
+
+	// add client to client list
+	if (!addClient(client)) {
+		// can only have one screen with a given name at any given time
+		LOG((CLOG_WARN "a client with name \"%s\" is already connected", getName(client).c_str()));
+		closeClient(client, kMsgEBusy);
+		return;
+	}
+	LOG((CLOG_NOTE "client \"%s\" has connected", getName(client).c_str()));
+
+	// send configuration options to client
+	sendOptions(client);
+
+	// activate screen saver on new client if active on the primary screen
+	if (m_activeSaver != NULL) {
+		client->screensaver(true);
+	}
+
+	// send notification
+	CServer::CScreenConnectedInfo* info =
+		CServer::CScreenConnectedInfo::alloc(getName(client));
+	EVENTQUEUE->addEvent(CEvent(CServer::getConnectedEvent(),
+								m_primaryClient->getEventTarget(), info));
+}
+
+void
+CServer::disconnect()
+{
+	// close all secondary clients
+	if (m_clients.size() > 1 || !m_oldClients.empty()) {
+		CConfig emptyConfig;
+		closeClients(emptyConfig);
+	}
+	else {
+		EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this));
+	}
+}
+
+UInt32
+CServer::getNumClients() const
+{
+	return m_clients.size();
+}
+
+void
+CServer::getClients(std::vector<CString>& list) const
+{
+	list.clear();
+	for (CClientList::const_iterator index = m_clients.begin();
+							index != m_clients.end(); ++index) {
+		list.push_back(index->first);
+	}
+}
+
+CEvent::Type
+CServer::getErrorEvent()
+{
+	return CEvent::registerTypeOnce(s_errorEvent,
+							"CServer::error");
+}
+
+CEvent::Type
+CServer::getConnectedEvent()
+{
+	return CEvent::registerTypeOnce(s_connectedEvent,
+							"CServer::connected");
+}
+
+CEvent::Type
+CServer::getDisconnectedEvent()
+{
+	return CEvent::registerTypeOnce(s_disconnectedEvent,
+							"CServer::disconnected");
+}
+
+CEvent::Type
+CServer::getSwitchToScreenEvent()
+{
+	return CEvent::registerTypeOnce(s_switchToScreen,
+							"CServer::switchToScreen");
+}
+
+CEvent::Type
+CServer::getSwitchInDirectionEvent()
+{
+	return CEvent::registerTypeOnce(s_switchInDirection,
+							"CServer::switchInDirection");
+}
+
+CEvent::Type
+CServer::getLockCursorToScreenEvent()
+{
+	return CEvent::registerTypeOnce(s_lockCursorToScreen,
+							"CServer::lockCursorToScreen");
+}
+
+CString
+CServer::getName(const CBaseClientProxy* client) const
+{
+	CString name = m_config.getCanonicalName(client->getName());
+	if (name.empty()) {
+		name = client->getName();
+	}
+	return name;
+}
+
+UInt32
+CServer::getActivePrimarySides() const
+{
+	UInt32 sides = 0;
+	if (!isLockedToScreenServer()) {
+		if (hasAnyNeighbor(m_primaryClient, kLeft)) {
+			sides |= kLeftMask;
+		}
+		if (hasAnyNeighbor(m_primaryClient, kRight)) {
+			sides |= kRightMask;
+		}
+		if (hasAnyNeighbor(m_primaryClient, kTop)) {
+			sides |= kTopMask;
+		}
+		if (hasAnyNeighbor(m_primaryClient, kBottom)) {
+			sides |= kBottomMask;
+		}
+	}
+	return sides;
+}
+
+bool
+CServer::isLockedToScreenServer() const
+{
+	// locked if scroll-lock is toggled on
+	return m_lockedToScreen;
+}
+
+bool
+CServer::isLockedToScreen() const
+{
+	// locked if we say we're locked
+	if (isLockedToScreenServer()) {
+		LOG((CLOG_DEBUG "locked to screen"));
+		return true;
+	}
+
+	// locked if primary says we're locked
+	if (m_primaryClient->isLockedToScreen()) {
+		return true;
+	}
+
+	// not locked
+	return false;
+}
+
+SInt32
+CServer::getJumpZoneSize(CBaseClientProxy* client) const
+{
+	if (client == m_primaryClient) {
+		return m_primaryClient->getJumpZoneSize();
+	}
+	else {
+		return 0;
+	}
+}
+
+void
+CServer::switchScreen(CBaseClientProxy* dst,
+				SInt32 x, SInt32 y, bool forScreensaver)
+{
+	assert(dst != NULL);
+#ifndef NDEBUG
+	{
+		SInt32 dx, dy, dw, dh;
+		dst->getShape(dx, dy, dw, dh);
+		assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh);
+	}
+#endif
+	assert(m_active != NULL);
+
+	LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", getName(m_active).c_str(), getName(dst).c_str(), x, y));
+
+	// stop waiting to switch
+	stopSwitch();
+
+	// record new position
+	m_x       = x;
+	m_y       = y;
+	m_xDelta  = 0;
+	m_yDelta  = 0;
+	m_xDelta2 = 0;
+	m_yDelta2 = 0;
+
+	// wrapping means leaving the active screen and entering it again.
+	// since that's a waste of time we skip that and just warp the
+	// mouse.
+	if (m_active != dst) {
+		// leave active screen
+		if (!m_active->leave()) {
+			// cannot leave screen
+			LOG((CLOG_WARN "can't leave screen"));
+			return;
+		}
+
+		// update the primary client's clipboards if we're leaving the
+		// primary screen.
+		if (m_active == m_primaryClient) {
+			for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+				CClipboardInfo& clipboard = m_clipboards[id];
+				if (clipboard.m_clipboardOwner == getName(m_primaryClient)) {
+					onClipboardChanged(m_primaryClient,
+						id, clipboard.m_clipboardSeqNum);
+				}
+			}
+		}
+
+		// cut over
+		m_active = dst;
+
+		// increment enter sequence number
+		++m_seqNum;
+
+		// enter new screen
+		m_active->enter(x, y, m_seqNum,
+								m_primaryClient->getToggleMask(),
+								forScreensaver);
+
+		// send the clipboard data to new active screen
+		for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+			m_active->setClipboard(id, &m_clipboards[id].m_clipboard);
+		}
+	}
+	else {
+		m_active->mouseMove(x, y);
+	}
+}
+
+void
+CServer::jumpToScreen(CBaseClientProxy* newScreen)
+{
+	assert(newScreen != NULL);
+
+	// record the current cursor position on the active screen
+	m_active->setJumpCursorPos(m_x, m_y);
+
+	// get the last cursor position on the target screen
+	SInt32 x, y;
+	newScreen->getJumpCursorPos(x, y);
+	
+	switchScreen(newScreen, x, y, false);
+}
+
+float
+CServer::mapToFraction(CBaseClientProxy* client,
+				EDirection dir, SInt32 x, SInt32 y) const
+{
+	SInt32 sx, sy, sw, sh;
+	client->getShape(sx, sy, sw, sh);
+	switch (dir) {
+	case kLeft:
+	case kRight:
+		return static_cast<float>(y - sy + 0.5f) / static_cast<float>(sh);
+
+	case kTop:
+	case kBottom:
+		return static_cast<float>(x - sx + 0.5f) / static_cast<float>(sw);
+
+	case kNoDirection:
+		assert(0 && "bad direction");
+		break;
+	}
+	return 0.0f;
+}
+
+void
+CServer::mapToPixel(CBaseClientProxy* client,
+				EDirection dir, float f, SInt32& x, SInt32& y) const
+{
+	SInt32 sx, sy, sw, sh;
+	client->getShape(sx, sy, sw, sh);
+	switch (dir) {
+	case kLeft:
+	case kRight:
+		y = static_cast<SInt32>(f * sh) + sy;
+		break;
+
+	case kTop:
+	case kBottom:
+		x = static_cast<SInt32>(f * sw) + sx;
+		break;
+
+	case kNoDirection:
+		assert(0 && "bad direction");
+		break;
+	}
+}
+
+bool
+CServer::hasAnyNeighbor(CBaseClientProxy* client, EDirection dir) const
+{
+	assert(client != NULL);
+
+	return m_config.hasNeighbor(getName(client), dir);
+}
+
+CBaseClientProxy*
+CServer::getNeighbor(CBaseClientProxy* src,
+				EDirection dir, SInt32& x, SInt32& y) const
+{
+	// note -- must be locked on entry
+
+	assert(src != NULL);
+
+	// get source screen name
+	CString srcName = getName(src);
+	assert(!srcName.empty());
+	LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str()));
+
+	// convert position to fraction
+	float t = mapToFraction(src, dir, x, y);
+
+	// search for the closest neighbor that exists in direction dir
+	float tTmp;
+	for (;;) {
+		CString dstName(m_config.getNeighbor(srcName, dir, t, &tTmp));
+
+		// if nothing in that direction then return NULL. if the
+		// destination is the source then we can make no more
+		// progress in this direction.  since we haven't found a
+		// connected neighbor we return NULL.
+		if (dstName.empty()) {
+			LOG((CLOG_DEBUG2 "no neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str()));
+			return NULL;
+		}
+
+		// look up neighbor cell.  if the screen is connected and
+		// ready then we can stop.
+		CClientList::const_iterator index = m_clients.find(dstName);
+		if (index != m_clients.end()) {
+			LOG((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\" at %f", dstName.c_str(), CConfig::dirName(dir), srcName.c_str(), t));
+			mapToPixel(index->second, dir, tTmp, x, y);
+			return index->second;
+		}
+
+		// skip over unconnected screen
+		LOG((CLOG_DEBUG2 "ignored \"%s\" on %s of \"%s\"", dstName.c_str(), CConfig::dirName(dir), srcName.c_str()));
+		srcName = dstName;
+
+		// use position on skipped screen
+		t = tTmp;
+	}
+}
+
+CBaseClientProxy*
+CServer::mapToNeighbor(CBaseClientProxy* src,
+				EDirection srcSide, SInt32& x, SInt32& y) const
+{
+	// note -- must be locked on entry
+
+	assert(src != NULL);
+
+	// get the first neighbor
+	CBaseClientProxy* dst = getNeighbor(src, srcSide, x, y);
+	if (dst == NULL) {
+		return NULL;
+	}
+
+	// get the source screen's size
+	SInt32 dx, dy, dw, dh;
+	CBaseClientProxy* lastGoodScreen = src;
+	lastGoodScreen->getShape(dx, dy, dw, dh);
+
+	// find destination screen, adjusting x or y (but not both).  the
+	// searches are done in a sort of canonical screen space where
+	// the upper-left corner is 0,0 for each screen.  we adjust from
+	// actual to canonical position on entry to and from canonical to
+	// actual on exit from the search.
+	switch (srcSide) {
+	case kLeft:
+		x -= dx;
+		while (dst != NULL) {
+			lastGoodScreen = dst;
+			lastGoodScreen->getShape(dx, dy, dw, dh);
+			x += dw;
+			if (x >= 0) {
+				break;
+			}
+			LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+			dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+		}
+		assert(lastGoodScreen != NULL);
+		x += dx;
+		break;
+
+	case kRight:
+		x -= dx;
+		while (dst != NULL) {
+			x -= dw;
+			lastGoodScreen = dst;
+			lastGoodScreen->getShape(dx, dy, dw, dh);
+			if (x < dw) {
+				break;
+			}
+			LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+			dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+		}
+		assert(lastGoodScreen != NULL);
+		x += dx;
+		break;
+
+	case kTop:
+		y -= dy;
+		while (dst != NULL) {
+			lastGoodScreen = dst;
+			lastGoodScreen->getShape(dx, dy, dw, dh);
+			y += dh;
+			if (y >= 0) {
+				break;
+			}
+			LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+			dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+		}
+		assert(lastGoodScreen != NULL);
+		y += dy;
+		break;
+
+	case kBottom:
+		y -= dy;
+		while (dst != NULL) {
+			y -= dh;
+			lastGoodScreen = dst;
+			lastGoodScreen->getShape(dx, dy, dw, dh);
+			if (y < dh) {
+				break;
+			}
+			LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+			dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+		}
+		assert(lastGoodScreen != NULL);
+		y += dy;
+		break;
+
+	case kNoDirection:
+		assert(0 && "bad direction");
+		return NULL;
+	}
+
+	// save destination screen
+	assert(lastGoodScreen != NULL);
+	dst = lastGoodScreen;
+
+	// if entering primary screen then be sure to move in far enough
+	// to avoid the jump zone.  if entering a side that doesn't have
+	// a neighbor (i.e. an asymmetrical side) then we don't need to
+	// move inwards because that side can't provoke a jump.
+	avoidJumpZone(dst, srcSide, x, y);
+
+	return dst;
+}
+
+void
+CServer::avoidJumpZone(CBaseClientProxy* dst,
+				EDirection dir, SInt32& x, SInt32& y) const
+{
+	// we only need to avoid jump zones on the primary screen
+	if (dst != m_primaryClient) {
+		return;
+	}
+
+	const CString dstName(getName(dst));
+	SInt32 dx, dy, dw, dh;
+	dst->getShape(dx, dy, dw, dh);
+	float t = mapToFraction(dst, dir, x, y);
+	SInt32 z = getJumpZoneSize(dst);
+
+	// move in far enough to avoid the jump zone.  if entering a side
+	// that doesn't have a neighbor (i.e. an asymmetrical side) then we
+	// don't need to move inwards because that side can't provoke a jump.
+	switch (dir) {
+	case kLeft:
+		if (!m_config.getNeighbor(dstName, kRight, t, NULL).empty() &&
+			x > dx + dw - 1 - z)
+			x = dx + dw - 1 - z;
+		break;
+
+	case kRight:
+		if (!m_config.getNeighbor(dstName, kLeft, t, NULL).empty() &&
+			x < dx + z)
+			x = dx + z;
+		break;
+
+	case kTop:
+		if (!m_config.getNeighbor(dstName, kBottom, t, NULL).empty() &&
+			y > dy + dh - 1 - z)
+			y = dy + dh - 1 - z;
+		break;
+
+	case kBottom:
+		if (!m_config.getNeighbor(dstName, kTop, t, NULL).empty() &&
+			y < dy + z)
+			y = dy + z;
+		break;
+
+	case kNoDirection:
+		assert(0 && "bad direction");
+	}
+}
+
+bool
+CServer::isSwitchOkay(CBaseClientProxy* newScreen,
+				EDirection dir, SInt32 x, SInt32 y,
+				SInt32 xActive, SInt32 yActive)
+{
+	LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", getName(m_active).c_str(), CConfig::dirName(dir)));
+
+	// is there a neighbor?
+	if (newScreen == NULL) {
+		// there's no neighbor.  we don't want to switch and we don't
+		// want to try to switch later.
+		LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(dir)));
+		stopSwitch();
+		return false;
+	}
+
+	// should we switch or not?
+	bool preventSwitch = false;
+	bool allowSwitch   = false;
+
+	// note if the switch direction has changed.  save the new
+	// direction and screen if so.
+	bool isNewDirection  = (dir != m_switchDir);
+	if (isNewDirection || m_switchScreen == NULL) {
+		m_switchDir    = dir;
+		m_switchScreen = newScreen;
+	}
+
+	// is this a double tap and do we care?
+	if (!allowSwitch && m_switchTwoTapDelay > 0.0) {
+		if (isNewDirection ||
+			!isSwitchTwoTapStarted() || !shouldSwitchTwoTap()) {
+			// tapping a different or new edge or second tap not
+			// fast enough.  prepare for second tap.
+			preventSwitch = true;
+			startSwitchTwoTap();
+		}
+		else {
+			// got second tap
+			allowSwitch = true;
+		}
+	}
+
+	// if waiting before a switch then prepare to switch later
+	if (!allowSwitch && m_switchWaitDelay > 0.0) {
+		if (isNewDirection || !isSwitchWaitStarted()) {
+			startSwitchWait(x, y);
+		}
+		preventSwitch = true;
+	}
+
+	// are we in a locked corner?  first check if screen has the option set
+	// and, if not, check the global options.
+	const CConfig::CScreenOptions* options =
+						m_config.getOptions(getName(m_active));
+	if (options == NULL || options->count(kOptionScreenSwitchCorners) == 0) {
+		options = m_config.getOptions("");
+	}
+	if (options != NULL && options->count(kOptionScreenSwitchCorners) > 0) {
+		// get corner mask and size
+		CConfig::CScreenOptions::const_iterator i =
+			options->find(kOptionScreenSwitchCorners);
+		UInt32 corners = static_cast<UInt32>(i->second);
+		i = options->find(kOptionScreenSwitchCornerSize);
+		SInt32 size = 0;
+		if (i != options->end()) {
+			size = i->second;
+		}
+
+		// see if we're in a locked corner
+		if ((getCorner(m_active, xActive, yActive, size) & corners) != 0) {
+			// yep, no switching
+			LOG((CLOG_DEBUG1 "locked in corner"));
+			preventSwitch = true;
+			stopSwitch();
+		}
+	}
+
+	// ignore if mouse is locked to screen and don't try to switch later
+	if (!preventSwitch && isLockedToScreen()) {
+		LOG((CLOG_DEBUG1 "locked to screen"));
+		preventSwitch = true;
+		stopSwitch();
+	}
+
+	return !preventSwitch;
+}
+
+void
+CServer::noSwitch(SInt32 x, SInt32 y)
+{
+	armSwitchTwoTap(x, y);
+	stopSwitchWait();
+}
+
+void
+CServer::stopSwitch()
+{
+	if (m_switchScreen != NULL) {
+		m_switchScreen = NULL;
+		m_switchDir    = kNoDirection;
+		stopSwitchTwoTap();
+		stopSwitchWait();
+	}
+}
+
+void
+CServer::startSwitchTwoTap()
+{
+	m_switchTwoTapEngaged = true;
+	m_switchTwoTapArmed   = false;
+	m_switchTwoTapTimer.reset();
+	LOG((CLOG_DEBUG1 "waiting for second tap"));
+}
+
+void
+CServer::armSwitchTwoTap(SInt32 x, SInt32 y)
+{
+	if (m_switchTwoTapEngaged) {
+		if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) {
+			// second tap took too long.  disengage.
+			stopSwitchTwoTap();
+		}
+		else if (!m_switchTwoTapArmed) {
+			// still time for a double tap.  see if we left the tap
+			// zone and, if so, arm the two tap.
+			SInt32 ax, ay, aw, ah;
+			m_active->getShape(ax, ay, aw, ah);
+			SInt32 tapZone = m_primaryClient->getJumpZoneSize();
+			if (tapZone < m_switchTwoTapZone) {
+				tapZone = m_switchTwoTapZone;
+			}
+			if (x >= ax + tapZone && x < ax + aw - tapZone &&
+				y >= ay + tapZone && y < ay + ah - tapZone) {
+				// win32 can generate bogus mouse events that appear to
+				// move in the opposite direction that the mouse actually
+				// moved.  try to ignore that crap here.
+				switch (m_switchDir) {
+				case kLeft:
+					m_switchTwoTapArmed = (m_xDelta > 0 && m_xDelta2 > 0);
+					break;
+
+				case kRight:
+					m_switchTwoTapArmed = (m_xDelta < 0 && m_xDelta2 < 0);
+					break;
+
+				case kTop:
+					m_switchTwoTapArmed = (m_yDelta > 0 && m_yDelta2 > 0);
+					break;
+
+				case kBottom:
+					m_switchTwoTapArmed = (m_yDelta < 0 && m_yDelta2 < 0);
+					break;
+
+				default:
+					break;
+				}
+			}
+		}
+	}
+}
+
+void
+CServer::stopSwitchTwoTap()
+{
+	m_switchTwoTapEngaged = false;
+	m_switchTwoTapArmed   = false;
+}
+
+bool
+CServer::isSwitchTwoTapStarted() const
+{
+	return m_switchTwoTapEngaged;
+}
+
+bool
+CServer::shouldSwitchTwoTap() const
+{
+	// this is the second tap if two-tap is armed and this tap
+	// came fast enough
+	return (m_switchTwoTapArmed &&
+			m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay);
+}
+
+void
+CServer::startSwitchWait(SInt32 x, SInt32 y)
+{
+	stopSwitchWait();
+	m_switchWaitX     = x;
+	m_switchWaitY     = y;
+	m_switchWaitTimer = EVENTQUEUE->newOneShotTimer(m_switchWaitDelay, this);
+	LOG((CLOG_DEBUG1 "waiting to switch"));
+}
+
+void
+CServer::stopSwitchWait()
+{
+	if (m_switchWaitTimer != NULL) {
+		EVENTQUEUE->deleteTimer(m_switchWaitTimer);
+		m_switchWaitTimer = NULL;
+	}
+}
+
+bool
+CServer::isSwitchWaitStarted() const
+{
+	return (m_switchWaitTimer != NULL);
+}
+
+UInt32
+CServer::getCorner(CBaseClientProxy* client,
+				SInt32 x, SInt32 y, SInt32 size) const
+{
+	assert(client != NULL);
+
+	// get client screen shape
+	SInt32 ax, ay, aw, ah;
+	client->getShape(ax, ay, aw, ah);
+
+	// check for x,y on the left or right
+	SInt32 xSide;
+	if (x <= ax) {
+		xSide = -1;
+	}
+	else if (x >= ax + aw - 1) {
+		xSide = 1;
+	}
+	else {
+		xSide = 0;
+	}
+
+	// check for x,y on the top or bottom
+	SInt32 ySide;
+	if (y <= ay) {
+		ySide = -1;
+	}
+	else if (y >= ay + ah - 1) {
+		ySide = 1;
+	}
+	else {
+		ySide = 0;
+	}
+
+	// if against the left or right then check if y is within size
+	if (xSide != 0) {
+		if (y < ay + size) {
+			return (xSide < 0) ? kTopLeftMask : kTopRightMask;
+		}
+		else if (y >= ay + ah - size) {
+			return (xSide < 0) ? kBottomLeftMask : kBottomRightMask;
+		}
+	}
+
+	// if against the left or right then check if y is within size
+	if (ySide != 0) {
+		if (x < ax + size) {
+			return (ySide < 0) ? kTopLeftMask : kBottomLeftMask;
+		}
+		else if (x >= ax + aw - size) {
+			return (ySide < 0) ? kTopRightMask : kBottomRightMask;
+		}
+	}
+
+	return kNoCornerMask;
+}
+
+void
+CServer::stopRelativeMoves()
+{
+	if (m_relativeMoves && m_active != m_primaryClient) {
+		// warp to the center of the active client so we know where we are
+		SInt32 ax, ay, aw, ah;
+		m_active->getShape(ax, ay, aw, ah);
+		m_x       = ax + (aw >> 1);
+		m_y       = ay + (ah >> 1);
+		m_xDelta  = 0;
+		m_yDelta  = 0;
+		m_xDelta2 = 0;
+		m_yDelta2 = 0;
+		LOG((CLOG_DEBUG2 "synchronize move on %s by %d,%d", getName(m_active).c_str(), m_x, m_y));
+		m_active->mouseMove(m_x, m_y);
+	}
+}
+
+void
+CServer::sendOptions(CBaseClientProxy* client) const
+{
+	COptionsList optionsList;
+
+	// look up options for client
+	const CConfig::CScreenOptions* options =
+						m_config.getOptions(getName(client));
+	if (options != NULL) {
+		// convert options to a more convenient form for sending
+		optionsList.reserve(2 * options->size());
+		for (CConfig::CScreenOptions::const_iterator index = options->begin();
+									index != options->end(); ++index) {
+			optionsList.push_back(index->first);
+			optionsList.push_back(static_cast<UInt32>(index->second));
+		}
+	}
+
+	// look up global options
+	options = m_config.getOptions("");
+	if (options != NULL) {
+		// convert options to a more convenient form for sending
+		optionsList.reserve(optionsList.size() + 2 * options->size());
+		for (CConfig::CScreenOptions::const_iterator index = options->begin();
+									index != options->end(); ++index) {
+			optionsList.push_back(index->first);
+			optionsList.push_back(static_cast<UInt32>(index->second));
+		}
+	}
+
+	// send the options
+	client->resetOptions();
+	client->setOptions(optionsList);
+}
+
+void
+CServer::processOptions()
+{
+	const CConfig::CScreenOptions* options = m_config.getOptions("");
+	if (options == NULL) {
+		return;
+	}
+
+	bool newRelativeMoves = m_relativeMoves;
+	for (CConfig::CScreenOptions::const_iterator index = options->begin();
+								index != options->end(); ++index) {
+		const OptionID id       = index->first;
+		const OptionValue value = index->second;
+		if (id == kOptionScreenSwitchDelay) {
+			m_switchWaitDelay = 1.0e-3 * static_cast<double>(value);
+			if (m_switchWaitDelay < 0.0) {
+				m_switchWaitDelay = 0.0;
+			}
+			stopSwitchWait();
+		}
+		else if (id == kOptionScreenSwitchTwoTap) {
+			m_switchTwoTapDelay = 1.0e-3 * static_cast<double>(value);
+			if (m_switchTwoTapDelay < 0.0) {
+				m_switchTwoTapDelay = 0.0;
+			}
+			stopSwitchTwoTap();
+		}
+		else if (id == kOptionRelativeMouseMoves) {
+			newRelativeMoves = (value != 0);
+		}
+	}
+
+	if (m_relativeMoves && !newRelativeMoves) {
+		stopRelativeMoves();
+	}
+	m_relativeMoves = newRelativeMoves;
+}
+
+void
+CServer::handleShapeChanged(const CEvent&, void* vclient)
+{
+	// ignore events from unknown clients
+	CBaseClientProxy* client = reinterpret_cast<CBaseClientProxy*>(vclient);
+	if (m_clientSet.count(client) == 0) {
+		return;
+	}
+
+	LOG((CLOG_INFO "screen \"%s\" shape changed", getName(client).c_str()));
+
+	// update jump coordinate
+	SInt32 x, y;
+	client->getCursorPos(x, y);
+	client->setJumpCursorPos(x, y);
+
+	// update the mouse coordinates
+	if (client == m_active) {
+		m_x = x;
+		m_y = y;
+	}
+
+	// handle resolution change to primary screen
+	if (client == m_primaryClient) {
+		if (client == m_active) {
+			onMouseMovePrimary(m_x, m_y);
+		}
+		else {
+			onMouseMoveSecondary(0, 0);
+		}
+	}
+}
+
+void
+CServer::handleClipboardGrabbed(const CEvent& event, void* vclient)
+{
+	// ignore events from unknown clients
+	CBaseClientProxy* grabber = reinterpret_cast<CBaseClientProxy*>(vclient);
+	if (m_clientSet.count(grabber) == 0) {
+		return;
+	}
+	const IScreen::CClipboardInfo* info =
+		reinterpret_cast<const IScreen::CClipboardInfo*>(event.getData());
+
+	// ignore grab if sequence number is old.  always allow primary
+	// screen to grab.
+	CClipboardInfo& clipboard = m_clipboards[info->m_id];
+	if (grabber != m_primaryClient &&
+		info->m_sequenceNumber < clipboard.m_clipboardSeqNum) {
+		LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", getName(grabber).c_str(), info->m_id));
+		return;
+	}
+
+	// mark screen as owning clipboard
+	LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", getName(grabber).c_str(), info->m_id, clipboard.m_clipboardOwner.c_str()));
+	clipboard.m_clipboardOwner  = getName(grabber);
+	clipboard.m_clipboardSeqNum = info->m_sequenceNumber;
+
+	// clear the clipboard data (since it's not known at this point)
+	if (clipboard.m_clipboard.open(0)) {
+		clipboard.m_clipboard.empty();
+		clipboard.m_clipboard.close();
+	}
+	clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
+
+	// tell all other screens to take ownership of clipboard.  tell the
+	// grabber that it's clipboard isn't dirty.
+	for (CClientList::iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+		CBaseClientProxy* client = index->second;
+		if (client == grabber) {
+			client->setClipboardDirty(info->m_id, false);
+		}
+		else {
+			client->grabClipboard(info->m_id);
+		}
+	}
+}
+
+void
+CServer::handleClipboardChanged(const CEvent& event, void* vclient)
+{
+	// ignore events from unknown clients
+	CBaseClientProxy* sender = reinterpret_cast<CBaseClientProxy*>(vclient);
+	if (m_clientSet.count(sender) == 0) {
+		return;
+	}
+	const IScreen::CClipboardInfo* info =
+		reinterpret_cast<const IScreen::CClipboardInfo*>(event.getData());
+	onClipboardChanged(sender, info->m_id, info->m_sequenceNumber);
+}
+
+void
+CServer::handleKeyDownEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CKeyInfo* info =
+		reinterpret_cast<IPlatformScreen::CKeyInfo*>(event.getData());
+	onKeyDown(info->m_key, info->m_mask, info->m_button, info->m_screens);
+}
+
+void
+CServer::handleKeyUpEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CKeyInfo* info =
+		 reinterpret_cast<IPlatformScreen::CKeyInfo*>(event.getData());
+	onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens);
+}
+
+void
+CServer::handleKeyRepeatEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CKeyInfo* info =
+		reinterpret_cast<IPlatformScreen::CKeyInfo*>(event.getData());
+	onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button);
+}
+
+void
+CServer::handleButtonDownEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CButtonInfo* info =
+		reinterpret_cast<IPlatformScreen::CButtonInfo*>(event.getData());
+	onMouseDown(info->m_button);
+}
+
+void
+CServer::handleButtonUpEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CButtonInfo* info =
+		reinterpret_cast<IPlatformScreen::CButtonInfo*>(event.getData());
+	onMouseUp(info->m_button);
+}
+
+void
+CServer::handleMotionPrimaryEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CMotionInfo* info =
+		reinterpret_cast<IPlatformScreen::CMotionInfo*>(event.getData());
+	onMouseMovePrimary(info->m_x, info->m_y);
+}
+
+void
+CServer::handleMotionSecondaryEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CMotionInfo* info =
+		reinterpret_cast<IPlatformScreen::CMotionInfo*>(event.getData());
+	onMouseMoveSecondary(info->m_x, info->m_y);
+}
+
+void
+CServer::handleWheelEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CWheelInfo* info =
+		reinterpret_cast<IPlatformScreen::CWheelInfo*>(event.getData());
+	onMouseWheel(info->m_xDelta, info->m_yDelta);
+}
+
+void
+CServer::handleScreensaverActivatedEvent(const CEvent&, void*)
+{
+	onScreensaver(true);
+}
+
+void
+CServer::handleScreensaverDeactivatedEvent(const CEvent&, void*)
+{
+	onScreensaver(false);
+}
+
+void
+CServer::handleSwitchWaitTimeout(const CEvent&, void*)
+{
+	// ignore if mouse is locked to screen
+	if (isLockedToScreen()) {
+		LOG((CLOG_DEBUG1 "locked to screen"));
+		stopSwitch();
+		return;
+	}
+
+	// switch screen
+	switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false);
+}
+
+void
+CServer::handleClientDisconnected(const CEvent&, void* vclient)
+{
+	// client has disconnected.  it might be an old client or an
+	// active client.  we don't care so just handle it both ways.
+	CBaseClientProxy* client = reinterpret_cast<CBaseClientProxy*>(vclient);
+	removeActiveClient(client);
+	removeOldClient(client);
+	delete client;
+}
+
+void
+CServer::handleClientCloseTimeout(const CEvent&, void* vclient)
+{
+	// client took too long to disconnect.  just dump it.
+	CBaseClientProxy* client = reinterpret_cast<CBaseClientProxy*>(vclient);
+	LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str()));
+	removeOldClient(client);
+	delete client;
+}
+
+void
+CServer::handleSwitchToScreenEvent(const CEvent& event, void*)
+{
+	CSwitchToScreenInfo* info = 
+		reinterpret_cast<CSwitchToScreenInfo*>(event.getData());
+
+	CClientList::const_iterator index = m_clients.find(info->m_screen);
+	if (index == m_clients.end()) {
+		LOG((CLOG_DEBUG1 "screen \"%s\" not active", info->m_screen));
+	}
+	else {
+		jumpToScreen(index->second);
+	}
+}
+
+void
+CServer::handleSwitchInDirectionEvent(const CEvent& event, void*)
+{
+	CSwitchInDirectionInfo* info = 
+		reinterpret_cast<CSwitchInDirectionInfo*>(event.getData());
+
+	// jump to screen in chosen direction from center of this screen
+	SInt32 x = m_x, y = m_y;
+	CBaseClientProxy* newScreen =
+		getNeighbor(m_active, info->m_direction, x, y);
+	if (newScreen == NULL) {
+		LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(info->m_direction)));
+	}
+	else {
+		jumpToScreen(newScreen);
+	}
+}
+
+void
+CServer::handleLockCursorToScreenEvent(const CEvent& event, void*)
+{
+	CLockCursorToScreenInfo* info = (CLockCursorToScreenInfo*)event.getData();
+
+	// choose new state
+	bool newState;
+	switch (info->m_state) {
+	case CLockCursorToScreenInfo::kOff:
+		newState = false;
+		break;
+
+	default:
+	case CLockCursorToScreenInfo::kOn:
+		newState = true;
+		break;
+
+	case CLockCursorToScreenInfo::kToggle:
+		newState = !m_lockedToScreen;
+		break;
+	}
+
+	// enter new state
+	if (newState != m_lockedToScreen) {
+		SInt32 x, y;
+		m_lockedToScreen = newState;
+
+		LOG((CLOG_DEBUG "cursor %s current screen", m_lockedToScreen ? "locked to" : "unlocked from"));
+
+		m_primaryClient->reconfigure(getActivePrimarySides());
+		if (!isLockedToScreenServer()) {
+			//stopRelativeMoves();
+		}
+	}
+}
+
+void
+CServer::handleFakeInputBeginEvent(const CEvent&, void*)
+{
+	m_primaryClient->fakeInputBegin();
+}
+
+void
+CServer::handleFakeInputEndEvent(const CEvent&, void*)
+{
+	m_primaryClient->fakeInputEnd();
+}
+
+void
+CServer::onClipboardChanged(CBaseClientProxy* sender,
+				ClipboardID id, UInt32 seqNum)
+{
+	CClipboardInfo& clipboard = m_clipboards[id];
+
+	// ignore update if sequence number is old
+	if (seqNum < clipboard.m_clipboardSeqNum) {
+		LOG((CLOG_INFO "ignored screen \"%s\" update of clipboard %d (missequenced)", getName(sender).c_str(), id));
+		return;
+	}
+
+	// should be the expected client
+	assert(sender == m_clients.find(clipboard.m_clipboardOwner)->second);
+
+	// get data
+	sender->getClipboard(id, &clipboard.m_clipboard);
+
+	// ignore if data hasn't changed
+	CString data = clipboard.m_clipboard.marshall();
+	if (data == clipboard.m_clipboardData) {
+		LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id));
+		return;
+	}
+
+	// got new data
+	LOG((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id));
+	clipboard.m_clipboardData = data;
+
+	// tell all clients except the sender that the clipboard is dirty
+	for (CClientList::const_iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+		CBaseClientProxy* client = index->second;
+		client->setClipboardDirty(id, client != sender);
+	}
+
+	// send the new clipboard to the active screen
+	m_active->setClipboard(id, &clipboard.m_clipboard);
+}
+
+void
+CServer::onScreensaver(bool activated)
+{
+	LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated"));
+
+	if (activated) {
+		// save current screen and position
+		m_activeSaver = m_active;
+		m_xSaver      = m_x;
+		m_ySaver      = m_y;
+
+		// jump to primary screen
+		if (m_active != m_primaryClient) {
+			switchScreen(m_primaryClient, 0, 0, true);
+		}
+	}
+	else {
+		// jump back to previous screen and position.  we must check
+		// that the position is still valid since the screen may have
+		// changed resolutions while the screen saver was running.
+		if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) {
+			// check position
+			CBaseClientProxy* screen = m_activeSaver;
+			SInt32 x, y, w, h;
+			screen->getShape(x, y, w, h);
+			SInt32 zoneSize = getJumpZoneSize(screen);
+			if (m_xSaver < x + zoneSize) {
+				m_xSaver = x + zoneSize;
+			}
+			else if (m_xSaver >= x + w - zoneSize) {
+				m_xSaver = x + w - zoneSize - 1;
+			}
+			if (m_ySaver < y + zoneSize) {
+				m_ySaver = y + zoneSize;
+			}
+			else if (m_ySaver >= y + h - zoneSize) {
+				m_ySaver = y + h - zoneSize - 1;
+			}
+
+			// jump
+			switchScreen(screen, m_xSaver, m_ySaver, false);
+		}
+
+		// reset state
+		m_activeSaver = NULL;
+	}
+
+	// send message to all clients
+	for (CClientList::const_iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+		CBaseClientProxy* client = index->second;
+		client->screensaver(activated);
+	}
+}
+
+void
+CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button,
+				const char* screens)
+{
+	LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button));
+	assert(m_active != NULL);
+
+	// relay
+	if (IKeyState::CKeyInfo::isDefault(screens)) {
+		if (m_active != m_primaryClient)
+		{
+			m_active->keyDown(id, mask, button);
+		}
+		else
+		{ 
+			if (id == 96 && mask == 0 && button == 0x0033)
+			{
+				if (m_broadcast)
+				{
+					m_broadcast = false;
+					AlertSoundPlayCustomSound(m_broadcastOffID);
+				}
+				else
+				{
+					m_broadcast = true;
+					AlertSoundPlayCustomSound(m_broadcastOnID);
+				}
+			}
+			if (m_broadcast)
+			{
+				for (CClientList::const_iterator index = m_clients.begin();
+						 index != m_clients.end(); ++index) {
+					index->second->keyDown(id, mask, button);
+				}
+			}
+		}
+	}
+	else {
+		for (CClientList::const_iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+			if (IKeyState::CKeyInfo::contains(screens, index->first)) {
+				index->second->keyDown(id, mask, button);
+			}
+		}
+	}
+}
+
+void
+CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button,
+				const char* screens)
+{
+	LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button));
+	assert(m_active != NULL);
+
+	// relay
+	if (IKeyState::CKeyInfo::isDefault(screens)) {
+		if (m_active != m_primaryClient)
+		{
+			m_active->keyUp(id, mask, button);
+		}
+		else if (m_broadcast)
+		{
+			for (CClientList::const_iterator index = m_clients.begin(); index != m_clients.end(); ++index) {
+				index->second->keyUp(id, mask, button);
+			}
+		}
+	}
+	else {
+		for (CClientList::const_iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+			if (IKeyState::CKeyInfo::contains(screens, index->first)) {
+				index->second->keyUp(id, mask, button);
+			}
+		}
+	}
+}
+
+void
+CServer::onKeyRepeat(KeyID id, KeyModifierMask mask,
+				SInt32 count, KeyButton button)
+{
+	LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button));
+	assert(m_active != NULL);
+
+	// relay
+	if (m_active != m_primaryClient)
+	{
+		m_active->keyRepeat(id, mask, count, button);
+	}
+	else if (m_broadcast)
+	{
+		for (CClientList::const_iterator index = m_clients.begin(); index != m_clients.end(); ++index) {
+			index->second->keyRepeat(id, mask, count, button);
+		}
+	}
+}
+
+void
+CServer::onMouseDown(ButtonID id)
+{
+	LOG((CLOG_DEBUG1 "onMouseDown id=%d", id));
+	assert(m_active != NULL);
+
+	// relay
+	m_active->mouseDown(id);
+}
+
+void
+CServer::onMouseUp(ButtonID id)
+{
+	LOG((CLOG_DEBUG1 "onMouseUp id=%d", id));
+	assert(m_active != NULL);
+
+	// relay
+	m_active->mouseUp(id);
+}
+
+bool
+CServer::onMouseMovePrimary(SInt32 x, SInt32 y)
+{
+	LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y));
+
+	// mouse move on primary (server's) screen
+	if (m_active != m_primaryClient) {
+		// stale event -- we're actually on a secondary screen
+		return false;
+	}
+
+	// save last delta
+	m_xDelta2 = m_xDelta;
+	m_yDelta2 = m_yDelta;
+
+	// save current delta
+	m_xDelta  = x - m_x;
+	m_yDelta  = y - m_y;
+
+	// save position
+	m_x       = x;
+	m_y       = y;
+
+	// get screen shape
+	SInt32 ax, ay, aw, ah;
+	m_active->getShape(ax, ay, aw, ah);
+	SInt32 zoneSize = getJumpZoneSize(m_active);
+
+	// clamp position to screen
+	SInt32 xc = x, yc = y;
+	if (xc < ax + zoneSize) {
+		xc = ax;
+	}
+	else if (xc >= ax + aw - zoneSize) {
+		xc = ax + aw - 1;
+	}
+	if (yc < ay + zoneSize) {
+		yc = ay;
+	}
+	else if (yc >= ay + ah - zoneSize) {
+		yc = ay + ah - 1;
+	}
+
+	// see if we should change screens
+	EDirection dir;
+	if (x < ax + zoneSize) {
+		x  -= zoneSize;
+		dir = kLeft;
+	}
+	else if (x >= ax + aw - zoneSize) {
+		x  += zoneSize;
+		dir = kRight;
+	}
+	else if (y < ay + zoneSize) {
+		y  -= zoneSize;
+		dir = kTop;
+	}
+	else if (y >= ay + ah - zoneSize) {
+		y  += zoneSize;
+		dir = kBottom;
+	}
+	else {
+		// still on local screen
+		noSwitch(x, y);
+		return false;
+	}
+
+	// get jump destination
+	CBaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y);
+
+	// should we switch or not?
+	if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) {
+		// switch screen
+		switchScreen(newScreen, x, y, false);
+		return true;
+	}
+	else {
+		return false;
+	}
+}
+
+void
+CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
+{
+	LOG((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy));
+
+	// mouse move on secondary (client's) screen
+	assert(m_active != NULL);
+	if (m_active == m_primaryClient) {
+		// stale event -- we're actually on the primary screen
+		return;
+	}
+
+	// if doing relative motion on secondary screens and we're locked
+	// to the screen (which activates relative moves) then send a
+	// relative mouse motion.  when we're doing this we pretend as if
+	// the mouse isn't actually moving because we're expecting some
+	// program on the secondary screen to warp the mouse on us, so we
+	// have no idea where it really is.
+	if (m_relativeMoves && isLockedToScreenServer()) {
+		LOG((CLOG_DEBUG2 "relative move on %s by %d,%d", getName(m_active).c_str(), dx, dy));
+		m_active->mouseRelativeMove(dx, dy);
+		return;
+	}
+
+	// save old position
+	const SInt32 xOld = m_x;
+	const SInt32 yOld = m_y;
+
+	// save last delta
+	m_xDelta2 = m_xDelta;
+	m_yDelta2 = m_yDelta;
+
+	// save current delta
+	m_xDelta  = dx;
+	m_yDelta  = dy;
+
+	// accumulate motion
+	m_x      += dx;
+	m_y      += dy;
+
+	// get screen shape
+	SInt32 ax, ay, aw, ah;
+	m_active->getShape(ax, ay, aw, ah);
+
+	// find direction of neighbor and get the neighbor
+	bool jump = true;
+	CBaseClientProxy* newScreen;
+	do {
+		// clamp position to screen
+		SInt32 xc = m_x, yc = m_y;
+		if (xc < ax) {
+			xc = ax;
+		}
+		else if (xc >= ax + aw) {
+			xc = ax + aw - 1;
+		}
+		if (yc < ay) {
+			yc = ay;
+		}
+		else if (yc >= ay + ah) {
+			yc = ay + ah - 1;
+		}
+
+		EDirection dir;
+		if (m_x < ax) {
+			dir = kLeft;
+		}
+		else if (m_x > ax + aw - 1) {
+			dir = kRight;
+		}
+		else if (m_y < ay) {
+			dir = kTop;
+		}
+		else if (m_y > ay + ah - 1) {
+			dir = kBottom;
+		}
+		else {
+			// we haven't left the screen
+			newScreen = m_active;
+			jump      = false;
+
+			// if waiting and mouse is not on the border we're waiting
+			// on then stop waiting.  also if it's not on the border
+			// then arm the double tap.
+			if (m_switchScreen != NULL) {
+				bool clearWait;
+				SInt32 zoneSize = m_primaryClient->getJumpZoneSize();
+				switch (m_switchDir) {
+				case kLeft:
+					clearWait = (m_x >= ax + zoneSize);
+					break;
+
+				case kRight:
+					clearWait = (m_x <= ax + aw - 1 - zoneSize);
+					break;
+
+				case kTop:
+					clearWait = (m_y >= ay + zoneSize);
+					break;
+
+				case kBottom:
+					clearWait = (m_y <= ay + ah - 1 + zoneSize);
+					break;
+
+				default:
+					clearWait = false;
+					break;
+				}
+				if (clearWait) {
+					// still on local screen
+					noSwitch(m_x, m_y);
+				}
+			}
+
+			// skip rest of block
+			break;
+		}
+
+		// try to switch screen.  get the neighbor.
+		newScreen = mapToNeighbor(m_active, dir, m_x, m_y);
+
+		// see if we should switch
+		if (!isSwitchOkay(newScreen, dir, m_x, m_y, xc, yc)) {
+			newScreen = m_active;
+			jump      = false;
+		}
+	} while (false);
+
+	if (jump) {
+		// switch screens
+		switchScreen(newScreen, m_x, m_y, false);
+	}
+	else {
+		// same screen.  clamp mouse to edge.
+		m_x = xOld + dx;
+		m_y = yOld + dy;
+		if (m_x < ax) {
+			m_x = ax;
+			LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", getName(m_active).c_str()));
+		}
+		else if (m_x > ax + aw - 1) {
+			m_x = ax + aw - 1;
+			LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", getName(m_active).c_str()));
+		}
+		if (m_y < ay) {
+			m_y = ay;
+			LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", getName(m_active).c_str()));
+		}
+		else if (m_y > ay + ah - 1) {
+			m_y = ay + ah - 1;
+			LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", getName(m_active).c_str()));
+		}
+
+		// warp cursor if it moved.
+		if (m_x != xOld || m_y != yOld) {
+			LOG((CLOG_DEBUG2 "move on %s to %d,%d", getName(m_active).c_str(), m_x, m_y));
+			m_active->mouseMove(m_x, m_y);
+		}
+	}
+}
+
+void
+CServer::onMouseWheel(SInt32 xDelta, SInt32 yDelta)
+{
+	LOG((CLOG_DEBUG1 "onMouseWheel %+d,%+d", xDelta, yDelta));
+	assert(m_active != NULL);
+
+	// relay
+	m_active->mouseWheel(xDelta, yDelta);
+}
+
+bool
+CServer::addClient(CBaseClientProxy* client)
+{
+	CString name = getName(client);
+	if (m_clients.count(name) != 0) {
+		return false;
+	}
+
+	// add event handlers
+	EVENTQUEUE->adoptHandler(IScreen::getShapeChangedEvent(),
+							client->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleShapeChanged, client));
+	EVENTQUEUE->adoptHandler(IScreen::getClipboardGrabbedEvent(),
+							client->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleClipboardGrabbed, client));
+	EVENTQUEUE->adoptHandler(CClientProxy::getClipboardChangedEvent(),
+							client->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleClipboardChanged, client));
+
+	// add to list
+	m_clientSet.insert(client);
+	m_clients.insert(std::make_pair(name, client));
+
+	// initialize client data
+	SInt32 x, y;
+	client->getCursorPos(x, y);
+	client->setJumpCursorPos(x, y);
+
+	// tell primary client about the active sides
+	m_primaryClient->reconfigure(getActivePrimarySides());
+
+	return true;
+}
+
+bool
+CServer::removeClient(CBaseClientProxy* client)
+{
+	// return false if not in list
+	CClientSet::iterator i = m_clientSet.find(client);
+	if (i == m_clientSet.end()) {
+		return false;
+	}
+
+	// remove event handlers
+	EVENTQUEUE->removeHandler(IScreen::getShapeChangedEvent(),
+							client->getEventTarget());
+	EVENTQUEUE->removeHandler(IScreen::getClipboardGrabbedEvent(),
+							client->getEventTarget());
+	EVENTQUEUE->removeHandler(CClientProxy::getClipboardChangedEvent(),
+							client->getEventTarget());
+
+	// remove from list
+	m_clients.erase(getName(client));
+	m_clientSet.erase(i);
+
+	return true;
+}
+
+void
+CServer::closeClient(CBaseClientProxy* client, const char* msg)
+{
+	assert(client != m_primaryClient);
+	assert(msg != NULL);
+
+	// send message to client.  this message should cause the client
+	// to disconnect.  we add this client to the closed client list
+	// and install a timer to remove the client if it doesn't respond
+	// quickly enough.  we also remove the client from the active
+	// client list since we're not going to listen to it anymore.
+	// note that this method also works on clients that are not in
+	// the m_clients list.  adoptClient() may call us with such a
+	// client.
+	LOG((CLOG_NOTE "disconnecting client \"%s\"", getName(client).c_str()));
+
+	// send message
+	// FIXME -- avoid type cast (kinda hard, though)
+	((CClientProxy*)client)->close(msg);
+
+	// install timer.  wait timeout seconds for client to close.
+	double timeout = 5.0;
+	CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(timeout, NULL);
+	EVENTQUEUE->adoptHandler(CEvent::kTimer, timer,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleClientCloseTimeout, client));
+
+	// move client to closing list
+	removeClient(client);
+	m_oldClients.insert(std::make_pair(client, timer));
+
+	// if this client is the active screen then we have to
+	// jump off of it
+	forceLeaveClient(client);
+}
+
+void
+CServer::closeClients(const CConfig& config)
+{
+	// collect the clients that are connected but are being dropped
+	// from the configuration (or who's canonical name is changing).
+	typedef std::set<CBaseClientProxy*> CRemovedClients;
+	CRemovedClients removed;
+	for (CClientList::iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+		if (!config.isCanonicalName(index->first)) {
+			removed.insert(index->second);
+		}
+	}
+
+	// don't close the primary client
+	removed.erase(m_primaryClient);
+
+	// now close them.  we collect the list then close in two steps
+	// because closeClient() modifies the collection we iterate over.
+	for (CRemovedClients::iterator index = removed.begin();
+								index != removed.end(); ++index) {
+		closeClient(*index, kMsgCClose);
+	}
+}
+
+void
+CServer::removeActiveClient(CBaseClientProxy* client)
+{
+	if (removeClient(client)) {
+		forceLeaveClient(client);
+		EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client);
+		if (m_clients.size() == 1 && m_oldClients.empty()) {
+			EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this));
+		}
+	}
+}
+
+void
+CServer::removeOldClient(CBaseClientProxy* client)
+{
+	COldClients::iterator i = m_oldClients.find(client);
+	if (i != m_oldClients.end()) {
+		EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client);
+		EVENTQUEUE->removeHandler(CEvent::kTimer, i->second);
+		EVENTQUEUE->deleteTimer(i->second);
+		m_oldClients.erase(i);
+		if (m_clients.size() == 1 && m_oldClients.empty()) {
+			EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this));
+		}
+	}
+}
+
+void
+CServer::forceLeaveClient(CBaseClientProxy* client)
+{
+	CBaseClientProxy* active =
+		(m_activeSaver != NULL) ? m_activeSaver : m_active;
+	if (active == client) {
+		// record new position (center of primary screen)
+		m_primaryClient->getCursorCenter(m_x, m_y);
+
+		// stop waiting to switch to this client
+		if (active == m_switchScreen) {
+			stopSwitch();
+		}
+
+		// don't notify active screen since it has probably already
+		// disconnected.
+		LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", getName(active).c_str(), getName(m_primaryClient).c_str(), m_x, m_y));
+
+		// cut over
+		m_active = m_primaryClient;
+
+		// enter new screen (unless we already have because of the
+		// screen saver)
+		if (m_activeSaver == NULL) {
+			m_primaryClient->enter(m_x, m_y, m_seqNum,
+								m_primaryClient->getToggleMask(), false);
+		}
+	}
+
+	// if this screen had the cursor when the screen saver activated
+	// then we can't switch back to it when the screen saver
+	// deactivates.
+	if (m_activeSaver == client) {
+		m_activeSaver = NULL;
+	}
+
+	// tell primary client about the active sides
+	m_primaryClient->reconfigure(getActivePrimarySides());
+}
+
+
+//
+// CServer::CClipboardInfo
+//
+
+CServer::CClipboardInfo::CClipboardInfo() :
+	m_clipboard(),
+	m_clipboardData(),
+	m_clipboardOwner(),
+	m_clipboardSeqNum(0)
+{
+	// do nothing
+}
+
+
+//
+// CServer::CLockCursorToScreenInfo
+//
+
+CServer::CLockCursorToScreenInfo*
+CServer::CLockCursorToScreenInfo::alloc(State state)
+{
+	CLockCursorToScreenInfo* info =
+		(CLockCursorToScreenInfo*)malloc(sizeof(CLockCursorToScreenInfo));
+	info->m_state = state;
+	return info;
+}
+
+
+//
+// CServer::CSwitchToScreenInfo
+//
+
+CServer::CSwitchToScreenInfo*
+CServer::CSwitchToScreenInfo::alloc(const CString& screen)
+{
+	CSwitchToScreenInfo* info =
+		(CSwitchToScreenInfo*)malloc(sizeof(CSwitchToScreenInfo) +
+								screen.size());
+	strcpy(info->m_screen, screen.c_str());
+	return info;
+}
+
+
+//
+// CServer::CSwitchInDirectionInfo
+//
+
+CServer::CSwitchInDirectionInfo*
+CServer::CSwitchInDirectionInfo::alloc(EDirection direction)
+{
+	CSwitchInDirectionInfo* info =
+		(CSwitchInDirectionInfo*)malloc(sizeof(CSwitchInDirectionInfo));
+	info->m_direction = direction;
+	return info;
+}
+
+
+//
+// CServer::CScreenConnectedInfo
+//
+
+CServer::CScreenConnectedInfo*
+CServer::CScreenConnectedInfo::alloc(const CString& screen)
+{
+	CScreenConnectedInfo* info =
+		(CScreenConnectedInfo*)malloc(sizeof(CScreenConnectedInfo) +
+								screen.size());
+	strcpy(info->m_screen, screen.c_str());
+	return info;
+}
diff -uNr synergy-1.3.1/lib/server/CServer.cpp.ok synergy-1.3.1-broadcast/lib/server/CServer.cpp.ok
--- synergy-1.3.1/lib/server/CServer.cpp.ok	1969-12-31 16:00:00.000000000 -0800
+++ synergy-1.3.1-broadcast/lib/server/CServer.cpp.ok	2007-08-17 01:09:16.000000000 -0700
@@ -0,0 +1,2137 @@
+/*
+ * synergy -- mouse and keyboard sharing utility
+ * Copyright (C) 2002 Chris Schoeneman
+ * 
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file COPYING that should have accompanied this file.
+ * 
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "CServer.h"
+#include "CClientProxy.h"
+#include "CClientProxyUnknown.h"
+#include "CPrimaryClient.h"
+#include "IPlatformScreen.h"
+#include "OptionTypes.h"
+#include "ProtocolTypes.h"
+#include "XScreen.h"
+#include "XSynergy.h"
+#include "IDataSocket.h"
+#include "IListenSocket.h"
+#include "XSocket.h"
+#include "IEventQueue.h"
+#include "CLog.h"
+#include "TMethodEventJob.h"
+#include "CArch.h"
+#include <string.h>
+
+//
+// CServer
+//
+
+CEvent::Type			CServer::s_errorEvent         = CEvent::kUnknown;
+CEvent::Type			CServer::s_connectedEvent     = CEvent::kUnknown;
+CEvent::Type			CServer::s_disconnectedEvent  = CEvent::kUnknown;
+CEvent::Type			CServer::s_switchToScreen     = CEvent::kUnknown;
+CEvent::Type			CServer::s_switchInDirection  = CEvent::kUnknown;
+CEvent::Type			CServer::s_lockCursorToScreen = CEvent::kUnknown;
+
+CServer::CServer(const CConfig& config, CPrimaryClient* primaryClient) :
+	m_primaryClient(primaryClient),
+	m_active(primaryClient),
+	m_seqNum(0),
+	m_xDelta(0),
+	m_yDelta(0),
+	m_xDelta2(0),
+	m_yDelta2(0),
+	m_config(),
+	m_inputFilter(m_config.getInputFilter()),
+	m_activeSaver(NULL),
+	m_switchDir(kNoDirection),
+	m_switchScreen(NULL),
+	m_switchWaitDelay(0.0),
+	m_switchWaitTimer(NULL),
+	m_switchTwoTapDelay(0.0),
+	m_switchTwoTapEngaged(false),
+	m_switchTwoTapArmed(false),
+	m_switchTwoTapZone(3),
+	m_relativeMoves(false),
+	m_lockedToScreen(false),
+	m_broadcast(false)
+{
+	// must have a primary client and it must have a canonical name
+	assert(m_primaryClient != NULL);
+	assert(config.isScreen(primaryClient->getName()));
+
+	CString primaryName = getName(primaryClient);
+
+	// clear clipboards
+	for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+		CClipboardInfo& clipboard   = m_clipboards[id];
+		clipboard.m_clipboardOwner  = primaryName;
+		clipboard.m_clipboardSeqNum = m_seqNum;
+		if (clipboard.m_clipboard.open(0)) {
+			clipboard.m_clipboard.empty();
+			clipboard.m_clipboard.close();
+		}
+		clipboard.m_clipboardData   = clipboard.m_clipboard.marshall();
+	}
+
+	// install event handlers
+	EVENTQUEUE->adoptHandler(CEvent::kTimer, this,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleSwitchWaitTimeout));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyDownEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleKeyDownEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyUpEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleKeyUpEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyRepeatEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleKeyRepeatEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonDownEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleButtonDownEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonUpEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleButtonUpEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnPrimaryEvent(),
+							m_primaryClient->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleMotionPrimaryEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnSecondaryEvent(),
+							m_primaryClient->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleMotionSecondaryEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getWheelEvent(),
+							m_primaryClient->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleWheelEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverActivatedEvent(),
+							m_primaryClient->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleScreensaverActivatedEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverDeactivatedEvent(),
+							m_primaryClient->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleScreensaverDeactivatedEvent));
+	EVENTQUEUE->adoptHandler(getSwitchToScreenEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleSwitchToScreenEvent));
+	EVENTQUEUE->adoptHandler(getSwitchInDirectionEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleSwitchInDirectionEvent));
+	EVENTQUEUE->adoptHandler(getLockCursorToScreenEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleLockCursorToScreenEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getFakeInputBeginEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleFakeInputBeginEvent));
+	EVENTQUEUE->adoptHandler(IPlatformScreen::getFakeInputEndEvent(),
+							m_inputFilter,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleFakeInputEndEvent));
+
+	// add connection
+	addClient(m_primaryClient);
+
+	// set initial configuration
+	setConfig(config);
+
+	// enable primary client
+	m_primaryClient->enable();
+	m_inputFilter->setPrimaryClient(m_primaryClient);
+}
+
+CServer::~CServer()
+{
+	// remove event handlers and timers
+	EVENTQUEUE->removeHandler(IPlatformScreen::getKeyDownEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getKeyUpEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getKeyRepeatEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getButtonDownEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getButtonUpEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnPrimaryEvent(),
+							m_primaryClient->getEventTarget());
+	EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnSecondaryEvent(),
+							m_primaryClient->getEventTarget());
+	EVENTQUEUE->removeHandler(IPlatformScreen::getWheelEvent(),
+							m_primaryClient->getEventTarget());
+	EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverActivatedEvent(),
+							m_primaryClient->getEventTarget());
+	EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverDeactivatedEvent(),
+							m_primaryClient->getEventTarget());
+	EVENTQUEUE->removeHandler(IPlatformScreen::getFakeInputBeginEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(IPlatformScreen::getFakeInputEndEvent(),
+							m_inputFilter);
+	EVENTQUEUE->removeHandler(CEvent::kTimer, this);
+	stopSwitch();
+
+	// force immediate disconnection of secondary clients
+	disconnect();
+	for (COldClients::iterator index = m_oldClients.begin();
+							index != m_oldClients.begin(); ++index) {
+		CBaseClientProxy* client = index->first;
+		EVENTQUEUE->deleteTimer(index->second);
+		EVENTQUEUE->removeHandler(CEvent::kTimer, client);
+		EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client);
+		delete client;
+	}
+
+	// remove input filter
+	m_inputFilter->setPrimaryClient(NULL);
+
+	// disable and disconnect primary client
+	m_primaryClient->disable();
+	removeClient(m_primaryClient);
+}
+
+bool
+CServer::setConfig(const CConfig& config)
+{
+	// refuse configuration if it doesn't include the primary screen
+	if (!config.isScreen(m_primaryClient->getName())) {
+		return false;
+	}
+
+	// close clients that are connected but being dropped from the
+	// configuration.
+	closeClients(config);
+
+	// cut over
+	m_config = config;
+	processOptions();
+
+	// add ScrollLock as a hotkey to lock to the screen.  this was a
+	// built-in feature in earlier releases and is now supported via
+	// the user configurable hotkey mechanism.  if the user has already
+	// registered ScrollLock for something else then that will win but
+	// we will unfortunately generate a warning.  if the user has
+	// configured a CLockCursorToScreenAction then we don't add
+	// ScrollLock as a hotkey.
+	if (!m_config.hasLockToScreenAction()) {
+		IPlatformScreen::CKeyInfo* key =
+			IPlatformScreen::CKeyInfo::alloc(kKeyScrollLock, 0, 0, 0);
+		CInputFilter::CRule rule(new CInputFilter::CKeystrokeCondition(key));
+		rule.adoptAction(new CInputFilter::CLockCursorToScreenAction, true);
+		m_inputFilter->addFilterRule(rule);
+	}
+
+	// tell primary screen about reconfiguration
+	m_primaryClient->reconfigure(getActivePrimarySides());
+
+	// tell all (connected) clients about current options
+	for (CClientList::const_iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+		CBaseClientProxy* client = index->second;
+		sendOptions(client);
+	}
+
+	return true;
+}
+
+void
+CServer::adoptClient(CBaseClientProxy* client)
+{
+	assert(client != NULL);
+
+	// watch for client disconnection
+	EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), client,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleClientDisconnected, client));
+
+	// name must be in our configuration
+	if (!m_config.isScreen(client->getName())) {
+		LOG((CLOG_WARN "a client with name \"%s\" is not in the map", client->getName().c_str()));
+		closeClient(client, kMsgEUnknown);
+		return;
+	}
+
+	// add client to client list
+	if (!addClient(client)) {
+		// can only have one screen with a given name at any given time
+		LOG((CLOG_WARN "a client with name \"%s\" is already connected", getName(client).c_str()));
+		closeClient(client, kMsgEBusy);
+		return;
+	}
+	LOG((CLOG_NOTE "client \"%s\" has connected", getName(client).c_str()));
+
+	// send configuration options to client
+	sendOptions(client);
+
+	// activate screen saver on new client if active on the primary screen
+	if (m_activeSaver != NULL) {
+		client->screensaver(true);
+	}
+
+	// send notification
+	CServer::CScreenConnectedInfo* info =
+		CServer::CScreenConnectedInfo::alloc(getName(client));
+	EVENTQUEUE->addEvent(CEvent(CServer::getConnectedEvent(),
+								m_primaryClient->getEventTarget(), info));
+}
+
+void
+CServer::disconnect()
+{
+	// close all secondary clients
+	if (m_clients.size() > 1 || !m_oldClients.empty()) {
+		CConfig emptyConfig;
+		closeClients(emptyConfig);
+	}
+	else {
+		EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this));
+	}
+}
+
+UInt32
+CServer::getNumClients() const
+{
+	return m_clients.size();
+}
+
+void
+CServer::getClients(std::vector<CString>& list) const
+{
+	list.clear();
+	for (CClientList::const_iterator index = m_clients.begin();
+							index != m_clients.end(); ++index) {
+		list.push_back(index->first);
+	}
+}
+
+CEvent::Type
+CServer::getErrorEvent()
+{
+	return CEvent::registerTypeOnce(s_errorEvent,
+							"CServer::error");
+}
+
+CEvent::Type
+CServer::getConnectedEvent()
+{
+	return CEvent::registerTypeOnce(s_connectedEvent,
+							"CServer::connected");
+}
+
+CEvent::Type
+CServer::getDisconnectedEvent()
+{
+	return CEvent::registerTypeOnce(s_disconnectedEvent,
+							"CServer::disconnected");
+}
+
+CEvent::Type
+CServer::getSwitchToScreenEvent()
+{
+	return CEvent::registerTypeOnce(s_switchToScreen,
+							"CServer::switchToScreen");
+}
+
+CEvent::Type
+CServer::getSwitchInDirectionEvent()
+{
+	return CEvent::registerTypeOnce(s_switchInDirection,
+							"CServer::switchInDirection");
+}
+
+CEvent::Type
+CServer::getLockCursorToScreenEvent()
+{
+	return CEvent::registerTypeOnce(s_lockCursorToScreen,
+							"CServer::lockCursorToScreen");
+}
+
+CString
+CServer::getName(const CBaseClientProxy* client) const
+{
+	CString name = m_config.getCanonicalName(client->getName());
+	if (name.empty()) {
+		name = client->getName();
+	}
+	return name;
+}
+
+UInt32
+CServer::getActivePrimarySides() const
+{
+	UInt32 sides = 0;
+	if (!isLockedToScreenServer()) {
+		if (hasAnyNeighbor(m_primaryClient, kLeft)) {
+			sides |= kLeftMask;
+		}
+		if (hasAnyNeighbor(m_primaryClient, kRight)) {
+			sides |= kRightMask;
+		}
+		if (hasAnyNeighbor(m_primaryClient, kTop)) {
+			sides |= kTopMask;
+		}
+		if (hasAnyNeighbor(m_primaryClient, kBottom)) {
+			sides |= kBottomMask;
+		}
+	}
+	return sides;
+}
+
+bool
+CServer::isLockedToScreenServer() const
+{
+	// locked if scroll-lock is toggled on
+	return m_lockedToScreen;
+}
+
+bool
+CServer::isLockedToScreen() const
+{
+	// locked if we say we're locked
+	if (isLockedToScreenServer()) {
+		LOG((CLOG_DEBUG "locked to screen"));
+		return true;
+	}
+
+	// locked if primary says we're locked
+	if (m_primaryClient->isLockedToScreen()) {
+		return true;
+	}
+
+	// not locked
+	return false;
+}
+
+SInt32
+CServer::getJumpZoneSize(CBaseClientProxy* client) const
+{
+	if (client == m_primaryClient) {
+		return m_primaryClient->getJumpZoneSize();
+	}
+	else {
+		return 0;
+	}
+}
+
+void
+CServer::switchScreen(CBaseClientProxy* dst,
+				SInt32 x, SInt32 y, bool forScreensaver)
+{
+	assert(dst != NULL);
+#ifndef NDEBUG
+	{
+		SInt32 dx, dy, dw, dh;
+		dst->getShape(dx, dy, dw, dh);
+		assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh);
+	}
+#endif
+	assert(m_active != NULL);
+
+	LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", getName(m_active).c_str(), getName(dst).c_str(), x, y));
+
+	// stop waiting to switch
+	stopSwitch();
+
+	// record new position
+	m_x       = x;
+	m_y       = y;
+	m_xDelta  = 0;
+	m_yDelta  = 0;
+	m_xDelta2 = 0;
+	m_yDelta2 = 0;
+
+	// wrapping means leaving the active screen and entering it again.
+	// since that's a waste of time we skip that and just warp the
+	// mouse.
+	if (m_active != dst) {
+		// leave active screen
+		if (!m_active->leave()) {
+			// cannot leave screen
+			LOG((CLOG_WARN "can't leave screen"));
+			return;
+		}
+
+		// update the primary client's clipboards if we're leaving the
+		// primary screen.
+		if (m_active == m_primaryClient) {
+			for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+				CClipboardInfo& clipboard = m_clipboards[id];
+				if (clipboard.m_clipboardOwner == getName(m_primaryClient)) {
+					onClipboardChanged(m_primaryClient,
+						id, clipboard.m_clipboardSeqNum);
+				}
+			}
+		}
+
+		// cut over
+		m_active = dst;
+
+		// increment enter sequence number
+		++m_seqNum;
+
+		// enter new screen
+		m_active->enter(x, y, m_seqNum,
+								m_primaryClient->getToggleMask(),
+								forScreensaver);
+
+		// send the clipboard data to new active screen
+		for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+			m_active->setClipboard(id, &m_clipboards[id].m_clipboard);
+		}
+	}
+	else {
+		m_active->mouseMove(x, y);
+	}
+}
+
+void
+CServer::jumpToScreen(CBaseClientProxy* newScreen)
+{
+	assert(newScreen != NULL);
+
+	// record the current cursor position on the active screen
+	m_active->setJumpCursorPos(m_x, m_y);
+
+	// get the last cursor position on the target screen
+	SInt32 x, y;
+	newScreen->getJumpCursorPos(x, y);
+	
+	switchScreen(newScreen, x, y, false);
+}
+
+float
+CServer::mapToFraction(CBaseClientProxy* client,
+				EDirection dir, SInt32 x, SInt32 y) const
+{
+	SInt32 sx, sy, sw, sh;
+	client->getShape(sx, sy, sw, sh);
+	switch (dir) {
+	case kLeft:
+	case kRight:
+		return static_cast<float>(y - sy + 0.5f) / static_cast<float>(sh);
+
+	case kTop:
+	case kBottom:
+		return static_cast<float>(x - sx + 0.5f) / static_cast<float>(sw);
+
+	case kNoDirection:
+		assert(0 && "bad direction");
+		break;
+	}
+	return 0.0f;
+}
+
+void
+CServer::mapToPixel(CBaseClientProxy* client,
+				EDirection dir, float f, SInt32& x, SInt32& y) const
+{
+	SInt32 sx, sy, sw, sh;
+	client->getShape(sx, sy, sw, sh);
+	switch (dir) {
+	case kLeft:
+	case kRight:
+		y = static_cast<SInt32>(f * sh) + sy;
+		break;
+
+	case kTop:
+	case kBottom:
+		x = static_cast<SInt32>(f * sw) + sx;
+		break;
+
+	case kNoDirection:
+		assert(0 && "bad direction");
+		break;
+	}
+}
+
+bool
+CServer::hasAnyNeighbor(CBaseClientProxy* client, EDirection dir) const
+{
+	assert(client != NULL);
+
+	return m_config.hasNeighbor(getName(client), dir);
+}
+
+CBaseClientProxy*
+CServer::getNeighbor(CBaseClientProxy* src,
+				EDirection dir, SInt32& x, SInt32& y) const
+{
+	// note -- must be locked on entry
+
+	assert(src != NULL);
+
+	// get source screen name
+	CString srcName = getName(src);
+	assert(!srcName.empty());
+	LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str()));
+
+	// convert position to fraction
+	float t = mapToFraction(src, dir, x, y);
+
+	// search for the closest neighbor that exists in direction dir
+	float tTmp;
+	for (;;) {
+		CString dstName(m_config.getNeighbor(srcName, dir, t, &tTmp));
+
+		// if nothing in that direction then return NULL. if the
+		// destination is the source then we can make no more
+		// progress in this direction.  since we haven't found a
+		// connected neighbor we return NULL.
+		if (dstName.empty()) {
+			LOG((CLOG_DEBUG2 "no neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str()));
+			return NULL;
+		}
+
+		// look up neighbor cell.  if the screen is connected and
+		// ready then we can stop.
+		CClientList::const_iterator index = m_clients.find(dstName);
+		if (index != m_clients.end()) {
+			LOG((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\" at %f", dstName.c_str(), CConfig::dirName(dir), srcName.c_str(), t));
+			mapToPixel(index->second, dir, tTmp, x, y);
+			return index->second;
+		}
+
+		// skip over unconnected screen
+		LOG((CLOG_DEBUG2 "ignored \"%s\" on %s of \"%s\"", dstName.c_str(), CConfig::dirName(dir), srcName.c_str()));
+		srcName = dstName;
+
+		// use position on skipped screen
+		t = tTmp;
+	}
+}
+
+CBaseClientProxy*
+CServer::mapToNeighbor(CBaseClientProxy* src,
+				EDirection srcSide, SInt32& x, SInt32& y) const
+{
+	// note -- must be locked on entry
+
+	assert(src != NULL);
+
+	// get the first neighbor
+	CBaseClientProxy* dst = getNeighbor(src, srcSide, x, y);
+	if (dst == NULL) {
+		return NULL;
+	}
+
+	// get the source screen's size
+	SInt32 dx, dy, dw, dh;
+	CBaseClientProxy* lastGoodScreen = src;
+	lastGoodScreen->getShape(dx, dy, dw, dh);
+
+	// find destination screen, adjusting x or y (but not both).  the
+	// searches are done in a sort of canonical screen space where
+	// the upper-left corner is 0,0 for each screen.  we adjust from
+	// actual to canonical position on entry to and from canonical to
+	// actual on exit from the search.
+	switch (srcSide) {
+	case kLeft:
+		x -= dx;
+		while (dst != NULL) {
+			lastGoodScreen = dst;
+			lastGoodScreen->getShape(dx, dy, dw, dh);
+			x += dw;
+			if (x >= 0) {
+				break;
+			}
+			LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+			dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+		}
+		assert(lastGoodScreen != NULL);
+		x += dx;
+		break;
+
+	case kRight:
+		x -= dx;
+		while (dst != NULL) {
+			x -= dw;
+			lastGoodScreen = dst;
+			lastGoodScreen->getShape(dx, dy, dw, dh);
+			if (x < dw) {
+				break;
+			}
+			LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+			dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+		}
+		assert(lastGoodScreen != NULL);
+		x += dx;
+		break;
+
+	case kTop:
+		y -= dy;
+		while (dst != NULL) {
+			lastGoodScreen = dst;
+			lastGoodScreen->getShape(dx, dy, dw, dh);
+			y += dh;
+			if (y >= 0) {
+				break;
+			}
+			LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+			dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+		}
+		assert(lastGoodScreen != NULL);
+		y += dy;
+		break;
+
+	case kBottom:
+		y -= dy;
+		while (dst != NULL) {
+			y -= dh;
+			lastGoodScreen = dst;
+			lastGoodScreen->getShape(dx, dy, dw, dh);
+			if (y < dh) {
+				break;
+			}
+			LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+			dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+		}
+		assert(lastGoodScreen != NULL);
+		y += dy;
+		break;
+
+	case kNoDirection:
+		assert(0 && "bad direction");
+		return NULL;
+	}
+
+	// save destination screen
+	assert(lastGoodScreen != NULL);
+	dst = lastGoodScreen;
+
+	// if entering primary screen then be sure to move in far enough
+	// to avoid the jump zone.  if entering a side that doesn't have
+	// a neighbor (i.e. an asymmetrical side) then we don't need to
+	// move inwards because that side can't provoke a jump.
+	avoidJumpZone(dst, srcSide, x, y);
+
+	return dst;
+}
+
+void
+CServer::avoidJumpZone(CBaseClientProxy* dst,
+				EDirection dir, SInt32& x, SInt32& y) const
+{
+	// we only need to avoid jump zones on the primary screen
+	if (dst != m_primaryClient) {
+		return;
+	}
+
+	const CString dstName(getName(dst));
+	SInt32 dx, dy, dw, dh;
+	dst->getShape(dx, dy, dw, dh);
+	float t = mapToFraction(dst, dir, x, y);
+	SInt32 z = getJumpZoneSize(dst);
+
+	// move in far enough to avoid the jump zone.  if entering a side
+	// that doesn't have a neighbor (i.e. an asymmetrical side) then we
+	// don't need to move inwards because that side can't provoke a jump.
+	switch (dir) {
+	case kLeft:
+		if (!m_config.getNeighbor(dstName, kRight, t, NULL).empty() &&
+			x > dx + dw - 1 - z)
+			x = dx + dw - 1 - z;
+		break;
+
+	case kRight:
+		if (!m_config.getNeighbor(dstName, kLeft, t, NULL).empty() &&
+			x < dx + z)
+			x = dx + z;
+		break;
+
+	case kTop:
+		if (!m_config.getNeighbor(dstName, kBottom, t, NULL).empty() &&
+			y > dy + dh - 1 - z)
+			y = dy + dh - 1 - z;
+		break;
+
+	case kBottom:
+		if (!m_config.getNeighbor(dstName, kTop, t, NULL).empty() &&
+			y < dy + z)
+			y = dy + z;
+		break;
+
+	case kNoDirection:
+		assert(0 && "bad direction");
+	}
+}
+
+bool
+CServer::isSwitchOkay(CBaseClientProxy* newScreen,
+				EDirection dir, SInt32 x, SInt32 y,
+				SInt32 xActive, SInt32 yActive)
+{
+	LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", getName(m_active).c_str(), CConfig::dirName(dir)));
+
+	// is there a neighbor?
+	if (newScreen == NULL) {
+		// there's no neighbor.  we don't want to switch and we don't
+		// want to try to switch later.
+		LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(dir)));
+		stopSwitch();
+		return false;
+	}
+
+	// should we switch or not?
+	bool preventSwitch = false;
+	bool allowSwitch   = false;
+
+	// note if the switch direction has changed.  save the new
+	// direction and screen if so.
+	bool isNewDirection  = (dir != m_switchDir);
+	if (isNewDirection || m_switchScreen == NULL) {
+		m_switchDir    = dir;
+		m_switchScreen = newScreen;
+	}
+
+	// is this a double tap and do we care?
+	if (!allowSwitch && m_switchTwoTapDelay > 0.0) {
+		if (isNewDirection ||
+			!isSwitchTwoTapStarted() || !shouldSwitchTwoTap()) {
+			// tapping a different or new edge or second tap not
+			// fast enough.  prepare for second tap.
+			preventSwitch = true;
+			startSwitchTwoTap();
+		}
+		else {
+			// got second tap
+			allowSwitch = true;
+		}
+	}
+
+	// if waiting before a switch then prepare to switch later
+	if (!allowSwitch && m_switchWaitDelay > 0.0) {
+		if (isNewDirection || !isSwitchWaitStarted()) {
+			startSwitchWait(x, y);
+		}
+		preventSwitch = true;
+	}
+
+	// are we in a locked corner?  first check if screen has the option set
+	// and, if not, check the global options.
+	const CConfig::CScreenOptions* options =
+						m_config.getOptions(getName(m_active));
+	if (options == NULL || options->count(kOptionScreenSwitchCorners) == 0) {
+		options = m_config.getOptions("");
+	}
+	if (options != NULL && options->count(kOptionScreenSwitchCorners) > 0) {
+		// get corner mask and size
+		CConfig::CScreenOptions::const_iterator i =
+			options->find(kOptionScreenSwitchCorners);
+		UInt32 corners = static_cast<UInt32>(i->second);
+		i = options->find(kOptionScreenSwitchCornerSize);
+		SInt32 size = 0;
+		if (i != options->end()) {
+			size = i->second;
+		}
+
+		// see if we're in a locked corner
+		if ((getCorner(m_active, xActive, yActive, size) & corners) != 0) {
+			// yep, no switching
+			LOG((CLOG_DEBUG1 "locked in corner"));
+			preventSwitch = true;
+			stopSwitch();
+		}
+	}
+
+	// ignore if mouse is locked to screen and don't try to switch later
+	if (!preventSwitch && isLockedToScreen()) {
+		LOG((CLOG_DEBUG1 "locked to screen"));
+		preventSwitch = true;
+		stopSwitch();
+	}
+
+	return !preventSwitch;
+}
+
+void
+CServer::noSwitch(SInt32 x, SInt32 y)
+{
+	armSwitchTwoTap(x, y);
+	stopSwitchWait();
+}
+
+void
+CServer::stopSwitch()
+{
+	if (m_switchScreen != NULL) {
+		m_switchScreen = NULL;
+		m_switchDir    = kNoDirection;
+		stopSwitchTwoTap();
+		stopSwitchWait();
+	}
+}
+
+void
+CServer::startSwitchTwoTap()
+{
+	m_switchTwoTapEngaged = true;
+	m_switchTwoTapArmed   = false;
+	m_switchTwoTapTimer.reset();
+	LOG((CLOG_DEBUG1 "waiting for second tap"));
+}
+
+void
+CServer::armSwitchTwoTap(SInt32 x, SInt32 y)
+{
+	if (m_switchTwoTapEngaged) {
+		if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) {
+			// second tap took too long.  disengage.
+			stopSwitchTwoTap();
+		}
+		else if (!m_switchTwoTapArmed) {
+			// still time for a double tap.  see if we left the tap
+			// zone and, if so, arm the two tap.
+			SInt32 ax, ay, aw, ah;
+			m_active->getShape(ax, ay, aw, ah);
+			SInt32 tapZone = m_primaryClient->getJumpZoneSize();
+			if (tapZone < m_switchTwoTapZone) {
+				tapZone = m_switchTwoTapZone;
+			}
+			if (x >= ax + tapZone && x < ax + aw - tapZone &&
+				y >= ay + tapZone && y < ay + ah - tapZone) {
+				// win32 can generate bogus mouse events that appear to
+				// move in the opposite direction that the mouse actually
+				// moved.  try to ignore that crap here.
+				switch (m_switchDir) {
+				case kLeft:
+					m_switchTwoTapArmed = (m_xDelta > 0 && m_xDelta2 > 0);
+					break;
+
+				case kRight:
+					m_switchTwoTapArmed = (m_xDelta < 0 && m_xDelta2 < 0);
+					break;
+
+				case kTop:
+					m_switchTwoTapArmed = (m_yDelta > 0 && m_yDelta2 > 0);
+					break;
+
+				case kBottom:
+					m_switchTwoTapArmed = (m_yDelta < 0 && m_yDelta2 < 0);
+					break;
+
+				default:
+					break;
+				}
+			}
+		}
+	}
+}
+
+void
+CServer::stopSwitchTwoTap()
+{
+	m_switchTwoTapEngaged = false;
+	m_switchTwoTapArmed   = false;
+}
+
+bool
+CServer::isSwitchTwoTapStarted() const
+{
+	return m_switchTwoTapEngaged;
+}
+
+bool
+CServer::shouldSwitchTwoTap() const
+{
+	// this is the second tap if two-tap is armed and this tap
+	// came fast enough
+	return (m_switchTwoTapArmed &&
+			m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay);
+}
+
+void
+CServer::startSwitchWait(SInt32 x, SInt32 y)
+{
+	stopSwitchWait();
+	m_switchWaitX     = x;
+	m_switchWaitY     = y;
+	m_switchWaitTimer = EVENTQUEUE->newOneShotTimer(m_switchWaitDelay, this);
+	LOG((CLOG_DEBUG1 "waiting to switch"));
+}
+
+void
+CServer::stopSwitchWait()
+{
+	if (m_switchWaitTimer != NULL) {
+		EVENTQUEUE->deleteTimer(m_switchWaitTimer);
+		m_switchWaitTimer = NULL;
+	}
+}
+
+bool
+CServer::isSwitchWaitStarted() const
+{
+	return (m_switchWaitTimer != NULL);
+}
+
+UInt32
+CServer::getCorner(CBaseClientProxy* client,
+				SInt32 x, SInt32 y, SInt32 size) const
+{
+	assert(client != NULL);
+
+	// get client screen shape
+	SInt32 ax, ay, aw, ah;
+	client->getShape(ax, ay, aw, ah);
+
+	// check for x,y on the left or right
+	SInt32 xSide;
+	if (x <= ax) {
+		xSide = -1;
+	}
+	else if (x >= ax + aw - 1) {
+		xSide = 1;
+	}
+	else {
+		xSide = 0;
+	}
+
+	// check for x,y on the top or bottom
+	SInt32 ySide;
+	if (y <= ay) {
+		ySide = -1;
+	}
+	else if (y >= ay + ah - 1) {
+		ySide = 1;
+	}
+	else {
+		ySide = 0;
+	}
+
+	// if against the left or right then check if y is within size
+	if (xSide != 0) {
+		if (y < ay + size) {
+			return (xSide < 0) ? kTopLeftMask : kTopRightMask;
+		}
+		else if (y >= ay + ah - size) {
+			return (xSide < 0) ? kBottomLeftMask : kBottomRightMask;
+		}
+	}
+
+	// if against the left or right then check if y is within size
+	if (ySide != 0) {
+		if (x < ax + size) {
+			return (ySide < 0) ? kTopLeftMask : kBottomLeftMask;
+		}
+		else if (x >= ax + aw - size) {
+			return (ySide < 0) ? kTopRightMask : kBottomRightMask;
+		}
+	}
+
+	return kNoCornerMask;
+}
+
+void
+CServer::stopRelativeMoves()
+{
+	if (m_relativeMoves && m_active != m_primaryClient) {
+		// warp to the center of the active client so we know where we are
+		SInt32 ax, ay, aw, ah;
+		m_active->getShape(ax, ay, aw, ah);
+		m_x       = ax + (aw >> 1);
+		m_y       = ay + (ah >> 1);
+		m_xDelta  = 0;
+		m_yDelta  = 0;
+		m_xDelta2 = 0;
+		m_yDelta2 = 0;
+		LOG((CLOG_DEBUG2 "synchronize move on %s by %d,%d", getName(m_active).c_str(), m_x, m_y));
+		m_active->mouseMove(m_x, m_y);
+	}
+}
+
+void
+CServer::sendOptions(CBaseClientProxy* client) const
+{
+	COptionsList optionsList;
+
+	// look up options for client
+	const CConfig::CScreenOptions* options =
+						m_config.getOptions(getName(client));
+	if (options != NULL) {
+		// convert options to a more convenient form for sending
+		optionsList.reserve(2 * options->size());
+		for (CConfig::CScreenOptions::const_iterator index = options->begin();
+									index != options->end(); ++index) {
+			optionsList.push_back(index->first);
+			optionsList.push_back(static_cast<UInt32>(index->second));
+		}
+	}
+
+	// look up global options
+	options = m_config.getOptions("");
+	if (options != NULL) {
+		// convert options to a more convenient form for sending
+		optionsList.reserve(optionsList.size() + 2 * options->size());
+		for (CConfig::CScreenOptions::const_iterator index = options->begin();
+									index != options->end(); ++index) {
+			optionsList.push_back(index->first);
+			optionsList.push_back(static_cast<UInt32>(index->second));
+		}
+	}
+
+	// send the options
+	client->resetOptions();
+	client->setOptions(optionsList);
+}
+
+void
+CServer::processOptions()
+{
+	const CConfig::CScreenOptions* options = m_config.getOptions("");
+	if (options == NULL) {
+		return;
+	}
+
+	bool newRelativeMoves = m_relativeMoves;
+	for (CConfig::CScreenOptions::const_iterator index = options->begin();
+								index != options->end(); ++index) {
+		const OptionID id       = index->first;
+		const OptionValue value = index->second;
+		if (id == kOptionScreenSwitchDelay) {
+			m_switchWaitDelay = 1.0e-3 * static_cast<double>(value);
+			if (m_switchWaitDelay < 0.0) {
+				m_switchWaitDelay = 0.0;
+			}
+			stopSwitchWait();
+		}
+		else if (id == kOptionScreenSwitchTwoTap) {
+			m_switchTwoTapDelay = 1.0e-3 * static_cast<double>(value);
+			if (m_switchTwoTapDelay < 0.0) {
+				m_switchTwoTapDelay = 0.0;
+			}
+			stopSwitchTwoTap();
+		}
+		else if (id == kOptionRelativeMouseMoves) {
+			newRelativeMoves = (value != 0);
+		}
+	}
+
+	if (m_relativeMoves && !newRelativeMoves) {
+		stopRelativeMoves();
+	}
+	m_relativeMoves = newRelativeMoves;
+}
+
+void
+CServer::handleShapeChanged(const CEvent&, void* vclient)
+{
+	// ignore events from unknown clients
+	CBaseClientProxy* client = reinterpret_cast<CBaseClientProxy*>(vclient);
+	if (m_clientSet.count(client) == 0) {
+		return;
+	}
+
+	LOG((CLOG_INFO "screen \"%s\" shape changed", getName(client).c_str()));
+
+	// update jump coordinate
+	SInt32 x, y;
+	client->getCursorPos(x, y);
+	client->setJumpCursorPos(x, y);
+
+	// update the mouse coordinates
+	if (client == m_active) {
+		m_x = x;
+		m_y = y;
+	}
+
+	// handle resolution change to primary screen
+	if (client == m_primaryClient) {
+		if (client == m_active) {
+			onMouseMovePrimary(m_x, m_y);
+		}
+		else {
+			onMouseMoveSecondary(0, 0);
+		}
+	}
+}
+
+void
+CServer::handleClipboardGrabbed(const CEvent& event, void* vclient)
+{
+	// ignore events from unknown clients
+	CBaseClientProxy* grabber = reinterpret_cast<CBaseClientProxy*>(vclient);
+	if (m_clientSet.count(grabber) == 0) {
+		return;
+	}
+	const IScreen::CClipboardInfo* info =
+		reinterpret_cast<const IScreen::CClipboardInfo*>(event.getData());
+
+	// ignore grab if sequence number is old.  always allow primary
+	// screen to grab.
+	CClipboardInfo& clipboard = m_clipboards[info->m_id];
+	if (grabber != m_primaryClient &&
+		info->m_sequenceNumber < clipboard.m_clipboardSeqNum) {
+		LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", getName(grabber).c_str(), info->m_id));
+		return;
+	}
+
+	// mark screen as owning clipboard
+	LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", getName(grabber).c_str(), info->m_id, clipboard.m_clipboardOwner.c_str()));
+	clipboard.m_clipboardOwner  = getName(grabber);
+	clipboard.m_clipboardSeqNum = info->m_sequenceNumber;
+
+	// clear the clipboard data (since it's not known at this point)
+	if (clipboard.m_clipboard.open(0)) {
+		clipboard.m_clipboard.empty();
+		clipboard.m_clipboard.close();
+	}
+	clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
+
+	// tell all other screens to take ownership of clipboard.  tell the
+	// grabber that it's clipboard isn't dirty.
+	for (CClientList::iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+		CBaseClientProxy* client = index->second;
+		if (client == grabber) {
+			client->setClipboardDirty(info->m_id, false);
+		}
+		else {
+			client->grabClipboard(info->m_id);
+		}
+	}
+}
+
+void
+CServer::handleClipboardChanged(const CEvent& event, void* vclient)
+{
+	// ignore events from unknown clients
+	CBaseClientProxy* sender = reinterpret_cast<CBaseClientProxy*>(vclient);
+	if (m_clientSet.count(sender) == 0) {
+		return;
+	}
+	const IScreen::CClipboardInfo* info =
+		reinterpret_cast<const IScreen::CClipboardInfo*>(event.getData());
+	onClipboardChanged(sender, info->m_id, info->m_sequenceNumber);
+}
+
+void
+CServer::handleKeyDownEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CKeyInfo* info =
+		reinterpret_cast<IPlatformScreen::CKeyInfo*>(event.getData());
+	onKeyDown(info->m_key, info->m_mask, info->m_button, info->m_screens);
+}
+
+void
+CServer::handleKeyUpEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CKeyInfo* info =
+		 reinterpret_cast<IPlatformScreen::CKeyInfo*>(event.getData());
+	onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens);
+}
+
+void
+CServer::handleKeyRepeatEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CKeyInfo* info =
+		reinterpret_cast<IPlatformScreen::CKeyInfo*>(event.getData());
+	onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button);
+}
+
+void
+CServer::handleButtonDownEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CButtonInfo* info =
+		reinterpret_cast<IPlatformScreen::CButtonInfo*>(event.getData());
+	onMouseDown(info->m_button);
+}
+
+void
+CServer::handleButtonUpEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CButtonInfo* info =
+		reinterpret_cast<IPlatformScreen::CButtonInfo*>(event.getData());
+	onMouseUp(info->m_button);
+}
+
+void
+CServer::handleMotionPrimaryEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CMotionInfo* info =
+		reinterpret_cast<IPlatformScreen::CMotionInfo*>(event.getData());
+	onMouseMovePrimary(info->m_x, info->m_y);
+}
+
+void
+CServer::handleMotionSecondaryEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CMotionInfo* info =
+		reinterpret_cast<IPlatformScreen::CMotionInfo*>(event.getData());
+	onMouseMoveSecondary(info->m_x, info->m_y);
+}
+
+void
+CServer::handleWheelEvent(const CEvent& event, void*)
+{
+	IPlatformScreen::CWheelInfo* info =
+		reinterpret_cast<IPlatformScreen::CWheelInfo*>(event.getData());
+	onMouseWheel(info->m_xDelta, info->m_yDelta);
+}
+
+void
+CServer::handleScreensaverActivatedEvent(const CEvent&, void*)
+{
+	onScreensaver(true);
+}
+
+void
+CServer::handleScreensaverDeactivatedEvent(const CEvent&, void*)
+{
+	onScreensaver(false);
+}
+
+void
+CServer::handleSwitchWaitTimeout(const CEvent&, void*)
+{
+	// ignore if mouse is locked to screen
+	if (isLockedToScreen()) {
+		LOG((CLOG_DEBUG1 "locked to screen"));
+		stopSwitch();
+		return;
+	}
+
+	// switch screen
+	switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false);
+}
+
+void
+CServer::handleClientDisconnected(const CEvent&, void* vclient)
+{
+	// client has disconnected.  it might be an old client or an
+	// active client.  we don't care so just handle it both ways.
+	CBaseClientProxy* client = reinterpret_cast<CBaseClientProxy*>(vclient);
+	removeActiveClient(client);
+	removeOldClient(client);
+	delete client;
+}
+
+void
+CServer::handleClientCloseTimeout(const CEvent&, void* vclient)
+{
+	// client took too long to disconnect.  just dump it.
+	CBaseClientProxy* client = reinterpret_cast<CBaseClientProxy*>(vclient);
+	LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str()));
+	removeOldClient(client);
+	delete client;
+}
+
+void
+CServer::handleSwitchToScreenEvent(const CEvent& event, void*)
+{
+	CSwitchToScreenInfo* info = 
+		reinterpret_cast<CSwitchToScreenInfo*>(event.getData());
+
+	CClientList::const_iterator index = m_clients.find(info->m_screen);
+	if (index == m_clients.end()) {
+		LOG((CLOG_DEBUG1 "screen \"%s\" not active", info->m_screen));
+	}
+	else {
+		jumpToScreen(index->second);
+	}
+}
+
+void
+CServer::handleSwitchInDirectionEvent(const CEvent& event, void*)
+{
+	CSwitchInDirectionInfo* info = 
+		reinterpret_cast<CSwitchInDirectionInfo*>(event.getData());
+
+	// jump to screen in chosen direction from center of this screen
+	SInt32 x = m_x, y = m_y;
+	CBaseClientProxy* newScreen =
+		getNeighbor(m_active, info->m_direction, x, y);
+	if (newScreen == NULL) {
+		LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(info->m_direction)));
+	}
+	else {
+		jumpToScreen(newScreen);
+	}
+}
+
+void
+CServer::handleLockCursorToScreenEvent(const CEvent& event, void*)
+{
+	CLockCursorToScreenInfo* info = (CLockCursorToScreenInfo*)event.getData();
+
+	// choose new state
+	bool newState;
+	switch (info->m_state) {
+	case CLockCursorToScreenInfo::kOff:
+		newState = false;
+		break;
+
+	default:
+	case CLockCursorToScreenInfo::kOn:
+		newState = true;
+		break;
+
+	case CLockCursorToScreenInfo::kToggle:
+		newState = !m_lockedToScreen;
+		break;
+	}
+
+	// enter new state
+	if (newState != m_lockedToScreen) {
+		SInt32 x, y;
+		m_lockedToScreen = newState;
+
+		LOG((CLOG_DEBUG "cursor %s current screen", m_lockedToScreen ? "locked to" : "unlocked from"));
+
+		m_primaryClient->reconfigure(getActivePrimarySides());
+		if (!isLockedToScreenServer()) {
+			//stopRelativeMoves();
+		}
+	}
+}
+
+void
+CServer::handleFakeInputBeginEvent(const CEvent&, void*)
+{
+	m_primaryClient->fakeInputBegin();
+}
+
+void
+CServer::handleFakeInputEndEvent(const CEvent&, void*)
+{
+	m_primaryClient->fakeInputEnd();
+}
+
+void
+CServer::onClipboardChanged(CBaseClientProxy* sender,
+				ClipboardID id, UInt32 seqNum)
+{
+	CClipboardInfo& clipboard = m_clipboards[id];
+
+	// ignore update if sequence number is old
+	if (seqNum < clipboard.m_clipboardSeqNum) {
+		LOG((CLOG_INFO "ignored screen \"%s\" update of clipboard %d (missequenced)", getName(sender).c_str(), id));
+		return;
+	}
+
+	// should be the expected client
+	assert(sender == m_clients.find(clipboard.m_clipboardOwner)->second);
+
+	// get data
+	sender->getClipboard(id, &clipboard.m_clipboard);
+
+	// ignore if data hasn't changed
+	CString data = clipboard.m_clipboard.marshall();
+	if (data == clipboard.m_clipboardData) {
+		LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id));
+		return;
+	}
+
+	// got new data
+	LOG((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id));
+	clipboard.m_clipboardData = data;
+
+	// tell all clients except the sender that the clipboard is dirty
+	for (CClientList::const_iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+		CBaseClientProxy* client = index->second;
+		client->setClipboardDirty(id, client != sender);
+	}
+
+	// send the new clipboard to the active screen
+	m_active->setClipboard(id, &clipboard.m_clipboard);
+}
+
+void
+CServer::onScreensaver(bool activated)
+{
+	LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated"));
+
+	if (activated) {
+		// save current screen and position
+		m_activeSaver = m_active;
+		m_xSaver      = m_x;
+		m_ySaver      = m_y;
+
+		// jump to primary screen
+		if (m_active != m_primaryClient) {
+			switchScreen(m_primaryClient, 0, 0, true);
+		}
+	}
+	else {
+		// jump back to previous screen and position.  we must check
+		// that the position is still valid since the screen may have
+		// changed resolutions while the screen saver was running.
+		if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) {
+			// check position
+			CBaseClientProxy* screen = m_activeSaver;
+			SInt32 x, y, w, h;
+			screen->getShape(x, y, w, h);
+			SInt32 zoneSize = getJumpZoneSize(screen);
+			if (m_xSaver < x + zoneSize) {
+				m_xSaver = x + zoneSize;
+			}
+			else if (m_xSaver >= x + w - zoneSize) {
+				m_xSaver = x + w - zoneSize - 1;
+			}
+			if (m_ySaver < y + zoneSize) {
+				m_ySaver = y + zoneSize;
+			}
+			else if (m_ySaver >= y + h - zoneSize) {
+				m_ySaver = y + h - zoneSize - 1;
+			}
+
+			// jump
+			switchScreen(screen, m_xSaver, m_ySaver, false);
+		}
+
+		// reset state
+		m_activeSaver = NULL;
+	}
+
+	// send message to all clients
+	for (CClientList::const_iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+		CBaseClientProxy* client = index->second;
+		client->screensaver(activated);
+	}
+}
+
+void
+CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button,
+				const char* screens)
+{
+	LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button));
+	assert(m_active != NULL);
+
+	// relay
+	if (IKeyState::CKeyInfo::isDefault(screens)) {
+		if (m_active != m_primaryClient)
+		{
+			m_active->keyDown(id, mask, button);
+		}
+		else
+		{ 
+			if (id == 96 && mask == 0 && button == 0x0033)
+			{
+				if (m_broadcast)
+				{
+					m_broadcast = false;
+				}
+				else
+				{
+					m_broadcast = true;
+				}
+			}
+			if (m_broadcast)
+			{
+				for (CClientList::const_iterator index = m_clients.begin();
+						 index != m_clients.end(); ++index) {
+					index->second->keyDown(id, mask, button);
+				}
+			}
+		}
+	}
+	else {
+		for (CClientList::const_iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+			if (IKeyState::CKeyInfo::contains(screens, index->first)) {
+				index->second->keyDown(id, mask, button);
+			}
+		}
+	}
+}
+
+void
+CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button,
+				const char* screens)
+{
+	LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button));
+	assert(m_active != NULL);
+
+	// relay
+	if (IKeyState::CKeyInfo::isDefault(screens)) {
+		if (m_active != m_primaryClient)
+		{
+			m_active->keyUp(id, mask, button);
+		}
+		else if (m_broadcast)
+		{
+			for (CClientList::const_iterator index = m_clients.begin(); index != m_clients.end(); ++index) {
+				index->second->keyUp(id, mask, button);
+			}
+		}
+	}
+	else {
+		for (CClientList::const_iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+			if (IKeyState::CKeyInfo::contains(screens, index->first)) {
+				index->second->keyUp(id, mask, button);
+			}
+		}
+	}
+}
+
+void
+CServer::onKeyRepeat(KeyID id, KeyModifierMask mask,
+				SInt32 count, KeyButton button)
+{
+	LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button));
+	assert(m_active != NULL);
+
+	// relay
+	if (m_active != m_primaryClient)
+	{
+		m_active->keyRepeat(id, mask, count, button);
+	}
+	else if (m_broadcast)
+	{
+		for (CClientList::const_iterator index = m_clients.begin(); index != m_clients.end(); ++index) {
+			index->second->keyRepeat(id, mask, count, button);
+		}
+	}
+}
+
+void
+CServer::onMouseDown(ButtonID id)
+{
+	LOG((CLOG_DEBUG1 "onMouseDown id=%d", id));
+	assert(m_active != NULL);
+
+	// relay
+	m_active->mouseDown(id);
+}
+
+void
+CServer::onMouseUp(ButtonID id)
+{
+	LOG((CLOG_DEBUG1 "onMouseUp id=%d", id));
+	assert(m_active != NULL);
+
+	// relay
+	m_active->mouseUp(id);
+}
+
+bool
+CServer::onMouseMovePrimary(SInt32 x, SInt32 y)
+{
+	LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y));
+
+	// mouse move on primary (server's) screen
+	if (m_active != m_primaryClient) {
+		// stale event -- we're actually on a secondary screen
+		return false;
+	}
+
+	// save last delta
+	m_xDelta2 = m_xDelta;
+	m_yDelta2 = m_yDelta;
+
+	// save current delta
+	m_xDelta  = x - m_x;
+	m_yDelta  = y - m_y;
+
+	// save position
+	m_x       = x;
+	m_y       = y;
+
+	// get screen shape
+	SInt32 ax, ay, aw, ah;
+	m_active->getShape(ax, ay, aw, ah);
+	SInt32 zoneSize = getJumpZoneSize(m_active);
+
+	// clamp position to screen
+	SInt32 xc = x, yc = y;
+	if (xc < ax + zoneSize) {
+		xc = ax;
+	}
+	else if (xc >= ax + aw - zoneSize) {
+		xc = ax + aw - 1;
+	}
+	if (yc < ay + zoneSize) {
+		yc = ay;
+	}
+	else if (yc >= ay + ah - zoneSize) {
+		yc = ay + ah - 1;
+	}
+
+	// see if we should change screens
+	EDirection dir;
+	if (x < ax + zoneSize) {
+		x  -= zoneSize;
+		dir = kLeft;
+	}
+	else if (x >= ax + aw - zoneSize) {
+		x  += zoneSize;
+		dir = kRight;
+	}
+	else if (y < ay + zoneSize) {
+		y  -= zoneSize;
+		dir = kTop;
+	}
+	else if (y >= ay + ah - zoneSize) {
+		y  += zoneSize;
+		dir = kBottom;
+	}
+	else {
+		// still on local screen
+		noSwitch(x, y);
+		return false;
+	}
+
+	// get jump destination
+	CBaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y);
+
+	// should we switch or not?
+	if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) {
+		// switch screen
+		switchScreen(newScreen, x, y, false);
+		return true;
+	}
+	else {
+		return false;
+	}
+}
+
+void
+CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
+{
+	LOG((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy));
+
+	// mouse move on secondary (client's) screen
+	assert(m_active != NULL);
+	if (m_active == m_primaryClient) {
+		// stale event -- we're actually on the primary screen
+		return;
+	}
+
+	// if doing relative motion on secondary screens and we're locked
+	// to the screen (which activates relative moves) then send a
+	// relative mouse motion.  when we're doing this we pretend as if
+	// the mouse isn't actually moving because we're expecting some
+	// program on the secondary screen to warp the mouse on us, so we
+	// have no idea where it really is.
+	if (m_relativeMoves && isLockedToScreenServer()) {
+		LOG((CLOG_DEBUG2 "relative move on %s by %d,%d", getName(m_active).c_str(), dx, dy));
+		m_active->mouseRelativeMove(dx, dy);
+		return;
+	}
+
+	// save old position
+	const SInt32 xOld = m_x;
+	const SInt32 yOld = m_y;
+
+	// save last delta
+	m_xDelta2 = m_xDelta;
+	m_yDelta2 = m_yDelta;
+
+	// save current delta
+	m_xDelta  = dx;
+	m_yDelta  = dy;
+
+	// accumulate motion
+	m_x      += dx;
+	m_y      += dy;
+
+	// get screen shape
+	SInt32 ax, ay, aw, ah;
+	m_active->getShape(ax, ay, aw, ah);
+
+	// find direction of neighbor and get the neighbor
+	bool jump = true;
+	CBaseClientProxy* newScreen;
+	do {
+		// clamp position to screen
+		SInt32 xc = m_x, yc = m_y;
+		if (xc < ax) {
+			xc = ax;
+		}
+		else if (xc >= ax + aw) {
+			xc = ax + aw - 1;
+		}
+		if (yc < ay) {
+			yc = ay;
+		}
+		else if (yc >= ay + ah) {
+			yc = ay + ah - 1;
+		}
+
+		EDirection dir;
+		if (m_x < ax) {
+			dir = kLeft;
+		}
+		else if (m_x > ax + aw - 1) {
+			dir = kRight;
+		}
+		else if (m_y < ay) {
+			dir = kTop;
+		}
+		else if (m_y > ay + ah - 1) {
+			dir = kBottom;
+		}
+		else {
+			// we haven't left the screen
+			newScreen = m_active;
+			jump      = false;
+
+			// if waiting and mouse is not on the border we're waiting
+			// on then stop waiting.  also if it's not on the border
+			// then arm the double tap.
+			if (m_switchScreen != NULL) {
+				bool clearWait;
+				SInt32 zoneSize = m_primaryClient->getJumpZoneSize();
+				switch (m_switchDir) {
+				case kLeft:
+					clearWait = (m_x >= ax + zoneSize);
+					break;
+
+				case kRight:
+					clearWait = (m_x <= ax + aw - 1 - zoneSize);
+					break;
+
+				case kTop:
+					clearWait = (m_y >= ay + zoneSize);
+					break;
+
+				case kBottom:
+					clearWait = (m_y <= ay + ah - 1 + zoneSize);
+					break;
+
+				default:
+					clearWait = false;
+					break;
+				}
+				if (clearWait) {
+					// still on local screen
+					noSwitch(m_x, m_y);
+				}
+			}
+
+			// skip rest of block
+			break;
+		}
+
+		// try to switch screen.  get the neighbor.
+		newScreen = mapToNeighbor(m_active, dir, m_x, m_y);
+
+		// see if we should switch
+		if (!isSwitchOkay(newScreen, dir, m_x, m_y, xc, yc)) {
+			newScreen = m_active;
+			jump      = false;
+		}
+	} while (false);
+
+	if (jump) {
+		// switch screens
+		switchScreen(newScreen, m_x, m_y, false);
+	}
+	else {
+		// same screen.  clamp mouse to edge.
+		m_x = xOld + dx;
+		m_y = yOld + dy;
+		if (m_x < ax) {
+			m_x = ax;
+			LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", getName(m_active).c_str()));
+		}
+		else if (m_x > ax + aw - 1) {
+			m_x = ax + aw - 1;
+			LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", getName(m_active).c_str()));
+		}
+		if (m_y < ay) {
+			m_y = ay;
+			LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", getName(m_active).c_str()));
+		}
+		else if (m_y > ay + ah - 1) {
+			m_y = ay + ah - 1;
+			LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", getName(m_active).c_str()));
+		}
+
+		// warp cursor if it moved.
+		if (m_x != xOld || m_y != yOld) {
+			LOG((CLOG_DEBUG2 "move on %s to %d,%d", getName(m_active).c_str(), m_x, m_y));
+			m_active->mouseMove(m_x, m_y);
+		}
+	}
+}
+
+void
+CServer::onMouseWheel(SInt32 xDelta, SInt32 yDelta)
+{
+	LOG((CLOG_DEBUG1 "onMouseWheel %+d,%+d", xDelta, yDelta));
+	assert(m_active != NULL);
+
+	// relay
+	m_active->mouseWheel(xDelta, yDelta);
+}
+
+bool
+CServer::addClient(CBaseClientProxy* client)
+{
+	CString name = getName(client);
+	if (m_clients.count(name) != 0) {
+		return false;
+	}
+
+	// add event handlers
+	EVENTQUEUE->adoptHandler(IScreen::getShapeChangedEvent(),
+							client->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleShapeChanged, client));
+	EVENTQUEUE->adoptHandler(IScreen::getClipboardGrabbedEvent(),
+							client->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleClipboardGrabbed, client));
+	EVENTQUEUE->adoptHandler(CClientProxy::getClipboardChangedEvent(),
+							client->getEventTarget(),
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleClipboardChanged, client));
+
+	// add to list
+	m_clientSet.insert(client);
+	m_clients.insert(std::make_pair(name, client));
+
+	// initialize client data
+	SInt32 x, y;
+	client->getCursorPos(x, y);
+	client->setJumpCursorPos(x, y);
+
+	// tell primary client about the active sides
+	m_primaryClient->reconfigure(getActivePrimarySides());
+
+	return true;
+}
+
+bool
+CServer::removeClient(CBaseClientProxy* client)
+{
+	// return false if not in list
+	CClientSet::iterator i = m_clientSet.find(client);
+	if (i == m_clientSet.end()) {
+		return false;
+	}
+
+	// remove event handlers
+	EVENTQUEUE->removeHandler(IScreen::getShapeChangedEvent(),
+							client->getEventTarget());
+	EVENTQUEUE->removeHandler(IScreen::getClipboardGrabbedEvent(),
+							client->getEventTarget());
+	EVENTQUEUE->removeHandler(CClientProxy::getClipboardChangedEvent(),
+							client->getEventTarget());
+
+	// remove from list
+	m_clients.erase(getName(client));
+	m_clientSet.erase(i);
+
+	return true;
+}
+
+void
+CServer::closeClient(CBaseClientProxy* client, const char* msg)
+{
+	assert(client != m_primaryClient);
+	assert(msg != NULL);
+
+	// send message to client.  this message should cause the client
+	// to disconnect.  we add this client to the closed client list
+	// and install a timer to remove the client if it doesn't respond
+	// quickly enough.  we also remove the client from the active
+	// client list since we're not going to listen to it anymore.
+	// note that this method also works on clients that are not in
+	// the m_clients list.  adoptClient() may call us with such a
+	// client.
+	LOG((CLOG_NOTE "disconnecting client \"%s\"", getName(client).c_str()));
+
+	// send message
+	// FIXME -- avoid type cast (kinda hard, though)
+	((CClientProxy*)client)->close(msg);
+
+	// install timer.  wait timeout seconds for client to close.
+	double timeout = 5.0;
+	CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(timeout, NULL);
+	EVENTQUEUE->adoptHandler(CEvent::kTimer, timer,
+							new TMethodEventJob<CServer>(this,
+								&CServer::handleClientCloseTimeout, client));
+
+	// move client to closing list
+	removeClient(client);
+	m_oldClients.insert(std::make_pair(client, timer));
+
+	// if this client is the active screen then we have to
+	// jump off of it
+	forceLeaveClient(client);
+}
+
+void
+CServer::closeClients(const CConfig& config)
+{
+	// collect the clients that are connected but are being dropped
+	// from the configuration (or who's canonical name is changing).
+	typedef std::set<CBaseClientProxy*> CRemovedClients;
+	CRemovedClients removed;
+	for (CClientList::iterator index = m_clients.begin();
+								index != m_clients.end(); ++index) {
+		if (!config.isCanonicalName(index->first)) {
+			removed.insert(index->second);
+		}
+	}
+
+	// don't close the primary client
+	removed.erase(m_primaryClient);
+
+	// now close them.  we collect the list then close in two steps
+	// because closeClient() modifies the collection we iterate over.
+	for (CRemovedClients::iterator index = removed.begin();
+								index != removed.end(); ++index) {
+		closeClient(*index, kMsgCClose);
+	}
+}
+
+void
+CServer::removeActiveClient(CBaseClientProxy* client)
+{
+	if (removeClient(client)) {
+		forceLeaveClient(client);
+		EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client);
+		if (m_clients.size() == 1 && m_oldClients.empty()) {
+			EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this));
+		}
+	}
+}
+
+void
+CServer::removeOldClient(CBaseClientProxy* client)
+{
+	COldClients::iterator i = m_oldClients.find(client);
+	if (i != m_oldClients.end()) {
+		EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client);
+		EVENTQUEUE->removeHandler(CEvent::kTimer, i->second);
+		EVENTQUEUE->deleteTimer(i->second);
+		m_oldClients.erase(i);
+		if (m_clients.size() == 1 && m_oldClients.empty()) {
+			EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this));
+		}
+	}
+}
+
+void
+CServer::forceLeaveClient(CBaseClientProxy* client)
+{
+	CBaseClientProxy* active =
+		(m_activeSaver != NULL) ? m_activeSaver : m_active;
+	if (active == client) {
+		// record new position (center of primary screen)
+		m_primaryClient->getCursorCenter(m_x, m_y);
+
+		// stop waiting to switch to this client
+		if (active == m_switchScreen) {
+			stopSwitch();
+		}
+
+		// don't notify active screen since it has probably already
+		// disconnected.
+		LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", getName(active).c_str(), getName(m_primaryClient).c_str(), m_x, m_y));
+
+		// cut over
+		m_active = m_primaryClient;
+
+		// enter new screen (unless we already have because of the
+		// screen saver)
+		if (m_activeSaver == NULL) {
+			m_primaryClient->enter(m_x, m_y, m_seqNum,
+								m_primaryClient->getToggleMask(), false);
+		}
+	}
+
+	// if this screen had the cursor when the screen saver activated
+	// then we can't switch back to it when the screen saver
+	// deactivates.
+	if (m_activeSaver == client) {
+		m_activeSaver = NULL;
+	}
+
+	// tell primary client about the active sides
+	m_primaryClient->reconfigure(getActivePrimarySides());
+}
+
+
+//
+// CServer::CClipboardInfo
+//
+
+CServer::CClipboardInfo::CClipboardInfo() :
+	m_clipboard(),
+	m_clipboardData(),
+	m_clipboardOwner(),
+	m_clipboardSeqNum(0)
+{
+	// do nothing
+}
+
+
+//
+// CServer::CLockCursorToScreenInfo
+//
+
+CServer::CLockCursorToScreenInfo*
+CServer::CLockCursorToScreenInfo::alloc(State state)
+{
+	CLockCursorToScreenInfo* info =
+		(CLockCursorToScreenInfo*)malloc(sizeof(CLockCursorToScreenInfo));
+	info->m_state = state;
+	return info;
+}
+
+
+//
+// CServer::CSwitchToScreenInfo
+//
+
+CServer::CSwitchToScreenInfo*
+CServer::CSwitchToScreenInfo::alloc(const CString& screen)
+{
+	CSwitchToScreenInfo* info =
+		(CSwitchToScreenInfo*)malloc(sizeof(CSwitchToScreenInfo) +
+								screen.size());
+	strcpy(info->m_screen, screen.c_str());
+	return info;
+}
+
+
+//
+// CServer::CSwitchInDirectionInfo
+//
+
+CServer::CSwitchInDirectionInfo*
+CServer::CSwitchInDirectionInfo::alloc(EDirection direction)
+{
+	CSwitchInDirectionInfo* info =
+		(CSwitchInDirectionInfo*)malloc(sizeof(CSwitchInDirectionInfo));
+	info->m_direction = direction;
+	return info;
+}
+
+
+//
+// CServer::CScreenConnectedInfo
+//
+
+CServer::CScreenConnectedInfo*
+CServer::CScreenConnectedInfo::alloc(const CString& screen)
+{
+	CScreenConnectedInfo* info =
+		(CScreenConnectedInfo*)malloc(sizeof(CScreenConnectedInfo) +
+								screen.size());
+	strcpy(info->m_screen, screen.c_str());
+	return info;
+}
diff -uNr synergy-1.3.1/lib/server/CServer.h synergy-1.3.1-broadcast/lib/server/CServer.h
--- synergy-1.3.1/lib/server/CServer.h	2006-04-01 17:47:04.000000000 -0800
+++ synergy-1.3.1-broadcast/lib/server/CServer.h	2007-08-31 16:44:20.000000000 -0700
@@ -423,6 +423,7 @@
 
 	// screen locking (former scroll lock)
 	bool				m_lockedToScreen;
+	bool				m_broadcast;
 
 	static CEvent::Type	s_errorEvent;
 	static CEvent::Type	s_connectedEvent;
diff -uNr synergy-1.3.1/lib/server/CServer.h.hack synergy-1.3.1-broadcast/lib/server/CServer.h.hack
--- synergy-1.3.1/lib/server/CServer.h.hack	1969-12-31 16:00:00.000000000 -0800
+++ synergy-1.3.1-broadcast/lib/server/CServer.h.hack	2007-08-17 01:08:20.000000000 -0700
@@ -0,0 +1,440 @@
+/*
+ * synergy -- mouse and keyboard sharing utility
+ * Copyright (C) 2002 Chris Schoeneman
+ * 
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file COPYING that should have accompanied this file.
+ * 
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef CSERVER_H
+#define CSERVER_H
+
+#include "CConfig.h"
+#include "CClipboard.h"
+#include "ClipboardTypes.h"
+#include "KeyTypes.h"
+#include "MouseTypes.h"
+#include "CEvent.h"
+#include "CStopwatch.h"
+#include "stdmap.h"
+#include "stdset.h"
+#include "stdvector.h"
+
+#include "/Developer/SDKs/MacOSX10.4u.sdk/Developer/Headers/CFMCarbon/OSServices/SystemSound.h"
+
+class CBaseClientProxy;
+class CEventQueueTimer;
+class CPrimaryClient;
+class CInputFilter;
+
+//! Synergy server
+/*!
+This class implements the top-level server algorithms for synergy.
+*/
+class CServer {
+public:
+	//! Lock cursor to screen data
+	class CLockCursorToScreenInfo {
+	public:
+		enum State { kOff, kOn, kToggle };
+
+		static CLockCursorToScreenInfo* alloc(State state = kToggle);
+
+	public:
+		State			m_state;
+	};
+
+	//! Switch to screen data
+	class CSwitchToScreenInfo {
+	public:
+		static CSwitchToScreenInfo* alloc(const CString& screen);
+
+	public:
+		// this is a C-string;  this type is a variable size structure
+		char			m_screen[1];
+	};
+
+	//! Switch in direction data
+	class CSwitchInDirectionInfo {
+	public:
+		static CSwitchInDirectionInfo* alloc(EDirection direction);
+
+	public:
+		EDirection		m_direction;
+	};
+
+	//! Screen connected data
+	class CScreenConnectedInfo {
+	public:
+		static CScreenConnectedInfo* alloc(const CString& screen);
+
+	public:
+		// this is a C-string;  this type is a variable size structure
+		char			m_screen[1];
+	};
+
+	/*!
+	Start the server with the configuration \p config and the primary
+	client (local screen) \p primaryClient.  The client retains
+	ownership of \p primaryClient.
+	*/
+	CServer(const CConfig& config, CPrimaryClient* primaryClient);
+	~CServer();
+
+	//! @name manipulators
+	//@{
+
+	//! Set configuration
+	/*!
+	Change the server's configuration.  Returns true iff the new
+	configuration was accepted (it must include the server's name).
+	This will disconnect any clients no longer in the configuration.
+	*/
+	bool				setConfig(const CConfig&);
+
+	//! Add a client
+	/*!
+	Adds \p client to the server.  The client is adopted and will be
+	destroyed when the client disconnects or is disconnected.
+	*/
+	void				adoptClient(CBaseClientProxy* client);
+
+	//! Disconnect clients
+	/*!
+	Disconnect clients.  This tells them to disconnect but does not wait
+	for them to actually do so.  The server sends the disconnected event
+	when they're all disconnected (or immediately if none are connected).
+	The caller can also just destroy this object to force the disconnection.
+	*/
+	void				disconnect();
+
+	//@}
+	//! @name accessors
+	//@{
+
+	//! Get number of connected clients
+	/*!
+	Returns the number of connected clients, including the server itself.
+	*/
+	UInt32				getNumClients() const;
+
+	//! Get the list of connected clients
+	/*!
+	Set the \c list to the names of the currently connected clients.
+	*/
+	void				getClients(std::vector<CString>& list) const;
+
+	//! Get error event type
+	/*!
+	Returns the error event type.  This is sent when the server fails
+	for some reason.
+	*/
+	static CEvent::Type	getErrorEvent();
+
+	//! Get connected event type
+	/*!
+	Returns the connected event type.  This is sent when a client screen
+	has connected.  The event data is a \c CScreenConnectedInfo* that
+	indicates the connected screen.
+	*/
+	static CEvent::Type	getConnectedEvent();
+
+	//! Get disconnected event type
+	/*!
+	Returns the disconnected event type.  This is sent when all the
+	clients have disconnected.
+	*/
+	static CEvent::Type	getDisconnectedEvent();
+
+	//! Get switch to screen event type
+	/*!
+	Returns the switch to screen event type.  The server responds to this
+	by switching screens.  The event data is a \c CSwitchToScreenInfo*
+	that indicates the target screen.
+	*/
+	static CEvent::Type	getSwitchToScreenEvent();
+
+	//! Get switch in direction event type
+	/*!
+	Returns the switch in direction event type.  The server responds to this
+	by switching screens.  The event data is a \c CSwitchInDirectionInfo*
+	that indicates the target direction.
+	*/
+	static CEvent::Type	getSwitchInDirectionEvent();
+
+	//! Get lock cursor event type
+	/*!
+	Returns the lock cursor event type.  The server responds to this
+	by locking the cursor to the active screen or unlocking it.  The
+	event data is a \c CLockCursorToScreenInfo*.
+	*/
+	static CEvent::Type	getLockCursorToScreenEvent();
+
+	//@}
+
+private:
+	// get canonical name of client
+	CString				getName(const CBaseClientProxy*) const;
+
+	// get the sides of the primary screen that have neighbors
+	UInt32				getActivePrimarySides() const;
+
+	// returns true iff mouse should be locked to the current screen
+	// according to this object only, ignoring what the primary client
+	// says.
+	bool				isLockedToScreenServer() const;
+
+	// returns true iff mouse should be locked to the current screen
+	// according to this object or the primary client.
+	bool				isLockedToScreen() const;
+
+	// returns the jump zone of the client
+	SInt32				getJumpZoneSize(CBaseClientProxy*) const;
+
+	// change the active screen
+	void				switchScreen(CBaseClientProxy*,
+							SInt32 x, SInt32 y, bool forScreenSaver);
+
+	// jump to screen
+	void				jumpToScreen(CBaseClientProxy*);
+
+	// convert pixel position to fraction, using x or y depending on the
+	// direction.
+	float				mapToFraction(CBaseClientProxy*, EDirection,
+							SInt32 x, SInt32 y) const;
+
+	// convert fraction to pixel position, writing only x or y depending
+	// on the direction.
+	void				mapToPixel(CBaseClientProxy*, EDirection, float f,
+							SInt32& x, SInt32& y) const;
+
+	// returns true if the client has a neighbor anywhere along the edge
+	// indicated by the direction.
+	bool				hasAnyNeighbor(CBaseClientProxy*, EDirection) const;
+
+	// lookup neighboring screen, mapping the coordinate independent of
+	// the direction to the neighbor's coordinate space.
+	CBaseClientProxy*	getNeighbor(CBaseClientProxy*, EDirection,
+							SInt32& x, SInt32& y) const;
+
+	// lookup neighboring screen.  given a position relative to the
+	// source screen, find the screen we should move onto and where.
+	// if the position is sufficiently far from the source then we
+	// cross multiple screens.  if there is no suitable screen then
+	// return NULL and x,y are not modified.
+	CBaseClientProxy*	mapToNeighbor(CBaseClientProxy*, EDirection,
+							SInt32& x, SInt32& y) const;
+
+	// adjusts x and y or neither to avoid ending up in a jump zone
+	// after entering the client in the given direction.
+	void				avoidJumpZone(CBaseClientProxy*, EDirection,
+							SInt32& x, SInt32& y) const;
+
+	// test if a switch is permitted.  this includes testing user
+	// options like switch delay and tracking any state required to
+	// implement them.  returns true iff a switch is permitted.
+	bool				isSwitchOkay(CBaseClientProxy* dst, EDirection,
+							SInt32 x, SInt32 y, SInt32 xActive, SInt32 yActive);
+
+	// update switch state due to a mouse move at \p x, \p y that
+	// doesn't switch screens.
+	void				noSwitch(SInt32 x, SInt32 y);
+
+	// stop switch timers
+	void				stopSwitch();
+
+	// start two tap switch timer
+	void				startSwitchTwoTap();
+
+	// arm the two tap switch timer if \p x, \p y is outside the tap zone
+	void				armSwitchTwoTap(SInt32 x, SInt32 y);
+
+	// stop the two tap switch timer
+	void				stopSwitchTwoTap();
+
+	// returns true iff the two tap switch timer is started
+	bool				isSwitchTwoTapStarted() const;
+
+	// returns true iff should switch because of two tap
+	bool				shouldSwitchTwoTap() const;
+
+	// start delay switch timer
+	void				startSwitchWait(SInt32 x, SInt32 y);
+
+	// stop delay switch timer
+	void				stopSwitchWait();
+
+	// returns true iff the delay switch timer is started
+	bool				isSwitchWaitStarted() const;
+
+	// returns the corner (EScreenSwitchCornerMasks) where x,y is on the
+	// given client.  corners have the given size.
+	UInt32				getCorner(CBaseClientProxy*,
+							SInt32 x, SInt32 y, SInt32 size) const;
+
+	// stop relative mouse moves
+	void				stopRelativeMoves();
+
+	// send screen options to \c client
+	void				sendOptions(CBaseClientProxy* client) const;
+
+	// process options from configuration
+	void				processOptions();
+
+	// event handlers
+	void				handleShapeChanged(const CEvent&, void*);
+	void				handleClipboardGrabbed(const CEvent&, void*);
+	void				handleClipboardChanged(const CEvent&, void*);
+	void				handleKeyDownEvent(const CEvent&, void*);
+	void				handleKeyUpEvent(const CEvent&, void*);
+	void				handleKeyRepeatEvent(const CEvent&, void*);
+	void				handleButtonDownEvent(const CEvent&, void*);
+	void				handleButtonUpEvent(const CEvent&, void*);
+	void				handleMotionPrimaryEvent(const CEvent&, void*);
+	void				handleMotionSecondaryEvent(const CEvent&, void*);
+	void				handleWheelEvent(const CEvent&, void*);
+	void				handleScreensaverActivatedEvent(const CEvent&, void*);
+	void				handleScreensaverDeactivatedEvent(const CEvent&, void*);
+	void				handleSwitchWaitTimeout(const CEvent&, void*);
+	void				handleClientDisconnected(const CEvent&, void*);
+	void				handleClientCloseTimeout(const CEvent&, void*);
+	void				handleSwitchToScreenEvent(const CEvent&, void*);
+	void				handleSwitchInDirectionEvent(const CEvent&, void*);
+	void				handleLockCursorToScreenEvent(const CEvent&, void*);
+	void				handleFakeInputBeginEvent(const CEvent&, void*);
+	void				handleFakeInputEndEvent(const CEvent&, void*);
+
+	// event processing
+	void				onClipboardChanged(CBaseClientProxy* sender,
+							ClipboardID id, UInt32 seqNum);
+	void				onScreensaver(bool activated);
+	void				onKeyDown(KeyID, KeyModifierMask, KeyButton,
+							const char* screens);
+	void				onKeyUp(KeyID, KeyModifierMask, KeyButton,
+							const char* screens);
+	void				onKeyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton);
+	void				onMouseDown(ButtonID);
+	void				onMouseUp(ButtonID);
+	bool				onMouseMovePrimary(SInt32 x, SInt32 y);
+	void				onMouseMoveSecondary(SInt32 dx, SInt32 dy);
+	void				onMouseWheel(SInt32 xDelta, SInt32 yDelta);
+
+	// add client to list and attach event handlers for client
+	bool				addClient(CBaseClientProxy*);
+
+	// remove client from list and detach event handlers for client
+	bool				removeClient(CBaseClientProxy*);
+
+	// close a client
+	void				closeClient(CBaseClientProxy*, const char* msg);
+
+	// close clients not in \p config
+	void				closeClients(const CConfig& config);
+
+	// close all clients whether they've completed the handshake or not,
+	// except the primary client
+	void				closeAllClients();
+
+	// remove clients from internal state
+	void				removeActiveClient(CBaseClientProxy*);
+	void				removeOldClient(CBaseClientProxy*);
+
+	// force the cursor off of \p client
+	void				forceLeaveClient(CBaseClientProxy* client);
+
+private:
+	class CClipboardInfo {
+	public:
+		CClipboardInfo();
+
+	public:
+		CClipboard		m_clipboard;
+		CString			m_clipboardData;
+		CString			m_clipboardOwner;
+		UInt32			m_clipboardSeqNum;
+	};
+
+	// the primary screen client
+	CPrimaryClient*		m_primaryClient;
+
+	// all clients (including the primary client) indexed by name
+	typedef std::map<CString, CBaseClientProxy*> CClientList;
+	typedef std::set<CBaseClientProxy*> CClientSet;
+	CClientList			m_clients;
+	CClientSet			m_clientSet;
+
+	// all old connections that we're waiting to hangup
+	typedef std::map<CBaseClientProxy*, CEventQueueTimer*> COldClients;
+	COldClients			m_oldClients;
+
+	// the client with focus
+	CBaseClientProxy*	m_active;
+
+	// the sequence number of enter messages
+	UInt32				m_seqNum;
+
+	// current mouse position (in absolute screen coordinates) on
+	// whichever screen is active
+	SInt32				m_x, m_y;
+
+	// last mouse deltas.  this is needed to smooth out double tap
+	// on win32 which reports bogus mouse motion at the edge of
+	// the screen when using low level hooks, synthesizing motion
+	// in the opposite direction the mouse actually moved.
+	SInt32				m_xDelta, m_yDelta;
+	SInt32				m_xDelta2, m_yDelta2;
+
+	// current configuration
+	CConfig				m_config;
+
+	// input filter (from m_config);
+	CInputFilter*		m_inputFilter;
+
+	// clipboard cache
+	CClipboardInfo		m_clipboards[kClipboardEnd];
+
+	// state saved when screen saver activates
+	CBaseClientProxy*	m_activeSaver;
+	SInt32				m_xSaver, m_ySaver;
+
+	// common state for screen switch tests.  all tests are always
+	// trying to reach the same screen in the same direction.
+	EDirection			m_switchDir;
+	CBaseClientProxy*	m_switchScreen;
+
+	// state for delayed screen switching
+	double				m_switchWaitDelay;
+	CEventQueueTimer*	m_switchWaitTimer;
+	SInt32				m_switchWaitX, m_switchWaitY;
+
+	// state for double-tap screen switching
+	double				m_switchTwoTapDelay;
+	CStopwatch			m_switchTwoTapTimer;
+	bool				m_switchTwoTapEngaged;
+	bool				m_switchTwoTapArmed;
+	SInt32				m_switchTwoTapZone;
+
+	// relative mouse move option
+	bool				m_relativeMoves;
+
+	// screen locking (former scroll lock)
+	bool				m_lockedToScreen;
+	bool				m_broadcast;
+	SystemSoundActionID		m_broadcastOnID;
+	SystemSoundActionID		m_broadcastOffID;
+
+	static CEvent::Type	s_errorEvent;
+	static CEvent::Type	s_connectedEvent;
+	static CEvent::Type	s_disconnectedEvent;
+	static CEvent::Type	s_switchToScreen;
+	static CEvent::Type	s_switchInDirection;
+	static CEvent::Type s_lockCursorToScreen;
+};
+
+#endif
diff -uNr synergy-1.3.1/lib/server/CServer.h.ok synergy-1.3.1-broadcast/lib/server/CServer.h.ok
--- synergy-1.3.1/lib/server/CServer.h.ok	1969-12-31 16:00:00.000000000 -0800
+++ synergy-1.3.1-broadcast/lib/server/CServer.h.ok	2007-08-17 01:09:32.000000000 -0700
@@ -0,0 +1,436 @@
+/*
+ * synergy -- mouse and keyboard sharing utility
+ * Copyright (C) 2002 Chris Schoeneman
+ * 
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file COPYING that should have accompanied this file.
+ * 
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef CSERVER_H
+#define CSERVER_H
+
+#include "CConfig.h"
+#include "CClipboard.h"
+#include "ClipboardTypes.h"
+#include "KeyTypes.h"
+#include "MouseTypes.h"
+#include "CEvent.h"
+#include "CStopwatch.h"
+#include "stdmap.h"
+#include "stdset.h"
+#include "stdvector.h"
+
+class CBaseClientProxy;
+class CEventQueueTimer;
+class CPrimaryClient;
+class CInputFilter;
+
+//! Synergy server
+/*!
+This class implements the top-level server algorithms for synergy.
+*/
+class CServer {
+public:
+	//! Lock cursor to screen data
+	class CLockCursorToScreenInfo {
+	public:
+		enum State { kOff, kOn, kToggle };
+
+		static CLockCursorToScreenInfo* alloc(State state = kToggle);
+
+	public:
+		State			m_state;
+	};
+
+	//! Switch to screen data
+	class CSwitchToScreenInfo {
+	public:
+		static CSwitchToScreenInfo* alloc(const CString& screen);
+
+	public:
+		// this is a C-string;  this type is a variable size structure
+		char			m_screen[1];
+	};
+
+	//! Switch in direction data
+	class CSwitchInDirectionInfo {
+	public:
+		static CSwitchInDirectionInfo* alloc(EDirection direction);
+
+	public:
+		EDirection		m_direction;
+	};
+
+	//! Screen connected data
+	class CScreenConnectedInfo {
+	public:
+		static CScreenConnectedInfo* alloc(const CString& screen);
+
+	public:
+		// this is a C-string;  this type is a variable size structure
+		char			m_screen[1];
+	};
+
+	/*!
+	Start the server with the configuration \p config and the primary
+	client (local screen) \p primaryClient.  The client retains
+	ownership of \p primaryClient.
+	*/
+	CServer(const CConfig& config, CPrimaryClient* primaryClient);
+	~CServer();
+
+	//! @name manipulators
+	//@{
+
+	//! Set configuration
+	/*!
+	Change the server's configuration.  Returns true iff the new
+	configuration was accepted (it must include the server's name).
+	This will disconnect any clients no longer in the configuration.
+	*/
+	bool				setConfig(const CConfig&);
+
+	//! Add a client
+	/*!
+	Adds \p client to the server.  The client is adopted and will be
+	destroyed when the client disconnects or is disconnected.
+	*/
+	void				adoptClient(CBaseClientProxy* client);
+
+	//! Disconnect clients
+	/*!
+	Disconnect clients.  This tells them to disconnect but does not wait
+	for them to actually do so.  The server sends the disconnected event
+	when they're all disconnected (or immediately if none are connected).
+	The caller can also just destroy this object to force the disconnection.
+	*/
+	void				disconnect();
+
+	//@}
+	//! @name accessors
+	//@{
+
+	//! Get number of connected clients
+	/*!
+	Returns the number of connected clients, including the server itself.
+	*/
+	UInt32				getNumClients() const;
+
+	//! Get the list of connected clients
+	/*!
+	Set the \c list to the names of the currently connected clients.
+	*/
+	void				getClients(std::vector<CString>& list) const;
+
+	//! Get error event type
+	/*!
+	Returns the error event type.  This is sent when the server fails
+	for some reason.
+	*/
+	static CEvent::Type	getErrorEvent();
+
+	//! Get connected event type
+	/*!
+	Returns the connected event type.  This is sent when a client screen
+	has connected.  The event data is a \c CScreenConnectedInfo* that
+	indicates the connected screen.
+	*/
+	static CEvent::Type	getConnectedEvent();
+
+	//! Get disconnected event type
+	/*!
+	Returns the disconnected event type.  This is sent when all the
+	clients have disconnected.
+	*/
+	static CEvent::Type	getDisconnectedEvent();
+
+	//! Get switch to screen event type
+	/*!
+	Returns the switch to screen event type.  The server responds to this
+	by switching screens.  The event data is a \c CSwitchToScreenInfo*
+	that indicates the target screen.
+	*/
+	static CEvent::Type	getSwitchToScreenEvent();
+
+	//! Get switch in direction event type
+	/*!
+	Returns the switch in direction event type.  The server responds to this
+	by switching screens.  The event data is a \c CSwitchInDirectionInfo*
+	that indicates the target direction.
+	*/
+	static CEvent::Type	getSwitchInDirectionEvent();
+
+	//! Get lock cursor event type
+	/*!
+	Returns the lock cursor event type.  The server responds to this
+	by locking the cursor to the active screen or unlocking it.  The
+	event data is a \c CLockCursorToScreenInfo*.
+	*/
+	static CEvent::Type	getLockCursorToScreenEvent();
+
+	//@}
+
+private:
+	// get canonical name of client
+	CString				getName(const CBaseClientProxy*) const;
+
+	// get the sides of the primary screen that have neighbors
+	UInt32				getActivePrimarySides() const;
+
+	// returns true iff mouse should be locked to the current screen
+	// according to this object only, ignoring what the primary client
+	// says.
+	bool				isLockedToScreenServer() const;
+
+	// returns true iff mouse should be locked to the current screen
+	// according to this object or the primary client.
+	bool				isLockedToScreen() const;
+
+	// returns the jump zone of the client
+	SInt32				getJumpZoneSize(CBaseClientProxy*) const;
+
+	// change the active screen
+	void				switchScreen(CBaseClientProxy*,
+							SInt32 x, SInt32 y, bool forScreenSaver);
+
+	// jump to screen
+	void				jumpToScreen(CBaseClientProxy*);
+
+	// convert pixel position to fraction, using x or y depending on the
+	// direction.
+	float				mapToFraction(CBaseClientProxy*, EDirection,
+							SInt32 x, SInt32 y) const;
+
+	// convert fraction to pixel position, writing only x or y depending
+	// on the direction.
+	void				mapToPixel(CBaseClientProxy*, EDirection, float f,
+							SInt32& x, SInt32& y) const;
+
+	// returns true if the client has a neighbor anywhere along the edge
+	// indicated by the direction.
+	bool				hasAnyNeighbor(CBaseClientProxy*, EDirection) const;
+
+	// lookup neighboring screen, mapping the coordinate independent of
+	// the direction to the neighbor's coordinate space.
+	CBaseClientProxy*	getNeighbor(CBaseClientProxy*, EDirection,
+							SInt32& x, SInt32& y) const;
+
+	// lookup neighboring screen.  given a position relative to the
+	// source screen, find the screen we should move onto and where.
+	// if the position is sufficiently far from the source then we
+	// cross multiple screens.  if there is no suitable screen then
+	// return NULL and x,y are not modified.
+	CBaseClientProxy*	mapToNeighbor(CBaseClientProxy*, EDirection,
+							SInt32& x, SInt32& y) const;
+
+	// adjusts x and y or neither to avoid ending up in a jump zone
+	// after entering the client in the given direction.
+	void				avoidJumpZone(CBaseClientProxy*, EDirection,
+							SInt32& x, SInt32& y) const;
+
+	// test if a switch is permitted.  this includes testing user
+	// options like switch delay and tracking any state required to
+	// implement them.  returns true iff a switch is permitted.
+	bool				isSwitchOkay(CBaseClientProxy* dst, EDirection,
+							SInt32 x, SInt32 y, SInt32 xActive, SInt32 yActive);
+
+	// update switch state due to a mouse move at \p x, \p y that
+	// doesn't switch screens.
+	void				noSwitch(SInt32 x, SInt32 y);
+
+	// stop switch timers
+	void				stopSwitch();
+
+	// start two tap switch timer
+	void				startSwitchTwoTap();
+
+	// arm the two tap switch timer if \p x, \p y is outside the tap zone
+	void				armSwitchTwoTap(SInt32 x, SInt32 y);
+
+	// stop the two tap switch timer
+	void				stopSwitchTwoTap();
+
+	// returns true iff the two tap switch timer is started
+	bool				isSwitchTwoTapStarted() const;
+
+	// returns true iff should switch because of two tap
+	bool				shouldSwitchTwoTap() const;
+
+	// start delay switch timer
+	void				startSwitchWait(SInt32 x, SInt32 y);
+
+	// stop delay switch timer
+	void				stopSwitchWait();
+
+	// returns true iff the delay switch timer is started
+	bool				isSwitchWaitStarted() const;
+
+	// returns the corner (EScreenSwitchCornerMasks) where x,y is on the
+	// given client.  corners have the given size.
+	UInt32				getCorner(CBaseClientProxy*,
+							SInt32 x, SInt32 y, SInt32 size) const;
+
+	// stop relative mouse moves
+	void				stopRelativeMoves();
+
+	// send screen options to \c client
+	void				sendOptions(CBaseClientProxy* client) const;
+
+	// process options from configuration
+	void				processOptions();
+
+	// event handlers
+	void				handleShapeChanged(const CEvent&, void*);
+	void				handleClipboardGrabbed(const CEvent&, void*);
+	void				handleClipboardChanged(const CEvent&, void*);
+	void				handleKeyDownEvent(const CEvent&, void*);
+	void				handleKeyUpEvent(const CEvent&, void*);
+	void				handleKeyRepeatEvent(const CEvent&, void*);
+	void				handleButtonDownEvent(const CEvent&, void*);
+	void				handleButtonUpEvent(const CEvent&, void*);
+	void				handleMotionPrimaryEvent(const CEvent&, void*);
+	void				handleMotionSecondaryEvent(const CEvent&, void*);
+	void				handleWheelEvent(const CEvent&, void*);
+	void				handleScreensaverActivatedEvent(const CEvent&, void*);
+	void				handleScreensaverDeactivatedEvent(const CEvent&, void*);
+	void				handleSwitchWaitTimeout(const CEvent&, void*);
+	void				handleClientDisconnected(const CEvent&, void*);
+	void				handleClientCloseTimeout(const CEvent&, void*);
+	void				handleSwitchToScreenEvent(const CEvent&, void*);
+	void				handleSwitchInDirectionEvent(const CEvent&, void*);
+	void				handleLockCursorToScreenEvent(const CEvent&, void*);
+	void				handleFakeInputBeginEvent(const CEvent&, void*);
+	void				handleFakeInputEndEvent(const CEvent&, void*);
+
+	// event processing
+	void				onClipboardChanged(CBaseClientProxy* sender,
+							ClipboardID id, UInt32 seqNum);
+	void				onScreensaver(bool activated);
+	void				onKeyDown(KeyID, KeyModifierMask, KeyButton,
+							const char* screens);
+	void				onKeyUp(KeyID, KeyModifierMask, KeyButton,
+							const char* screens);
+	void				onKeyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton);
+	void				onMouseDown(ButtonID);
+	void				onMouseUp(ButtonID);
+	bool				onMouseMovePrimary(SInt32 x, SInt32 y);
+	void				onMouseMoveSecondary(SInt32 dx, SInt32 dy);
+	void				onMouseWheel(SInt32 xDelta, SInt32 yDelta);
+
+	// add client to list and attach event handlers for client
+	bool				addClient(CBaseClientProxy*);
+
+	// remove client from list and detach event handlers for client
+	bool				removeClient(CBaseClientProxy*);
+
+	// close a client
+	void				closeClient(CBaseClientProxy*, const char* msg);
+
+	// close clients not in \p config
+	void				closeClients(const CConfig& config);
+
+	// close all clients whether they've completed the handshake or not,
+	// except the primary client
+	void				closeAllClients();
+
+	// remove clients from internal state
+	void				removeActiveClient(CBaseClientProxy*);
+	void				removeOldClient(CBaseClientProxy*);
+
+	// force the cursor off of \p client
+	void				forceLeaveClient(CBaseClientProxy* client);
+
+private:
+	class CClipboardInfo {
+	public:
+		CClipboardInfo();
+
+	public:
+		CClipboard		m_clipboard;
+		CString			m_clipboardData;
+		CString			m_clipboardOwner;
+		UInt32			m_clipboardSeqNum;
+	};
+
+	// the primary screen client
+	CPrimaryClient*		m_primaryClient;
+
+	// all clients (including the primary client) indexed by name
+	typedef std::map<CString, CBaseClientProxy*> CClientList;
+	typedef std::set<CBaseClientProxy*> CClientSet;
+	CClientList			m_clients;
+	CClientSet			m_clientSet;
+
+	// all old connections that we're waiting to hangup
+	typedef std::map<CBaseClientProxy*, CEventQueueTimer*> COldClients;
+	COldClients			m_oldClients;
+
+	// the client with focus
+	CBaseClientProxy*	m_active;
+
+	// the sequence number of enter messages
+	UInt32				m_seqNum;
+
+	// current mouse position (in absolute screen coordinates) on
+	// whichever screen is active
+	SInt32				m_x, m_y;
+
+	// last mouse deltas.  this is needed to smooth out double tap
+	// on win32 which reports bogus mouse motion at the edge of
+	// the screen when using low level hooks, synthesizing motion
+	// in the opposite direction the mouse actually moved.
+	SInt32				m_xDelta, m_yDelta;
+	SInt32				m_xDelta2, m_yDelta2;
+
+	// current configuration
+	CConfig				m_config;
+
+	// input filter (from m_config);
+	CInputFilter*		m_inputFilter;
+
+	// clipboard cache
+	CClipboardInfo		m_clipboards[kClipboardEnd];
+
+	// state saved when screen saver activates
+	CBaseClientProxy*	m_activeSaver;
+	SInt32				m_xSaver, m_ySaver;
+
+	// common state for screen switch tests.  all tests are always
+	// trying to reach the same screen in the same direction.
+	EDirection			m_switchDir;
+	CBaseClientProxy*	m_switchScreen;
+
+	// state for delayed screen switching
+	double				m_switchWaitDelay;
+	CEventQueueTimer*	m_switchWaitTimer;
+	SInt32				m_switchWaitX, m_switchWaitY;
+
+	// state for double-tap screen switching
+	double				m_switchTwoTapDelay;
+	CStopwatch			m_switchTwoTapTimer;
+	bool				m_switchTwoTapEngaged;
+	bool				m_switchTwoTapArmed;
+	SInt32				m_switchTwoTapZone;
+
+	// relative mouse move option
+	bool				m_relativeMoves;
+
+	// screen locking (former scroll lock)
+	bool				m_lockedToScreen;
+	bool				m_broadcast;
+
+	static CEvent::Type	s_errorEvent;
+	static CEvent::Type	s_connectedEvent;
+	static CEvent::Type	s_disconnectedEvent;
+	static CEvent::Type	s_switchToScreen;
+	static CEvent::Type	s_switchInDirection;
+	static CEvent::Type s_lockCursorToScreen;
+};
+
+#endif
diff -uNr synergy-1.3.1/lib/synergy/CScreen.cpp synergy-1.3.1-broadcast/lib/synergy/CScreen.cpp
--- synergy-1.3.1/lib/synergy/CScreen.cpp	2006-03-08 20:07:17.000000000 -0800
+++ synergy-1.3.1-broadcast/lib/synergy/CScreen.cpp	2007-08-15 14:12:36.000000000 -0700
@@ -176,6 +176,8 @@
 {
 	assert(!m_isPrimary || m_fakeInput);
 
+	LOG((CLOG_DEBUG1 "MAIN keyDown!"));
+
 	// check for ctrl+alt+del emulation
 	if (id == kKeyDelete &&
 		(mask & (KeyModifierControl | KeyModifierAlt)) ==
@@ -193,6 +195,7 @@
 				KeyModifierMask mask, SInt32 count, KeyButton button)
 {
 	assert(!m_isPrimary);
+	LOG((CLOG_DEBUG1 "MAIN keyRepeat!"));
 	m_screen->fakeKeyRepeat(id, mask, count, button);
 }
 
@@ -200,6 +203,7 @@
 CScreen::keyUp(KeyID, KeyModifierMask, KeyButton button)
 {
 	assert(!m_isPrimary || m_fakeInput);
+	LOG((CLOG_DEBUG1 "MAIN keyUp!"));
 	m_screen->fakeKeyUp(button);
 }
 
diff -uNr synergy-1.3.1/synergy.conf synergy-1.3.1-broadcast/synergy.conf
--- synergy-1.3.1/synergy.conf	1969-12-31 16:00:00.000000000 -0800
+++ synergy-1.3.1-broadcast/synergy.conf	2007-08-14 12:24:30.000000000 -0700
@@ -0,0 +1,30 @@
+# sample synergy configuration file
+#
+# comments begin with the # character and continue to the end of
+# line.  comments may appear anywhere the syntax permits.
+
+section: screens
+	mac1:
+	winxp1:
+end
+
+section: links
+	mac1:
+		right = winxp1
+
+	winxp1:
+		left  = mac1
+
+end
+
+section: aliases
+	mac1:
+		main
+end
+
+section: options
+	relativeMouseMoves = true
+	mousebutton(3) = mouseDown(3) = lockCursorToScreen(on); mouseUp(3) = lockCursorToScreen(off)
+	#mousebutton(1) = mouseDown(1) = lockCursorToScreen(on); mouseUp(1) = lockCursorToScreen(off)
+end
+
diff -uNr synergy-1.3.1/synergy.conf.ok synergy-1.3.1-broadcast/synergy.conf.ok
--- synergy-1.3.1/synergy.conf.ok	1969-12-31 16:00:00.000000000 -0800
+++ synergy-1.3.1-broadcast/synergy.conf.ok	2007-08-14 11:15:46.000000000 -0700
@@ -0,0 +1,31 @@
+# sample synergy configuration file
+#
+# comments begin with the # character and continue to the end of
+# line.  comments may appear anywhere the syntax permits.
+
+section: screens
+	# three hosts named:  moe, larry, and curly
+	mac1:
+	winxp1:
+end
+
+section: links
+	# larry is to the right of moe and curly is above moe
+	mac1:
+		right = winxp1
+
+	# moe is to the left of larry and curly is above larry.
+	# note that curly is above both moe and larry and moe
+	# and larry have a symmetric connection (they're in
+	# opposite directions of each other).
+	winxp1:
+		left  = mac1
+
+end
+
+section: aliases
+	# curly is also known as shemp
+	mac1:
+		main
+end
+
diff -uNr synergy-1.3.1/synergy.conf.orig synergy-1.3.1-broadcast/synergy.conf.orig
--- synergy-1.3.1/synergy.conf.orig	1969-12-31 16:00:00.000000000 -0800
+++ synergy-1.3.1-broadcast/synergy.conf.orig	2006-04-02 14:17:12.000000000 -0700
@@ -0,0 +1,37 @@
+# sample synergy configuration file
+#
+# comments begin with the # character and continue to the end of
+# line.  comments may appear anywhere the syntax permits.
+
+section: screens
+	# three hosts named:  moe, larry, and curly
+	moe:
+	larry:
+	curly:
+end
+
+section: links
+	# larry is to the right of moe and curly is above moe
+	moe:
+		right = larry
+		up    = curly
+
+	# moe is to the left of larry and curly is above larry.
+	# note that curly is above both moe and larry and moe
+	# and larry have a symmetric connection (they're in
+	# opposite directions of each other).
+	larry:
+		left  = moe
+		up    = curly
+
+	# larry is below curly.  if you move up from moe and then
+	# down, you'll end up on larry.
+	curly:
+		down  = larry
+end
+
+section: aliases
+	# curly is also known as shemp
+	curly:
+		shemp
+end

