/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): none yet.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file ghost/intern/GHOST_SystemWin32.cpp
 *  \ingroup GHOST
 *
 * \author	Maarten Gribnau
 */

#ifdef WITH_GHOST_DEBUG
#  include <iostream>
#endif

#include <stdio.h> // [mce] temporary debug, remove soon!

#include "GHOST_SystemWin32.h"
#include "GHOST_EventDragnDrop.h"

#ifndef _WIN32_IE
#  define _WIN32_IE 0x0501 /* shipped before XP, so doesn't impose additional requirements */
#endif

#include <shlobj.h>
#include <tlhelp32.h>
#include <Psapi.h>
#include <windowsx.h>

#include "utfconv.h"

#include "GHOST_DisplayManagerWin32.h"
#include "GHOST_EventButton.h"
#include "GHOST_EventCursor.h"
#include "GHOST_EventKey.h"
#include "GHOST_EventWheel.h"
#include "GHOST_TimerTask.h"
#include "GHOST_TimerManager.h"
#include "GHOST_WindowManager.h"
#include "GHOST_WindowWin32.h"

#ifdef WITH_INPUT_NDOF
#include "GHOST_NDOFManagerWin32.h"
#endif

// Key code values not found in winuser.h
#ifndef VK_MINUS
#define VK_MINUS 0xBD
#endif // VK_MINUS
#ifndef VK_SEMICOLON
#define VK_SEMICOLON 0xBA
#endif // VK_SEMICOLON
#ifndef VK_PERIOD
#define VK_PERIOD 0xBE
#endif // VK_PERIOD
#ifndef VK_COMMA
#define VK_COMMA 0xBC
#endif // VK_COMMA
#ifndef VK_QUOTE
#define VK_QUOTE 0xDE
#endif // VK_QUOTE
#ifndef VK_BACK_QUOTE
#define VK_BACK_QUOTE 0xC0
#endif // VK_BACK_QUOTE
#ifndef VK_SLASH
#define VK_SLASH 0xBF
#endif // VK_SLASH
#ifndef VK_BACK_SLASH
#define VK_BACK_SLASH 0xDC
#endif // VK_BACK_SLASH
#ifndef VK_EQUALS
#define VK_EQUALS 0xBB
#endif // VK_EQUALS
#ifndef VK_OPEN_BRACKET
#define VK_OPEN_BRACKET 0xDB
#endif // VK_OPEN_BRACKET
#ifndef VK_CLOSE_BRACKET
#define VK_CLOSE_BRACKET 0xDD
#endif // VK_CLOSE_BRACKET
#ifndef VK_GR_LESS
#define VK_GR_LESS 0xE2
#endif // VK_GR_LESS

#ifndef VK_MEDIA_NEXT_TRACK
#define VK_MEDIA_NEXT_TRACK 0xB0
#endif // VK_MEDIA_NEXT_TRACK
#ifndef VK_MEDIA_PREV_TRACK
#define VK_MEDIA_PREV_TRACK 0xB1
#endif // VK_MEDIA_PREV_TRACK
#ifndef VK_MEDIA_STOP
#define VK_MEDIA_STOP 0xB2
#endif // VK_MEDIA_STOP
#ifndef VK_MEDIA_PLAY_PAUSE
#define VK_MEDIA_PLAY_PAUSE 0xB3
#endif // VK_MEDIA_PLAY_PAUSE

/* Workaround for some laptop touchpads, some of which seems to
 * have driver issues which makes it so window function receives
 * the message, but PeekMessage doesn't pick those messages for
 * some reason.
 *
 * We send a dummy WM_USER message to force PeekMessage to receive
 * something, making it so blender's window manager sees the new
 * messages coming in.
 */
#define BROKEN_PEEK_TOUCHPAD

static void initRawInput()
{
#ifdef WITH_INPUT_NDOF
#define DEVICE_COUNT 2
#else
#define DEVICE_COUNT 1
#endif

	RAWINPUTDEVICE devices[DEVICE_COUNT];
	memset(devices, 0, DEVICE_COUNT * sizeof(RAWINPUTDEVICE));

	// Initiates WM_INPUT messages from keyboard
	// That way GHOST can retrieve true keys
	devices[0].usUsagePage = 0x01;
	devices[0].usUsage = 0x06; /* http://msdn.microsoft.com/en-us/windows/hardware/gg487473.aspx */

#ifdef WITH_INPUT_NDOF
	// multi-axis mouse (SpaceNavigator, etc.)
	devices[1].usUsagePage = 0x01;
	devices[1].usUsage = 0x08;
#endif

	if (RegisterRawInputDevices(devices, DEVICE_COUNT, sizeof(RAWINPUTDEVICE)))
		;  // yay!
	else
		printf("could not register for RawInput: %d\n", (int)GetLastError());

#undef DEVICE_COUNT
}

GHOST_SystemWin32::GHOST_SystemWin32()
	: m_hasPerformanceCounter(false), m_freq(0), m_start(0)
{
	m_displayManager = new GHOST_DisplayManagerWin32();
	GHOST_ASSERT(m_displayManager, "GHOST_SystemWin32::GHOST_SystemWin32(): m_displayManager==0\n");
	m_displayManager->initialize();

	m_consoleStatus = 1;

	// Check if current keyboard layout uses AltGr and save keylayout ID for
	// specialized handling if keys like VK_OEM_*. I.e. french keylayout
	// generates VK_OEM_8 for their exclamation key (key left of right shift)
	this->handleKeyboardChange();
	// Require COM for GHOST_DropTargetWin32 created in GHOST_WindowWin32.
	OleInitialize(0);

#ifdef WITH_INPUT_NDOF
	m_ndofManager = new GHOST_NDOFManagerWin32(*this);
#endif
}

GHOST_SystemWin32::~GHOST_SystemWin32()
{
	// Shutdown COM
	OleUninitialize();
	toggleConsole(1);
}


GHOST_TUns64 GHOST_SystemWin32::getMilliSeconds() const
{
	// Hardware does not support high resolution timers. We will use GetTickCount instead then.
	if (!m_hasPerformanceCounter) {
		return ::GetTickCount();
	}

	// Retrieve current count
	__int64 count = 0;
	::QueryPerformanceCounter((LARGE_INTEGER *)&count);

	// Calculate the time passed since system initialization.
	__int64 delta = 1000 * (count - m_start);

	GHOST_TUns64 t = (GHOST_TUns64)(delta / m_freq);
	return t; 
}


GHOST_TUns8 GHOST_SystemWin32::getNumDisplays() const
{
	GHOST_ASSERT(m_displayManager, "GHOST_SystemWin32::getNumDisplays(): m_displayManager==0\n");
	GHOST_TUns8 numDisplays;
	m_displayManager->getNumDisplays(numDisplays);
	return numDisplays;
}


void GHOST_SystemWin32::getMainDisplayDimensions(GHOST_TUns32 &width, GHOST_TUns32 &height) const
{
	width = ::GetSystemMetrics(SM_CXSCREEN);
	height = ::GetSystemMetrics(SM_CYSCREEN);
}

void GHOST_SystemWin32::getAllDisplayDimensions(GHOST_TUns32 &width, GHOST_TUns32 &height) const
{
	width = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
	height = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
}

GHOST_IWindow *GHOST_SystemWin32::createWindow(
        const STR_String &title,
        GHOST_TInt32 left, GHOST_TInt32 top,
        GHOST_TUns32 width, GHOST_TUns32 height,
        GHOST_TWindowState state, GHOST_TDrawingContextType type,
        bool wantStereoVisual,
        const bool exclusive,
        const GHOST_TUns16 wantNumOfAASamples,
        const GHOST_TEmbedderWindowID parentWindow)
{
	GHOST_Window *window =
		new GHOST_WindowWin32(
		        this,
		        title,
		        left,
		        top,
		        width,
		        height,
		        state,
		        type,
		        wantStereoVisual,
		        wantNumOfAASamples,
		        parentWindow);

	if (window->getValid()) {
		// Store the pointer to the window
		m_windowManager->addWindow(window);
		m_windowManager->setActiveWindow(window);
	}
	else {
		GHOST_PRINT("GHOST_SystemWin32::createWindow(): window invalid\n");
		delete window;
		window = 0;
	}

	return window;
}


bool GHOST_SystemWin32::processEvents(bool waitForEvent)
{
	MSG msg;
	bool anyProcessed = false;

	do {
		GHOST_TimerManager *timerMgr = getTimerManager();

		if (waitForEvent && !::PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE)) {
#if 1
			::Sleep(1);
#else
			GHOST_TUns64 next = timerMgr->nextFireTime();
			GHOST_TInt64 maxSleep = next - getMilliSeconds();
			
			if (next == GHOST_kFireTimeNever) {
				::WaitMessage();
			}
			else if (maxSleep >= 0.0) {
				::SetTimer(NULL, 0, maxSleep, NULL);
				::WaitMessage();
				::KillTimer(NULL, 0);
			}
#endif
		}

		if (timerMgr->fireTimers(getMilliSeconds())) {
			anyProcessed = true;
		}

		// Process all the events waiting for us
		while (::PeekMessageW(&msg, 0, 0, 0, PM_REMOVE) != 0) {
			// TranslateMessage doesn't alter the message, and doesn't change our raw keyboard data.
			// Needed for MapVirtualKey or if we ever need to get chars from wm_ime_char or similar.
			::TranslateMessage(&msg);
			::DispatchMessageW(&msg);
			anyProcessed = true;
		}
	} while (waitForEvent && !anyProcessed);

	return anyProcessed;
}


GHOST_TSuccess GHOST_SystemWin32::getCursorPosition(GHOST_TInt32 &x, GHOST_TInt32 &y) const
{
	POINT point;
	if (::GetCursorPos(&point)) {
		x = point.x;
		y = point.y;
		return GHOST_kSuccess;
	}
	return GHOST_kFailure;
}


GHOST_TSuccess GHOST_SystemWin32::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
{
	if (!::GetActiveWindow())
		return GHOST_kFailure;
	return ::SetCursorPos(x, y) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
}


GHOST_TSuccess GHOST_SystemWin32::getModifierKeys(GHOST_ModifierKeys &keys) const
{
	bool down = HIBYTE(::GetKeyState(VK_LSHIFT)) != 0;
	keys.set(GHOST_kModifierKeyLeftShift, down);
	down = HIBYTE(::GetKeyState(VK_RSHIFT)) != 0;
	keys.set(GHOST_kModifierKeyRightShift, down);
	
	down = HIBYTE(::GetKeyState(VK_LMENU)) != 0;
	keys.set(GHOST_kModifierKeyLeftAlt, down);
	down = HIBYTE(::GetKeyState(VK_RMENU)) != 0;
	keys.set(GHOST_kModifierKeyRightAlt, down);
	
	down = HIBYTE(::GetKeyState(VK_LCONTROL)) != 0;
	keys.set(GHOST_kModifierKeyLeftControl, down);
	down = HIBYTE(::GetKeyState(VK_RCONTROL)) != 0;
	keys.set(GHOST_kModifierKeyRightControl, down);
	
	bool lwindown = HIBYTE(::GetKeyState(VK_LWIN)) != 0;
	bool rwindown = HIBYTE(::GetKeyState(VK_RWIN)) != 0;
	if (lwindown || rwindown)
		keys.set(GHOST_kModifierKeyOS, true);
	else
		keys.set(GHOST_kModifierKeyOS, false);
	return GHOST_kSuccess;
}


GHOST_TSuccess GHOST_SystemWin32::getButtons(GHOST_Buttons &buttons) const
{
	/* Check for swapped buttons (left-handed mouse buttons)
	 * GetAsyncKeyState() will give back the state of the physical mouse buttons.
	 */
	bool swapped = ::GetSystemMetrics(SM_SWAPBUTTON) == TRUE;

	bool down = HIBYTE(::GetKeyState(VK_LBUTTON)) != 0;
	buttons.set(swapped ? GHOST_kButtonMaskRight : GHOST_kButtonMaskLeft, down);

	down = HIBYTE(::GetKeyState(VK_MBUTTON)) != 0;
	buttons.set(GHOST_kButtonMaskMiddle, down);

	down = HIBYTE(::GetKeyState(VK_RBUTTON)) != 0;
	buttons.set(swapped ? GHOST_kButtonMaskLeft : GHOST_kButtonMaskRight, down);
	return GHOST_kSuccess;
}


GHOST_TSuccess GHOST_SystemWin32::init()
{
	GHOST_TSuccess success = GHOST_System::init();
	
	/* Disable scaling on high DPI displays on Vista */
	HMODULE
	    user32 = ::LoadLibraryA("user32.dll");
	typedef BOOL (WINAPI * LPFNSETPROCESSDPIAWARE)();
	LPFNSETPROCESSDPIAWARE SetProcessDPIAware =
	    (LPFNSETPROCESSDPIAWARE)GetProcAddress(user32, "SetProcessDPIAware");
	if (SetProcessDPIAware)
		SetProcessDPIAware();
	FreeLibrary(user32);
	initRawInput();

	// Determine whether this system has a high frequency performance counter. */
	m_hasPerformanceCounter = ::QueryPerformanceFrequency((LARGE_INTEGER *)&m_freq) == TRUE;
	if (m_hasPerformanceCounter) {
		GHOST_PRINT("GHOST_SystemWin32::init: High Frequency Performance Timer available\n");
		::QueryPerformanceCounter((LARGE_INTEGER *)&m_start);
	}
	else {
		GHOST_PRINT("GHOST_SystemWin32::init: High Frequency Performance Timer not available\n");
	}

	if (success) {
		WNDCLASSW wc = {0};
		wc.style = CS_HREDRAW | CS_VREDRAW;
		wc.lpfnWndProc = s_wndProc;
		wc.cbClsExtra = 0;
		wc.cbWndExtra = 0;
		wc.hInstance = ::GetModuleHandle(0);
		wc.hIcon = ::LoadIcon(wc.hInstance, "APPICON");
		
		if (!wc.hIcon) {
			::LoadIcon(NULL, IDI_APPLICATION);
		}
		wc.hCursor = ::LoadCursor(0, IDC_ARROW);
		wc.hbrBackground = 0;
		wc.lpszMenuName = 0;
		wc.lpszClassName = L"GHOST_WindowClass";

		// Use RegisterClassEx for setting small icon
		if (::RegisterClassW(&wc) == 0) {
			success = GHOST_kFailure;
		}
	}
	
	return success;
}


GHOST_TSuccess GHOST_SystemWin32::exit()
{
	return GHOST_System::exit();
}

GHOST_TKey GHOST_SystemWin32::hardKey(GHOST_IWindow *window, RAWINPUT const &raw, int *keyDown, char *vk)
{
	GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
	GHOST_TKey key = GHOST_kKeyUnknown;
	GHOST_ModifierKeys modifiers;
	system->retrieveModifierKeys(modifiers);

	// RI_KEY_BREAK doesn't work for sticky keys release, so we also
	// check for the up message
	unsigned int msg = raw.data.keyboard.Message;
	*keyDown = !(raw.data.keyboard.Flags & RI_KEY_BREAK) && msg != WM_KEYUP && msg != WM_SYSKEYUP;

	key = this->convertKey(window, raw.data.keyboard.VKey, raw.data.keyboard.MakeCode, (raw.data.keyboard.Flags & (RI_KEY_E1 | RI_KEY_E0)));
	
	// extra handling of modifier keys: don't send repeats out from GHOST
	if (key >= GHOST_kKeyLeftShift && key <= GHOST_kKeyRightAlt) {
		bool changed = false;
		GHOST_TModifierKeyMask modifier;
		switch (key) {
			case GHOST_kKeyLeftShift:
			{
				changed = (modifiers.get(GHOST_kModifierKeyLeftShift) != (bool)*keyDown);
				modifier = GHOST_kModifierKeyLeftShift;
				break;
			}
			case GHOST_kKeyRightShift:
			{
				changed = (modifiers.get(GHOST_kModifierKeyRightShift) != (bool)*keyDown);
				modifier = GHOST_kModifierKeyRightShift;
				break;
			}
			case GHOST_kKeyLeftControl:
			{
				changed = (modifiers.get(GHOST_kModifierKeyLeftControl) != (bool)*keyDown);
				modifier = GHOST_kModifierKeyLeftControl;
				break;
			}
			case GHOST_kKeyRightControl:
			{
				changed = (modifiers.get(GHOST_kModifierKeyRightControl) != (bool)*keyDown);
				modifier = GHOST_kModifierKeyRightControl;
				break;
			}
			case GHOST_kKeyLeftAlt:
			{
				changed = (modifiers.get(GHOST_kModifierKeyLeftAlt) != (bool)*keyDown);
				modifier = GHOST_kModifierKeyLeftAlt;
				break;
			}
			case GHOST_kKeyRightAlt:
			{
				changed = (modifiers.get(GHOST_kModifierKeyRightAlt) != (bool)*keyDown);
				modifier = GHOST_kModifierKeyRightAlt;
				break;
			}
			default:
				break;
		}
		
		if (changed) {
			modifiers.set(modifier, (bool)*keyDown);
			system->storeModifierKeys(modifiers);
		}
		else {
			key = GHOST_kKeyUnknown;
		}
	}
	

	if (vk) *vk = raw.data.keyboard.VKey;

	return key;
}

//! note: this function can be extended to include other exotic cases as they arise.
// This function was added in response to bug [#25715]
GHOST_TKey GHOST_SystemWin32::processSpecialKey(GHOST_IWindow *window, short vKey, short scanCode) const
{
	GHOST_TKey key = GHOST_kKeyUnknown;
	switch (PRIMARYLANGID(m_langId)) {
		case LANG_FRENCH:
			if (vKey == VK_OEM_8) key = GHOST_kKeyF13;  // oem key; used purely for shortcuts .
			break;
	}

	return key;
}

GHOST_TKey GHOST_SystemWin32::convertKey(GHOST_IWindow *window, short vKey, short scanCode, short extend) const
{
	GHOST_TKey key;

	if ((vKey >= '0') && (vKey <= '9')) {
		// VK_0 thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39)
		key = (GHOST_TKey)(vKey - '0' + GHOST_kKey0);
	}
	else if ((vKey >= 'A') && (vKey <= 'Z')) {
		// VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A)
		key = (GHOST_TKey)(vKey - 'A' + GHOST_kKeyA);
	}
	else if ((vKey >= VK_F1) && (vKey <= VK_F24)) {
		key = (GHOST_TKey)(vKey - VK_F1 + GHOST_kKeyF1);
	}
	else {
		switch (vKey) {
			case VK_RETURN:
				key = (extend) ? GHOST_kKeyNumpadEnter : GHOST_kKeyEnter; break;
			
			case VK_BACK:     key = GHOST_kKeyBackSpace;        break;
			case VK_TAB:      key = GHOST_kKeyTab;              break;
			case VK_ESCAPE:   key = GHOST_kKeyEsc;              break;
			case VK_SPACE:    key = GHOST_kKeySpace;            break;

			case VK_INSERT:
			case VK_NUMPAD0:
				key = (extend) ? GHOST_kKeyInsert : GHOST_kKeyNumpad0;  break;
			case VK_END:
			case VK_NUMPAD1:
				key = (extend) ? GHOST_kKeyEnd : GHOST_kKeyNumpad1; break;
			case VK_DOWN:
			case VK_NUMPAD2:
				key = (extend) ? GHOST_kKeyDownArrow : GHOST_kKeyNumpad2;   break;
			case VK_NEXT:
			case VK_NUMPAD3:
				key = (extend) ?  GHOST_kKeyDownPage : GHOST_kKeyNumpad3;   break;
			case VK_LEFT:
			case VK_NUMPAD4:
				key = (extend) ? GHOST_kKeyLeftArrow : GHOST_kKeyNumpad4;   break;
			case VK_CLEAR:
			case VK_NUMPAD5:
				key = (extend) ? GHOST_kKeyUnknown : GHOST_kKeyNumpad5;  break;
			case VK_RIGHT:
			case VK_NUMPAD6:
				key = (extend) ? GHOST_kKeyRightArrow : GHOST_kKeyNumpad6;  break;
			case VK_HOME:
			case VK_NUMPAD7:
				key = (extend) ? GHOST_kKeyHome : GHOST_kKeyNumpad7;    break;
			case VK_UP:
			case VK_NUMPAD8:
				key = (extend) ? GHOST_kKeyUpArrow : GHOST_kKeyNumpad8; break;
			case VK_PRIOR:
			case VK_NUMPAD9:
				key = (extend) ? GHOST_kKeyUpPage : GHOST_kKeyNumpad9;  break;
			case VK_DECIMAL:
			case VK_DELETE:
				key = (extend) ? GHOST_kKeyDelete : GHOST_kKeyNumpadPeriod; break;

			case VK_SNAPSHOT: key = GHOST_kKeyPrintScreen;      break;
			case VK_PAUSE:    key = GHOST_kKeyPause;            break;
			case VK_MULTIPLY: key = GHOST_kKeyNumpadAsterisk;   break;
			case VK_SUBTRACT: key = GHOST_kKeyNumpadMinus;      break;
			case VK_DIVIDE:   key = GHOST_kKeyNumpadSlash;      break;
			case VK_ADD:      key = GHOST_kKeyNumpadPlus;       break;

			case VK_SEMICOLON:      key = GHOST_kKeySemicolon;      break;
			case VK_EQUALS:         key = GHOST_kKeyEqual;          break;
			case VK_COMMA:          key = GHOST_kKeyComma;          break;
			case VK_MINUS:          key = GHOST_kKeyMinus;          break;
			case VK_PERIOD:         key = GHOST_kKeyPeriod;         break;
			case VK_SLASH:          key = GHOST_kKeySlash;          break;
			case VK_BACK_QUOTE:     key = GHOST_kKeyAccentGrave;    break;
			case VK_OPEN_BRACKET:   key = GHOST_kKeyLeftBracket;    break;
			case VK_BACK_SLASH:     key = GHOST_kKeyBackslash;      break;
			case VK_CLOSE_BRACKET:  key = GHOST_kKeyRightBracket;   break;
			case VK_QUOTE:          key = GHOST_kKeyQuote;          break;
			case VK_GR_LESS:        key = GHOST_kKeyGrLess;         break;

			case VK_SHIFT:
				/* Check single shift presses */
				if (scanCode == 0x36) {
					key = GHOST_kKeyRightShift;
				}
				else if (scanCode == 0x2a) {
					key = GHOST_kKeyLeftShift;
				}
				else {
					/* Must be a combination SHIFT (Left or Right) + a Key
					 * Ignore this as the next message will contain
					 * the desired "Key" */
					key = GHOST_kKeyUnknown;
				}
				break;
			case VK_CONTROL:
				key = (extend) ? GHOST_kKeyRightControl : GHOST_kKeyLeftControl;
				break;
			case VK_MENU:
				key = (extend) ? GHOST_kKeyRightAlt : GHOST_kKeyLeftAlt;
				break;
			case VK_LWIN:
			case VK_RWIN:
				key = GHOST_kKeyOS;
				break;
			case VK_NUMLOCK: key = GHOST_kKeyNumLock; break;
			case VK_SCROLL: key = GHOST_kKeyScrollLock; break;
			case VK_CAPITAL: key = GHOST_kKeyCapsLock; break;
			case VK_OEM_8:
				key = ((GHOST_SystemWin32 *)getSystem())->processSpecialKey(window, vKey, scanCode);
				break;
			case VK_MEDIA_PLAY_PAUSE: key = GHOST_kKeyMediaPlay; break;
			case VK_MEDIA_STOP: key = GHOST_kKeyMediaStop; break;
			case VK_MEDIA_PREV_TRACK: key = GHOST_kKeyMediaFirst; break;
			case VK_MEDIA_NEXT_TRACK: key = GHOST_kKeyMediaLast; break;
			default:
				key = GHOST_kKeyUnknown;
				break;
		}
	}
	
	return key;
}

GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(
        GHOST_TEventType type,
        GHOST_IWindow *window,
        GHOST_TButtonMask mask)
{
	return new GHOST_EventButton(getSystem()->getMilliSeconds(), type, window, mask);
}


GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_TEventType type, GHOST_IWindow *Iwindow)
{
	GHOST_TInt32 x_screen, y_screen;
	GHOST_SystemWin32 *system = (GHOST_SystemWin32 *) getSystem();
	GHOST_WindowWin32 *window = (GHOST_WindowWin32 *) Iwindow;
	
	system->getCursorPosition(x_screen, y_screen);

	/* TODO: CHECK IF THIS IS A TABLET EVENT */
	bool is_tablet = false;

	if (is_tablet == false && window->getCursorGrabModeIsWarp()) {
		GHOST_TInt32 x_new = x_screen;
		GHOST_TInt32 y_new = y_screen;
		GHOST_TInt32 x_accum, y_accum;
		GHOST_Rect bounds;

		/* fallback to window bounds */
		if (window->getCursorGrabBounds(bounds) == GHOST_kFailure) {
			window->getClientBounds(bounds);
		}

		/* could also clamp to screen bounds
		 * wrap with a window outside the view will fail atm  */

		bounds.wrapPoint(x_new, y_new, 2); /* offset of one incase blender is at screen bounds */

		window->getCursorGrabAccum(x_accum, y_accum);
		if (x_new != x_screen || y_new != y_screen) {
			/* when wrapping we don't need to add an event because the
			 * setCursorPosition call will cause a new event after */
			system->setCursorPosition(x_new, y_new); /* wrap */
			window->setCursorGrabAccum(x_accum + (x_screen - x_new), y_accum + (y_screen - y_new));
		}
		else {
			return new GHOST_EventCursor(system->getMilliSeconds(),
			                             GHOST_kEventCursorMove,
			                             window,
			                             x_screen + x_accum,
			                             y_screen + y_accum
			                             );
		}

	}
	else {
		return new GHOST_EventCursor(system->getMilliSeconds(),
		                             GHOST_kEventCursorMove,
		                             window,
		                             x_screen,
		                             y_screen
		                             );
	}
	return NULL;
}


GHOST_EventWheel *GHOST_SystemWin32::processWheelEvent(GHOST_IWindow *window, WPARAM wParam, LPARAM lParam)
{
	// short fwKeys = LOWORD(wParam);			// key flags
	int zDelta = (short) HIWORD(wParam);    // wheel rotation
	
	// zDelta /= WHEEL_DELTA;
	// temporary fix below: microsoft now has added more precision, making the above division not work
	if (zDelta <= 0) zDelta = -1; else zDelta = 1;
	
	// short xPos = (short) LOWORD(lParam);	// horizontal position of pointer
	// short yPos = (short) HIWORD(lParam);	// vertical position of pointer
	return new GHOST_EventWheel(getSystem()->getMilliSeconds(), window, zDelta);
}


GHOST_EventKey *GHOST_SystemWin32::processKeyEvent(GHOST_IWindow *window, RAWINPUT const &raw)
{
	int keyDown = 0;
	char vk;
	GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
	GHOST_TKey key = system->hardKey(window, raw, &keyDown, &vk);
	GHOST_EventKey *event;

	if (key != GHOST_kKeyUnknown) {
		char utf8_char[6] = {0};
		char ascii = 0;

		wchar_t utf16[3] = {0};
		BYTE state[256] = {0};
		int r;
		GetKeyboardState((PBYTE)state);

		// don't call ToUnicodeEx on dead keys as it clears the buffer and so won't allow diacritical composition.
		if (MapVirtualKeyW(vk, 2) != 0) {
			// todo: ToUnicodeEx can respond with up to 4 utf16 chars (only 2 here). Could be up to 24 utf8 bytes.
			if ((r = ToUnicodeEx(vk, raw.data.keyboard.MakeCode, state, utf16, 2, 0, system->m_keylayout))) {
				if ((r > 0 && r < 3)) {
					utf16[r] = 0;
					conv_utf_16_to_8(utf16, utf8_char, 6);
				}
				else if (r == -1) {
					utf8_char[0] = '\0';
				}
			}
		}

		if (!keyDown) {
			utf8_char[0] = '\0';
			ascii = '\0';
		}
		else {
			ascii = utf8_char[0] & 0x80 ? '?' : utf8_char[0];
		}

		event = new GHOST_EventKey(system->getMilliSeconds(), keyDown ? GHOST_kEventKeyDown : GHOST_kEventKeyUp, window, key, ascii, utf8_char);
		
#ifdef GHOST_DEBUG
		std::cout << ascii << std::endl;
#endif
	}
	else {
		event = 0;
	}
	return event;
}


GHOST_Event *GHOST_SystemWin32::processWindowEvent(GHOST_TEventType type, GHOST_IWindow *window)
{
	GHOST_System *system = (GHOST_System *)getSystem();

	if (type == GHOST_kEventWindowActivate) {
		system->getWindowManager()->setActiveWindow(window);
		((GHOST_WindowWin32 *)window)->bringTabletContextToFront();
	}

	return new GHOST_Event(system->getMilliSeconds(), type, window);
}

GHOST_TSuccess GHOST_SystemWin32::pushDragDropEvent(
        GHOST_TEventType eventType,
        GHOST_TDragnDropTypes draggedObjectType,
        GHOST_IWindow *window,
        int mouseX, int mouseY,
        void *data)
{
	GHOST_SystemWin32 *system = ((GHOST_SystemWin32 *)getSystem());
	return system->pushEvent(new GHOST_EventDragnDrop(system->getMilliSeconds(),
	                                                  eventType,
	                                                  draggedObjectType,
	                                                  window, mouseX, mouseY, data)
	                         );
}

void GHOST_SystemWin32::processMinMaxInfo(MINMAXINFO *minmax)
{
	minmax->ptMinTrackSize.x = 320;
	minmax->ptMinTrackSize.y = 240;
}

#ifdef WITH_INPUT_NDOF
bool GHOST_SystemWin32::processNDOF(RAWINPUT const &raw)
{
	bool eventSent = false;
	GHOST_TUns64 now = getMilliSeconds();

	static bool firstEvent = true;
	if (firstEvent) { // determine exactly which device is plugged in
		RID_DEVICE_INFO info;
		unsigned infoSize = sizeof(RID_DEVICE_INFO);
		info.cbSize = infoSize;

		GetRawInputDeviceInfo(raw.header.hDevice, RIDI_DEVICEINFO, &info, &infoSize);
		if (info.dwType == RIM_TYPEHID)
			m_ndofManager->setDevice(info.hid.dwVendorId, info.hid.dwProductId);
		else
			puts("<!> not a HID device... mouse/kb perhaps?");

		firstEvent = false;
	}

	// The NDOF manager sends button changes immediately, and *pretends* to
	// send motion. Mark as 'sent' so motion will always get dispatched.
	eventSent = true;

#if defined(_MSC_VER) || defined(FREE_WINDOWS64)
	// using Microsoft compiler & header files
	// they invented the RawInput API, so this version is (probably) correct.
	// MinGW64 also works fine with this
	BYTE const *data = raw.data.hid.bRawData;
	// struct RAWHID {
	// DWORD dwSizeHid;
	// DWORD dwCount;
	// BYTE  bRawData[1];
	// };
#else
	// MinGW's definition (below) doesn't agree, so we need a slight
	// workaround until it's fixed
	BYTE const *data = &raw.data.hid.bRawData;
	// struct RAWHID {
	// DWORD dwSizeHid;
	// DWORD dwCount;
	// BYTE bRawData; // <== isn't this s'posed to be a BYTE*?
	// };
#endif

	BYTE packetType = data[0];
	switch (packetType) {
		case 1: // translation
		{
			const short *axis = (short *)(data + 1);
			// massage into blender view coords (same goes for rotation)
			const short t[3] = {axis[0], -axis[2], axis[1]};
			m_ndofManager->updateTranslation(t, now);

			if (raw.data.hid.dwSizeHid == 13) {
				// this report also includes rotation
				const short r[3] = {-axis[3], axis[5], -axis[4]};
				m_ndofManager->updateRotation(r, now);

				// I've never gotten one of these, has anyone else?
				puts("ndof: combined T + R");
			}
			break;
		}
		case 2: // rotation
		{
			const short *axis = (short *)(data + 1);
			const short r[3] = {-axis[0], axis[2], -axis[1]};
			m_ndofManager->updateRotation(r, now);
			break;
		}
		case 3: // buttons
		{
			int button_bits;
			memcpy(&button_bits, data + 1, sizeof(button_bits));
			m_ndofManager->updateButtons(button_bits, now);
			break;
		}
	}
	return eventSent;
}
#endif // WITH_INPUT_NDOF

LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	GHOST_Event *event = 0;
	bool eventHandled = false;

	LRESULT lResult = 0;
	GHOST_SystemWin32 *system = ((GHOST_SystemWin32 *)getSystem());
	GHOST_ASSERT(system, "GHOST_SystemWin32::s_wndProc(): system not initialized");

	if (hwnd) {
		GHOST_WindowWin32 *window = (GHOST_WindowWin32 *)::GetWindowLongPtr(hwnd, GWLP_USERDATA);
		if (window) {
			switch (msg) {
				// we need to check if new key layout has AltGr
				case WM_INPUTLANGCHANGE:
					system->handleKeyboardChange();
					break;
				////////////////////////////////////////////////////////////////////////
				// Keyboard events, processed
				////////////////////////////////////////////////////////////////////////
				case WM_INPUT:
				{
					// check WM_INPUT from input sink when ghost window is not in the foreground
					if (wParam == RIM_INPUTSINK) {
						if (GetFocus() != hwnd) // WM_INPUT message not for this window
							return 0;
					} //else wParam == RIM_INPUT

					RAWINPUT raw;
					RAWINPUT *raw_ptr = &raw;
					UINT rawSize = sizeof(RAWINPUT);

					GetRawInputData((HRAWINPUT)lParam, RID_INPUT, raw_ptr, &rawSize, sizeof(RAWINPUTHEADER));

					switch (raw.header.dwType) {
						case RIM_TYPEKEYBOARD:
							event = processKeyEvent(window, raw);
							if (!event) {
								GHOST_PRINT("GHOST_SystemWin32::wndProc: key event ");
								GHOST_PRINT(msg);
								GHOST_PRINT(" key ignored\n");
							}
							break;
#ifdef WITH_INPUT_NDOF
						case RIM_TYPEHID:
							if (system->processNDOF(raw))
								eventHandled = true;
							break;
#endif
					}
					break;
				}
				////////////////////////////////////////////////////////////////////////
				// Keyboard events, ignored
				////////////////////////////////////////////////////////////////////////
				case WM_KEYDOWN:
				case WM_SYSKEYDOWN:
				case WM_KEYUP:
				case WM_SYSKEYUP:
				/* These functions were replaced by WM_INPUT*/
				case WM_CHAR:
				/* The WM_CHAR message is posted to the window with the keyboard focus when
				 * a WM_KEYDOWN message is translated by the TranslateMessage function. WM_CHAR
				 * contains the character code of the key that was pressed.
				 */
				case WM_DEADCHAR:
					/* The WM_DEADCHAR message is posted to the window with the keyboard focus when a
					 * WM_KEYUP message is translated by the TranslateMessage function. WM_DEADCHAR 
					 * specifies a character code generated by a dead key. A dead key is a key that 
					 * generates a character, such as the umlaut (double-dot), that is combined with 
					 * another character to form a composite character. For example, the umlaut-O 
					 * character (Ö) is generated by typing the dead key for the umlaut character, and
					 * then typing the O key.
					 */
					break;
				case WM_SYSDEADCHAR:
				/* The WM_SYSDEADCHAR message is sent to the window with the keyboard focus when
				 * a WM_SYSKEYDOWN message is translated by the TranslateMessage function.
				 * WM_SYSDEADCHAR specifies the character code of a system dead key - that is,
				 * a dead key that is pressed while holding down the alt key.
				 */
				case WM_SYSCHAR:
					/* The WM_SYSCHAR message is sent to the window with the keyboard focus when 
					 * a WM_SYSCHAR message is translated by the TranslateMessage function. 
					 * WM_SYSCHAR specifies the character code of a dead key - that is, 
					 * a dead key that is pressed while holding down the alt key.
					 * To prevent the sound, DefWindowProc must be avoided by return
					 */
					break;
				case WM_SYSCOMMAND:
					/* The WM_SYSCHAR message is sent to the window when system commands such as 
					 * maximize, minimize  or close the window are triggered. Also it is sent when ALT 
					 * button is press for menu. To prevent this we must return preventing DefWindowProc.
					 */
					if (wParam == SC_KEYMENU) {
						eventHandled = true;
					}
					break;
				////////////////////////////////////////////////////////////////////////
				// Tablet events, processed
				////////////////////////////////////////////////////////////////////////
				case WT_PACKET:
					((GHOST_WindowWin32 *)window)->processWin32TabletEvent(wParam, lParam);
					break;
				case WT_CSRCHANGE:
				case WT_PROXIMITY:
					((GHOST_WindowWin32 *)window)->processWin32TabletInitEvent();
					break;
				////////////////////////////////////////////////////////////////////////
				// Mouse events, processed
				////////////////////////////////////////////////////////////////////////
				case WM_LBUTTONDOWN:
					window->registerMouseClickEvent(0);
					event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskLeft);
					break;
				case WM_MBUTTONDOWN:
					window->registerMouseClickEvent(0);
					event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskMiddle);
					break;
				case WM_RBUTTONDOWN:
					window->registerMouseClickEvent(0);
					event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskRight);
					break;
				case WM_XBUTTONDOWN:
					window->registerMouseClickEvent(0);
					if ((short) HIWORD(wParam) == XBUTTON1) {
						event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskButton4);
					}
					else if ((short) HIWORD(wParam) == XBUTTON2) {
						event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskButton5);
					}
					break;
				case WM_LBUTTONUP:
					window->registerMouseClickEvent(1);
					event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskLeft);
					break;
				case WM_MBUTTONUP:
					window->registerMouseClickEvent(1);
					event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskMiddle);
					break;
				case WM_RBUTTONUP:
					window->registerMouseClickEvent(1);
					event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskRight);
					break;
				case WM_XBUTTONUP:
					window->registerMouseClickEvent(1);
					if ((short) HIWORD(wParam) == XBUTTON1) {
						event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskButton4);
					}
					else if ((short) HIWORD(wParam) == XBUTTON2) {
						event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskButton5);
					}
					break;
				case WM_MOUSEMOVE:
					event = processCursorEvent(GHOST_kEventCursorMove, window);
					break;
				case WM_MOUSEWHEEL:
				{
					/* The WM_MOUSEWHEEL message is sent to the focus window 
					 * when the mouse wheel is rotated. The DefWindowProc 
					 * function propagates the message to the window's parent.
					 * There should be no internal forwarding of the message, 
					 * since DefWindowProc propagates it up the parent chain 
					 * until it finds a window that processes it.
					 */

					/* Get the winow under the mouse and send event to it's queue. */
					POINT mouse_pos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
					HWND mouse_hwnd = WindowFromPoint(mouse_pos);
					GHOST_WindowWin32 *mouse_window = (GHOST_WindowWin32 *)::GetWindowLongPtr(mouse_hwnd, GWLP_USERDATA);
					if (mouse_window != NULL) {
						event = processWheelEvent(mouse_window, wParam, lParam);
					}
					else {
						/* If it happened so window under the mouse is not found (which i'm not
						 * really sure might happen), then we add event to the focused window
						 * in order to avoid some possible negative side effects.
						 *                                                    - sergey -
						 */
						event = processWheelEvent(window, wParam, lParam);
					}

#ifdef BROKEN_PEEK_TOUCHPAD
					PostMessage(hwnd, WM_USER, 0, 0);
#endif
					break;
				}
				case WM_SETCURSOR:
					/* The WM_SETCURSOR message is sent to a window if the mouse causes the cursor
					 * to move within a window and mouse input is not captured.
					 * This means we have to set the cursor shape every time the mouse moves!
					 * The DefWindowProc function uses this message to set the cursor to an 
					 * arrow if it is not in the client area.
					 */
					if (LOWORD(lParam) == HTCLIENT) {
						// Load the current cursor
						window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
						// Bypass call to DefWindowProc
						return 0;
					} 
					else {
						// Outside of client area show standard cursor
						window->loadCursor(true, GHOST_kStandardCursorDefault);
					}
					break;

				////////////////////////////////////////////////////////////////////////
				// Mouse events, ignored
				////////////////////////////////////////////////////////////////////////
				case WM_NCMOUSEMOVE:
				/* The WM_NCMOUSEMOVE message is posted to a window when the cursor is moved
				 * within the nonclient area of the window. This message is posted to the window
				 * that contains the cursor. If a window has captured the mouse, this message is not posted.
				 */
				case WM_NCHITTEST:
					/* The WM_NCHITTEST message is sent to a window when the cursor moves, or 
					 * when a mouse button is pressed or released. If the mouse is not captured, 
					 * the message is sent to the window beneath the cursor. Otherwise, the message 
					 * is sent to the window that has captured the mouse. 
					 */
					break;

				////////////////////////////////////////////////////////////////////////
				// Window events, processed
				////////////////////////////////////////////////////////////////////////
				case WM_CLOSE:
					/* The WM_CLOSE message is sent as a signal that a window or an application should terminate. */
					event = processWindowEvent(GHOST_kEventWindowClose, window);
					break;
				case WM_ACTIVATE:
					/* The WM_ACTIVATE message is sent to both the window being activated and the window being 
					 * deactivated. If the windows use the same input queue, the message is sent synchronously, 
					 * first to the window procedure of the top-level window being deactivated, then to the window
					 * procedure of the top-level window being activated. If the windows use different input queues,
					 * the message is sent asynchronously, so the window is activated immediately. 
					 */
				{
					GHOST_ModifierKeys modifiers;
					modifiers.clear();
					system->storeModifierKeys(modifiers);
					event = processWindowEvent(LOWORD(wParam) ? GHOST_kEventWindowActivate : GHOST_kEventWindowDeactivate, window);
					/* WARNING: Let DefWindowProc handle WM_ACTIVATE, otherwise WM_MOUSEWHEEL
					 * will not be dispatched to OUR active window if we minimize one of OUR windows. */
					if (LOWORD(wParam) == WA_INACTIVE)
						window->lostMouseCapture();

					lResult = ::DefWindowProc(hwnd, msg, wParam, lParam);
					break;
				}
				case WM_ENTERSIZEMOVE:
					/* The WM_ENTERSIZEMOVE message is sent one time to a window after it enters the moving 
					 * or sizing modal loop. The window enters the moving or sizing modal loop when the user 
					 * clicks the window's title bar or sizing border, or when the window passes the 
					 * WM_SYSCOMMAND message to the DefWindowProc function and the wParam parameter of the 
					 * message specifies the SC_MOVE or SC_SIZE value. The operation is complete when 
					 * DefWindowProc returns. 
					 */
					window->m_inLiveResize = 1;
					break;
				case WM_EXITSIZEMOVE:
					window->m_inLiveResize = 0;
					break;
				case WM_PAINT:
					/* An application sends the WM_PAINT message when the system or another application 
					 * makes a request to paint a portion of an application's window. The message is sent
					 * when the UpdateWindow or RedrawWindow function is called, or by the DispatchMessage 
					 * function when the application obtains a WM_PAINT message by using the GetMessage or 
					 * PeekMessage function. 
					 */
					if (!window->m_inLiveResize) {
						event = processWindowEvent(GHOST_kEventWindowUpdate, window);
						::ValidateRect(hwnd, NULL);
					}
					else {
						eventHandled = true;
					}
					break;
				case WM_GETMINMAXINFO:
					/* The WM_GETMINMAXINFO message is sent to a window when the size or 
					 * position of the window is about to change. An application can use 
					 * this message to override the window's default maximized size and 
					 * position, or its default minimum or maximum tracking size. 
					 */
					processMinMaxInfo((MINMAXINFO *) lParam);
					/* Let DefWindowProc handle it. */
					break;
				case WM_SIZING:
				case WM_SIZE:
					/* The WM_SIZE message is sent to a window after its size has changed.
					 * The WM_SIZE and WM_MOVE messages are not sent if an application handles the 
					 * WM_WINDOWPOSCHANGED message without calling DefWindowProc. It is more efficient
					 * to perform any move or size change processing during the WM_WINDOWPOSCHANGED 
					 * message without calling DefWindowProc.
					 */
					/* we get first WM_SIZE before we fully init. So, do not dispatch before we continiously resizng */
					if (window->m_inLiveResize) {
						system->pushEvent(processWindowEvent(GHOST_kEventWindowSize, window));
						system->dispatchEvents();
					}
					else {
						event = processWindowEvent(GHOST_kEventWindowSize, window);
					}
					break;
				case WM_CAPTURECHANGED:
					window->lostMouseCapture();
					break;
				case WM_MOVING:
					/* The WM_MOVING message is sent to a window that the user is moving. By processing
					 * this message, an application can monitor the size and position of the drag rectangle
					 * and, if needed, change its size or position.
					 */
				case WM_MOVE:
					/* The WM_SIZE and WM_MOVE messages are not sent if an application handles the 
					 * WM_WINDOWPOSCHANGED message without calling DefWindowProc. It is more efficient
					 * to perform any move or size change processing during the WM_WINDOWPOSCHANGED 
					 * message without calling DefWindowProc. 
					 */
					/* see WM_SIZE comment*/
					if (window->m_inLiveResize) {
						system->pushEvent(processWindowEvent(GHOST_kEventWindowMove, window));
						system->dispatchEvents();
					}
					else {
						event = processWindowEvent(GHOST_kEventWindowMove, window);
					}

					break;
				////////////////////////////////////////////////////////////////////////
				// Window events, ignored
				////////////////////////////////////////////////////////////////////////
				case WM_WINDOWPOSCHANGED:
				/* The WM_WINDOWPOSCHANGED message is sent to a window whose size, position, or place
				 * in the Z order has changed as a result of a call to the SetWindowPos function or
				 * another window-management function.
				 * The WM_SIZE and WM_MOVE messages are not sent if an application handles the
				 * WM_WINDOWPOSCHANGED message without calling DefWindowProc. It is more efficient
				 * to perform any move or size change processing during the WM_WINDOWPOSCHANGED
				 * message without calling DefWindowProc.
				 */
				case WM_ERASEBKGND:
				/* An application sends the WM_ERASEBKGND message when the window background must be
				 * erased (for example, when a window is resized). The message is sent to prepare an
				 * invalidated portion of a window for painting.
				 */
				case WM_NCPAINT:
				/* An application sends the WM_NCPAINT message to a window when its frame must be painted. */
				case WM_NCACTIVATE:
				/* The WM_NCACTIVATE message is sent to a window when its nonclient area needs to be changed
				 * to indicate an active or inactive state.
				 */
				case WM_DESTROY:
				/* The WM_DESTROY message is sent when a window is being destroyed. It is sent to the window
				 * procedure of the window being destroyed after the window is removed from the screen.
				 * This message is sent first to the window being destroyed and then to the child windows
				 * (if any) as they are destroyed. During the processing of the message, it can be assumed
				 * that all child windows still exist.
				 */
				case WM_NCDESTROY:
					/* The WM_NCDESTROY message informs a window that its nonclient area is being destroyed. The 
					 * DestroyWindow function sends the WM_NCDESTROY message to the window following the WM_DESTROY
					 * message. WM_DESTROY is used to free the allocated memory object associated with the window. 
					 */
					break;
				case WM_KILLFOCUS:
					/* The WM_KILLFOCUS message is sent to a window immediately before it loses the keyboard focus. 
					 * We want to prevent this if a window is still active and it loses focus to nowhere*/
					if (!wParam && hwnd == ::GetActiveWindow())
						::SetFocus(hwnd);
				case WM_SHOWWINDOW:
				/* The WM_SHOWWINDOW message is sent to a window when the window is about to be hidden or shown. */
				case WM_WINDOWPOSCHANGING:
				/* The WM_WINDOWPOSCHANGING message is sent to a window whose size, position, or place in
				 * the Z order is about to change as a result of a call to the SetWindowPos function or
				 * another window-management function.
				 */
				case WM_SETFOCUS:
				/* The WM_SETFOCUS message is sent to a window after it has gained the keyboard focus. */
					break;
				////////////////////////////////////////////////////////////////////////
				// Other events
				////////////////////////////////////////////////////////////////////////
				case WM_GETTEXT:
				/* An application sends a WM_GETTEXT message to copy the text that
				 * corresponds to a window into a buffer provided by the caller.
				 */
				case WM_ACTIVATEAPP:
				/* The WM_ACTIVATEAPP message is sent when a window belonging to a
				 * different application than the active window is about to be activated.
				 * The message is sent to the application whose window is being activated
				 * and to the application whose window is being deactivated.
				 */
				case WM_TIMER:
					/* The WIN32 docs say:
					 * The WM_TIMER message is posted to the installing thread's message queue
					 * when a timer expires. You can process the message by providing a WM_TIMER
					 * case in the window procedure. Otherwise, the default window procedure will
					 * call the TimerProc callback function specified in the call to the SetTimer
					 * function used to install the timer. 
					 *
					 * In GHOST, we let DefWindowProc call the timer callback.
					 */
					break;

			}
		}
		else {
			// Event found for a window before the pointer to the class has been set.
			GHOST_PRINT("GHOST_SystemWin32::wndProc: GHOST window event before creation\n");
			/* These are events we typically miss at this point:
			 * WM_GETMINMAXINFO	0x24
			 * WM_NCCREATE			0x81
			 * WM_NCCALCSIZE		0x83
			 * WM_CREATE			0x01
			 * We let DefWindowProc do the work.
			 */
		}
	}
	else {
		// Events without valid hwnd
		GHOST_PRINT("GHOST_SystemWin32::wndProc: event without window\n");
	}

	if (event) {
		system->pushEvent(event);
		eventHandled = true;
	}

	if (!eventHandled)
		lResult = ::DefWindowProcW(hwnd, msg, wParam, lParam);

	return lResult;
}

GHOST_TUns8 *GHOST_SystemWin32::getClipboard(bool selection) const
{
	char *temp_buff;
	
	if (IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(NULL) ) {
		wchar_t *buffer;
		HANDLE hData = GetClipboardData(CF_UNICODETEXT);
		if (hData == NULL) {
			CloseClipboard();
			return NULL;
		}
		buffer = (wchar_t *)GlobalLock(hData);
		if (!buffer) {
			CloseClipboard();
			return NULL;
		}
		
		temp_buff = alloc_utf_8_from_16(buffer, 0);
		
		/* Buffer mustn't be accessed after CloseClipboard
		 * it would like accessing free-d memory */
		GlobalUnlock(hData);
		CloseClipboard();
		
		return (GHOST_TUns8 *)temp_buff;
	}
	else if (IsClipboardFormatAvailable(CF_TEXT) && OpenClipboard(NULL) ) {
		char *buffer;
		size_t len = 0;
		HANDLE hData = GetClipboardData(CF_TEXT);
		if (hData == NULL) {
			CloseClipboard();
			return NULL;
		}
		buffer = (char *)GlobalLock(hData);
		if (!buffer) {
			CloseClipboard();
			return NULL;
		}
		
		len = strlen(buffer);
		temp_buff = (char *) malloc(len + 1);
		strncpy(temp_buff, buffer, len);
		temp_buff[len] = '\0';
		
		/* Buffer mustn't be accessed after CloseClipboard
		 * it would like accessing free-d memory */
		GlobalUnlock(hData);
		CloseClipboard();
		
		return (GHOST_TUns8 *)temp_buff;
	}
	else {
		return NULL;
	}
}

void GHOST_SystemWin32::putClipboard(GHOST_TInt8 *buffer, bool selection) const
{
	if (selection) {return; } // for copying the selection, used on X11

	if (OpenClipboard(NULL)) {
		HLOCAL clipbuffer;
		wchar_t *data;
		
		if (buffer) {
			size_t len = count_utf_16_from_8(buffer);
			EmptyClipboard();
			
			clipbuffer = LocalAlloc(LMEM_FIXED, sizeof(wchar_t) * len);
			data = (wchar_t *)GlobalLock(clipbuffer);

			conv_utf_8_to_16(buffer, data, len);

			LocalUnlock(clipbuffer);
			SetClipboardData(CF_UNICODETEXT, clipbuffer);
		}
		CloseClipboard();
	}
	else {
		return;
	}
}

static DWORD GetParentProcessID(void)
{
	HANDLE snapshot;
	PROCESSENTRY32 pe32 = {0};
	DWORD ppid = 0, pid = GetCurrentProcessId();
	snapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
	if (snapshot == INVALID_HANDLE_VALUE) {
		return -1;
	}
	pe32.dwSize = sizeof( pe32 );
	if (!Process32First(snapshot, &pe32)) {
		CloseHandle(snapshot);
		return -1;
	}
	do {
		if (pe32.th32ProcessID == pid) {
			ppid = pe32.th32ParentProcessID;
			break;
		}
	} while (Process32Next(snapshot, &pe32));
	CloseHandle(snapshot);
	return ppid;
}

static bool getProcessName(int pid, char *buffer, int max_len)
{
	bool result = false;
	HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
	                            FALSE, pid);
	if (handle) {
		GetModuleFileNameEx(handle, 0, buffer, max_len);
		result = true;
	}
	CloseHandle(handle);
	return result;
}

static bool isStartedFromCommandPrompt()
{
	HWND hwnd = GetConsoleWindow();
	
	if (hwnd) {
		DWORD pid = (DWORD)-1;
		DWORD ppid = GetParentProcessID();
		char parent_name[MAX_PATH];
		bool start_from_launcher = false;

		GetWindowThreadProcessId(hwnd, &pid);
		if (getProcessName(ppid, parent_name, sizeof(parent_name))) {
			char *filename = strrchr(parent_name, '\\');
			if (filename != NULL) {
				start_from_launcher = strstr(filename, "blender.exe") != NULL;
			}
		}

		/* When we're starting from a wrapper we need to compare with parent process ID. */
		if (pid == (start_from_launcher ? ppid : GetCurrentProcessId()))
			return true;
	}

	return false;
}

int GHOST_SystemWin32::toggleConsole(int action)
{
	switch (action)
	{
		case 3: // startup: hide if not started from command prompt
		{
			if (isStartedFromCommandPrompt()) {
				ShowWindow(GetConsoleWindow(), SW_HIDE);
				m_consoleStatus = 0;
			}
			break;
		}
		case 0: // hide
			ShowWindow(GetConsoleWindow(), SW_HIDE);
			m_consoleStatus = 0;
			break;
		case 1: // show
			ShowWindow(GetConsoleWindow(), SW_SHOW);
			m_consoleStatus = 1;
			break;
		case 2: // toggle
			ShowWindow(GetConsoleWindow(), m_consoleStatus ? SW_HIDE : SW_SHOW);
			m_consoleStatus = !m_consoleStatus;
			break;
	}


	return m_consoleStatus;
}

int GHOST_SystemWin32::confirmQuit(GHOST_IWindow *window) const
{
	return (MessageBox(window ? ((GHOST_WindowWin32 *)window)->getHWND() : 0, "Some changes have not been saved.\nDo you really want to quit?",
	                   "Exit Blender", MB_OKCANCEL | MB_ICONWARNING | MB_TOPMOST) == IDOK);
}
