/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2012 Synergy Si Ltd.
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "platform/MSWindowsScreen.h"

#include "platform/MSWindowsDropTarget.h"
#include "client/Client.h"
#include "platform/MSWindowsClipboard.h"
#include "platform/MSWindowsDesks.h"
#include "platform/MSWindowsEventQueueBuffer.h"
#include "platform/MSWindowsKeyState.h"
#include "platform/MSWindowsScreenSaver.h"
#include "synergy/Clipboard.h"
#include "synergy/KeyMap.h"
#include "synergy/XScreen.h"
#include "synergy/App.h"
#include "synergy/ArgsBase.h"
#include "synergy/ClientApp.h"
#include "mt/Lock.h"
#include "mt/Thread.h"
#include "arch/win32/ArchMiscWindows.h"
#include "arch/Arch.h"
#include "base/FunctionJob.h"
#include "base/Log.h"
#include "base/String.h"
#include "base/IEventQueue.h"
#include "base/TMethodEventJob.h"
#include "base/TMethodJob.h"

#include <string.h>
#include <pbt.h>
#include <Shlobj.h>
#include <comutil.h>
#include <algorithm>

//
// add backwards compatible multihead support (and suppress bogus warning).
// this isn't supported on MinGW yet AFAICT.
//
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable: 4706) // assignment within conditional
#define COMPILE_MULTIMON_STUBS
#include <multimon.h>
#pragma warning(pop)
#endif

// X button stuff
#if !defined(WM_XBUTTONDOWN)
#define WM_XBUTTONDOWN		0x020B
#define WM_XBUTTONUP		0x020C
#define WM_XBUTTONDBLCLK	0x020D
#define WM_NCXBUTTONDOWN	0x00AB
#define WM_NCXBUTTONUP		0x00AC
#define WM_NCXBUTTONDBLCLK	0x00AD
#define MOUSEEVENTF_XDOWN	0x0080
#define MOUSEEVENTF_XUP		0x0100
#define XBUTTON1			0x0001
#define XBUTTON2			0x0002
#endif
#if !defined(VK_XBUTTON1)
#define VK_XBUTTON1			0x05
#define VK_XBUTTON2			0x06
#endif

// WM_POWERBROADCAST stuff
#if !defined(PBT_APMRESUMEAUTOMATIC)
#define PBT_APMRESUMEAUTOMATIC	0x0012
#endif

//
// MSWindowsScreen
//

HINSTANCE				MSWindowsScreen::s_windowInstance = NULL;
MSWindowsScreen*		MSWindowsScreen::s_screen   = NULL;

MSWindowsScreen::MSWindowsScreen(
	bool isPrimary,
	bool noHooks,
	bool stopOnDeskSwitch,
	IEventQueue* events) :
	PlatformScreen(events),
	m_isPrimary(isPrimary),
	m_noHooks(noHooks),
	m_isOnScreen(m_isPrimary),
	m_class(0),
	m_x(0), m_y(0),
	m_w(0), m_h(0),
	m_xCenter(0), m_yCenter(0),
	m_multimon(false),
	m_xCursor(0), m_yCursor(0),
	m_sequenceNumber(0),
	m_mark(0),
	m_markReceived(0),
	m_fixTimer(NULL),
	m_keyLayout(NULL),
	m_screensaver(NULL),
	m_screensaverNotify(false),
	m_screensaverActive(false),
	m_window(NULL),
	m_nextClipboardWindow(NULL),
	m_ownClipboard(false),
	m_desks(NULL),
	m_keyState(NULL),
	m_hasMouse(GetSystemMetrics(SM_MOUSEPRESENT) != 0),
	m_showingMouse(false),
	m_events(events),
	m_dropWindow(NULL),
	m_dropWindowSize(20)
{
	assert(s_windowInstance != NULL);
	assert(s_screen   == NULL);

	s_screen = this;
	try {
		if (m_isPrimary && !m_noHooks) {
			m_hook.loadLibrary();
		}

		m_screensaver = new MSWindowsScreenSaver();
		m_desks       = new MSWindowsDesks(
							m_isPrimary,
							m_noHooks,
							m_hook.getInstance(),
							m_screensaver,
							m_events,
							new TMethodJob<MSWindowsScreen>(
								this, &MSWindowsScreen::updateKeysCB),
							stopOnDeskSwitch);
		m_keyState    = new MSWindowsKeyState(m_desks, getEventTarget(), m_events);
		updateScreenShape();
		m_class       = createWindowClass();
		m_window      = createWindow(m_class, "Synergy");
		forceShowCursor();
		LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : ""));
		LOG((CLOG_DEBUG "window is 0x%08x", m_window));
		
		// SHGetFolderPath is deprecated in vista, but use it for xp support.
		char desktopPath[MAX_PATH];
		if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, desktopPath))) {
			m_desktopPath = String(desktopPath);
			LOG((CLOG_DEBUG "using desktop for drop target: %s", m_desktopPath.c_str()));
		}
		else {
			LOG((CLOG_ERR "failed to get desktop path, no drop target available, error=%d", GetLastError()));
		}

		OleInitialize(0);
		m_dropWindow = createDropWindow(m_class, "DropWindow");
		m_dropTarget = new MSWindowsDropTarget();
		RegisterDragDrop(m_dropWindow, m_dropTarget);
	}
	catch (...) {
		delete m_keyState;
		delete m_desks;
		delete m_screensaver;
		destroyWindow(m_window);
		destroyClass(m_class);
		s_screen = NULL;
		throw;
	}

	// install event handlers
	m_events->adoptHandler(Event::kSystem, m_events->getSystemTarget(),
							new TMethodEventJob<MSWindowsScreen>(this,
								&MSWindowsScreen::handleSystemEvent));

	// install the platform event queue
	m_events->adoptBuffer(new MSWindowsEventQueueBuffer(m_events));
}

MSWindowsScreen::~MSWindowsScreen()
{
	assert(s_screen != NULL);

	disable();
	m_events->adoptBuffer(NULL);
	m_events->removeHandler(Event::kSystem, m_events->getSystemTarget());
	delete m_keyState;
	delete m_desks;
	delete m_screensaver;
	destroyWindow(m_window);
	destroyClass(m_class);

	RevokeDragDrop(m_dropWindow);
	m_dropTarget->Release();
	OleUninitialize();
	destroyWindow(m_dropWindow);

	s_screen = NULL;
}

void
MSWindowsScreen::init(HINSTANCE windowInstance)
{
	assert(s_windowInstance == NULL);
	assert(windowInstance   != NULL);

	s_windowInstance = windowInstance;
}

HINSTANCE
MSWindowsScreen::getWindowInstance()
{
	return s_windowInstance;
}

void
MSWindowsScreen::enable()
{
	assert(m_isOnScreen == m_isPrimary);

	// we need to poll some things to fix them
	m_fixTimer = m_events->newTimer(1.0, NULL);
	m_events->adoptHandler(Event::kTimer, m_fixTimer,
							new TMethodEventJob<MSWindowsScreen>(this,
								&MSWindowsScreen::handleFixes));

	// install our clipboard snooper
	m_nextClipboardWindow = SetClipboardViewer(m_window);

	// track the active desk and (re)install the hooks
	m_desks->enable();

	if (m_isPrimary) {
		// set jump zones
		m_hook.setZone(m_x, m_y, m_w, m_h, getJumpZoneSize());

		// watch jump zones
		m_hook.setMode(kHOOK_WATCH_JUMP_ZONE);
	}
	else {
		// prevent the system from entering power saving modes.  if
		// it did we'd be forced to disconnect from the server and
		// the server would not be able to wake us up.
		ArchMiscWindows::addBusyState(ArchMiscWindows::kSYSTEM);
	}
}

void
MSWindowsScreen::disable()
{
	// stop tracking the active desk
	m_desks->disable();

	if (m_isPrimary) {
		// disable hooks
		m_hook.setMode(kHOOK_DISABLE);

		// enable special key sequences on win95 family
		enableSpecialKeys(true);
	}
	else {
		// allow the system to enter power saving mode
		ArchMiscWindows::removeBusyState(ArchMiscWindows::kSYSTEM |
							ArchMiscWindows::kDISPLAY);
	}

	// tell key state
	m_keyState->disable();

	// stop snooping the clipboard
	ChangeClipboardChain(m_window, m_nextClipboardWindow);
	m_nextClipboardWindow = NULL;

	// uninstall fix timer
	if (m_fixTimer != NULL) {
		m_events->removeHandler(Event::kTimer, m_fixTimer);
		m_events->deleteTimer(m_fixTimer);
		m_fixTimer = NULL;
	}

	m_isOnScreen = m_isPrimary;
	forceShowCursor();
}

void
MSWindowsScreen::enter()
{
	m_desks->enter();
	if (m_isPrimary) {
		// enable special key sequences on win95 family
		enableSpecialKeys(true);

		// watch jump zones
		m_hook.setMode(kHOOK_WATCH_JUMP_ZONE);

		// all messages prior to now are invalid
		nextMark();

		m_primaryKeyDownList.clear();
	}
	else {
		// Entering a secondary screen. Ensure that no screensaver is active
		// and that the screen is not in powersave mode.
		ArchMiscWindows::wakeupDisplay();

		if(m_screensaver != NULL && m_screensaverActive)
		{
			m_screensaver->deactivate();
			m_screensaverActive = 0;
		}
	}

	// now on screen
	m_isOnScreen = true;
	forceShowCursor();
}

bool
MSWindowsScreen::leave()
{
	// get keyboard layout of foreground window.  we'll use this
	// keyboard layout for translating keys sent to clients.
	HWND window  = GetForegroundWindow();
	DWORD thread = GetWindowThreadProcessId(window, NULL);
	m_keyLayout  = GetKeyboardLayout(thread);

	// tell the key mapper about the keyboard layout
	m_keyState->setKeyLayout(m_keyLayout);

	// tell desk that we're leaving and tell it the keyboard layout
	m_desks->leave(m_keyLayout);

	if (m_isPrimary) {

		// warp to center
		LOG((CLOG_DEBUG1 "warping cursor to center: %+d, %+d", m_xCenter, m_yCenter));
		warpCursor(m_xCenter, m_yCenter);

		// disable special key sequences on win95 family
		enableSpecialKeys(false);

		// all messages prior to now are invalid
		nextMark();

		// remember the modifier state.  this is the modifier state
		// reflected in the internal keyboard state.
		m_keyState->saveModifiers();

		m_hook.setMode(kHOOK_RELAY_EVENTS);

		m_primaryKeyDownList.clear();
		for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) {
			if (m_keyState->isKeyDown(i)) {
				m_primaryKeyDownList.push_back(i);
				LOG((CLOG_DEBUG1 "key button %d is down before leaving to another screen", i));
			}
		}
	}

	// now off screen
	m_isOnScreen = false;
	forceShowCursor();

	if (isDraggingStarted() && !m_isPrimary) {
		m_sendDragThread = new Thread(
			new TMethodJob<MSWindowsScreen>(
				this,
				&MSWindowsScreen::sendDragThread));
	}

	return true;
}

void
MSWindowsScreen::sendDragThread(void*)
{
	String& draggingFilename = getDraggingFilename();
	size_t size = draggingFilename.size();

	if (draggingFilename.empty() == false) {
		ClientApp& app = ClientApp::instance();
		Client* client = app.getClientPtr();
		UInt32 fileCount = 1;
		LOG((CLOG_DEBUG "send dragging info to server: %s", draggingFilename.c_str()));
		client->sendDragInfo(fileCount, draggingFilename, size);
		LOG((CLOG_DEBUG "send dragging file to server"));
		client->sendFileToServer(draggingFilename.c_str());
	}
	
	m_draggingStarted = false;
}

bool
MSWindowsScreen::setClipboard(ClipboardID, const IClipboard* src)
{
	MSWindowsClipboard dst(m_window);
	if (src != NULL) {
		// save clipboard data
		return Clipboard::copy(&dst, src);
	}
	else {
		// assert clipboard ownership
		if (!dst.open(0)) {
			return false;
		}
		dst.empty();
		dst.close();
		return true;
	}
}

void
MSWindowsScreen::checkClipboards()
{
	// if we think we own the clipboard but we don't then somebody
	// grabbed the clipboard on this screen without us knowing.
	// tell the server that this screen grabbed the clipboard.
	//
	// this works around bugs in the clipboard viewer chain.
	// sometimes NT will simply never send WM_DRAWCLIPBOARD
	// messages for no apparent reason and rebooting fixes the
	// problem.  since we don't want a broken clipboard until the
	// next reboot we do this double check.  clipboard ownership
	// won't be reflected on other screens until we leave but at
	// least the clipboard itself will work.
	if (m_ownClipboard && !MSWindowsClipboard::isOwnedBySynergy()) {
		LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received"));
		m_ownClipboard = false;
		sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard);
		sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection);
	}
}

void
MSWindowsScreen::openScreensaver(bool notify)
{
	assert(m_screensaver != NULL);

	m_screensaverNotify = notify;
	if (m_screensaverNotify) {
		m_desks->installScreensaverHooks(true);
	}
	else if (m_screensaver) {
		m_screensaver->disable();
	}
}

void
MSWindowsScreen::closeScreensaver()
{
	if (m_screensaver != NULL) {
		if (m_screensaverNotify) {
			m_desks->installScreensaverHooks(false);
		}
		else {
			m_screensaver->enable();
		}
	}
	m_screensaverNotify = false;
}

void
MSWindowsScreen::screensaver(bool activate)
{
	assert(m_screensaver != NULL);
	if (m_screensaver==NULL) return;

	if (activate) {
		m_screensaver->activate();
	}
	else {
		m_screensaver->deactivate();
	}
}

void
MSWindowsScreen::resetOptions()
{
	m_desks->resetOptions();
}

void
MSWindowsScreen::setOptions(const OptionsList& options)
{
	m_desks->setOptions(options);
}

void
MSWindowsScreen::setSequenceNumber(UInt32 seqNum)
{
	m_sequenceNumber = seqNum;
}

bool
MSWindowsScreen::isPrimary() const
{
	return m_isPrimary;
}

void*
MSWindowsScreen::getEventTarget() const
{
	return const_cast<MSWindowsScreen*>(this);
}

bool
MSWindowsScreen::getClipboard(ClipboardID, IClipboard* dst) const
{
	MSWindowsClipboard src(m_window);
	Clipboard::copy(dst, &src);
	return true;
}

void
MSWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
{
	assert(m_class != 0);

	x = m_x;
	y = m_y;
	w = m_w;
	h = m_h;
}

void
MSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const
{
	m_desks->getCursorPos(x, y);
}

void
MSWindowsScreen::reconfigure(UInt32 activeSides)
{
	assert(m_isPrimary);

	LOG((CLOG_DEBUG "active sides: %x", activeSides));
	m_hook.setSides(activeSides);
}

void
MSWindowsScreen::warpCursor(SInt32 x, SInt32 y)
{
	// warp mouse
	warpCursorNoFlush(x, y);

	// remove all input events before and including warp
	MSG msg;
	while (PeekMessage(&msg, NULL, SYNERGY_MSG_INPUT_FIRST,
								SYNERGY_MSG_INPUT_LAST, PM_REMOVE)) {
		// do nothing
	}

	// save position to compute delta of next motion
	saveMousePosition(x, y);
}

void MSWindowsScreen::saveMousePosition(SInt32 x, SInt32 y) {

	m_xCursor = x;
	m_yCursor = y;

	LOG((CLOG_DEBUG5 "saved mouse position for next delta: %+d,%+d", x,y));
}

UInt32
MSWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask)
{
	// only allow certain modifiers
	if ((mask & ~(KeyModifierShift | KeyModifierControl |
				  KeyModifierAlt   | KeyModifierSuper)) != 0) {
		// this should be a warning, but this can confuse users,
		// as this warning happens almost always.
		LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask));
		return 0;
	}

	// fail if no keys
	if (key == kKeyNone && mask == 0) {
		return 0;
	}

	// convert to win32
	UINT modifiers = 0;
	if ((mask & KeyModifierShift) != 0) {
		modifiers |= MOD_SHIFT;
	}
	if ((mask & KeyModifierControl) != 0) {
		modifiers |= MOD_CONTROL;
	}
	if ((mask & KeyModifierAlt) != 0) {
		modifiers |= MOD_ALT;
	}
	if ((mask & KeyModifierSuper) != 0) {
		modifiers |= MOD_WIN;
	}
	UINT vk = m_keyState->mapKeyToVirtualKey(key);
	if (key != kKeyNone && vk == 0) {
		// can't map key
		// this should be a warning, but this can confuse users,
		// as this warning happens almost always.
		LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask));
		return 0;
	}

	// choose hotkey id
	UInt32 id;
	if (!m_oldHotKeyIDs.empty()) {
		id = m_oldHotKeyIDs.back();
		m_oldHotKeyIDs.pop_back();
	}
	else {
		//id = m_hotKeys.size() + 1;
		id = (UInt32)m_hotKeys.size() + 1;
	}

	// if this hot key has modifiers only then we'll handle it specially
	bool err;
	if (key == kKeyNone) {
		// check if already registered
		err = (m_hotKeyToIDMap.count(HotKeyItem(vk, modifiers)) > 0);
	}
	else {
		// register with OS
		err = (RegisterHotKey(NULL, id, modifiers, vk) == 0);
	}

	if (!err) {
		m_hotKeys.insert(std::make_pair(id, HotKeyItem(vk, modifiers)));
		m_hotKeyToIDMap[HotKeyItem(vk, modifiers)] = id;
	}
	else {
		m_oldHotKeyIDs.push_back(id);
		m_hotKeys.erase(id);
		LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", synergy::KeyMap::formatKey(key, mask).c_str(), key, mask));
		return 0;
	}
	
	LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", synergy::KeyMap::formatKey(key, mask).c_str(), key, mask, id));
	return id;
}

void
MSWindowsScreen::unregisterHotKey(UInt32 id)
{
	// look up hotkey
	HotKeyMap::iterator i = m_hotKeys.find(id);
	if (i == m_hotKeys.end()) {
		return;
	}

	// unregister with OS
	bool err;
	if (i->second.getVirtualKey() != 0) {
		err = !UnregisterHotKey(NULL, id);
	}
	else {
		err = false;
	}
	if (err) {
		LOG((CLOG_WARN "failed to unregister hotkey id=%d", id));
	}
	else {
		LOG((CLOG_DEBUG "unregistered hotkey id=%d", id));
	}

	// discard hot key from map and record old id for reuse
	m_hotKeyToIDMap.erase(i->second);
	m_hotKeys.erase(i);
	m_oldHotKeyIDs.push_back(id);
}

void
MSWindowsScreen::fakeInputBegin()
{
	assert(m_isPrimary);

	if (!m_isOnScreen) {
		m_keyState->useSavedModifiers(true);
	}
	m_desks->fakeInputBegin();
}

void
MSWindowsScreen::fakeInputEnd()
{
	assert(m_isPrimary);

	m_desks->fakeInputEnd();
	if (!m_isOnScreen) {
		m_keyState->useSavedModifiers(false);
	}
}

SInt32
MSWindowsScreen::getJumpZoneSize() const
{
	return 1;
}

bool
MSWindowsScreen::isAnyMouseButtonDown(UInt32& buttonID) const
{
	static const char* buttonToName[] = {
		"<invalid>",
		"Left Button",
		"Middle Button",
		"Right Button",
		"X Button 1",
		"X Button 2"
	};

	for (UInt32 i = 1; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) {
		if (m_buttons[i]) {
			buttonID = i;
			LOG((CLOG_DEBUG "locked by \"%s\"", buttonToName[i]));
			return true;
		}
	}

	return false;
}

void
MSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const
{
	x = m_xCenter;
	y = m_yCenter;
}

void
MSWindowsScreen::fakeMouseButton(ButtonID id, bool press)
{
	m_desks->fakeMouseButton(id, press);

	if (id == kButtonLeft) {
		if (press) {
			m_buttons[kButtonLeft] = true;
		}
		else {
			m_buttons[kButtonLeft] = false;
			m_fakeDraggingStarted = false;
			m_draggingStarted = false;
		}
	}
}

void
MSWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y)
{
	m_desks->fakeMouseMove(x, y);
	if (m_buttons[kButtonLeft]) {
		m_draggingStarted = true;
	}
}

void
MSWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
{
	m_desks->fakeMouseRelativeMove(dx, dy);
}

void
MSWindowsScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
{
	m_desks->fakeMouseWheel(xDelta, yDelta);
}

void
MSWindowsScreen::updateKeys()
{
	m_desks->updateKeys();
}

void
MSWindowsScreen::fakeKeyDown(KeyID id, KeyModifierMask mask,
				KeyButton button)
{
	PlatformScreen::fakeKeyDown(id, mask, button);
	updateForceShowCursor();
}

bool
MSWindowsScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask,
				SInt32 count, KeyButton button)
{
	bool result = PlatformScreen::fakeKeyRepeat(id, mask, count, button);
	updateForceShowCursor();
	return result;
}

bool
MSWindowsScreen::fakeKeyUp(KeyButton button)
{
	bool result = PlatformScreen::fakeKeyUp(button);
	updateForceShowCursor();
	return result;
}

void
MSWindowsScreen::fakeAllKeysUp()
{
	PlatformScreen::fakeAllKeysUp();
	updateForceShowCursor();
}

HCURSOR
MSWindowsScreen::createBlankCursor() const
{
	// create a transparent cursor
	int cw = GetSystemMetrics(SM_CXCURSOR);
	int ch = GetSystemMetrics(SM_CYCURSOR);

	UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)];
	UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)];
	memset(cursorAND, 0xff, ch * ((cw + 31) >> 2));
	memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2));
	HCURSOR c = CreateCursor(s_windowInstance, 0, 0, cw, ch, cursorAND, cursorXOR);
	delete[] cursorXOR;
	delete[] cursorAND;
	return c;
}

void
MSWindowsScreen::destroyCursor(HCURSOR cursor) const
{
	if (cursor != NULL) {
		DestroyCursor(cursor);
	}
}

ATOM
MSWindowsScreen::createWindowClass() const
{
	WNDCLASSEX classInfo;
	classInfo.cbSize        = sizeof(classInfo);
	classInfo.style         = CS_DBLCLKS | CS_NOCLOSE;
	classInfo.lpfnWndProc   = &MSWindowsScreen::wndProc;
	classInfo.cbClsExtra    = 0;
	classInfo.cbWndExtra    = 0;
	classInfo.hInstance     = s_windowInstance;
	classInfo.hIcon         = NULL;
	classInfo.hCursor       = NULL;
	classInfo.hbrBackground = NULL;
	classInfo.lpszMenuName  = NULL;
	classInfo.lpszClassName = "Synergy";
	classInfo.hIconSm       = NULL;
	return RegisterClassEx(&classInfo);
}

void
MSWindowsScreen::destroyClass(ATOM windowClass) const
{
	if (windowClass != 0) {
		UnregisterClass(reinterpret_cast<LPCTSTR>(windowClass), s_windowInstance);
	}
}

HWND
MSWindowsScreen::createWindow(ATOM windowClass, const char* name) const
{
	HWND window = CreateWindowEx(WS_EX_TOPMOST |
									WS_EX_TRANSPARENT |
									WS_EX_TOOLWINDOW,
								reinterpret_cast<LPCTSTR>(windowClass),
								name,
								WS_POPUP,
								0, 0, 1, 1,
								NULL, NULL,
								s_windowInstance,
								NULL);
	if (window == NULL) {
		LOG((CLOG_ERR "failed to create window: %d", GetLastError()));
		throw XScreenOpenFailure();
	}
	return window;
}

HWND
MSWindowsScreen::createDropWindow(ATOM windowClass, const char* name) const
{
	HWND window = CreateWindowEx(
		WS_EX_TOPMOST |
		WS_EX_TRANSPARENT |
		WS_EX_ACCEPTFILES,
		reinterpret_cast<LPCTSTR>(m_class),
		name,
		WS_POPUP,
		0, 0, m_dropWindowSize, m_dropWindowSize,
		NULL, NULL,
		s_windowInstance,
		NULL);

	if (window == NULL) {
		LOG((CLOG_ERR "failed to create drop window: %d", GetLastError()));
		throw XScreenOpenFailure();
	}

	return window;
}

void
MSWindowsScreen::destroyWindow(HWND hwnd) const
{
	if (hwnd != NULL) {
		DestroyWindow(hwnd);
	}
}

void
MSWindowsScreen::sendEvent(Event::Type type, void* data)
{
	m_events->addEvent(Event(type, getEventTarget(), data));
}

void
MSWindowsScreen::sendClipboardEvent(Event::Type type, ClipboardID id)
{
	ClipboardInfo* info   = (ClipboardInfo*)malloc(sizeof(ClipboardInfo));
	if(info == NULL) {
		LOG((CLOG_ERR "malloc failed on %s:%s", __FILE__, __LINE__ ));
		return;
	}
	info->m_id             = id;
	info->m_sequenceNumber = m_sequenceNumber;
	sendEvent(type, info);
}

void
MSWindowsScreen::handleSystemEvent(const Event& event, void*)
{
	MSG* msg = reinterpret_cast<MSG*>(event.getData());
	assert(msg != NULL);

	if (ArchMiscWindows::processDialog(msg)) {
		return;
	}
	if (onPreDispatch(msg->hwnd, msg->message, msg->wParam, msg->lParam)) {
		return;
	}
	TranslateMessage(msg);
	DispatchMessage(msg);
}

void
MSWindowsScreen::updateButtons()
{
	int numButtons               = GetSystemMetrics(SM_CMOUSEBUTTONS);
	m_buttons[kButtonNone]       = false;
	m_buttons[kButtonLeft]       = (GetKeyState(VK_LBUTTON)  < 0);
	m_buttons[kButtonRight]      = (GetKeyState(VK_RBUTTON)  < 0);
	m_buttons[kButtonMiddle]     = (GetKeyState(VK_MBUTTON)  < 0);
	m_buttons[kButtonExtra0 + 0] = (numButtons >= 4) &&
								   (GetKeyState(VK_XBUTTON1) < 0);
	m_buttons[kButtonExtra0 + 1] = (numButtons >= 5) &&
								   (GetKeyState(VK_XBUTTON2) < 0);
}

IKeyState*
MSWindowsScreen::getKeyState() const
{
	return m_keyState;
}

bool
MSWindowsScreen::onPreDispatch(HWND hwnd,
				UINT message, WPARAM wParam, LPARAM lParam)
{
	// handle event
	switch (message) {
	case SYNERGY_MSG_SCREEN_SAVER:
		return onScreensaver(wParam != 0);

	case SYNERGY_MSG_DEBUG:
		LOG((CLOG_DEBUG1 "hook: 0x%08x 0x%08x", wParam, lParam));
		return true;
	}

	if (m_isPrimary) {
		return onPreDispatchPrimary(hwnd, message, wParam, lParam);
	}

	return false;
}

bool
MSWindowsScreen::onPreDispatchPrimary(HWND,
				UINT message, WPARAM wParam, LPARAM lParam)
{
	LOG((CLOG_DEBUG5 "handling pre-dispatch primary"));

	// handle event
	switch (message) {
	case SYNERGY_MSG_MARK:
		return onMark(static_cast<UInt32>(wParam));

	case SYNERGY_MSG_KEY:
		return onKey(wParam, lParam);

	case SYNERGY_MSG_MOUSE_BUTTON:
		return onMouseButton(wParam, lParam);

	case SYNERGY_MSG_MOUSE_MOVE:
		return onMouseMove(static_cast<SInt32>(wParam),
							static_cast<SInt32>(lParam));

	case SYNERGY_MSG_MOUSE_WHEEL:
		// XXX -- support x-axis scrolling
		return onMouseWheel(0, static_cast<SInt32>(wParam));

	case SYNERGY_MSG_PRE_WARP:
		{
			// save position to compute delta of next motion
			saveMousePosition(static_cast<SInt32>(wParam), static_cast<SInt32>(lParam));

			// we warped the mouse.  discard events until we find the
			// matching post warp event.  see warpCursorNoFlush() for
			// where the events are sent.  we discard the matching
			// post warp event and can be sure we've skipped the warp
			// event.
			MSG msg;
			do {
				GetMessage(&msg, NULL, SYNERGY_MSG_MOUSE_MOVE,
										SYNERGY_MSG_POST_WARP);
			} while (msg.message != SYNERGY_MSG_POST_WARP);
		}
		return true;

	case SYNERGY_MSG_POST_WARP:
		LOG((CLOG_WARN "unmatched post warp"));
		return true;

	case WM_HOTKEY:
		// we discard these messages.  we'll catch the hot key in the
		// regular key event handling, where we can detect both key
		// press and release.  we only register the hot key so no other
		// app will act on the key combination.
		break;
	}

	return false;
}

bool
MSWindowsScreen::onEvent(HWND, UINT msg,
				WPARAM wParam, LPARAM lParam, LRESULT* result)
{
	switch (msg) {
	case WM_DRAWCLIPBOARD:
		// first pass on the message
		if (m_nextClipboardWindow != NULL) {
			SendMessage(m_nextClipboardWindow, msg, wParam, lParam);
		}

		// now handle the message
		return onClipboardChange();

	case WM_CHANGECBCHAIN:
		if (m_nextClipboardWindow == (HWND)wParam) {
			m_nextClipboardWindow = (HWND)lParam;
			LOG((CLOG_DEBUG "clipboard chain: new next: 0x%08x", m_nextClipboardWindow));
		}
		else if (m_nextClipboardWindow != NULL) {
			SendMessage(m_nextClipboardWindow, msg, wParam, lParam);
		}
		return true;

	case WM_DISPLAYCHANGE:
		return onDisplayChange();

	case WM_POWERBROADCAST:
		switch (wParam) {
		case PBT_APMRESUMEAUTOMATIC:
		case PBT_APMRESUMECRITICAL:
		case PBT_APMRESUMESUSPEND:
			m_events->addEvent(Event(m_events->forIScreen().resume(),
							getEventTarget(), NULL,
							Event::kDeliverImmediately));
			break;

		case PBT_APMSUSPEND:
			m_events->addEvent(Event(m_events->forIScreen().suspend(),
							getEventTarget(), NULL,
							Event::kDeliverImmediately));
			break;
		}
		*result = TRUE;
		return true;

	case WM_DEVICECHANGE:
		forceShowCursor();
		break;

	case WM_SETTINGCHANGE:
		if (wParam == SPI_SETMOUSEKEYS) {
			forceShowCursor();
		}
		break;
	}

	return false;
}

bool
MSWindowsScreen::onMark(UInt32 mark)
{
	m_markReceived = mark;
	return true;
}

bool
MSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam)
{
	static const KeyModifierMask s_ctrlAlt =
		KeyModifierControl | KeyModifierAlt;

	LOG((CLOG_DEBUG1 "event: Key char=%d, vk=0x%02x, nagr=%d, lParam=0x%08x", (wParam & 0xff00u) >> 8, wParam & 0xffu, (wParam & 0x10000u) ? 1 : 0, lParam));

	// get event info
	KeyButton button         = (KeyButton)((lParam & 0x01ff0000) >> 16);
	bool down                = ((lParam & 0x80000000u) == 0x00000000u);
	bool wasDown             = isKeyDown(button);
	KeyModifierMask oldState = pollActiveModifiers();

	// check for autorepeat
	if (m_keyState->testAutoRepeat(down, (lParam & 0x40000000u) == 1, button)) {
		lParam |= 0x40000000u;
	}

	// if the button is zero then guess what the button should be.
	// these are badly synthesized key events and logitech software
	// that maps mouse buttons to keys is known to do this.
	// alternatively, we could just throw these events out.
	if (button == 0) {
		button = m_keyState->virtualKeyToButton(wParam & 0xffu);
		if (button == 0) {
			return true;
		}
		wasDown = isKeyDown(button);
	}

	// record keyboard state
	m_keyState->onKey(button, down, oldState);

	if (!down && m_isPrimary && !m_isOnScreen) {
		PrimaryKeyDownList::iterator find = std::find(m_primaryKeyDownList.begin(), m_primaryKeyDownList.end(), button);
		if (find != m_primaryKeyDownList.end()) {
			LOG((CLOG_DEBUG1 "release key button %d on primary", *find));
			m_hook.setMode(kHOOK_WATCH_JUMP_ZONE);
			fakeLocalKey(*find, false);
			m_primaryKeyDownList.erase(find);
			m_hook.setMode(kHOOK_RELAY_EVENTS);
			return true;
		}
	}

	// windows doesn't tell us the modifier key state on mouse or key
	// events so we have to figure it out.  most apps would use
	// GetKeyState() or even GetAsyncKeyState() for that but we can't
	// because our hook doesn't pass on key events for several modifiers.
	// it can't otherwise the system would interpret them normally on
	// the primary screen even when on a secondary screen.  so tapping
	// alt would activate menus and tapping the windows key would open
	// the start menu.  if you don't pass those events on in the hook
	// then GetKeyState() understandably doesn't reflect the effect of
	// the event.  curiously, neither does GetAsyncKeyState(), which is
	// surprising.
	//
	// so anyway, we have to track the modifier state ourselves for
	// at least those modifiers we don't pass on.  pollActiveModifiers()
	// does that but we have to update the keyboard state before calling
	// pollActiveModifiers() to get the right answer.  but the only way
	// to set the modifier state or to set the up/down state of a key
	// is via onKey().  so we have to call onKey() twice.
	KeyModifierMask state = pollActiveModifiers();
	m_keyState->onKey(button, down, state);

	// check for hot keys
	if (oldState != state) {
		// modifier key was pressed/released
		if (onHotKey(0, lParam)) {
			return true;
		}
	}
	else {
		// non-modifier was pressed/released
		if (onHotKey(wParam, lParam)) {
			return true;
		}
	}

	// stop sending modifier keys over and over again
	if (isModifierRepeat(oldState, state, wParam)) {
		return true;
	}

	// ignore message if posted prior to last mark change
	if (!ignore()) {
		// check for ctrl+alt+del.  we do not want to pass that to the
		// client.  the user can use ctrl+alt+pause to emulate it.
		UINT virtKey = (wParam & 0xffu);
		if (virtKey == VK_DELETE && (state & s_ctrlAlt) == s_ctrlAlt) {
			LOG((CLOG_DEBUG "discard ctrl+alt+del"));
			return true;
		}

		// check for ctrl+alt+del emulation
		if ((virtKey == VK_PAUSE || virtKey == VK_CANCEL) &&
			(state & s_ctrlAlt) == s_ctrlAlt) {
			LOG((CLOG_DEBUG "emulate ctrl+alt+del"));
			// switch wParam and lParam to be as if VK_DELETE was
			// pressed or released.  when mapping the key we require that
			// we not use AltGr (the 0x10000 flag in wParam) and we not
			// use the keypad delete key (the 0x01000000 flag in lParam).
			wParam  = VK_DELETE | 0x00010000u;
			lParam &= 0xfe000000;
			lParam |= m_keyState->virtualKeyToButton(wParam & 0xffu) << 16;
			lParam |= 0x01000001;
		}

		// process key
		KeyModifierMask mask;
		KeyID key = m_keyState->mapKeyFromEvent(wParam, lParam, &mask);
		button    = static_cast<KeyButton>((lParam & 0x01ff0000u) >> 16);
		if (key != kKeyNone) {
			// do it
			m_keyState->sendKeyEvent(getEventTarget(),
							((lParam & 0x80000000u) == 0),
							((lParam & 0x40000000u) != 0),
							key, mask, (SInt32)(lParam & 0xffff), button);
		}
		else {
			LOG((CLOG_DEBUG1 "cannot map key"));
		}
	}

	return true;
}

bool
MSWindowsScreen::onHotKey(WPARAM wParam, LPARAM lParam)
{
	// get the key info
	KeyModifierMask state = getActiveModifiers();
	UINT virtKey   = (wParam & 0xffu);
	UINT modifiers = 0;
	if ((state & KeyModifierShift) != 0) {
		modifiers |= MOD_SHIFT;
	}
	if ((state & KeyModifierControl) != 0) {
		modifiers |= MOD_CONTROL;
	}
	if ((state & KeyModifierAlt) != 0) {
		modifiers |= MOD_ALT;
	}
	if ((state & KeyModifierSuper) != 0) {
		modifiers |= MOD_WIN;
	}

	// find the hot key id
	HotKeyToIDMap::const_iterator i =
		m_hotKeyToIDMap.find(HotKeyItem(virtKey, modifiers));
	if (i == m_hotKeyToIDMap.end()) {
		return false;
	}

	// find what kind of event
	Event::Type type;
	if ((lParam & 0x80000000u) == 0u) {
		if ((lParam & 0x40000000u) != 0u) {
			// ignore key repeats but it counts as a hot key
			return true;
		}
		type = m_events->forIPrimaryScreen().hotKeyDown();
	}
	else {
		type = m_events->forIPrimaryScreen().hotKeyUp();
	}

	// generate event
	m_events->addEvent(Event(type, getEventTarget(),
							HotKeyInfo::alloc(i->second)));

	return true;
}

bool
MSWindowsScreen::onMouseButton(WPARAM wParam, LPARAM lParam)
{
	// get which button
	bool pressed    = mapPressFromEvent(wParam, lParam);
	ButtonID button = mapButtonFromEvent(wParam, lParam);

	// keep our shadow key state up to date
	if (button >= kButtonLeft && button <= kButtonExtra0 + 1) {
		if (pressed) {
			m_buttons[button] = true;
			if (button == kButtonLeft) {
				m_draggingFilename.clear();
				LOG((CLOG_DEBUG2 "dragging filename is cleared"));
			}
		}
		else {
			m_buttons[button] = false;
			if (m_draggingStarted && button == kButtonLeft) {
				m_draggingStarted = false;
			}
		}
	}

	// ignore message if posted prior to last mark change
	if (!ignore()) {
		KeyModifierMask mask = m_keyState->getActiveModifiers();
		if (pressed) {
			LOG((CLOG_DEBUG1 "event: button press button=%d", button));
			if (button != kButtonNone) {
				sendEvent(m_events->forIPrimaryScreen().buttonDown(),
								ButtonInfo::alloc(button, mask));
			}
		}
		else {
			LOG((CLOG_DEBUG1 "event: button release button=%d", button));
			if (button != kButtonNone) {
				sendEvent(m_events->forIPrimaryScreen().buttonUp(),
								ButtonInfo::alloc(button, mask));
			}
		}
	}

	return true;
}

// here's how mouse movements are sent across the network to a client:
//   1. synergy checks the mouse position on server screen
//   2. records the delta (current x,y minus last x,y)
//   3. records the current x,y as "last" (so we can calc delta next time)
//   4. on the server, puts the cursor back to the center of the screen
//      - remember the cursor is hidden on the server at this point
//      - this actually records the current x,y as "last" a second time (it seems)
//   5. sends the delta movement to the client (could be +1,+1 or -1,+4 for example)
bool
MSWindowsScreen::onMouseMove(SInt32 mx, SInt32 my)
{
	// compute motion delta (relative to the last known
	// mouse position)
	SInt32 x = mx - m_xCursor;
	SInt32 y = my - m_yCursor;

	LOG((CLOG_DEBUG3
		"mouse move - motion delta: %+d=(%+d - %+d),%+d=(%+d - %+d)",
		x, mx, m_xCursor, y, my, m_yCursor));

	// ignore if the mouse didn't move or if message posted prior
	// to last mark change.
	if (ignore() || (x == 0 && y == 0)) {
		return true;
	}

	// save position to compute delta of next motion
	saveMousePosition(mx, my);

	if (m_isOnScreen) {
		
		// motion on primary screen
		sendEvent(
			m_events->forIPrimaryScreen().motionOnPrimary(),
			MotionInfo::alloc(m_xCursor, m_yCursor));

		if (m_buttons[kButtonLeft] == true && m_draggingStarted == false) {
			m_draggingStarted = true;
		}
	}
	else 
	{
		// the motion is on the secondary screen, so we warp mouse back to
		// center on the server screen. if we don't do this, then the mouse 
		// will always try to return to the original entry point on the 
		// secondary screen.
		LOG((CLOG_DEBUG5 "warping server cursor to center: %+d,%+d", m_xCenter, m_yCenter));
		warpCursorNoFlush(m_xCenter, m_yCenter);
		
		// examine the motion.  if it's about the distance
		// from the center of the screen to an edge then
		// it's probably a bogus motion that we want to
		// ignore (see warpCursorNoFlush() for a further
		// description).
		static SInt32 bogusZoneSize = 10;
		if (-x + bogusZoneSize > m_xCenter - m_x ||
			 x + bogusZoneSize > m_x + m_w - m_xCenter ||
			-y + bogusZoneSize > m_yCenter - m_y ||
			 y + bogusZoneSize > m_y + m_h - m_yCenter) {
			
			LOG((CLOG_DEBUG "dropped bogus delta motion: %+d,%+d", x, y));
		}
		else {
			// send motion
			sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(x, y));
		}
	}

	return true;
}

bool
MSWindowsScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta)
{
	// ignore message if posted prior to last mark change
	if (!ignore()) {
		LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta));
		sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(xDelta, yDelta));
	}
	return true;
}

bool
MSWindowsScreen::onScreensaver(bool activated)
{
	// ignore this message if there are any other screen saver
	// messages already in the queue.  this is important because
	// our checkStarted() function has a deliberate delay, so it
	// can't respond to events at full CPU speed and will fall
	// behind if a lot of screen saver events are generated.
	// that can easily happen because windows will continually
	// send SC_SCREENSAVE until the screen saver starts, even if
	// the screen saver is disabled!
	MSG msg;
	if (PeekMessage(&msg, NULL, SYNERGY_MSG_SCREEN_SAVER,
						SYNERGY_MSG_SCREEN_SAVER, PM_NOREMOVE)) {
		return true;
	}

	if (activated) {
		if (!m_screensaverActive &&
			m_screensaver->checkStarted(SYNERGY_MSG_SCREEN_SAVER, FALSE, 0)) {
			m_screensaverActive = true;
			sendEvent(m_events->forIPrimaryScreen().screensaverActivated());

			// enable display power down
			ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY);
		}
	}
	else {
		if (m_screensaverActive) {
			m_screensaverActive = false;
			sendEvent(m_events->forIPrimaryScreen().screensaverDeactivated());

			// disable display power down
			ArchMiscWindows::addBusyState(ArchMiscWindows::kDISPLAY);
		}
	}

	return true;
}

bool
MSWindowsScreen::onDisplayChange()
{
	// screen resolution may have changed.  save old shape.
	SInt32 xOld = m_x, yOld = m_y, wOld = m_w, hOld = m_h;

	// update shape
	updateScreenShape();

	// do nothing if resolution hasn't changed
	if (xOld != m_x || yOld != m_y || wOld != m_w || hOld != m_h) {
		if (m_isPrimary) {
			// warp mouse to center if off screen
			if (!m_isOnScreen) {

				LOG((CLOG_DEBUG1 "warping cursor to center: %+d, %+d", m_xCenter, m_yCenter));
				warpCursor(m_xCenter, m_yCenter);
			}

			// tell hook about resize if on screen
			else {
				m_hook.setZone(m_x, m_y, m_w, m_h, getJumpZoneSize());
			}
		}

		// send new screen info
		sendEvent(m_events->forIScreen().shapeChanged());

		LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : ""));
	}

	return true;
}

bool
MSWindowsScreen::onClipboardChange()
{
	// now notify client that somebody changed the clipboard (unless
	// we're the owner).
	if (!MSWindowsClipboard::isOwnedBySynergy()) {
		if (m_ownClipboard) {
			LOG((CLOG_DEBUG "clipboard changed: lost ownership"));
			m_ownClipboard = false;
			sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard);
			sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection);
		}
	}
	else if (!m_ownClipboard) {
		LOG((CLOG_DEBUG "clipboard changed: synergy owned"));
		m_ownClipboard = true;
	}

	return true;
}

void
MSWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y)
{
	// send an event that we can recognize before the mouse warp
	PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_PRE_WARP, x, y);

	// warp mouse.  hopefully this inserts a mouse motion event
	// between the previous message and the following message.
	SetCursorPos(x, y);

	// check to see if the mouse pos was set correctly
	POINT cursorPos;
	GetCursorPos(&cursorPos);

	if ((cursorPos.x != x) && (cursorPos.y != y)) {
		LOG((CLOG_DEBUG "SetCursorPos did not work; using fakeMouseMove instead"));
		
		// when at Vista/7 login screen, SetCursorPos does not work (which could be
		// an MS security feature). instead we can use fakeMouseMove, which calls
		// mouse_event.
		// IMPORTANT: as of implementing this function, it has an annoying side 
		// effect; instead of the mouse returning to the correct exit point, it
		// returns to the center of the screen. this could have something to do with
		// the center screen warping technique used (see comments for onMouseMove
		// definition).
		fakeMouseMove(x, y);
	}
	
	// yield the CPU.  there's a race condition when warping:
	//   a hardware mouse event occurs
	//   the mouse hook is not called because that process doesn't have the CPU
	//   we send PRE_WARP, SetCursorPos(), send POST_WARP
	//   we process all of those events and update m_x, m_y
	//   we finish our time slice
	//   the hook is called
	//   the hook sends us a mouse event from the pre-warp position
	//   we get the CPU
	//   we compute a bogus warp
	// we need the hook to process all mouse events that occur
	// before we warp before we do the warp but i'm not sure how
	// to guarantee that.  yielding the CPU here may reduce the
	// chance of undesired behavior.  we'll also check for very
	// large motions that look suspiciously like about half width
	// or height of the screen.
	ARCH->sleep(0.0);

	// send an event that we can recognize after the mouse warp
	PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_POST_WARP, 0, 0);
}

void
MSWindowsScreen::nextMark()
{
	// next mark
	++m_mark;

	// mark point in message queue where the mark was changed
	PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_MARK, m_mark, 0);
}

bool
MSWindowsScreen::ignore() const
{
	return (m_mark != m_markReceived);
}

void
MSWindowsScreen::updateScreenShape()
{
	// get shape
	m_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
	m_y = GetSystemMetrics(SM_YVIRTUALSCREEN);
	m_w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
	m_h = GetSystemMetrics(SM_CYVIRTUALSCREEN);

	// get center for cursor
	m_xCenter = GetSystemMetrics(SM_CXSCREEN) >> 1;
	m_yCenter = GetSystemMetrics(SM_CYSCREEN) >> 1;

	// check for multiple monitors
	m_multimon = (m_w != GetSystemMetrics(SM_CXSCREEN) ||
				  m_h != GetSystemMetrics(SM_CYSCREEN));

	// tell the desks
	m_desks->setShape(m_x, m_y, m_w, m_h, m_xCenter, m_yCenter, m_multimon);
}

void
MSWindowsScreen::handleFixes(const Event&, void*)
{
	// fix clipboard chain
	fixClipboardViewer();

	// update keys if keyboard layouts have changed
	if (m_keyState->didGroupsChange()) {
		updateKeys();
	}
}

void
MSWindowsScreen::fixClipboardViewer()
{
	// XXX -- disable this code for now.  somehow it can cause an infinite
	// recursion in the WM_DRAWCLIPBOARD handler.  either we're sending
	// the message to our own window or some window farther down the chain
	// forwards the message to our window or a window farther up the chain.
	// i'm not sure how that could happen.  the m_nextClipboardWindow = NULL
	// was not in the code that infinite loops and may fix the bug but i
	// doubt it.
/*
	ChangeClipboardChain(m_window, m_nextClipboardWindow);
	m_nextClipboardWindow = NULL;
	m_nextClipboardWindow = SetClipboardViewer(m_window);
*/
}

void
MSWindowsScreen::enableSpecialKeys(bool enable) const
{
}

ButtonID
MSWindowsScreen::mapButtonFromEvent(WPARAM msg, LPARAM button) const
{
	switch (msg) {
	case WM_LBUTTONDOWN:
	case WM_LBUTTONDBLCLK:
	case WM_LBUTTONUP:
	case WM_NCLBUTTONDOWN:
	case WM_NCLBUTTONDBLCLK:
	case WM_NCLBUTTONUP:
		return kButtonLeft;

	case WM_MBUTTONDOWN:
	case WM_MBUTTONDBLCLK:
	case WM_MBUTTONUP:
	case WM_NCMBUTTONDOWN:
	case WM_NCMBUTTONDBLCLK:
	case WM_NCMBUTTONUP:
		return kButtonMiddle;

	case WM_RBUTTONDOWN:
	case WM_RBUTTONDBLCLK:
	case WM_RBUTTONUP:
	case WM_NCRBUTTONDOWN:
	case WM_NCRBUTTONDBLCLK:
	case WM_NCRBUTTONUP:
		return kButtonRight;

	case WM_XBUTTONDOWN:
	case WM_XBUTTONDBLCLK:
	case WM_XBUTTONUP:
	case WM_NCXBUTTONDOWN:
	case WM_NCXBUTTONDBLCLK:
	case WM_NCXBUTTONUP:
		switch (button) {
		case XBUTTON1:
			if (GetSystemMetrics(SM_CMOUSEBUTTONS) >= 4) {
				return kButtonExtra0 + 0;
			}
			break;

		case XBUTTON2:
			if (GetSystemMetrics(SM_CMOUSEBUTTONS) >= 5) {
				return kButtonExtra0 + 1;
			}
			break;
		}
		return kButtonNone;

	default:
		return kButtonNone;
	}
}

bool
MSWindowsScreen::mapPressFromEvent(WPARAM msg, LPARAM) const
{
	switch (msg) {
	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
	case WM_XBUTTONDOWN:
	case WM_LBUTTONDBLCLK:
	case WM_MBUTTONDBLCLK:
	case WM_RBUTTONDBLCLK:
	case WM_XBUTTONDBLCLK:
	case WM_NCLBUTTONDOWN:
	case WM_NCMBUTTONDOWN:
	case WM_NCRBUTTONDOWN:
	case WM_NCXBUTTONDOWN:
	case WM_NCLBUTTONDBLCLK:
	case WM_NCMBUTTONDBLCLK:
	case WM_NCRBUTTONDBLCLK:
	case WM_NCXBUTTONDBLCLK:
		return true;

	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
	case WM_XBUTTONUP:
	case WM_NCLBUTTONUP:
	case WM_NCMBUTTONUP:
	case WM_NCRBUTTONUP:
	case WM_NCXBUTTONUP:
		return false;

	default:
		return false;
	}
}

void
MSWindowsScreen::updateKeysCB(void*)
{
	// record which keys we think are down
	bool down[IKeyState::kNumButtons];
	bool sendFixes = (isPrimary() && !m_isOnScreen);
	if (sendFixes) {
		for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) {
			down[i] = m_keyState->isKeyDown(i);
		}
	}

	// update layouts if necessary
	if (m_keyState->didGroupsChange()) {
		PlatformScreen::updateKeyMap();
	}

	// now update the keyboard state
	PlatformScreen::updateKeyState();

	// now see which keys we thought were down but now think are up.
	// send key releases for these keys to the active client.
	if (sendFixes) {
		KeyModifierMask mask = pollActiveModifiers();
		for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) {
			if (down[i] && !m_keyState->isKeyDown(i)) {
				m_keyState->sendKeyEvent(getEventTarget(),
							false, false, kKeyNone, mask, 1, i);
			}
		}
	}
}

void
MSWindowsScreen::forceShowCursor()
{
	// check for mouse
	m_hasMouse = (GetSystemMetrics(SM_MOUSEPRESENT) != 0);

	// decide if we should show the mouse
	bool showMouse = (!m_hasMouse && !m_isPrimary && m_isOnScreen);

	// show/hide the mouse
	if (showMouse != m_showingMouse) {
		if (showMouse) {
			m_oldMouseKeys.cbSize = sizeof(m_oldMouseKeys);
			m_gotOldMouseKeys =
				(SystemParametersInfo(SPI_GETMOUSEKEYS,
							m_oldMouseKeys.cbSize,	&m_oldMouseKeys, 0) != 0);
			if (m_gotOldMouseKeys) {
				m_mouseKeys    = m_oldMouseKeys;
				m_showingMouse = true;
				updateForceShowCursor();
			}
		}
		else {
			if (m_gotOldMouseKeys) {
				SystemParametersInfo(SPI_SETMOUSEKEYS,
							m_oldMouseKeys.cbSize,
							&m_oldMouseKeys, SPIF_SENDCHANGE);
				m_showingMouse = false;
			}
		}
	}
}

void
MSWindowsScreen::updateForceShowCursor()
{
	DWORD oldFlags = m_mouseKeys.dwFlags;

	// turn on MouseKeys
	m_mouseKeys.dwFlags = MKF_AVAILABLE | MKF_MOUSEKEYSON;

	// make sure MouseKeys is active in whatever state the NumLock is
	// not currently in.
	if ((m_keyState->getActiveModifiers() & KeyModifierNumLock) != 0) {
		m_mouseKeys.dwFlags |= MKF_REPLACENUMBERS;
	}

	// update MouseKeys
	if (oldFlags != m_mouseKeys.dwFlags) {
		SystemParametersInfo(SPI_SETMOUSEKEYS,
							m_mouseKeys.cbSize, &m_mouseKeys, SPIF_SENDCHANGE);
	}
}

LRESULT CALLBACK
MSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	assert(s_screen != NULL);

	LRESULT result = 0;
	if (!s_screen->onEvent(hwnd, msg, wParam, lParam, &result)) {
		result = DefWindowProc(hwnd, msg, wParam, lParam);
	}

	return result;
}

void
MSWindowsScreen::fakeLocalKey(KeyButton button, bool press) const
{
	INPUT input;
	input.type = INPUT_KEYBOARD;
	input.ki.wVk =  m_keyState->mapButtonToVirtualKey(button);
	DWORD pressFlag = press ? KEYEVENTF_EXTENDEDKEY : KEYEVENTF_KEYUP;
	input.ki.dwFlags = pressFlag;
	input.ki.time = 0;
	input.ki.dwExtraInfo = 0;
	SendInput(1,&input,sizeof(input));
}

//
// MSWindowsScreen::HotKeyItem
//

MSWindowsScreen::HotKeyItem::HotKeyItem(UINT keycode, UINT mask) :
	m_keycode(keycode),
	m_mask(mask)
{
	// do nothing
}

UINT
MSWindowsScreen::HotKeyItem::getVirtualKey() const
{
	return m_keycode;
}

bool
MSWindowsScreen::HotKeyItem::operator<(const HotKeyItem& x) const
{
	return (m_keycode < x.m_keycode ||
			(m_keycode == x.m_keycode && m_mask < x.m_mask));
}

void
MSWindowsScreen::fakeDraggingFiles(DragFileList fileList)
{
	// possible design flaw: this function stops a "not implemented"
	// exception from being thrown.
}

String&
MSWindowsScreen::getDraggingFilename()
{
	if (m_draggingStarted) {
		m_dropTarget->clearDraggingFilename();
		m_draggingFilename.clear();

		int halfSize = m_dropWindowSize / 2;

		SInt32 xPos = m_isPrimary ? m_xCursor : m_xCenter;
		SInt32 yPos = m_isPrimary ? m_yCursor : m_yCenter;
		xPos = (xPos - halfSize) < 0 ? 0 : xPos - halfSize;
		yPos = (yPos - halfSize) < 0 ? 0 : yPos - halfSize;
		SetWindowPos(
			m_dropWindow,
			HWND_TOPMOST,
			xPos,
			yPos,
			m_dropWindowSize,
			m_dropWindowSize,
			SWP_SHOWWINDOW);

		// TODO: fake these keys properly
		fakeKeyDown(kKeyEscape, 8192, 1);
		fakeKeyUp(1);
		fakeMouseButton(kButtonLeft, false);

		String filename;
		DOUBLE timeout = ARCH->time() + .5f;
		while (ARCH->time() < timeout) {
			ARCH->sleep(.05f);
			filename = m_dropTarget->getDraggingFilename();
			if (!filename.empty()) {
				break;
			}
		}

		ShowWindow(m_dropWindow, SW_HIDE);

		if (!filename.empty()) {
			if (DragInformation::isFileValid(filename)) {
				m_draggingFilename = filename;
			}
			else {
				LOG((CLOG_DEBUG "drag file name is invalid: %s", filename.c_str()));
			}
		}

		if (m_draggingFilename.empty()) {
			LOG((CLOG_DEBUG "failed to get drag file name from OLE"));
		}
	}

	return m_draggingFilename;
}

const String&
MSWindowsScreen::getDropTarget() const
{
	return m_desktopPath;
}

bool
MSWindowsScreen::isModifierRepeat(KeyModifierMask oldState, KeyModifierMask state, WPARAM wParam) const
{
	bool result = false;

	if (oldState == state && state != 0) {
		UINT virtKey = (wParam & 0xffu);
		if ((state & KeyModifierShift) != 0
			&& (virtKey == VK_LSHIFT || virtKey == VK_RSHIFT)) {
			result = true;
		}
		if ((state & KeyModifierControl) != 0
			&& (virtKey == VK_LCONTROL || virtKey == VK_RCONTROL)) {
			result = true;
		}
		if ((state & KeyModifierAlt) != 0
			&& (virtKey == VK_LMENU || virtKey == VK_RMENU)) {
			result = true;
		}
		if ((state & KeyModifierSuper) != 0
			&& (virtKey == VK_LWIN || virtKey == VK_RWIN)) {
			result = true;
		}
	}

	return result;
}
