//============================================================
//
//  input.c - Win32 implementation of MAME input routines
//
//============================================================
//
//  Copyright Aaron Giles
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or
//  without modification, are permitted provided that the
//  following conditions are met:
//
//    * Redistributions of source code must retain the above
//      copyright notice, this list of conditions and the
//      following disclaimer.
//    * Redistributions in binary form must reproduce the
//      above copyright notice, this list of conditions and
//      the following disclaimer in the documentation and/or
//      other materials provided with the distribution.
//    * Neither the name 'MAME' nor the names of its
//      contributors may be used to endorse or promote
//      products derived from this software without specific
//      prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND
//  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
//  EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
//  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
//  DAMAGE (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
//  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
//  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
//  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//============================================================

// For testing purposes: force DirectInput
#define FORCE_DIRECTINPUT	0

// standard windows headers
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winioctl.h>
#include <tchar.h>

// undef WINNT for dinput.h to prevent duplicate definition
#undef WINNT
#include <dinput.h>
#undef interface

// standard C headers
#include <conio.h>
#include <ctype.h>
#include <stddef.h>

// MAME headers
#include "emu.h"
#include "osdepend.h"
#include "ui.h"

// MAMEOS headers
#include "winmain.h"
#include "window.h"
#include "input.h"
#include "debugwin.h"
#include "video.h"
#include "strconv.h"
#include "config.h"

//============================================================
//  PARAMETERS
//============================================================

enum
{
	POVDIR_LEFT = 0,
	POVDIR_RIGHT,
	POVDIR_UP,
	POVDIR_DOWN
};

#define MAX_KEYS			256

#define MAME_KEY			0
#define DI_KEY				1
#define VIRTUAL_KEY			2
#define ASCII_KEY			3



//============================================================
//  MACROS
//============================================================

#define STRUCTSIZE(x)		((dinput_version == 0x0300) ? sizeof(x##_DX3) : sizeof(x))

#ifdef UNICODE
#define UNICODE_SUFFIX		"W"
#else
#define UNICODE_SUFFIX		"A"
#endif



//============================================================
//  TYPEDEFS
//============================================================

// state information for a keyboard; DirectInput state must be first element
typedef struct _keyboard_state keyboard_state;
struct _keyboard_state
{
	UINT8					state[MAX_KEYS];
	INT8					oldkey[MAX_KEYS];
	INT8					currkey[MAX_KEYS];
};


// state information for a mouse; DirectInput state must be first element
typedef struct _mouse_state mouse_state;
struct _mouse_state
{
	DIMOUSESTATE2			state;
	LONG					raw_x, raw_y, raw_z;
};


// state information for a joystick; DirectInput state must be first element
typedef struct _joystick_state joystick_state;
struct _joystick_state
{
	DIJOYSTATE				state;
	LONG					rangemin[8];
	LONG					rangemax[8];
};


// DirectInput-specific information about a device
typedef struct _dinput_device_info dinput_device_info;
struct _dinput_device_info
{
	LPDIRECTINPUTDEVICE		device;
	LPDIRECTINPUTDEVICE2	device2;
	DIDEVCAPS				caps;
	LPCDIDATAFORMAT			format;
};


// RawInput-specific information about a device
typedef struct _rawinput_device_info rawinput_device_info;
struct _rawinput_device_info
{
	HANDLE					device;
};


// generic device information
class device_info
{
public:
	device_info(running_machine &machine)
		: m_machine(machine) { }

	running_machine &machine() const { return m_machine; }

	// device information
	device_info **			head;
	device_info *			next;
	const char *			name;
	void					(*poll)(device_info *info);

	// MAME information
	input_device *			device;

	// device state
	union
	{
		keyboard_state		keyboard;
		mouse_state			mouse;
		joystick_state		joystick;
	};

	// DirectInput/RawInput-specific state
	dinput_device_info		dinput;
	rawinput_device_info	rawinput;

private:
	running_machine &		m_machine;
};


// RawInput APIs
typedef /*WINUSERAPI*/ INT (WINAPI *get_rawinput_device_list_ptr)(OUT PRAWINPUTDEVICELIST pRawInputDeviceList, IN OUT PINT puiNumDevices, IN UINT cbSize);
typedef /*WINUSERAPI*/ INT (WINAPI *get_rawinput_data_ptr)(IN HRAWINPUT hRawInput, IN UINT uiCommand, OUT LPVOID pData, IN OUT PINT pcbSize, IN UINT cbSizeHeader);
typedef /*WINUSERAPI*/ INT (WINAPI *get_rawinput_device_info_ptr)(IN HANDLE hDevice, IN UINT uiCommand, OUT LPVOID pData, IN OUT PINT pcbSize);
typedef /*WINUSERAPI*/ BOOL (WINAPI *register_rawinput_devices_ptr)(IN PCRAWINPUTDEVICE pRawInputDevices, IN UINT uiNumDevices, IN UINT cbSize);



//============================================================
//  LOCAL VARIABLES
//============================================================

// global states
static UINT8				input_enabled;
static osd_lock *			input_lock;
static UINT8				input_paused;
static DWORD				last_poll;

// DirectInput variables
static LPDIRECTINPUT		dinput;
static int					dinput_version;

// RawInput variables
static get_rawinput_device_list_ptr		get_rawinput_device_list;
static get_rawinput_data_ptr			get_rawinput_data;
static get_rawinput_device_info_ptr 	get_rawinput_device_info;
static register_rawinput_devices_ptr	register_rawinput_devices;

// keyboard states
static UINT8				keyboard_win32_reported_key_down;
static device_info *		keyboard_list;

// mouse states
static UINT8				mouse_enabled;
static device_info *		mouse_list;

// lightgun states
static UINT8				lightgun_shared_axis_mode;
static UINT8				lightgun_enabled;
static device_info *		lightgun_list;

// joystick states
static device_info *		joystick_list;

// default axis names
static const TCHAR *const default_axis_name[] =
{
	TEXT("X"), TEXT("Y"), TEXT("Z"), TEXT("RX"),
	TEXT("RY"), TEXT("RZ"), TEXT("SL1"), TEXT("SL2")
};



//============================================================
//  PROTOTYPES
//============================================================

static void wininput_pause(running_machine &machine);
static void wininput_resume(running_machine &machine);
static void wininput_exit(running_machine &machine);

// device list management
static void device_list_poll_devices(device_info *devlist_head);
static void device_list_reset_devices(device_info *devlist_head);

// generic device management
static device_info *generic_device_alloc(running_machine &machine, device_info **devlist_head_ptr, const TCHAR *name);
static void generic_device_free(device_info *devinfo);
static int generic_device_index(device_info *devlist_head, device_info *devinfo);
static void generic_device_reset(device_info *devinfo);
static INT32 generic_button_get_state(void *device_internal, void *item_internal);
static INT32 generic_axis_get_state(void *device_internal, void *item_internal);

// Win32-specific input code
static void win32_init(running_machine &machine);
static void win32_exit(running_machine &machine);
static void win32_keyboard_poll(device_info *devinfo);
static void win32_lightgun_poll(device_info *devinfo);

// DirectInput-specific code
static void dinput_init(running_machine &machine);
static void dinput_exit(running_machine &machine);
static HRESULT dinput_set_dword_property(LPDIRECTINPUTDEVICE device, REFGUID property_guid, DWORD object, DWORD how, DWORD value);
static device_info *dinput_device_create(running_machine &machine, device_info **devlist_head_ptr, LPCDIDEVICEINSTANCE instance, LPCDIDATAFORMAT format1, LPCDIDATAFORMAT format2, DWORD cooperative_level);
static void dinput_device_release(device_info *devinfo);
static char *dinput_device_item_name(device_info *devinfo, int offset, const TCHAR *defstring, const TCHAR *suffix);
static HRESULT dinput_device_poll(device_info *devinfo);
static BOOL CALLBACK dinput_keyboard_enum(LPCDIDEVICEINSTANCE instance, LPVOID ref);
static void dinput_keyboard_poll(device_info *devinfo);
static BOOL CALLBACK dinput_mouse_enum(LPCDIDEVICEINSTANCE instance, LPVOID ref);
static void dinput_mouse_poll(device_info *devinfo);
static BOOL CALLBACK dinput_joystick_enum(LPCDIDEVICEINSTANCE instance, LPVOID ref);
static void dinput_joystick_poll(device_info *devinfo);
static INT32 dinput_joystick_pov_get_state(void *device_internal, void *item_internal);

// RawInput-specific code
static void rawinput_init(running_machine &machine);
static void rawinput_exit(running_machine &machine);
static device_info *rawinput_device_create(running_machine &machine, device_info **devlist_head_ptr, PRAWINPUTDEVICELIST device);
static void rawinput_device_release(device_info *info);
static TCHAR *rawinput_device_improve_name(TCHAR *name);
static void rawinput_keyboard_enum(running_machine &machine, PRAWINPUTDEVICELIST device);
static void rawinput_keyboard_update(HANDLE device, RAWKEYBOARD *data);
static void rawinput_mouse_enum(running_machine &machine, PRAWINPUTDEVICELIST device);
static void rawinput_mouse_update(HANDLE device, RAWMOUSE *data);
static void rawinput_mouse_poll(device_info *devinfo);

// misc utilities
static TCHAR *reg_query_string(HKEY key, const TCHAR *path);
static const TCHAR *default_button_name(int which);
static const TCHAR *default_pov_name(int which);



//============================================================
//  KEYBOARD/JOYSTICK LIST
//============================================================

// master keyboard translation table
static const int win_key_trans_table[][4] =
{
	// MAME key             dinput key          virtual key     ascii
	{ ITEM_ID_ESC,			DIK_ESCAPE,			VK_ESCAPE,		27 },
	{ ITEM_ID_1,			DIK_1,				'1',			'1' },
	{ ITEM_ID_2,			DIK_2,				'2',			'2' },
	{ ITEM_ID_3,			DIK_3,				'3',			'3' },
	{ ITEM_ID_4,			DIK_4,				'4',			'4' },
	{ ITEM_ID_5,			DIK_5,				'5',			'5' },
	{ ITEM_ID_6,			DIK_6,				'6',			'6' },
	{ ITEM_ID_7,			DIK_7,				'7',			'7' },
	{ ITEM_ID_8,			DIK_8,				'8',			'8' },
	{ ITEM_ID_9,			DIK_9,				'9',			'9' },
	{ ITEM_ID_0,			DIK_0,				'0',			'0' },
	{ ITEM_ID_MINUS,		DIK_MINUS,			VK_OEM_MINUS,	'-' },
	{ ITEM_ID_EQUALS,		DIK_EQUALS,			VK_OEM_PLUS,	'=' },
	{ ITEM_ID_BACKSPACE,	DIK_BACK,			VK_BACK,		8 },
	{ ITEM_ID_TAB,			DIK_TAB,			VK_TAB, 		9 },
	{ ITEM_ID_Q,			DIK_Q,				'Q',			'Q' },
	{ ITEM_ID_W,			DIK_W,				'W',			'W' },
	{ ITEM_ID_E,			DIK_E,				'E',			'E' },
	{ ITEM_ID_R,			DIK_R,				'R',			'R' },
	{ ITEM_ID_T,			DIK_T,				'T',			'T' },
	{ ITEM_ID_Y,			DIK_Y,				'Y',			'Y' },
	{ ITEM_ID_U,			DIK_U,				'U',			'U' },
	{ ITEM_ID_I,			DIK_I,				'I',			'I' },
	{ ITEM_ID_O,			DIK_O,				'O',			'O' },
	{ ITEM_ID_P,			DIK_P,				'P',			'P' },
	{ ITEM_ID_OPENBRACE,	DIK_LBRACKET,		VK_OEM_4,		'[' },
	{ ITEM_ID_CLOSEBRACE,	DIK_RBRACKET,		VK_OEM_6,		']' },
	{ ITEM_ID_ENTER,		DIK_RETURN, 		VK_RETURN,		13 },
	{ ITEM_ID_LCONTROL, 	DIK_LCONTROL,		VK_LCONTROL,	0 },
	{ ITEM_ID_A,			DIK_A,				'A',			'A' },
	{ ITEM_ID_S,			DIK_S,				'S',			'S' },
	{ ITEM_ID_D,			DIK_D,				'D',			'D' },
	{ ITEM_ID_F,			DIK_F,				'F',			'F' },
	{ ITEM_ID_G,			DIK_G,				'G',			'G' },
	{ ITEM_ID_H,			DIK_H,				'H',			'H' },
	{ ITEM_ID_J,			DIK_J,				'J',			'J' },
	{ ITEM_ID_K,			DIK_K,				'K',			'K' },
	{ ITEM_ID_L,			DIK_L,				'L',			'L' },
	{ ITEM_ID_COLON,		DIK_SEMICOLON,		VK_OEM_1,		';' },
	{ ITEM_ID_QUOTE,		DIK_APOSTROPHE,		VK_OEM_7,		'\'' },
	{ ITEM_ID_TILDE,		DIK_GRAVE,			VK_OEM_3,		'`' },
	{ ITEM_ID_LSHIFT,		DIK_LSHIFT, 		VK_LSHIFT,		0 },
	{ ITEM_ID_BACKSLASH,	DIK_BACKSLASH,		VK_OEM_5,		'\\' },
	{ ITEM_ID_BACKSLASH2,	DIK_OEM_102,		VK_OEM_102,		'<' },
	{ ITEM_ID_Z,			DIK_Z,				'Z',			'Z' },
	{ ITEM_ID_X,			DIK_X,				'X',			'X' },
	{ ITEM_ID_C,			DIK_C,				'C',			'C' },
	{ ITEM_ID_V,			DIK_V,				'V',			'V' },
	{ ITEM_ID_B,			DIK_B,				'B',			'B' },
	{ ITEM_ID_N,			DIK_N,				'N',			'N' },
	{ ITEM_ID_M,			DIK_M,				'M',			'M' },
	{ ITEM_ID_COMMA,		DIK_COMMA,			VK_OEM_COMMA,	',' },
	{ ITEM_ID_STOP, 		DIK_PERIOD, 		VK_OEM_PERIOD,	'.' },
	{ ITEM_ID_SLASH,		DIK_SLASH,			VK_OEM_2,		'/' },
	{ ITEM_ID_RSHIFT,		DIK_RSHIFT, 		VK_RSHIFT,		0 },
	{ ITEM_ID_ASTERISK, 	DIK_MULTIPLY,		VK_MULTIPLY,	'*' },
	{ ITEM_ID_LALT, 		DIK_LMENU,			VK_LMENU,		0 },
	{ ITEM_ID_SPACE,		DIK_SPACE,			VK_SPACE,		' ' },
	{ ITEM_ID_CAPSLOCK, 	DIK_CAPITAL,		VK_CAPITAL, 	0 },
	{ ITEM_ID_F1,			DIK_F1,				VK_F1,			0 },
	{ ITEM_ID_F2,			DIK_F2,				VK_F2,			0 },
	{ ITEM_ID_F3,			DIK_F3,				VK_F3,			0 },
	{ ITEM_ID_F4,			DIK_F4,				VK_F4,			0 },
	{ ITEM_ID_F5,			DIK_F5,				VK_F5,			0 },
	{ ITEM_ID_F6,			DIK_F6,				VK_F6,			0 },
	{ ITEM_ID_F7,			DIK_F7,				VK_F7,			0 },
	{ ITEM_ID_F8,			DIK_F8,				VK_F8,			0 },
	{ ITEM_ID_F9,			DIK_F9,				VK_F9,			0 },
	{ ITEM_ID_F10,			DIK_F10,			VK_F10, 		0 },
	{ ITEM_ID_NUMLOCK,		DIK_NUMLOCK,		VK_NUMLOCK, 	0 },
	{ ITEM_ID_SCRLOCK,		DIK_SCROLL,			VK_SCROLL,		0 },
	{ ITEM_ID_7_PAD,		DIK_NUMPAD7,		VK_NUMPAD7, 	0 },
	{ ITEM_ID_8_PAD,		DIK_NUMPAD8,		VK_NUMPAD8, 	0 },
	{ ITEM_ID_9_PAD,		DIK_NUMPAD9,		VK_NUMPAD9, 	0 },
	{ ITEM_ID_MINUS_PAD,	DIK_SUBTRACT,		VK_SUBTRACT,	0 },
	{ ITEM_ID_4_PAD,		DIK_NUMPAD4,		VK_NUMPAD4, 	0 },
	{ ITEM_ID_5_PAD,		DIK_NUMPAD5,		VK_NUMPAD5, 	0 },
	{ ITEM_ID_6_PAD,		DIK_NUMPAD6,		VK_NUMPAD6, 	0 },
	{ ITEM_ID_PLUS_PAD, 	DIK_ADD,			VK_ADD, 		0 },
	{ ITEM_ID_1_PAD,		DIK_NUMPAD1,		VK_NUMPAD1, 	0 },
	{ ITEM_ID_2_PAD,		DIK_NUMPAD2,		VK_NUMPAD2, 	0 },
	{ ITEM_ID_3_PAD,		DIK_NUMPAD3,		VK_NUMPAD3, 	0 },
	{ ITEM_ID_0_PAD,		DIK_NUMPAD0,		VK_NUMPAD0, 	0 },
	{ ITEM_ID_DEL_PAD,		DIK_DECIMAL,		VK_DECIMAL, 	0 },
	{ ITEM_ID_F11,			DIK_F11,			VK_F11, 		0 },
	{ ITEM_ID_F12,			DIK_F12,			VK_F12, 		0 },
	{ ITEM_ID_F13,			DIK_F13,			VK_F13, 		0 },
	{ ITEM_ID_F14,			DIK_F14,			VK_F14, 		0 },
	{ ITEM_ID_F15,			DIK_F15,			VK_F15, 		0 },
	{ ITEM_ID_ENTER_PAD,	DIK_NUMPADENTER,	VK_RETURN,		0 },
	{ ITEM_ID_RCONTROL, 	DIK_RCONTROL,		VK_RCONTROL,	0 },
	{ ITEM_ID_SLASH_PAD,	DIK_DIVIDE,			VK_DIVIDE,		0 },
	{ ITEM_ID_PRTSCR,		DIK_SYSRQ,			0,				0 },
	{ ITEM_ID_RALT, 		DIK_RMENU,			VK_RMENU,		0 },
	{ ITEM_ID_HOME, 		DIK_HOME,			VK_HOME,		0 },
	{ ITEM_ID_UP,			DIK_UP,				VK_UP,			0 },
	{ ITEM_ID_PGUP, 		DIK_PRIOR,			VK_PRIOR,		0 },
	{ ITEM_ID_LEFT, 		DIK_LEFT,			VK_LEFT,		0 },
	{ ITEM_ID_RIGHT,		DIK_RIGHT,			VK_RIGHT,		0 },
	{ ITEM_ID_END,			DIK_END,			VK_END, 		0 },
	{ ITEM_ID_DOWN, 		DIK_DOWN,			VK_DOWN,		0 },
	{ ITEM_ID_PGDN, 		DIK_NEXT,			VK_NEXT,		0 },
	{ ITEM_ID_INSERT,		DIK_INSERT,			VK_INSERT,		0 },
	{ ITEM_ID_DEL,			DIK_DELETE,			VK_DELETE,		0 },
	{ ITEM_ID_LWIN, 		DIK_LWIN,			VK_LWIN,		0 },
	{ ITEM_ID_RWIN, 		DIK_RWIN,			VK_RWIN,		0 },
	{ ITEM_ID_MENU, 		DIK_APPS,			VK_APPS,		0 },
	{ ITEM_ID_PAUSE,		DIK_PAUSE,			VK_PAUSE,		0 },
	{ ITEM_ID_CANCEL,		0,					VK_CANCEL,		0 },

	// New keys introduced in Windows 2000. These have no MAME codes to
	// preserve compatibility with old config files that may refer to them
	// as e.g. FORWARD instead of e.g. KEYCODE_WEBFORWARD. They need table
	// entries anyway because otherwise they aren't recognized when
	// GetAsyncKeyState polling is used (as happens currently when MAME is
	// paused). Some codes are missing because the mapping to vkey codes
	// isn't clear, and MapVirtualKey is no help.

	{ ITEM_ID_OTHER_SWITCH,	DIK_MUTE,			VK_VOLUME_MUTE,			0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_VOLUMEDOWN,		VK_VOLUME_DOWN,			0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_VOLUMEUP,		VK_VOLUME_UP,			0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_WEBHOME,		VK_BROWSER_HOME,		0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_WEBSEARCH,		VK_BROWSER_SEARCH,		0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_WEBFAVORITES,	VK_BROWSER_FAVORITES,	0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_WEBREFRESH,		VK_BROWSER_REFRESH,		0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_WEBSTOP,		VK_BROWSER_STOP,		0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_WEBFORWARD,		VK_BROWSER_FORWARD,		0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_WEBBACK,		VK_BROWSER_BACK,		0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_MAIL,			VK_LAUNCH_MAIL,			0 },
	{ ITEM_ID_OTHER_SWITCH,	DIK_MEDIASELECT,	VK_LAUNCH_MEDIA_SELECT,	0 },
};


//============================================================
//  INLINE FUNCTIONS
//============================================================

INLINE void poll_if_necessary(running_machine &machine)
{
	// make sure we poll at least once every 1/4 second
	if (GetTickCount() > last_poll + 1000 / 4)
		wininput_poll(machine);
}


INLINE input_item_id keyboard_map_scancode_to_itemid(int scancode)
{
	int tablenum;

	// scan the table for a match
	for (tablenum = 0; tablenum < ARRAY_LENGTH(win_key_trans_table); tablenum++)
		if (win_key_trans_table[tablenum][DI_KEY] == scancode)
			return (input_item_id)win_key_trans_table[tablenum][MAME_KEY];

	// default to an "other" switch
	return ITEM_ID_OTHER_SWITCH;
}


INLINE INT32 normalize_absolute_axis(INT32 raw, INT32 rawmin, INT32 rawmax)
{
	INT32 center = (rawmax + rawmin) / 2;

	// make sure we have valid data
	if (rawmin >= rawmax)
		return raw;

	// above center
	if (raw >= center)
	{
		INT32 result = (INT64)(raw - center) * (INT64)INPUT_ABSOLUTE_MAX / (INT64)(rawmax - center);
		return MIN(result, INPUT_ABSOLUTE_MAX);
	}

	// below center
	else
	{
		INT32 result = -((INT64)(center - raw) * (INT64)-INPUT_ABSOLUTE_MIN / (INT64)(center - rawmin));
		return MAX(result, INPUT_ABSOLUTE_MIN);
	}
}



//============================================================
//  wininput_init
//============================================================

void wininput_init(running_machine &machine)
{
	// we need pause and exit callbacks
	machine.add_notifier(MACHINE_NOTIFY_PAUSE, machine_notify_delegate(FUNC(wininput_pause), &machine));
	machine.add_notifier(MACHINE_NOTIFY_RESUME, machine_notify_delegate(FUNC(wininput_resume), &machine));
	machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(wininput_exit), &machine));

	// allocate a lock for input synchronizations, since messages sometimes come from another thread
	input_lock = osd_lock_alloc();
	assert_always(input_lock != NULL, "Failed to allocate input_lock");

	// decode the options
	lightgun_shared_axis_mode = downcast<windows_options &>(machine.options()).dual_lightgun();

	// initialize RawInput and DirectInput (RawInput first so we can fall back)
	rawinput_init(machine);
	dinput_init(machine);
	win32_init(machine);

	// poll once to get the initial states
	input_enabled = TRUE;
	wininput_poll(machine);
}


//============================================================
//  wininput_pause
//============================================================

static void wininput_pause(running_machine &machine)
{
	// keep track of the paused state
	input_paused = true;
}

static void wininput_resume(running_machine &machine)
{
	// keep track of the paused state
	input_paused = false;
}


//============================================================
//  wininput_exit
//============================================================

static void wininput_exit(running_machine &machine)
{
	// acquire the lock and turn off input (this ensures everyone is done)
	osd_lock_acquire(input_lock);
	input_enabled = FALSE;
	osd_lock_release(input_lock);

	// free the lock
	osd_lock_free(input_lock);
}


//============================================================
//  wininput_poll
//============================================================

void wininput_poll(running_machine &machine)
{
	int hasfocus = winwindow_has_focus() && input_enabled;

	// ignore if not enabled
	if (input_enabled)
	{
		// remember when this happened
		last_poll = GetTickCount();

		// periodically process events, in case they're not coming through
		// this also will make sure the mouse state is up-to-date
		winwindow_process_events_periodic(machine);

		// track if mouse/lightgun is enabled, for mouse hiding purposes
		mouse_enabled = machine.input().device_class(DEVICE_CLASS_MOUSE).enabled();
		lightgun_enabled = machine.input().device_class(DEVICE_CLASS_LIGHTGUN).enabled();
	}

	// poll all of the devices
	if (hasfocus)
	{
		device_list_poll_devices(keyboard_list);
		device_list_poll_devices(mouse_list);
		device_list_poll_devices(lightgun_list);
		device_list_poll_devices(joystick_list);
	}
	else
	{
		device_list_reset_devices(keyboard_list);
		device_list_reset_devices(mouse_list);
		device_list_reset_devices(lightgun_list);
		device_list_reset_devices(joystick_list);
	}
}


//============================================================
//  wininput_should_hide_mouse
//============================================================

int wininput_should_hide_mouse(void)
{
	// if we are paused or disabled, no
	if (input_paused || !input_enabled)
		return FALSE;

	// if neither mice nor lightguns enabled in the core, then no
	if (!mouse_enabled && !lightgun_enabled)
		return FALSE;

	// if the window has a menu, no
	if (win_window_list != NULL && win_has_menu(win_window_list))
		return FALSE;

	// otherwise, yes
	return TRUE;
}


//============================================================
//  wininput_handle_mouse_button
//============================================================

BOOL wininput_handle_mouse_button(int button, int down, int x, int y)
{
	device_info *devinfo;

	// ignore if not enabled
	if (!input_enabled)
		return FALSE;

	// only need this for shared axis hack
	if (!lightgun_shared_axis_mode || button >= 4)
		return FALSE;

	// choose a device based on the button
	devinfo = lightgun_list;
	if (button >= 2 && devinfo != NULL)
	{
		button -= 2;
		devinfo = devinfo->next;
	}

	// take the lock
	osd_lock_acquire(input_lock);

	// set the button state
	devinfo->mouse.state.rgbButtons[button] = down ? 0x80 : 0x00;
	if (down)
	{
		RECT client_rect;
		POINT mousepos;

		// get the position relative to the window
		GetClientRect(win_window_list->hwnd, &client_rect);
		mousepos.x = x;
		mousepos.y = y;
		ScreenToClient(win_window_list->hwnd, &mousepos);

		// convert to absolute coordinates
		devinfo->mouse.state.lX = normalize_absolute_axis(mousepos.x, client_rect.left, client_rect.right);
		devinfo->mouse.state.lY = normalize_absolute_axis(mousepos.y, client_rect.top, client_rect.bottom);
	}

	// release the lock
	osd_lock_release(input_lock);
	return TRUE;
}


//============================================================
//  wininput_handle_raw
//============================================================

BOOL wininput_handle_raw(HANDLE device)
{
	BYTE small_buffer[4096];
	LPBYTE data = small_buffer;
	BOOL result = FALSE;
	int size;

	// ignore if not enabled
	if (!input_enabled)
		return result;

	// determine the size of databuffer we need
	if ((*get_rawinput_data)((HRAWINPUT)device, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER)) != 0)
		return result;

	// if necessary, allocate a temporary buffer and fetch the data
	if (size > sizeof(small_buffer))
	{
		data = global_alloc_array(BYTE, size);
		if (data == NULL)
			return result;
	}

	// fetch the data and process the appropriate message types
	result = (*get_rawinput_data)((HRAWINPUT)device, RID_INPUT, data, &size, sizeof(RAWINPUTHEADER));
	if (result != 0)
	{
		RAWINPUT *input = (RAWINPUT *)data;

		// handle keyboard input
		if (input->header.dwType == RIM_TYPEKEYBOARD)
		{
			osd_lock_acquire(input_lock);
			rawinput_keyboard_update(input->header.hDevice, &input->data.keyboard);
			osd_lock_release(input_lock);
			result = TRUE;
		}

		// handle mouse input
		else if (input->header.dwType == RIM_TYPEMOUSE)
		{
			osd_lock_acquire(input_lock);
			rawinput_mouse_update(input->header.hDevice, &input->data.mouse);
			osd_lock_release(input_lock);
			result = TRUE;
		}
	}

	// free the temporary buffer and return the result
	if (data != small_buffer)
		global_free(data);
	return result;
}


//============================================================
//  wininput_vkey_for_mame_code
//============================================================

int wininput_vkey_for_mame_code(input_code code)
{
	// only works for keyboard switches
	if (code.device_class() == DEVICE_CLASS_KEYBOARD && code.item_class() == ITEM_CLASS_SWITCH)
	{
		input_item_id id = code.item_id();
		int tablenum;

		// scan the table for a match
		for (tablenum = 0; tablenum < ARRAY_LENGTH(win_key_trans_table); tablenum++)
			if (win_key_trans_table[tablenum][MAME_KEY] == id)
				return win_key_trans_table[tablenum][VIRTUAL_KEY];
	}
	return 0;
}


//============================================================
//  customize_input_type_list
//============================================================

void windows_osd_interface::customize_input_type_list(simple_list<input_type_entry> &typelist)
{
	input_type_entry *entry;

	// loop over the defaults
	for (entry = typelist.first(); entry != NULL; entry = entry->next())
		switch (entry->type())
		{
			// disable the config menu if the ALT key is down
			// (allows ALT-TAB to switch between windows apps)
			case IPT_UI_CONFIGURE:
				entry->defseq(SEQ_TYPE_STANDARD).set(KEYCODE_TAB, input_seq::not_code, KEYCODE_LALT, input_seq::not_code, KEYCODE_RALT);
				break;

			// alt-enter for fullscreen
			case IPT_OSD_1:
				entry->configure_osd("TOGGLE_FULLSCREEN", "Toggle Fullscreen");
				entry->defseq(SEQ_TYPE_STANDARD).set(KEYCODE_LALT, KEYCODE_ENTER);
				break;

			// alt-F12 for fullscreen snap
			case IPT_OSD_2:
				entry->configure_osd("RENDER_SNAP", "Take Rendered Snapshot");
				entry->defseq(SEQ_TYPE_STANDARD).set(KEYCODE_LALT, KEYCODE_F12);
				break;

			// alt-F11 for fullscreen video
			case IPT_OSD_3:
				entry->configure_osd("RENDER_AVI", "Record Rendered Video");
				entry->defseq(SEQ_TYPE_STANDARD).set(KEYCODE_LALT, KEYCODE_F11);
				break;

			// leave everything else alone
			default:
				break;
		}
}


//============================================================
//  device_list_poll_devices
//============================================================

static void device_list_poll_devices(device_info *devlist_head)
{
	device_info *curdev;

	for (curdev = devlist_head; curdev != NULL; curdev = curdev->next)
		if (curdev->poll != NULL)
			(*curdev->poll)(curdev);
}


//============================================================
//  device_list_reset_devices
//============================================================

static void device_list_reset_devices(device_info *devlist_head)
{
	device_info *curdev;

	for (curdev = devlist_head; curdev != NULL; curdev = curdev->next)
		generic_device_reset(curdev);
}


//============================================================
//  generic_device_alloc
//============================================================

static device_info *generic_device_alloc(running_machine &machine, device_info **devlist_head_ptr, const TCHAR *name)
{
	device_info **curdev_ptr;
	device_info *devinfo;

	// allocate memory for the device object
	devinfo = global_alloc_clear(device_info(machine));
	devinfo->head = devlist_head_ptr;

	// allocate a UTF8 copy of the name
	devinfo->name = utf8_from_tstring(name);
	if (devinfo->name == NULL)
		goto error;

	// append us to the list
	for (curdev_ptr = devinfo->head; *curdev_ptr != NULL; curdev_ptr = &(*curdev_ptr)->next) ;
	*curdev_ptr = devinfo;

	return devinfo;

error:
	global_free(devinfo);
	return NULL;
}


//============================================================
//  generic_device_free
//============================================================

static void generic_device_free(device_info *devinfo)
{
	device_info **curdev_ptr;

	// remove us from the list
	for (curdev_ptr = devinfo->head; *curdev_ptr != devinfo && *curdev_ptr != NULL; curdev_ptr = &(*curdev_ptr)->next) ;
	if (*curdev_ptr == devinfo)
		*curdev_ptr = devinfo->next;

	// free the copy of the name if present
	if (devinfo->name != NULL)
		osd_free((void *)devinfo->name);
	devinfo->name = NULL;

	// and now free the info
	global_free(devinfo);
}


//============================================================
//  generic_device_index
//============================================================

static int generic_device_index(device_info *devlist_head, device_info *devinfo)
{
	int index = 0;
	while (devlist_head != NULL)
	{
		if (devlist_head == devinfo)
			return index;
		index++;
		devlist_head = devlist_head->next;
	}
	return -1;
}


//============================================================
//  generic_device_reset
//============================================================

static void generic_device_reset(device_info *devinfo)
{
	// keyboard case
	if (devinfo->head == &keyboard_list)
		memset(devinfo->keyboard.state, 0, sizeof(devinfo->keyboard.state));

	// mouse/lightgun case
	else if (devinfo->head == &mouse_list || devinfo->head == &lightgun_list)
		memset(&devinfo->mouse.state, 0, sizeof(devinfo->mouse.state));

	// joystick case
	else if (devinfo->head == &joystick_list)
	{
		int povnum;

		memset(&devinfo->joystick.state, 0, sizeof(devinfo->joystick.state));
		for (povnum = 0; povnum < ARRAY_LENGTH(devinfo->joystick.state.rgdwPOV); povnum++)
			devinfo->joystick.state.rgdwPOV[povnum] = 0xffff;
	}
}


//============================================================
//  generic_button_get_state
//============================================================

static INT32 generic_button_get_state(void *device_internal, void *item_internal)
{
	device_info *devinfo = (device_info *)device_internal;
	BYTE *itemdata = (BYTE *)item_internal;

	// return the current state
	poll_if_necessary(devinfo->machine());
	return *itemdata >> 7;
}


//============================================================
//  generic_axis_get_state
//============================================================

static INT32 generic_axis_get_state(void *device_internal, void *item_internal)
{
	device_info *devinfo = (device_info *)device_internal;
	LONG *axisdata = (LONG *)item_internal;

	// return the current state
	poll_if_necessary(devinfo->machine());
	return *axisdata;
}


//============================================================
//  win32_init
//============================================================

static void win32_init(running_machine &machine)
{
	int gunnum;

	// we don't need any initialization unless we are using shared axis mode for lightguns
	if (!lightgun_shared_axis_mode)
		return;

	// we need an exit callback
	machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(win32_exit), &machine));

	// allocate two lightgun devices
	for (gunnum = 0; gunnum < 2; gunnum++)
	{
		static const TCHAR *const gun_names[] = { TEXT("Shared Axis Gun 1"), TEXT("Shared Axis Gun 2") };
		device_info *devinfo;
		int axisnum, butnum;

		// allocate a device
		devinfo = generic_device_alloc(machine, &lightgun_list, gun_names[gunnum]);
		if (devinfo == NULL)
			break;

		// add the device
		devinfo->device = machine.input().device_class(DEVICE_CLASS_LIGHTGUN).add_device(devinfo->name, devinfo);

		// populate the axes
		for (axisnum = 0; axisnum < 2; axisnum++)
		{
			char *name = utf8_from_tstring(default_axis_name[axisnum]);
			devinfo->device->add_item(name, (input_item_id)(ITEM_ID_XAXIS + axisnum), generic_axis_get_state, &devinfo->mouse.state.lX + axisnum);
			osd_free(name);
		}

		// populate the buttons
		for (butnum = 0; butnum < 2; butnum++)
		{
			char *name = utf8_from_tstring(default_button_name(butnum));
			devinfo->device->add_item(name, (input_item_id)(ITEM_ID_BUTTON1 + butnum), generic_button_get_state, &devinfo->mouse.state.rgbButtons[butnum]);
			osd_free(name);
		}
	}
}


//============================================================
//  win32_exit
//============================================================

static void win32_exit(running_machine &machine)
{
	// skip if we're in shared axis mode
	if (!lightgun_shared_axis_mode)
		return;

	// delete the lightgun devices
	while (lightgun_list != NULL)
		generic_device_free(lightgun_list);
}


//============================================================
//  win32_keyboard_poll
//============================================================

static void win32_keyboard_poll(device_info *devinfo)
{
	int keynum;

	// clear the flag that says we detected a key down via win32
	keyboard_win32_reported_key_down = FALSE;

	// reset the keyboard state and then repopulate
	memset(devinfo->keyboard.state, 0, sizeof(devinfo->keyboard.state));

	// iterate over keys
	for (keynum = 0; keynum < ARRAY_LENGTH(win_key_trans_table); keynum++)
	{
		int vk = win_key_trans_table[keynum][VIRTUAL_KEY];
		if (vk != 0 && (GetAsyncKeyState(vk) & 0x8000) != 0)
		{
			int dik = win_key_trans_table[keynum][DI_KEY];

			// conver the VK code to a scancode (DIK code)
			if (dik != 0)
				devinfo->keyboard.state[dik] = 0x80;

			// set this flag so that we continue to use win32 until all keys are up
			keyboard_win32_reported_key_down = TRUE;
		}
	}
}


//============================================================
//  win32_lightgun_poll
//============================================================

static void win32_lightgun_poll(device_info *devinfo)
{
	INT32 xpos = 0, ypos = 0;
	POINT mousepos;

	// if we are using the shared axis hack, the data is updated via Windows messages only
	if (lightgun_shared_axis_mode)
		return;

	// get the cursor position and transform into final results
	GetCursorPos(&mousepos);
	if (win_window_list != NULL)
	{
		RECT client_rect;

		// get the position relative to the window
		GetClientRect(win_window_list->hwnd, &client_rect);
		ScreenToClient(win_window_list->hwnd, &mousepos);

		// convert to absolute coordinates
		xpos = normalize_absolute_axis(mousepos.x, client_rect.left, client_rect.right);
		ypos = normalize_absolute_axis(mousepos.y, client_rect.top, client_rect.bottom);
	}

	// update the X/Y positions
	devinfo->mouse.state.lX = xpos;
	devinfo->mouse.state.lY = ypos;
}


//============================================================
//  dinput_init
//============================================================

static void dinput_init(running_machine &machine)
{
	HRESULT result;
#if DIRECTINPUT_VERSION >= 0x800
	int didevtype_keyboard = DI8DEVCLASS_KEYBOARD;
	int didevtype_mouse = DI8DEVCLASS_POINTER;
	int didevtype_joystick = DI8DEVCLASS_GAMECTRL;

	dinput_version = DIRECTINPUT_VERSION;
	result = DirectInput8Create(GetModuleHandle(NULL), dinput_version, IID_IDirectInput8, (void **)&dinput, NULL);
	if (result != DI_OK)
	{
		dinput_version = 0;
		return;
	}
#else
	int didevtype_keyboard = DIDEVTYPE_KEYBOARD;
	int didevtype_mouse = DIDEVTYPE_MOUSE;
	int didevtype_joystick = DIDEVTYPE_JOYSTICK;

	// first attempt to initialize DirectInput at the current version
	dinput_version = DIRECTINPUT_VERSION;
	result = DirectInputCreate(GetModuleHandle(NULL), dinput_version, &dinput, NULL);
	if (result != DI_OK)
	{
		// if that fails, try version 5
		dinput_version = 0x0500;
		result = DirectInputCreate(GetModuleHandle(NULL), dinput_version, &dinput, NULL);
		if (result != DI_OK)
		{
			// if that fails, try version 3
			dinput_version = 0x0300;
			result = DirectInputCreate(GetModuleHandle(NULL), dinput_version, &dinput, NULL);
			if (result != DI_OK)
			{
				dinput_version = 0;
				return;
			}
		}
	}
#endif

	mame_printf_verbose("DirectInput: Using DirectInput %d\n", dinput_version >> 8);

	// we need an exit callback
	machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(dinput_exit), &machine));

	// initialize keyboard devices, but only if we don't have any yet
	if (keyboard_list == NULL)
	{
		// enumerate the ones we have
		result = IDirectInput_EnumDevices(dinput, didevtype_keyboard, dinput_keyboard_enum, &machine, DIEDFL_ATTACHEDONLY);
		if (result != DI_OK)
			fatalerror("DirectInput: Unable to enumerate keyboards (result=%08X)\n", (UINT32)result);
	}

	// initialize mouse & lightgun devices, but only if we don't have any yet
	if (mouse_list == NULL)
	{
		// enumerate the ones we have
		result = IDirectInput_EnumDevices(dinput, didevtype_mouse, dinput_mouse_enum, &machine, DIEDFL_ATTACHEDONLY);
		if (result != DI_OK)
			fatalerror("DirectInput: Unable to enumerate mice (result=%08X)\n", (UINT32)result);
	}

	// initialize joystick devices
	result = IDirectInput_EnumDevices(dinput, didevtype_joystick, dinput_joystick_enum, &machine, DIEDFL_ATTACHEDONLY);
	if (result != DI_OK)
		fatalerror("DirectInput: Unable to enumerate joysticks (result=%08X)\n", (UINT32)result);
}


//============================================================
//  dinput_exit
//============================================================

static void dinput_exit(running_machine &machine)
{
	// release all our devices
	while (joystick_list != NULL && joystick_list->dinput.device != NULL)
		dinput_device_release(joystick_list);
	while (lightgun_list != NULL)
		generic_device_free(lightgun_list);
	while (mouse_list != NULL && mouse_list->dinput.device != NULL)
		dinput_device_release(mouse_list);
	while (keyboard_list != NULL && keyboard_list->dinput.device != NULL)
		dinput_device_release(keyboard_list);

	// release DirectInput
	if (dinput != NULL)
		IDirectInput_Release(dinput);
	dinput = NULL;
}


//============================================================
//  dinput_set_dword_property
//============================================================

static HRESULT dinput_set_dword_property(LPDIRECTINPUTDEVICE device, REFGUID property_guid, DWORD object, DWORD how, DWORD value)
{
	DIPROPDWORD dipdw;

	dipdw.diph.dwSize       = sizeof(dipdw);
	dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
	dipdw.diph.dwObj        = object;
	dipdw.diph.dwHow        = how;
	dipdw.dwData            = value;

	return IDirectInputDevice_SetProperty(device, property_guid, &dipdw.diph);
}


//============================================================
//  dinput_device_create
//============================================================

static device_info *dinput_device_create(running_machine &machine, device_info **devlist_head_ptr, LPCDIDEVICEINSTANCE instance, LPCDIDATAFORMAT format1, LPCDIDATAFORMAT format2, DWORD cooperative_level)
{
	device_info *devinfo;
	HRESULT result;

	// allocate memory for the device object
	devinfo = generic_device_alloc(machine, devlist_head_ptr, instance->tszInstanceName);

	// attempt to create a device
	result = IDirectInput_CreateDevice(dinput, WRAP_REFIID(instance->guidInstance), &devinfo->dinput.device, NULL);
	if (result != DI_OK)
		goto error;

	// try to get a version 2 device for it
	result = IDirectInputDevice_QueryInterface(devinfo->dinput.device, WRAP_REFIID(IID_IDirectInputDevice2), (void **)&devinfo->dinput.device2);
	if (result != DI_OK)
		devinfo->dinput.device2 = NULL;

	// get the caps
	devinfo->dinput.caps.dwSize = STRUCTSIZE(DIDEVCAPS);
	result = IDirectInputDevice_GetCapabilities(devinfo->dinput.device, &devinfo->dinput.caps);
	if (result != DI_OK)
		goto error;

	// attempt to set the data format
	devinfo->dinput.format = format1;
	result = IDirectInputDevice_SetDataFormat(devinfo->dinput.device, devinfo->dinput.format);
	if (result != DI_OK)
	{
		// use the secondary format if available
		if (format2 != NULL)
		{
			devinfo->dinput.format = format2;
			result = IDirectInputDevice_SetDataFormat(devinfo->dinput.device, devinfo->dinput.format);
		}
		if (result != DI_OK)
			goto error;
	}

	// set the cooperative level
	result = IDirectInputDevice_SetCooperativeLevel(devinfo->dinput.device, win_window_list->hwnd, cooperative_level);
	if (result != DI_OK)
		goto error;
	return devinfo;

error:
	dinput_device_release(devinfo);
	return NULL;
}


//============================================================
//  dinput_device_release
//============================================================

static void dinput_device_release(device_info *devinfo)
{
	// release the version 2 device if present
	if (devinfo->dinput.device2 != NULL)
		IDirectInputDevice_Release(devinfo->dinput.device2);
	devinfo->dinput.device2 = NULL;

	// release the regular device if present
	if (devinfo->dinput.device != NULL)
		IDirectInputDevice_Release(devinfo->dinput.device);
	devinfo->dinput.device = NULL;

	// free the item list
	generic_device_free(devinfo);
}


//============================================================
//  dinput_device_item_name
//============================================================

static char *dinput_device_item_name(device_info *devinfo, int offset, const TCHAR *defstring, const TCHAR *suffix)
{
	DIDEVICEOBJECTINSTANCE instance = { 0 };
	const TCHAR *namestring = instance.tszName;
	TCHAR *combined;
	HRESULT result;
	char *utf8;

	// query the key name
	instance.dwSize = STRUCTSIZE(DIDEVICEOBJECTINSTANCE);
	result = IDirectInputDevice_GetObjectInfo(devinfo->dinput.device, &instance, offset, DIPH_BYOFFSET);

	// if we got an error and have no default string, just return NULL
	if (result != DI_OK)
	{
		if (defstring == NULL)
			return NULL;
		namestring = defstring;
	}

	// if no suffix, return as-is
	if (suffix == NULL)
		return utf8_from_tstring(namestring);

	// otherwise, allocate space to add the suffix
	combined = global_alloc_array(TCHAR, _tcslen(namestring) + 1 + _tcslen(suffix) + 1);
	_tcscpy(combined, namestring);
	_tcscat(combined, TEXT(" "));
	_tcscat(combined, suffix);

	// convert to UTF8, free the temporary string, and return
	utf8 = utf8_from_tstring(combined);
	global_free(combined);
	return utf8;
}


//============================================================
//  dinput_device_poll
//============================================================

static HRESULT dinput_device_poll(device_info *devinfo)
{
	HRESULT result;

	// first poll the device, then get the state
	if (devinfo->dinput.device2 != NULL)
		IDirectInputDevice2_Poll(devinfo->dinput.device2);
	result = IDirectInputDevice_GetDeviceState(devinfo->dinput.device, devinfo->dinput.format->dwDataSize, &devinfo->joystick.state);

	// handle lost inputs here
	if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED)
	{
		result = IDirectInputDevice_Acquire(devinfo->dinput.device);
		if (result == DI_OK)
			result = IDirectInputDevice_GetDeviceState(devinfo->dinput.device, devinfo->dinput.format->dwDataSize, &devinfo->joystick.state);
	}

	return result;
}


//============================================================
//  dinput_keyboard_enum
//============================================================

static BOOL CALLBACK dinput_keyboard_enum(LPCDIDEVICEINSTANCE instance, LPVOID ref)
{
	running_machine &machine = *(running_machine *)ref;
	device_info *devinfo;
	int keynum;

	// allocate and link in a new device
	devinfo = dinput_device_create(machine, &keyboard_list, instance, &c_dfDIKeyboard, NULL, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
	if (devinfo == NULL)
		goto exit;

	// add the device
	devinfo->device = machine.input().device_class(DEVICE_CLASS_KEYBOARD).add_device(devinfo->name, devinfo);
	devinfo->poll = dinput_keyboard_poll;

	// populate it
	for (keynum = 0; keynum < MAX_KEYS; keynum++)
	{
		input_item_id itemid = keyboard_map_scancode_to_itemid(keynum);
		TCHAR defname[20];
		char *name;

		// generate/fetch the name
		_sntprintf(defname, ARRAY_LENGTH(defname), TEXT("Scan%03d"), keynum);
		name = dinput_device_item_name(devinfo, keynum, defname, NULL);

		// add the item to the device
		devinfo->device->add_item(name, itemid, generic_button_get_state, &devinfo->keyboard.state[keynum]);
		osd_free(name);
	}

exit:
	return DIENUM_CONTINUE;
}


//============================================================
//  dinput_keyboard_poll
//============================================================

static void dinput_keyboard_poll(device_info *devinfo)
{
	HRESULT result = dinput_device_poll(devinfo);

	// for the first device, if we errored, or if we previously reported win32 keys,
	// then ignore the dinput state and poll using win32
	if (devinfo == keyboard_list && (result != DI_OK || keyboard_win32_reported_key_down))
		win32_keyboard_poll(devinfo);
}


//============================================================
//  dinput_mouse_enum
//============================================================

static BOOL CALLBACK dinput_mouse_enum(LPCDIDEVICEINSTANCE instance, LPVOID ref)
{
	device_info *devinfo, *guninfo = NULL;
	running_machine &machine = *(running_machine *)ref;
	int axisnum, butnum;
	HRESULT result;

	// allocate and link in a new device
	devinfo = dinput_device_create(machine, &mouse_list, instance, &c_dfDIMouse2, &c_dfDIMouse, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
	if (devinfo == NULL)
		goto exit;

	// allocate a second device for the gun (unless we are using the shared axis mode)
	// we only support a single gun in dinput mode, so only add one
	if (!lightgun_shared_axis_mode && devinfo == mouse_list)
	{
		guninfo = generic_device_alloc(machine, &lightgun_list, instance->tszInstanceName);
		if (guninfo == NULL)
			goto exit;
	}

	// set relative mode on the mouse device
	result = dinput_set_dword_property(devinfo->dinput.device, DIPROP_AXISMODE, 0, DIPH_DEVICE, DIPROPAXISMODE_REL);
	if (result != DI_OK && result != DI_PROPNOEFFECT)
	{
		mame_printf_error("DirectInput: Unable to set relative mode for mouse %d (%s)\n", generic_device_index(mouse_list, devinfo), devinfo->name);
		goto error;
	}

	// add the device
	devinfo->device = machine.input().device_class(DEVICE_CLASS_MOUSE).add_device(devinfo->name, devinfo);
	devinfo->poll = dinput_mouse_poll;
	if (guninfo != NULL)
	{
		guninfo->device = machine.input().device_class(DEVICE_CLASS_LIGHTGUN).add_device(guninfo->name, guninfo);
		guninfo->poll = win32_lightgun_poll;
	}

	// cap the number of axes and buttons based on the format
	devinfo->dinput.caps.dwAxes = MIN(devinfo->dinput.caps.dwAxes, 3);
	devinfo->dinput.caps.dwButtons = MIN(devinfo->dinput.caps.dwButtons, (devinfo->dinput.format == &c_dfDIMouse) ? 4 : 8);

	// populate the axes
	for (axisnum = 0; axisnum < devinfo->dinput.caps.dwAxes; axisnum++)
	{
		char *name = dinput_device_item_name(devinfo, offsetof(DIMOUSESTATE, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], NULL);

		// add to the mouse device and optionally to the gun device as well
		devinfo->device->add_item(name, (input_item_id)(ITEM_ID_XAXIS + axisnum), generic_axis_get_state, &devinfo->mouse.state.lX + axisnum);
		if (guninfo != NULL && axisnum < 2)
			guninfo->device->add_item(name, (input_item_id)(ITEM_ID_XAXIS + axisnum), generic_axis_get_state, &guninfo->mouse.state.lX + axisnum);

		osd_free(name);
	}

	// populate the buttons
	for (butnum = 0; butnum < devinfo->dinput.caps.dwButtons; butnum++)
	{
		FPTR offset = (FPTR)(&((DIMOUSESTATE *)NULL)->rgbButtons[butnum]);
		char *name = dinput_device_item_name(devinfo, offset, default_button_name(butnum), NULL);

		// add to the mouse device and optionally to the gun device as well
		// note that the gun device points to the mouse buttons rather than its own
		devinfo->device->add_item(name, (input_item_id)(ITEM_ID_BUTTON1 + butnum), generic_button_get_state, &devinfo->mouse.state.rgbButtons[butnum]);
		if (guninfo != NULL)
			guninfo->device->add_item(name, (input_item_id)(ITEM_ID_BUTTON1 + butnum), generic_button_get_state, &devinfo->mouse.state.rgbButtons[butnum]);

		osd_free(name);
	}

exit:
	return DIENUM_CONTINUE;

error:
	if (guninfo != NULL)
		generic_device_free(guninfo);
	if (devinfo != NULL)
		dinput_device_release(devinfo);
	goto exit;
}


//============================================================
//  dinput_mouse_poll
//============================================================

static void dinput_mouse_poll(device_info *devinfo)
{
	// poll
	dinput_device_poll(devinfo);

	// scale the axis data
	devinfo->mouse.state.lX *= INPUT_RELATIVE_PER_PIXEL;
	devinfo->mouse.state.lY *= INPUT_RELATIVE_PER_PIXEL;
	devinfo->mouse.state.lZ *= INPUT_RELATIVE_PER_PIXEL;
}


//============================================================
//  dinput_joystick_enum
//============================================================

static BOOL CALLBACK dinput_joystick_enum(LPCDIDEVICEINSTANCE instance, LPVOID ref)
{
	DWORD cooperative_level = DISCL_FOREGROUND | DISCL_NONEXCLUSIVE;
	int axisnum, axiscount, povnum, butnum;
	running_machine &machine = *(running_machine *)ref;
	device_info *devinfo;
	HRESULT result;

	if (win_window_list != NULL && win_has_menu(win_window_list)) {
		cooperative_level = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE;
	}
	// allocate and link in a new device
	devinfo = dinput_device_create(machine, &joystick_list, instance, &c_dfDIJoystick, NULL, cooperative_level);
	if (devinfo == NULL)
		goto exit;

	// set absolute mode
	result = dinput_set_dword_property(devinfo->dinput.device, DIPROP_AXISMODE, 0, DIPH_DEVICE, DIPROPAXISMODE_ABS);
	if (result != DI_OK && result != DI_PROPNOEFFECT)
		mame_printf_warning("DirectInput: Unable to set absolute mode for joystick %d (%s)\n", generic_device_index(joystick_list, devinfo), devinfo->name);

	// turn off deadzone; we do our own calculations
	result = dinput_set_dword_property(devinfo->dinput.device, DIPROP_DEADZONE, 0, DIPH_DEVICE, 0);
	if (result != DI_OK && result != DI_PROPNOEFFECT)
		mame_printf_warning("DirectInput: Unable to reset deadzone for joystick %d (%s)\n", generic_device_index(joystick_list, devinfo), devinfo->name);

	// turn off saturation; we do our own calculations
	result = dinput_set_dword_property(devinfo->dinput.device, DIPROP_SATURATION, 0, DIPH_DEVICE, 10000);
	if (result != DI_OK && result != DI_PROPNOEFFECT)
		mame_printf_warning("DirectInput: Unable to reset saturation for joystick %d (%s)\n", generic_device_index(joystick_list, devinfo), devinfo->name);

	// cap the number of axes, POVs, and buttons based on the format
	devinfo->dinput.caps.dwAxes = MIN(devinfo->dinput.caps.dwAxes, 8);
	devinfo->dinput.caps.dwPOVs = MIN(devinfo->dinput.caps.dwPOVs, 4);
	devinfo->dinput.caps.dwButtons = MIN(devinfo->dinput.caps.dwButtons, 128);

	// add the device
	devinfo->device = machine.input().device_class(DEVICE_CLASS_JOYSTICK).add_device(devinfo->name, devinfo);
	devinfo->poll = dinput_joystick_poll;

	// populate the axes
	for (axisnum = axiscount = 0; axiscount < devinfo->dinput.caps.dwAxes && axisnum < 8; axisnum++)
	{
		DIPROPRANGE dipr;
		char *name;

		// fetch the range of this axis
		dipr.diph.dwSize = sizeof(dipr);
		dipr.diph.dwHeaderSize = sizeof(dipr.diph);
		dipr.diph.dwObj = offsetof(DIJOYSTATE2, lX) + axisnum * sizeof(LONG);
		dipr.diph.dwHow = DIPH_BYOFFSET;
		result = IDirectInputDevice_GetProperty(devinfo->dinput.device, DIPROP_RANGE, &dipr.diph);
		if (result != DI_OK)
			continue;
		devinfo->joystick.rangemin[axisnum] = dipr.lMin;
		devinfo->joystick.rangemax[axisnum] = dipr.lMax;

		// populate the item description as well
		name = dinput_device_item_name(devinfo, offsetof(DIJOYSTATE2, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], NULL);
		devinfo->device->add_item(name, (input_item_id)(ITEM_ID_XAXIS + axisnum), generic_axis_get_state, &devinfo->joystick.state.lX + axisnum);
		osd_free(name);

		axiscount++;
	}

	// populate the POVs
	for (povnum = 0; povnum < devinfo->dinput.caps.dwPOVs; povnum++)
	{
		char *name;

		// left
		name = dinput_device_item_name(devinfo, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("L"));
		devinfo->device->add_item(name, ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, (void *)(FPTR)(povnum * 4 + POVDIR_LEFT));
		osd_free(name);

		// right
		name = dinput_device_item_name(devinfo, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("R"));
		devinfo->device->add_item(name, ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, (void *)(FPTR)(povnum * 4 + POVDIR_RIGHT));
		osd_free(name);

		// up
		name = dinput_device_item_name(devinfo, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("U"));
		devinfo->device->add_item(name, ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, (void *)(FPTR)(povnum * 4 + POVDIR_UP));
		osd_free(name);

		// down
		name = dinput_device_item_name(devinfo, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("D"));
		devinfo->device->add_item(name, ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, (void *)(FPTR)(povnum * 4 + POVDIR_DOWN));
		osd_free(name);
	}

	// populate the buttons
	for (butnum = 0; butnum < devinfo->dinput.caps.dwButtons; butnum++)
	{
		FPTR offset = (FPTR)(&((DIJOYSTATE2 *)NULL)->rgbButtons[butnum]);
		char *name = dinput_device_item_name(devinfo, offset, default_button_name(butnum), NULL);
		devinfo->device->add_item(name, (butnum < 16) ? (input_item_id)(ITEM_ID_BUTTON1 + butnum) : ITEM_ID_OTHER_SWITCH, generic_button_get_state, &devinfo->joystick.state.rgbButtons[butnum]);
		osd_free(name);
	}

exit:
	return DIENUM_CONTINUE;
}


//============================================================
//  dinput_joystick_poll
//============================================================

static void dinput_joystick_poll(device_info *devinfo)
{
	int axisnum;

	// poll the device first
	dinput_device_poll(devinfo);

	// normalize axis values
	for (axisnum = 0; axisnum < 8; axisnum++)
	{
		LONG *axis = (&devinfo->joystick.state.lX) + axisnum;
		*axis = normalize_absolute_axis(*axis, devinfo->joystick.rangemin[axisnum], devinfo->joystick.rangemax[axisnum]);
	}
}


//============================================================
//  dinput_joystick_pov_get_state
//============================================================

static INT32 dinput_joystick_pov_get_state(void *device_internal, void *item_internal)
{
	device_info *devinfo = (device_info *)device_internal;
	int povnum = (FPTR)item_internal / 4;
	int povdir = (FPTR)item_internal % 4;
	INT32 result = 0;
	DWORD pov;

	// get the current state
	poll_if_necessary(devinfo->machine());
	pov = devinfo->joystick.state.rgdwPOV[povnum];

	// if invalid, return 0
	if ((pov & 0xffff) == 0xffff)
		return result;

	// return the current state
	switch (povdir)
	{
		case POVDIR_LEFT:	result = (pov >= 22500 && pov <= 31500);	break;
		case POVDIR_RIGHT:	result = (pov >= 4500 && pov <= 13500);		break;
		case POVDIR_UP:		result = (pov >= 31500 || pov <= 4500);		break;
		case POVDIR_DOWN:	result = (pov >= 13500 && pov <= 22500);	break;
	}
	return result;
}


//============================================================
//  rawinput_init
//============================================================

static void rawinput_init(running_machine &machine)
{
	RAWINPUTDEVICELIST *devlist = NULL;
	int device_count, devnum, regcount;
	RAWINPUTDEVICE reglist[2];
	HMODULE user32;

	// we need pause and exit callbacks
	machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(rawinput_exit), &machine));

	// look in user32 for the raw input APIs
	user32 = LoadLibrary(TEXT("user32.dll"));
	if (user32 == NULL)
		goto error;

	// look up the entry points
	register_rawinput_devices = (register_rawinput_devices_ptr)GetProcAddress(user32, "RegisterRawInputDevices");
	get_rawinput_device_list = (get_rawinput_device_list_ptr)GetProcAddress(user32, "GetRawInputDeviceList");
	get_rawinput_device_info = (get_rawinput_device_info_ptr)GetProcAddress(user32, "GetRawInputDeviceInfo" UNICODE_SUFFIX);
	get_rawinput_data = (get_rawinput_data_ptr)GetProcAddress(user32, "GetRawInputData");
	if (register_rawinput_devices == NULL || get_rawinput_device_list == NULL || get_rawinput_device_info == NULL || get_rawinput_data == NULL)
		goto error;
	mame_printf_verbose("RawInput: APIs detected\n");

	// get the number of devices, allocate a device list, and fetch it
	if ((*get_rawinput_device_list)(NULL, &device_count, sizeof(*devlist)) != 0)
		goto error;
	if (device_count == 0)
		goto error;
	devlist = global_alloc_array(RAWINPUTDEVICELIST, device_count);
	if ((*get_rawinput_device_list)(devlist, &device_count, sizeof(*devlist)) == -1)
		goto error;

	// iterate backwards through devices; new devices are added at the head
	for (devnum = device_count - 1; devnum >= 0; devnum--)
	{
		RAWINPUTDEVICELIST *device = &devlist[devnum];

		// handle keyboards
		if (device->dwType == RIM_TYPEKEYBOARD && !FORCE_DIRECTINPUT)
			rawinput_keyboard_enum(machine, device);

		// handle mice
		else if (device->dwType == RIM_TYPEMOUSE && !FORCE_DIRECTINPUT)
			rawinput_mouse_enum(machine, device);
	}

	// finally, register to receive raw input WM_INPUT messages
	regcount = 0;
	if (keyboard_list != NULL)
	{
		reglist[regcount].usUsagePage = 0x01;
		reglist[regcount].usUsage = 0x06;
		reglist[regcount].dwFlags = RIDEV_INPUTSINK;
		reglist[regcount].hwndTarget = win_window_list->hwnd;
		regcount++;
	}
	if (mouse_list != NULL)
	{
		reglist[regcount].usUsagePage = 0x01;
		reglist[regcount].usUsage = 0x02;
		reglist[regcount].dwFlags = 0;
		reglist[regcount].hwndTarget = win_window_list->hwnd;
		regcount++;
	}

	// if the registration fails, we need to back off
	if (regcount > 0)
		if (!(*register_rawinput_devices)(reglist, regcount, sizeof(reglist[0])))
			goto error;

	global_free(devlist);
	return;

error:
	if (devlist != NULL)
		global_free(devlist);
}


//============================================================
//  rawinput_exit
//============================================================

static void rawinput_exit(running_machine &machine)
{
	// release all our devices
	while (lightgun_list != NULL && lightgun_list->rawinput.device != NULL)
		rawinput_device_release(lightgun_list);
	while (mouse_list != NULL && mouse_list->rawinput.device != NULL)
		rawinput_device_release(mouse_list);
	while (keyboard_list != NULL && keyboard_list->rawinput.device != NULL)
		rawinput_device_release(keyboard_list);
}


//============================================================
//  rawinput_device_create
//============================================================

static device_info *rawinput_device_create(running_machine &machine, device_info **devlist_head_ptr, PRAWINPUTDEVICELIST device)
{
	device_info *devinfo = NULL;
	TCHAR *tname = NULL;
	INT name_length;

	// determine the length of the device name, allocate it, and fetch it
	if ((*get_rawinput_device_info)(device->hDevice, RIDI_DEVICENAME, NULL, &name_length) != 0)
		goto error;
	tname = global_alloc_array(TCHAR, name_length);
	if ((*get_rawinput_device_info)(device->hDevice, RIDI_DEVICENAME, tname, &name_length) == -1)
		goto error;

	// if this is an RDP name, skip it
	if (_tcsstr(tname, TEXT("Root#RDP_")) != NULL)
		goto error;

	// improve the name and then allocate a device
	tname = rawinput_device_improve_name(tname);
	devinfo = generic_device_alloc(machine, devlist_head_ptr, tname);
	global_free(tname);

	// copy the handle
	devinfo->rawinput.device = device->hDevice;
	return devinfo;

error:
	if (tname != NULL)
		global_free(tname);
	if (devinfo != NULL)
		rawinput_device_release(devinfo);
	return NULL;
}


//============================================================
//  rawinput_device_release
//============================================================

static void rawinput_device_release(device_info *devinfo)
{
	// free the item list
	generic_device_free(devinfo);
}


//============================================================
//  rawinput_device_improve_name
//============================================================

static TCHAR *rawinput_device_improve_name(TCHAR *name)
{
	static const TCHAR usbbasepath[] = TEXT("SYSTEM\\CurrentControlSet\\Enum\\USB");
	static const TCHAR basepath[] = TEXT("SYSTEM\\CurrentControlSet\\Enum\\");
	TCHAR *regstring = NULL;
	TCHAR *parentid = NULL;
	TCHAR *regpath = NULL;
	const TCHAR *chsrc;
	HKEY regkey = NULL;
	int usbindex;
	TCHAR *chdst;
	LONG result;

	// The RAW name received is formatted as:
	//   \??\type-id#hardware-id#instance-id#{DeviceClasses-id}
	// XP starts with "\??\"
	// Vista64 starts with "\\?\"

	// ensure the name is something we can handle
	if (_tcsncmp(name, TEXT("\\\\?\\"), 4) != 0 && _tcsncmp(name, TEXT("\\??\\"), 4) != 0)
		return name;

	// allocate a temporary string and concatenate the base path plus the name
	regpath = global_alloc_array(TCHAR, _tcslen(basepath) + 1 + _tcslen(name));
	_tcscpy(regpath, basepath);
	chdst = regpath + _tcslen(regpath);

	// convert all # to \ in the name
	for (chsrc = name + 4; *chsrc != 0; chsrc++)
		*chdst++ = (*chsrc == '#') ? '\\' : *chsrc;
	*chdst = 0;

	// remove the final chunk
	chdst = _tcsrchr(regpath, '\\');
	if (chdst == NULL)
		goto exit;
	*chdst = 0;

	// now try to open the registry key
	result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &regkey);
	if (result != ERROR_SUCCESS)
		goto exit;

	// fetch the device description; if it exists, we are finished
	regstring = reg_query_string(regkey, TEXT("DeviceDesc"));
	if (regstring != NULL)
		goto convert;

	// close this key
	RegCloseKey(regkey);
	regkey = NULL;

	// if the key name does not contain "HID", it's not going to be in the USB tree; give up
	if (_tcsstr(regpath, TEXT("HID")) == NULL)
		goto exit;

	// extract the expected parent ID from the regpath
	parentid = _tcsrchr(regpath, '\\');
	if (parentid == NULL)
		goto exit;
	parentid++;

	// open the USB key
	result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, usbbasepath, 0, KEY_READ, &regkey);
	if (result != ERROR_SUCCESS)
		goto exit;

	// enumerate the USB key
	for (usbindex = 0; result == ERROR_SUCCESS && regstring == NULL; usbindex++)
	{
		TCHAR keyname[MAX_PATH];
		DWORD namelen;

		// get the next enumerated subkey and scan it
		namelen = ARRAY_LENGTH(keyname) - 1;
		result = RegEnumKeyEx(regkey, usbindex, keyname, &namelen, NULL, NULL, NULL, NULL);
		if (result == ERROR_SUCCESS)
		{
			LONG subresult;
			int subindex;
			HKEY subkey;

			// open the subkey
			subresult = RegOpenKeyEx(regkey, keyname, 0, KEY_READ, &subkey);
			if (subresult != ERROR_SUCCESS)
				continue;

			// enumerate the subkey
			for (subindex = 0; subresult == ERROR_SUCCESS && regstring == NULL; subindex++)
			{
				// get the next enumerated subkey and scan it
				namelen = ARRAY_LENGTH(keyname) - 1;
				subresult = RegEnumKeyEx(subkey, subindex, keyname, &namelen, NULL, NULL, NULL, NULL);
				if (subresult == ERROR_SUCCESS)
				{
					TCHAR *endparentid;
					LONG endresult;
					HKEY endkey;

					// open this final key
					endresult = RegOpenKeyEx(subkey, keyname, 0, KEY_READ, &endkey);
					if (endresult != ERROR_SUCCESS)
						continue;

					// do we have a match?
					endparentid = reg_query_string(endkey, TEXT("ParentIdPrefix"));
					if (endparentid != NULL && _tcsncmp(parentid, endparentid, _tcslen(endparentid)) == 0)
						regstring = reg_query_string(endkey, TEXT("DeviceDesc"));

					// free memory and close the key
					if (endparentid != NULL)
						global_free(endparentid);
					RegCloseKey(endkey);
				}
			}

			// close the subkey
			RegCloseKey(subkey);
		}
	}

	// if we didn't find anything, go to the exit
	if (regstring == NULL)
		goto exit;

convert:
	// replace the name with the nicer one
	global_free(name);

	// remove anything prior to the final semicolon
	chsrc = _tcsrchr(regstring, ';');
	if (chsrc != NULL)
		chsrc++;
	else
		chsrc = regstring;
	name = global_alloc_array(TCHAR, _tcslen(chsrc) + 1);
	_tcscpy(name, chsrc);

exit:
	if (regstring != NULL)
		global_free(regstring);
	if (regpath != NULL)
		global_free(regpath);
	if (regkey != NULL)
		RegCloseKey(regkey);

	return name;
}


//============================================================
//  rawinput_keyboard_enum
//============================================================

static void rawinput_keyboard_enum(running_machine &machine, PRAWINPUTDEVICELIST device)
{
	device_info *devinfo;
	int keynum;

	// allocate and link in a new device
	devinfo = rawinput_device_create(machine, &keyboard_list, device);
	if (devinfo == NULL)
		return;

	// add the device
	devinfo->device = machine.input().device_class(DEVICE_CLASS_KEYBOARD).add_device(devinfo->name, devinfo);

	// populate it
	for (keynum = 0; keynum < MAX_KEYS; keynum++)
	{
		input_item_id itemid = keyboard_map_scancode_to_itemid(keynum);
		TCHAR keyname[100];
		char *name;

		// generate the name
		if (GetKeyNameText(((keynum & 0x7f) << 16) | ((keynum & 0x80) << 17), keyname, ARRAY_LENGTH(keyname)) == 0)
			_sntprintf(keyname, ARRAY_LENGTH(keyname), TEXT("Scan%03d"), keynum);
		name = utf8_from_tstring(keyname);

		// add the item to the device
		devinfo->device->add_item(name, itemid, generic_button_get_state, &devinfo->keyboard.state[keynum]);
		osd_free(name);
	}
}


//============================================================
//  rawinput_keyboard_update
//============================================================

static void rawinput_keyboard_update(HANDLE device, RAWKEYBOARD *data)
{
	device_info *devinfo;

	// find the keyboard in the list and process
	for (devinfo = keyboard_list; devinfo != NULL; devinfo = devinfo->next)
		if (devinfo->rawinput.device == device)
		{
			// determine the full DIK-compatible scancode
			UINT8 scancode = (data->MakeCode & 0x7f) | ((data->Flags & RI_KEY_E0) ? 0x80 : 0x00);

			// scancode 0xaa is a special shift code we need to ignore
			if (scancode == 0xaa)
				break;

			// set or clear the key
			if (!(data->Flags & RI_KEY_BREAK))
				devinfo->keyboard.state[scancode] = 0x80;
			else
				devinfo->keyboard.state[scancode] = 0x00;
			break;
		}
}


//============================================================
//  rawinput_mouse_enum
//============================================================

static void rawinput_mouse_enum(running_machine &machine, PRAWINPUTDEVICELIST device)
{
	device_info *devinfo, *guninfo = NULL;
	int axisnum, butnum;

	// allocate and link in a new mouse device
	devinfo = rawinput_device_create(machine, &mouse_list, device);
	if (devinfo == NULL)
		return;
	devinfo->poll = rawinput_mouse_poll;

	// allocate a second device for the gun (unless we are using the shared axis mode)
	if (!lightgun_shared_axis_mode)
	{
		guninfo = rawinput_device_create(machine, &lightgun_list, device);
		assert(guninfo != NULL);
	}

	// add the device
	devinfo->device = machine.input().device_class(DEVICE_CLASS_MOUSE).add_device(devinfo->name, devinfo);
	if (guninfo != NULL)
	{
		guninfo->device = machine.input().device_class(DEVICE_CLASS_LIGHTGUN).add_device(guninfo->name, guninfo);
		guninfo->poll = NULL;
	}

	// populate the axes
	for (axisnum = 0; axisnum < 3; axisnum++)
	{
		char *name = utf8_from_tstring(default_axis_name[axisnum]);

		// add to the mouse device and optionally to the gun device as well
		devinfo->device->add_item(name, (input_item_id)(ITEM_ID_XAXIS + axisnum), generic_axis_get_state, &devinfo->mouse.state.lX + axisnum);
		if (guninfo != NULL && axisnum < 2)
			guninfo->device->add_item(name, (input_item_id)(ITEM_ID_XAXIS + axisnum), generic_axis_get_state, &guninfo->mouse.state.lX + axisnum);

		osd_free(name);
	}

	// populate the buttons
	for (butnum = 0; butnum < 5; butnum++)
	{
		char *name = utf8_from_tstring(default_button_name(butnum));

		// add to the mouse device and optionally to the gun device as well
		devinfo->device->add_item(name, (input_item_id)(ITEM_ID_BUTTON1 + butnum), generic_button_get_state, &devinfo->mouse.state.rgbButtons[butnum]);
		if (guninfo != NULL)
			guninfo->device->add_item(name, (input_item_id)(ITEM_ID_BUTTON1 + butnum), generic_button_get_state, &guninfo->mouse.state.rgbButtons[butnum]);

		osd_free(name);
	}
}


//============================================================
//  rawinput_mouse_update
//============================================================

static void rawinput_mouse_update(HANDLE device, RAWMOUSE *data)
{
	device_info *devlist = (data->usFlags & MOUSE_MOVE_ABSOLUTE) ? lightgun_list : mouse_list;
	device_info *devinfo;

	// find the mouse in the list and process
	for (devinfo = devlist; devinfo != NULL; devinfo = devinfo->next)
		if (devinfo->rawinput.device == device)
		{
			// if we got relative data, update it as a mouse
			if (!(data->usFlags & MOUSE_MOVE_ABSOLUTE))
			{
				devinfo->mouse.raw_x += data->lLastX * INPUT_RELATIVE_PER_PIXEL;
				devinfo->mouse.raw_y += data->lLastY * INPUT_RELATIVE_PER_PIXEL;

				// update zaxis
				if (data->usButtonFlags & RI_MOUSE_WHEEL)
					devinfo->mouse.raw_z += (INT16)data->usButtonData * INPUT_RELATIVE_PER_PIXEL;
			}

			// otherwise, update it as a lightgun
			else
			{
				devinfo->mouse.state.lX = normalize_absolute_axis(data->lLastX, 0, 0xffff);
				devinfo->mouse.state.lY = normalize_absolute_axis(data->lLastY, 0, 0xffff);
			}

			// update the button states; always update the corresponding mouse buttons
			if (data->usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) devinfo->mouse.state.rgbButtons[0] = 0x80;
			if (data->usButtonFlags & RI_MOUSE_BUTTON_1_UP)   devinfo->mouse.state.rgbButtons[0] = 0x00;
			if (data->usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) devinfo->mouse.state.rgbButtons[1] = 0x80;
			if (data->usButtonFlags & RI_MOUSE_BUTTON_2_UP)   devinfo->mouse.state.rgbButtons[1] = 0x00;
			if (data->usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) devinfo->mouse.state.rgbButtons[2] = 0x80;
			if (data->usButtonFlags & RI_MOUSE_BUTTON_3_UP)   devinfo->mouse.state.rgbButtons[2] = 0x00;
			if (data->usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) devinfo->mouse.state.rgbButtons[3] = 0x80;
			if (data->usButtonFlags & RI_MOUSE_BUTTON_4_UP)   devinfo->mouse.state.rgbButtons[3] = 0x00;
			if (data->usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) devinfo->mouse.state.rgbButtons[4] = 0x80;
			if (data->usButtonFlags & RI_MOUSE_BUTTON_5_UP)   devinfo->mouse.state.rgbButtons[4] = 0x00;
			break;
		}
}


//============================================================
//  rawinput_mouse_poll
//============================================================

static void rawinput_mouse_poll(device_info *devinfo)
{
	poll_if_necessary(devinfo->machine());

	// copy the accumulated raw state to the actual state
	osd_lock_acquire(input_lock);
	devinfo->mouse.state.lX = devinfo->mouse.raw_x;
	devinfo->mouse.state.lY = devinfo->mouse.raw_y;
	devinfo->mouse.state.lZ = devinfo->mouse.raw_z;
	devinfo->mouse.raw_x = 0;
	devinfo->mouse.raw_y = 0;
	devinfo->mouse.raw_z = 0;
	osd_lock_release(input_lock);
}


//============================================================
//  reg_query_string
//============================================================

static TCHAR *reg_query_string(HKEY key, const TCHAR *path)
{
	TCHAR *buffer;
	DWORD datalen;
	LONG result;

	// first query to get the length
	result = RegQueryValueEx(key, path, NULL, NULL, NULL, &datalen);
	if (result != ERROR_SUCCESS)
		return NULL;

	// allocate a buffer
	buffer = global_alloc_array(TCHAR, datalen + sizeof(*buffer));
	buffer[datalen / sizeof(*buffer)] = 0;

	// now get the actual data
	result = RegQueryValueEx(key, path, NULL, NULL, (LPBYTE)buffer, &datalen);
	if (result == ERROR_SUCCESS)
		return buffer;

	// otherwise return a NULL buffer
	global_free(buffer);
	return NULL;
}


//============================================================
//  default_button_name
//============================================================

static const TCHAR *default_button_name(int which)
{
	static TCHAR buffer[20];
	_sntprintf(buffer, ARRAY_LENGTH(buffer), TEXT("B%d"), which);
	return buffer;
}


//============================================================
//  default_pov_name
//============================================================

static const TCHAR *default_pov_name(int which)
{
	static TCHAR buffer[20];
	_sntprintf(buffer, ARRAY_LENGTH(buffer), TEXT("POV%d"), which);
	return buffer;
}
