/*
 * 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 "CMSWindowsSecondaryScreen.h"
#include "CMSWindowsScreen.h"
#include "CPlatform.h"
#include "XScreen.h"
#include "CLock.h"
#include "CLog.h"
#include <cctype>

// these are only defined when WINVER >= 0x0500
#if !defined(SPI_GETMOUSESPEED)
#define SPI_GETMOUSESPEED 112
#endif
#if !defined(SPI_SETMOUSESPEED)
#define SPI_SETMOUSESPEED 113
#endif

//
// CMSWindowsSecondaryScreen
//

CMSWindowsSecondaryScreen::CMSWindowsSecondaryScreen(
				IScreenReceiver* receiver) :
	m_is95Family(CPlatform::isWindows95Family()),
	m_window(NULL),
	m_mask(0)
{
	m_screen = new CMSWindowsScreen(receiver, this);
}

CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen()
{
	assert(m_window == NULL);

	delete m_screen;
}

void
CMSWindowsSecondaryScreen::keyDown(KeyID key, KeyModifierMask mask)
{
	Keystrokes keys;
	UINT virtualKey;

	CLock lock(&m_mutex);
	m_screen->syncDesktop();

	// get the sequence of keys to simulate key press and the final
	// modifier state.
	m_mask = mapKey(keys, virtualKey, key, mask, kPress);
	if (keys.empty()) {
		return;
	}

	// generate key events
	doKeystrokes(keys, 1);

	// note that key is now down
	m_keys[virtualKey] |= 0x80;
	switch (virtualKey) {
	case VK_LSHIFT:
	case VK_RSHIFT:
		m_keys[VK_SHIFT]   |= 0x80;
		break;

	case VK_LCONTROL:
	case VK_RCONTROL:
		m_keys[VK_CONTROL] |= 0x80;
		break;

	case VK_LMENU:
	case VK_RMENU:
		m_keys[VK_MENU]    |= 0x80;
		break;
	}
}

void
CMSWindowsSecondaryScreen::keyRepeat(KeyID key,
				KeyModifierMask mask, SInt32 count)
{
	Keystrokes keys;
	UINT virtualKey;

	CLock lock(&m_mutex);
	m_screen->syncDesktop();

	// get the sequence of keys to simulate key repeat and the final
	// modifier state.
	m_mask = mapKey(keys, virtualKey, key, mask, kRepeat);
	if (keys.empty()) {
		return;
	}

	// generate key events
	doKeystrokes(keys, count);
}

void
CMSWindowsSecondaryScreen::keyUp(KeyID key, KeyModifierMask mask)
{
	Keystrokes keys;
	UINT virtualKey;

	CLock lock(&m_mutex);
	m_screen->syncDesktop();

	// get the sequence of keys to simulate key release and the final
	// modifier state.
	m_mask = mapKey(keys, virtualKey, key, mask, kRelease);
	if (keys.empty()) {
		return;
	}

	// generate key events
	doKeystrokes(keys, 1);

	// note that key is now up
	m_keys[virtualKey] &= ~0x80;
	switch (virtualKey) {
	case VK_LSHIFT:
		if ((m_keys[VK_RSHIFT] & 0x80) == 0) {
			m_keys[VK_SHIFT]   &= ~0x80;
		}
		break;

	case VK_RSHIFT:
		if ((m_keys[VK_LSHIFT] & 0x80) == 0) {
			m_keys[VK_SHIFT]   &= ~0x80;
		}
		break;

	case VK_LCONTROL:
		if ((m_keys[VK_RCONTROL] & 0x80) == 0) {
			m_keys[VK_CONTROL] &= ~0x80;
		}
		break;

	case VK_RCONTROL:
		if ((m_keys[VK_LCONTROL] & 0x80) == 0) {
			m_keys[VK_CONTROL] &= ~0x80;
		}
		break;

	case VK_LMENU:
		if ((m_keys[VK_RMENU] & 0x80) == 0) {
			m_keys[VK_MENU]    &= ~0x80;
		}
		break;

	case VK_RMENU:
		if ((m_keys[VK_LMENU] & 0x80) == 0) {
			m_keys[VK_MENU]    &= ~0x80;
		}
		break;
	}
}

void
CMSWindowsSecondaryScreen::mouseDown(ButtonID button)
{
	CLock lock(&m_mutex);
	m_screen->syncDesktop();

	// map button id to button flag
	DWORD flags = mapButton(button, true);

	// send event
	if (flags != 0) {
		mouse_event(flags, 0, 0, 0, 0);
	}
}

void
CMSWindowsSecondaryScreen::mouseUp(ButtonID button)
{
	CLock lock(&m_mutex);
	m_screen->syncDesktop();

	// map button id to button flag
	DWORD flags = mapButton(button, false);

	// send event
	if (flags != 0) {
		mouse_event(flags, 0, 0, 0, 0);
	}
}

void
CMSWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y)
{
	CLock lock(&m_mutex);
	m_screen->syncDesktop();
	warpCursor(x, y);
}

void
CMSWindowsSecondaryScreen::mouseWheel(SInt32 delta)
{
	CLock lock(&m_mutex);
	m_screen->syncDesktop();
	mouse_event(MOUSEEVENTF_WHEEL, 0, 0, delta, 0);
}

IScreen*
CMSWindowsSecondaryScreen::getScreen() const
{
	return m_screen;
}

void
CMSWindowsSecondaryScreen::onScreensaver(bool)
{
	// ignore
}

bool
CMSWindowsSecondaryScreen::onPreDispatch(const CEvent*)
{
	return false;
}

bool
CMSWindowsSecondaryScreen::onEvent(CEvent* event)
{
	assert(event != NULL);

	const MSG& msg = event->m_msg;
	switch (msg.message) {
	case WM_ACTIVATEAPP:
		if (msg.wParam == FALSE) {
			// some other app activated.  hide the hider window.
			ShowWindow(m_window, SW_HIDE);
		}
		break;
	}

	return false;
}

SInt32
CMSWindowsSecondaryScreen::getJumpZoneSize() const
{
	return 0;
}

void
CMSWindowsSecondaryScreen::postCreateWindow(HWND window)
{
	m_window = window;
	if (!isActive()) {
		showWindow();
	}
}

void
CMSWindowsSecondaryScreen::preDestroyWindow(HWND)
{
	// do nothing
}

void
CMSWindowsSecondaryScreen::onPreMainLoop()
{
	assert(m_window != NULL);
}

void
CMSWindowsSecondaryScreen::onPreOpen()
{
	assert(m_window == NULL);
}

void
CMSWindowsSecondaryScreen::onPreEnter()
{
	assert(m_window != NULL);
}

void
CMSWindowsSecondaryScreen::onPreLeave()
{
	assert(m_window != NULL);
}

void
CMSWindowsSecondaryScreen::createWindow()
{
	// open the desktop and the window
	m_window = m_screen->openDesktop();
	if (m_window == NULL) {
		throw XScreenOpenFailure();
	}
}

void
CMSWindowsSecondaryScreen::destroyWindow()
{
	// release keys that are logically pressed
	releaseKeys();

	// close the desktop and the window
	m_screen->closeDesktop();
	m_window = NULL;
}

void
CMSWindowsSecondaryScreen::showWindow()
{
	// move hider window under the mouse (rather than moving the mouse
	// somewhere else on the screen)
	SInt32 x, y;
	getCursorPos(x, y);
	MoveWindow(m_window, x, y, 1, 1, FALSE);

	// raise and show the hider window.  take activation.
	ShowWindow(m_window, SW_SHOWNORMAL);
}

void
CMSWindowsSecondaryScreen::hideWindow()
{
	ShowWindow(m_window, SW_HIDE);
}

void
CMSWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y)
{
	// move the mouse directly to target position on NT family or if
	// not using multiple monitors.
	if (m_screen->isMultimon() || !m_is95Family) {
		SInt32 x0, y0, w, h;
		m_screen->getShape(x0, y0, w, h);
		mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
								(DWORD)((65535.99 * (x - x0)) / (w - 1)),
								(DWORD)((65535.99 * (y - y0)) / (h - 1)),
								0, 0);
	}

	// windows 98 (and Me?) is broken.  you cannot set the absolute
	// position of the mouse except on the primary monitor but you
	// can do relative moves onto any monitor.  this is, in microsoft's
	// words, "by design."  apparently the designers of windows 2000
	// we're a little less lazy and did it right.
	//
	// we use the microsoft recommendation (Q193003): set the absolute
	// position on the primary monitor, disable mouse acceleration,
	// relative move the mouse to the final location, restore mouse
	// acceleration.  to avoid one kind of race condition (the user
	// clicking the mouse or pressing a key between the absolute and
	// relative move) we'll use SendInput() which guarantees that the
	// events are delivered uninterrupted.  we cannot prevent changes
	// to the mouse acceleration at inopportune times, though.
	//
	// point-to-activate (x-mouse) doesn't seem to be bothered by the
	// absolute/relative combination.  a window over the absolute
	// position (0,0) does *not* get activated (at least not on win2k)
	// if the relative move puts the cursor elsewhere.  similarly, the
	// app under the final mouse position does *not* get deactivated
	// by the absolute move to 0,0.
	else {
		// save mouse speed & acceleration
		int oldSpeed[4];
		bool accelChanged =
					SystemParametersInfo(SPI_GETMOUSE,0, oldSpeed,0) &&
					SystemParametersInfo(SPI_GETMOUSESPEED, 0, oldSpeed + 3, 0);

		// use 1:1 motion
		if (accelChanged) {
			int newSpeed[4] = { 0, 0, 0, 1 };
			accelChanged =
					SystemParametersInfo(SPI_SETMOUSE, 0, newSpeed, 0) ||
					SystemParametersInfo(SPI_SETMOUSESPEED, 0, newSpeed + 3, 0);
		}

		// send events
		INPUT events[2];
		events[0].type           = INPUT_MOUSE;
		events[0].mi.dx          = 0;
		events[0].mi.dy          = 0;
		events[0].mi.mouseData   = 0;
		events[0].mi.dwFlags     = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
		events[0].mi.time        = GetTickCount();
		events[0].mi.dwExtraInfo = 0;
		events[1].type           = INPUT_MOUSE;
		events[1].mi.dx          = x;
		events[1].mi.dy          = y;
		events[1].mi.mouseData   = 0;
		events[1].mi.dwFlags     = MOUSEEVENTF_MOVE;
		events[1].mi.time        = events[0].mi.time;
		events[1].mi.dwExtraInfo = 0;
		SendInput(sizeof(events) / sizeof(events[0]),
								events, sizeof(events[0]));

		// restore mouse speed & acceleration
		if (accelChanged) {
			SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0);
			SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0);
		}
	}
}

void
CMSWindowsSecondaryScreen::updateKeys()
{
	// clear key state
	memset(m_keys, 0, sizeof(m_keys));

	// we only care about the modifier key states
	m_keys[VK_LSHIFT]   = static_cast<BYTE>(GetKeyState(VK_LSHIFT));
	m_keys[VK_RSHIFT]   = static_cast<BYTE>(GetKeyState(VK_RSHIFT));
	m_keys[VK_SHIFT]    = static_cast<BYTE>(GetKeyState(VK_SHIFT));
	m_keys[VK_LCONTROL] = static_cast<BYTE>(GetKeyState(VK_LCONTROL));
	m_keys[VK_RCONTROL] = static_cast<BYTE>(GetKeyState(VK_RCONTROL));
	m_keys[VK_CONTROL]  = static_cast<BYTE>(GetKeyState(VK_CONTROL));
	m_keys[VK_LMENU]    = static_cast<BYTE>(GetKeyState(VK_LMENU));
	m_keys[VK_RMENU]    = static_cast<BYTE>(GetKeyState(VK_RMENU));
	m_keys[VK_MENU]     = static_cast<BYTE>(GetKeyState(VK_MENU));
	m_keys[VK_LWIN]     = static_cast<BYTE>(GetKeyState(VK_LWIN));
	m_keys[VK_RWIN]     = static_cast<BYTE>(GetKeyState(VK_RWIN));
	m_keys[VK_APPS]     = static_cast<BYTE>(GetKeyState(VK_APPS));
	m_keys[VK_CAPITAL]  = static_cast<BYTE>(GetKeyState(VK_CAPITAL));
	m_keys[VK_NUMLOCK]  = static_cast<BYTE>(GetKeyState(VK_NUMLOCK));
	m_keys[VK_SCROLL]   = static_cast<BYTE>(GetKeyState(VK_SCROLL));

	// update active modifier mask
	m_mask = 0;
	if ((m_keys[VK_LSHIFT] & 0x80) != 0 || (m_keys[VK_RSHIFT] & 0x80) != 0) {
		m_mask |= KeyModifierShift;
	}
	if ((m_keys[VK_LCONTROL] & 0x80) != 0 ||
		(m_keys[VK_RCONTROL] & 0x80) != 0) {
		m_mask |= KeyModifierControl;
	}
	if ((m_keys[VK_LMENU] & 0x80) != 0 || (m_keys[VK_RMENU] & 0x80) != 0) {
		m_mask |= KeyModifierAlt;
	}
	if ((m_keys[VK_LWIN] & 0x80) != 0 || (m_keys[VK_RWIN] & 0x80) != 0) {
		m_mask |= KeyModifierMeta;
	}
	if ((m_keys[VK_CAPITAL] & 0x01) != 0) {
		m_mask |= KeyModifierCapsLock;
	}
	if ((m_keys[VK_NUMLOCK] & 0x01) != 0) {
		m_mask |= KeyModifierNumLock;
	}
	if ((m_keys[VK_SCROLL] & 0x01) != 0) {
		m_mask |= KeyModifierScrollLock;
	}
	log((CLOG_DEBUG2 "modifiers on update: 0x%04x", m_mask));
}

void
CMSWindowsSecondaryScreen::setToggleState(KeyModifierMask mask)
{
	// toggle modifiers that don't match the desired state
	if ((mask & KeyModifierCapsLock)   != (m_mask & KeyModifierCapsLock)) {
		toggleKey(VK_CAPITAL, KeyModifierCapsLock);
	}
	if ((mask & KeyModifierNumLock)    != (m_mask & KeyModifierNumLock)) {
		toggleKey(VK_NUMLOCK | 0x100, KeyModifierNumLock);
	}
	if ((mask & KeyModifierScrollLock) != (m_mask & KeyModifierScrollLock)) {
		toggleKey(VK_SCROLL, KeyModifierScrollLock);
	}
}

// map special KeyID keys to virtual key codes. if the key is an
// extended key then the entry is the virtual key code | 0x100.
// unmapped keys have a 0 entry.
static const UINT		g_mapEE00[] =
{
	/* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x20 */ VK_TAB, 0, 0, 0, 0, 0, 0, 0,
	/* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x60 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x68 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x78 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x88 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x98 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xa8 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xb0 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xb8 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xc0 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xc8 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xd0 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xe0 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xe8 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, 0
};
static const UINT		g_mapEF00[] =
{
	/* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x08 */ VK_BACK, VK_TAB, 0, VK_CLEAR, 0, VK_RETURN, 0, 0,
	/* 0x10 */ 0, 0, 0, VK_PAUSE, VK_SCROLL, 0/*sys-req*/, 0, 0,
	/* 0x18 */ 0, 0, 0, VK_ESCAPE, 0, 0, 0, 0,
	/* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x50 */ VK_HOME|0x100, VK_LEFT|0x100, VK_UP|0x100, VK_RIGHT|0x100,
	/* 0x54 */ VK_DOWN|0x100, VK_PRIOR|0x100, VK_NEXT|0x100, VK_END|0x100,
	/* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x60 */ VK_SELECT|0x100, VK_SNAPSHOT|0x100, VK_EXECUTE|0x100, VK_INSERT|0x100,
	/* 0x64 */ 0, 0, 0, VK_APPS|0x100,
	/* 0x68 */ 0, 0, VK_HELP|0x100, VK_CANCEL|0x100, 0, 0, 0, 0,
	/* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x78 */ 0, 0, 0, 0, 0, 0, VK_MODECHANGE|0x100, VK_NUMLOCK|0x100,
	/* 0x80 */ VK_SPACE, 0, 0, 0, 0, 0, 0, 0,
	/* 0x88 */ 0, VK_TAB, 0, 0, 0, VK_RETURN|0x100, 0, 0,
	/* 0x90 */ 0, 0, 0, 0, 0, VK_HOME, VK_LEFT, VK_UP,
	/* 0x98 */ VK_RIGHT, VK_DOWN, VK_PRIOR, VK_NEXT,
	/* 0x9c */ VK_END, 0, VK_INSERT, VK_DELETE,
	/* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xa8 */ 0, 0, VK_MULTIPLY, VK_ADD,
	/* 0xac */ VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL, VK_DIVIDE|0x100,
	/* 0xb0 */ VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3,
	/* 0xb4 */ VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7,
	/* 0xb8 */ VK_NUMPAD8, VK_NUMPAD9, 0, 0, 0, 0, VK_F1, VK_F2,
	/* 0xc0 */ VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_F10,
	/* 0xc8 */ VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18,
	/* 0xd0 */ VK_F19, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, 0, 0,
	/* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xe0 */ 0, VK_LSHIFT, VK_RSHIFT, VK_LCONTROL,
	/* 0xe4 */ VK_RCONTROL|0x100, VK_CAPITAL, 0, VK_LWIN|0x100,
	/* 0xe8 */ VK_RWIN|0x100, VK_LMENU, VK_RMENU|0x100, 0, 0, 0, 0, 0,
	/* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, VK_DELETE|0x100
};

DWORD
CMSWindowsSecondaryScreen::mapButton(ButtonID button, bool press) const
{
	// map button id to button flag
	switch (button) {
	case kButtonLeft:
		return press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;

	case kButtonMiddle:
		return press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;

	case kButtonRight:
		return press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;

	default:
		return 0;
	}
}

KeyModifierMask
CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey,
				KeyID id, KeyModifierMask mask, EKeyAction action) const
{
	virtualKey = 0;

	// check for special keys
	if ((id & 0xfffff000) == 0xe000) {
		if ((id & 0xff00) == 0xee00) {
			virtualKey = g_mapEE00[id & 0xff];
		}
		else if ((id & 0xff00) == 0xef00) {
			virtualKey = g_mapEF00[id & 0xff];
		}
		if (virtualKey == 0) {
			log((CLOG_DEBUG2 "unknown special key"));
			return m_mask;
		}
	}

	// special handling of VK_SNAPSHOT
	if ((virtualKey & 0xff) == VK_SNAPSHOT) {
		// ignore key repeats on print screen
		if (action != kRepeat) {
			// get event flags
			DWORD flags = 0;
			if (isExtendedKey(virtualKey)) {
				flags |= KEYEVENTF_EXTENDEDKEY;
			}
			if (action != kPress) {
				flags |= KEYEVENTF_KEYUP;
			}

			// active window or fullscreen?
			BYTE scan = 0;
			if ((mask & KeyModifierAlt) == 0) {
				scan = 1;
			}

			// send event
			keybd_event(static_cast<BYTE>(virtualKey & 0xff), scan, flags, 0);
		}
		return m_mask;
	}

	// get output mask.  default output mask carries over the current
	// toggle modifier states and includes desired shift, control, alt,
	// and meta states.
	KeyModifierMask outMask = (m_mask &
								(KeyModifierCapsLock |
								KeyModifierNumLock |
								KeyModifierScrollLock));
	outMask                |= (mask &
								(KeyModifierShift |
								KeyModifierControl |
								KeyModifierAlt |
								KeyModifierMeta));

	// extract extended key flag
	const bool isExtended = ((virtualKey & 0x100) != 0);
	virtualKey           &= ~0x100;

	// if not in map then ask system to convert character
	if (virtualKey == 0) {
		// translate.  return no keys if unknown key.
		// FIXME -- handle unicode
		TCHAR ascii = static_cast<TCHAR>(id & 0x000000ff);
		SHORT vk = VkKeyScan(ascii);
		if (vk == 0xffff) {
			log((CLOG_DEBUG2 "no virtual key for character %d", id));
			return m_mask;
		}

		// use whatever shift state VkKeyScan says
		// FIXME -- also for control and alt, but it's more difficult
		// to determine if control and alt must be off or if it just
		// doesn't matter.
		outMask &= ~KeyModifierShift;

		// convert system modifier mask to our mask
		if (HIBYTE(vk) & 1) {
			outMask |= KeyModifierShift;
		}
		if (HIBYTE(vk) & 2) {
			outMask |= KeyModifierControl;
		}
		if (HIBYTE(vk) & 4) {
			outMask |= KeyModifierAlt;
		}

		// handle combination of caps-lock and shift.  if caps-lock is
		// off locally then use shift as necessary.  if caps-lock is on
		// locally then it reverses the meaning of shift for keys that
		// are subject to case conversion.
		if ((outMask & KeyModifierCapsLock) != 0) {
			if (tolower(ascii) != toupper(ascii)) {
				log((CLOG_DEBUG2 "flip shift"));
				outMask ^= KeyModifierShift;
			}
		}

		// get virtual key
		virtualKey = LOBYTE(vk);
	}

	// if in map then figure out correct modifier state
	else {
		// check numeric keypad.  note that while KeyID distinguishes
		// between the keypad movement keys (e.g. Home, left arrow),
		// the virtual keys do not.  however, the virtual keys do
		// distinguish between keypad numbers and operators (e.g.
		// add, multiply) and their main keyboard counterparts.
		// therefore, we can ignore the num-lock state for movement
		// virtual keys but not for numeric keys.
		if (virtualKey >= VK_NUMPAD0 && virtualKey <= VK_DIVIDE) {
			// set required shift state based on current numlock state
			if ((outMask & KeyModifierNumLock) == 0) {
				if ((m_mask & KeyModifierNumLock) == 0) {
					log((CLOG_DEBUG2 "turn on num lock for keypad key"));
					outMask |= KeyModifierNumLock;
				}
				else {
					log((CLOG_DEBUG2 "turn on shift for keypad key"));
					outMask |= KeyModifierShift;
				}
			}
		}

		// check for left tab
		else if (id == kKeyLeftTab) {
			outMask |= KeyModifierShift;
		}
	}
	log((CLOG_DEBUG2 "KeyID %d to virtual key %d mask 0x%04x", id, virtualKey, outMask));

	// a list of modifier key info
	class CModifierInfo {
	public:
		KeyModifierMask	m_mask;
		UINT			m_virtualKey;
		UINT			m_virtualKey2;
		bool			m_isToggle;
	};
	static const CModifierInfo s_modifier[] = {
		{ KeyModifierShift,		VK_LSHIFT,			VK_RSHIFT,			false },
		{ KeyModifierControl,	VK_LCONTROL,		VK_RCONTROL | 0x100,false },
		{ KeyModifierAlt,		VK_LMENU,			VK_RMENU | 0x100,	false },
		{ KeyModifierMeta,		VK_LWIN | 0x100,	VK_RWIN | 0x100,	false },
		{ KeyModifierCapsLock,	VK_CAPITAL,			0,					true },
		{ KeyModifierNumLock,	VK_NUMLOCK | 0x100,	0,					true },
		{ KeyModifierScrollLock,VK_SCROLL,			0,					true }
	};
	static const unsigned int s_numModifiers =
								sizeof(s_modifier) / sizeof(s_modifier[0]);

	// note if the key is a modifier
	unsigned int modifierIndex;
	switch (virtualKey) {
	case VK_SHIFT:
	case VK_LSHIFT:
	case VK_RSHIFT:
		modifierIndex = 0;
		break;

	case VK_CONTROL:
	case VK_LCONTROL:
	case VK_RCONTROL:
		modifierIndex = 1;
		break;

	case VK_MENU:
	case VK_LMENU:
	case VK_RMENU:
		modifierIndex = 2;
		break;

	case VK_LWIN:
	case VK_RWIN:
		modifierIndex = 3;
		break;

	case VK_CAPITAL:
		modifierIndex = 4;
		break;

	case VK_NUMLOCK:
		modifierIndex = 5;
		break;

	case VK_SCROLL:
		modifierIndex = 6;
		break;

	default:
		modifierIndex = s_numModifiers;
		break;
	}
	const bool isModifier = (modifierIndex != s_numModifiers);

	// add the key events required to get to the modifier state
	// necessary to generate an event yielding id.  also save the
	// key events required to restore the state.  if the key is
	// a modifier key then skip this because modifiers should not
	// modify modifiers.
	Keystrokes undo;
	Keystroke keystroke;
	if (outMask != m_mask && !isModifier) {
		for (unsigned int i = 0; i < s_numModifiers; ++i) {
			KeyModifierMask bit = s_modifier[i].m_mask;
			if ((outMask & bit) != (m_mask & bit)) {
				if ((outMask & bit) != 0) {
					// modifier is not active but should be.  if the
					// modifier is a toggle then toggle it on with a
					// press/release, otherwise activate it with a
					// press.
					keystroke.m_virtualKey = s_modifier[i].m_virtualKey;
					keystroke.m_press      = true;
					keystroke.m_repeat     = false;
					keys.push_back(keystroke);
					if (s_modifier[i].m_isToggle) {
						keystroke.m_press = false;
						keys.push_back(keystroke);
						undo.push_back(keystroke);
						keystroke.m_press = true;
						undo.push_back(keystroke);
					}
					else {
						keystroke.m_press = false;
						undo.push_back(keystroke);
					}
				}

				else {
					// modifier is active but should not be.  if the
					// modifier is a toggle then toggle it off with a
					// press/release, otherwise deactivate it with a
					// release.  we must check each keycode for the
					// modifier if not a toggle.
					if (s_modifier[i].m_isToggle) {
						keystroke.m_virtualKey = s_modifier[i].m_virtualKey;
						keystroke.m_press      = true;
						keystroke.m_repeat     = false;
						keys.push_back(keystroke);
						keystroke.m_press      = false;
						keys.push_back(keystroke);
						undo.push_back(keystroke);
						keystroke.m_press      = true;
						undo.push_back(keystroke);
					}
					else {
						UINT key = s_modifier[i].m_virtualKey;
						if ((m_keys[key & 0xff] & 0x80) != 0) {
							keystroke.m_virtualKey = key;
							keystroke.m_press      = false;
							keystroke.m_repeat     = false;
							keys.push_back(keystroke);
							keystroke.m_press      = true;
							undo.push_back(keystroke);
						}
						key = s_modifier[i].m_virtualKey2;
						if (key != 0 && (m_keys[key & 0xff] & 0x80) != 0) {
							keystroke.m_virtualKey = key;
							keystroke.m_press      = false;
							keystroke.m_repeat     = false;
							keys.push_back(keystroke);
							keystroke.m_press      = true;
							undo.push_back(keystroke);
						}
					}
				}
			}
		}
	}

	// add the key event
	keystroke.m_virtualKey = virtualKey;
	if (isExtended) {
		keystroke.m_virtualKey |= 0x100;
	}
	switch (action) {
	case kPress:
		keystroke.m_press      = true;
		keystroke.m_repeat     = false;
		keys.push_back(keystroke);
		break;

	case kRelease:
		keystroke.m_press      = false;
		keystroke.m_repeat     = false;
		keys.push_back(keystroke);
		break;

	case kRepeat:
		keystroke.m_press      = true;
		keystroke.m_repeat     = true;
		keys.push_back(keystroke);
		break;
	}

	// add key events to restore the modifier state.  apply events in
	// the reverse order that they're stored in undo.
	while (!undo.empty()) {
		keys.push_back(undo.back());
		undo.pop_back();
	}

	// if the key is a modifier key then compute the modifier mask after
	// this key is pressed.
	mask = m_mask;
	if (isModifier && action != kRepeat) {
		// toggle keys modify the state on release.  other keys set
		// the bit on press and clear the bit on release.
		const CModifierInfo& modifier = s_modifier[modifierIndex];
		if (modifier.m_isToggle) {
			if (action == kRelease) {
				mask ^= modifier.m_mask;
			}
		}
		else if (action == kPress) {
			mask |= modifier.m_mask;
		}
		else {
			// can't reset bit until all keys that set it are released.
			// scan those keys to see if any are pressed.
			bool down = false;
			if (virtualKey != (modifier.m_virtualKey & 0xff) &&
				(m_keys[modifier.m_virtualKey & 0xff] & 0x80) != 0) {
				down = true;
			}
			if (modifier.m_virtualKey2 != 0 &&
				virtualKey != (modifier.m_virtualKey2 & 0xff) &&
				(m_keys[modifier.m_virtualKey2 & 0xff] & 0x80) != 0) {
				down = true;
			}
			if (!down)
				mask &= ~modifier.m_mask;
		}
	}

	log((CLOG_DEBUG2 "previous modifiers 0x%04x, final modifiers 0x%04x", m_mask, mask));
	return mask;
}

void
CMSWindowsSecondaryScreen::doKeystrokes(const Keystrokes& keys, SInt32 count)
{
	// do nothing if no keys or no repeats
	if (count < 1 || keys.empty()) {
		return;
	}

	// generate key events
	for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) {
		if (k->m_repeat) {
			// repeat from here up to but not including the next key
			// with m_repeat == false count times.
			Keystrokes::const_iterator start = k;
			for (; count > 0; --count) {
				// send repeating events
				for (k = start; k != keys.end() && k->m_repeat; ++k) {
					sendKeyEvent(k->m_virtualKey, k->m_press);
				}
			}

			// note -- k is now on the first non-repeat key after the
			// repeat keys, exactly where we'd like to continue from.
		}
		else {
			// send event
			sendKeyEvent(k->m_virtualKey, k->m_press);

			// next key
			++k;
		}
	}
}

void
CMSWindowsSecondaryScreen::releaseKeys()
{
	CLock lock(&m_mutex);

	m_screen->syncDesktop();

	// release left/right modifier keys first.  if the platform doesn't
	// support them then they won't be set and the non-side-distinuishing
	// key will retain its state.  if the platform does support them then
	// the non-side-distinguishing will be reset.
	if ((m_keys[VK_LSHIFT] & 0x80) != 0) {
		sendKeyEvent(VK_LSHIFT, false);
		m_keys[VK_SHIFT]    = 0;
		m_keys[VK_LSHIFT]   = 0;
	}
	if ((m_keys[VK_RSHIFT] & 0x80) != 0) {
		sendKeyEvent(VK_RSHIFT, false);
		m_keys[VK_SHIFT]    = 0;
		m_keys[VK_RSHIFT]   = 0;
	}
	if ((m_keys[VK_LCONTROL] & 0x80) != 0) {
		sendKeyEvent(VK_LCONTROL, false);
		m_keys[VK_CONTROL]  = 0;
		m_keys[VK_LCONTROL] = 0;
	}
	if ((m_keys[VK_RCONTROL] & 0x80) != 0) {
		sendKeyEvent(VK_RCONTROL, false);
		m_keys[VK_CONTROL]  = 0;
		m_keys[VK_RCONTROL] = 0;
	}
	if ((m_keys[VK_LMENU] & 0x80) != 0) {
		sendKeyEvent(VK_LMENU, false);
		m_keys[VK_MENU]     = 0;
		m_keys[VK_LMENU]    = 0;
	}
	if ((m_keys[VK_RMENU] & 0x80) != 0) {
		sendKeyEvent(VK_RMENU, false);
		m_keys[VK_MENU]     = 0;
		m_keys[VK_RMENU]    = 0;
	}

	// now check all the other keys
	for (UInt32 i = 0; i < sizeof(m_keys) / sizeof(m_keys[0]); ++i) {
		if ((m_keys[i] & 0x80) != 0) {
			sendKeyEvent(i, false);
			m_keys[i] = 0;
		}
	}
}

void
CMSWindowsSecondaryScreen::toggleKey(UINT virtualKey, KeyModifierMask mask)
{
	// send key events to simulate a press and release
	sendKeyEvent(virtualKey, true);
	sendKeyEvent(virtualKey, false);

	// toggle shadow state
	m_mask                    ^= mask;
	m_keys[virtualKey & 0xff] ^= 0x01;
}

UINT
CMSWindowsSecondaryScreen::virtualKeyToScanCode(UINT& virtualKey) const
{
	// try mapping given virtual key
	UINT code = MapVirtualKey(virtualKey & 0xff, 0);
	if (code != 0) {
		return code;
	}

	// no dice.  if the virtual key distinguishes between left/right
	// then try the one that doesn't distinguish sides.  windows (or
	// keyboard drivers) are inconsistent in their treatment of these
	// virtual keys.  the following behaviors have been observed:
	//
	//  win2k (gateway desktop):
	//      MapVirtualKey(vk, 0):
	//        VK_SHIFT == VK_LSHIFT != VK_RSHIFT
	//        VK_CONTROL == VK_LCONTROL == VK_RCONTROL
	//        VK_MENU == VK_LMENU == VK_RMENU
	//      MapVirtualKey(sc, 3):
	//        VK_LSHIFT and VK_RSHIFT mapped independently
	//        VK_LCONTROL is mapped but not VK_RCONTROL
	//        VK_LMENU is mapped but not VK_RMENU
	//
	//  win me (sony vaio laptop):
	//      MapVirtualKey(vk, 0):
	//        VK_SHIFT mapped;  VK_LSHIFT, VK_RSHIFT not mapped
	//        VK_CONTROL mapped;  VK_LCONTROL, VK_RCONTROL not mapped
	//        VK_MENU mapped;  VK_LMENU, VK_RMENU not mapped
	//      MapVirtualKey(sc, 3):
	//        all scan codes unmapped (function apparently unimplemented)
	switch (virtualKey & 0xff) {
	case VK_LSHIFT:
	case VK_RSHIFT:
		virtualKey = VK_SHIFT;
		return MapVirtualKey(VK_SHIFT, 0);

	case VK_LCONTROL:
	case VK_RCONTROL:
		virtualKey = VK_CONTROL;
		return MapVirtualKey(VK_CONTROL, 0);

	case VK_LMENU:
	case VK_RMENU:
		virtualKey = VK_MENU;
		return MapVirtualKey(VK_MENU, 0);

	default:
		return 0;
	}
}

bool
CMSWindowsSecondaryScreen::isExtendedKey(UINT virtualKey) const
{
	// see if we've already encoded the extended flag
	if ((virtualKey & 0x100) != 0) {
		return true;
	}

	// check known virtual keys
	switch (virtualKey & 0xff) {
	case VK_NUMLOCK:
	case VK_RCONTROL:
	case VK_RMENU:
	case VK_LWIN:
	case VK_RWIN:
	case VK_APPS:
		return true;

	default:
		return false;
	}
}

void
CMSWindowsSecondaryScreen::sendKeyEvent(UINT virtualKey, bool press)
{
	DWORD flags = 0;
	if (isExtendedKey(virtualKey)) {
		flags |= KEYEVENTF_EXTENDEDKEY;
	}
	if (!press) {
		flags |= KEYEVENTF_KEYUP;
	}
	const UINT code = virtualKeyToScanCode(virtualKey);
	keybd_event(static_cast<BYTE>(virtualKey & 0xff),
								static_cast<BYTE>(code), flags, 0);
	log((CLOG_DEBUG1 "send key %d, 0x%04x, %s%s", virtualKey & 0xff, code, ((flags & KEYEVENTF_KEYUP) ? "release" : "press"), ((flags & KEYEVENTF_EXTENDEDKEY) ? " extended" : "")));
}