//============================================================
//
// ledutil.c - Win32 example code that tracks changing
// outputs and updates the keyboard LEDs in response
//
//============================================================
//
// 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.
//
//============================================================
//
// This is sample code. To use it as a starting point, you
// should do the following:
//
// 1. Change the CLIENT_ID define to something unique.
//
// 2. Change the WINDOW_CLASS and WINDOW_NAME defines to
// something unique.
//
// 3. Delete all the code from the >8 snip 8< comment and
// downward.
//
// 4. Implement the following functions:
//
// output_startup - called at app init time
// output_shutdown - called before the app exits
// output_mame_start - called when MAME starts
// output_mame_stop - called when MAME exits
// output_set_state - called whenever state changes
//
//============================================================
// standard windows headers
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winioctl.h>
// standard C headers
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// MAME output header file
typedef int running_machine;
#include "osdcomm.h"
#include "output.h"
//============================================================
// DEBUGGING
//============================================================
// note you need to compile as a console app to have any of
// these printfs show up
#define DEBUG_VERSION 0
#if DEBUG_VERSION
#define DEBUG_PRINTF(x) printf x
#else
#define DEBUG_PRINTF(x)
#endif
//============================================================
// CONSTANTS
//============================================================
// unique client ID
#define CLIENT_ID (('M' << 24) | ('L' << 16) | ('E' << 8) | ('D' << 0))
// LED methods
#define LED_METHOD_PS2 0
#define LED_METHOD_USB 1
#define LED_METHOD_WIN9X 2
// window parameters
#define WINDOW_CLASS TEXT("LEDSample")
#define WINDOW_NAME TEXT("LEDSample")
// window styles
#define WINDOW_STYLE WS_OVERLAPPEDWINDOW
#define WINDOW_STYLE_EX 0
// Define the keyboard indicators.
// (Definitions borrowed from ntddkbd.h)
#define IOCTL_KEYBOARD_SET_INDICATORS CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0002, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_KEYBOARD_QUERY_TYPEMATIC CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0008, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_KEYBOARD_QUERY_INDICATORS CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0010, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define KEYBOARD_SCROLL_LOCK_ON 1
#define KEYBOARD_NUM_LOCK_ON 2
#define KEYBOARD_CAPS_LOCK_ON 4
//============================================================
// TYPE DEFINITIONS
//============================================================
struct KEYBOARD_INDICATOR_PARAMETERS
{
USHORT UnitId; // Unit identifier.
USHORT LedFlags; // LED indicator state.
};
struct id_map_entry
{
id_map_entry * next;
const char * name;
WPARAM id;
};
//============================================================
// GLOBAL VARIABLES
//============================================================
static int ledmethod;
static int original_state;
static int current_state;
static int pause_state;
static HANDLE hKbdDev;
static HWND mame_target;
static HWND listener_hwnd;
static id_map_entry * idmaplist;
// message IDs
static UINT om_mame_start;
static UINT om_mame_stop;
static UINT om_mame_update_state;
static UINT om_mame_register_client;
static UINT om_mame_unregister_client;
static UINT om_mame_get_id_string;
//============================================================
// FUNCTION PROTOTYPES
//============================================================
static int create_window_class(void);
static LRESULT CALLBACK listener_window_proc(HWND wnd, UINT message, WPARAM wparam, LPARAM lparam);
static LRESULT handle_mame_start(WPARAM wparam, LPARAM lparam);
static LRESULT handle_mame_stop(WPARAM wparam, LPARAM lparam);
static LRESULT handle_copydata(WPARAM wparam, LPARAM lparam);
static void reset_id_to_outname_cache(void);
static const char *map_id_to_outname(WPARAM id);
static LRESULT handle_update_state(WPARAM wparam, LPARAM lparam);
// these functions provide the meat
static void output_startup(const char *commandline);
static void output_mame_start(void);
static void output_set_state(const char *name, INT32 state);
static void output_mame_stop(void);
static void output_shutdown(void);
static int led_get_state(void);
static void led_set_state(int state);
//============================================================
// main
//============================================================
int main(int argc, char *argv[])
{
const char *arg = (argc > 1) ? argv[1] : "";
int exitcode = 1;
HWND otherwnd;
MSG message;
int result;
// see if there is another instance of us running
otherwnd = FindWindow(WINDOW_CLASS, WINDOW_NAME);
// if the argument is "-kill", post a close message
if (strcmp(arg, "-kill") == 0)
{
if (otherwnd != NULL)
PostMessage(otherwnd, WM_QUIT, 0, 0);
return (otherwnd != NULL) ? 1 : 0;
}
// if we had another instance, defer to it
if (otherwnd != NULL)
return 0;
// call the startup code
output_startup(arg);
// create our window class
result = create_window_class();
if (result != 0)
goto error;
// create a window
listener_hwnd = CreateWindowEx(
WINDOW_STYLE_EX,
WINDOW_CLASS,
WINDOW_NAME,
WINDOW_STYLE,
0, 0,
1, 1,
NULL,
NULL,
GetModuleHandle(NULL),
NULL);
if (listener_hwnd == NULL)
goto error;
// allocate message ids
om_mame_start = RegisterWindowMessage(OM_MAME_START);
if (om_mame_start == 0)
goto error;
om_mame_stop = RegisterWindowMessage(OM_MAME_STOP);
if (om_mame_stop == 0)
goto error;
om_mame_update_state = RegisterWindowMessage(OM_MAME_UPDATE_STATE);
if (om_mame_update_state == 0)
goto error;
om_mame_register_client = RegisterWindowMessage(OM_MAME_REGISTER_CLIENT);
if (om_mame_register_client == 0)
goto error;
om_mame_unregister_client = RegisterWindowMessage(OM_MAME_UNREGISTER_CLIENT);
if (om_mame_unregister_client == 0)
goto error;
om_mame_get_id_string = RegisterWindowMessage(OM_MAME_GET_ID_STRING);
if (om_mame_get_id_string == 0)
goto error;
// see if MAME is already running
otherwnd = FindWindow(OUTPUT_WINDOW_CLASS, OUTPUT_WINDOW_NAME);
if (otherwnd != NULL)
handle_mame_start((WPARAM)otherwnd, 0);
// process messages
while (GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
// reset on the way out if still live
if (mame_target != NULL)
handle_mame_stop((WPARAM)mame_target, 0);
exitcode = 0;
error:
// call the shutdown code
output_shutdown();
return exitcode;
}
//============================================================
// create_window_class
//============================================================
static int create_window_class(void)
{
static int classes_created = FALSE;
/* only do this once */
if (!classes_created)
{
WNDCLASS wc = { 0 };
// initialize the description of the window class
wc.lpszClassName = WINDOW_CLASS;
wc.hInstance = GetModuleHandle(NULL);
wc.lpfnWndProc = listener_window_proc;
// register the class; fail if we can't
if (!RegisterClass(&wc))
return 1;
classes_created = TRUE;
}
return 0;
}
//============================================================
// window_proc
//============================================================
static LRESULT CALLBACK listener_window_proc(HWND wnd, UINT message, WPARAM wparam, LPARAM lparam)
{
// OM_MAME_START: register ourselves with the new MAME (first instance only)
if (message == om_mame_start)
return handle_mame_start(wparam, lparam);
// OM_MAME_STOP: no need to unregister, just note that we've stopped caring and reset the LEDs
else if (message == om_mame_stop)
return handle_mame_stop(wparam, lparam);
// OM_MAME_UPDATE_STATE: update the state of this item if we care
else if (message == om_mame_update_state)
return handle_update_state(wparam, lparam);
// WM_COPYDATA: extract the string and create an ID map entry
else if (message == WM_COPYDATA)
return handle_copydata(wparam, lparam);
// everything else is default
else
return DefWindowProc(wnd, message, wparam, lparam);
}
//============================================================
// handle_mame_start
//============================================================
static LRESULT handle_mame_start(WPARAM wparam, LPARAM lparam)
{
DEBUG_PRINTF(("mame_start (%08X)\n", (UINT32)wparam));
// make this the targeted version of MAME
mame_target = (HWND)wparam;
// initialize the LED states
output_mame_start();
reset_id_to_outname_cache();
// register ourselves as a client
PostMessage(mame_target, om_mame_register_client, (WPARAM)listener_hwnd, CLIENT_ID);
// get the game name
map_id_to_outname(0);
return 0;
}
//============================================================
// handle_mame_stop
//============================================================
static LRESULT handle_mame_stop(WPARAM wparam, LPARAM lparam)
{
DEBUG_PRINTF(("mame_stop (%08X)\n", (UINT32)wparam));
// ignore if this is not the instance we care about
if (mame_target != (HWND)wparam)
return 1;
// clear our target out
mame_target = NULL;
reset_id_to_outname_cache();
// reset the LED states
output_mame_stop();
return 0;
}
//============================================================
// handle_copydata
//============================================================
static LRESULT handle_copydata(WPARAM wparam, LPARAM lparam)
{
COPYDATASTRUCT *copydata = (COPYDATASTRUCT *)lparam;
copydata_id_string *data = (copydata_id_string *)copydata->lpData;
id_map_entry *entry;
char *string;
DEBUG_PRINTF(("copydata (%08X)\n", (UINT32)wparam));
// ignore requests we don't care about
if (mame_target != (HWND)wparam)
return 1;
// allocate memory
entry = (id_map_entry *)malloc(sizeof(*entry));
if (entry == NULL)
return 0;
string = (char *)malloc(strlen(data->string) + 1);
if (string == NULL)
{
free(entry);
return 0;
}
// if all allocations worked, make a new entry
entry->next = idmaplist;
entry->name = string;
entry->id = data->id;
// copy the string and hook us into the list
strcpy(string, data->string);
idmaplist = entry;
DEBUG_PRINTF((" id %d = '%s'\n", entry->id, entry->name));
return 0;
}
//============================================================
// reset_id_to_outname_cache
//============================================================
static void reset_id_to_outname_cache(void)
{
// free our ID list
while (idmaplist != NULL)
{
id_map_entry *temp = idmaplist;
idmaplist = temp->next;
free((void*)temp->name);
free(temp);
}
}
//============================================================
// map_id_to_outname
//============================================================
static const char *map_id_to_outname(WPARAM id)
{
id_map_entry *entry;
// see if we have an entry in our map
for (entry = idmaplist; entry != NULL; entry = entry->next)
if (entry->id == id)
return entry->name;
// no entry yet; we have to ask
SendMessage(mame_target, om_mame_get_id_string, (WPARAM)listener_hwnd, id);
// now see if we have the entry in our map
for (entry = idmaplist; entry != NULL; entry = entry->next)
if (entry->id == id)
return entry->name;
// if not, use an empty string
return "";
}
//============================================================
// handle_update_state
//============================================================
static LRESULT handle_update_state(WPARAM wparam, LPARAM lparam)
{
DEBUG_PRINTF(("update_state: id=%d state=%d\n", (UINT32)wparam, (UINT32)lparam));
output_set_state(map_id_to_outname(wparam), lparam);
return 0;
}
//
// END BOILERPLATE CODE
//
// ------------------------>8 snip 8<-------------------------
//
// BEGIN LED-SPECIFIC CODE
//
//============================================================
// output_startup
//============================================================
static void output_startup(const char *commandline)
{
OSVERSIONINFO osinfo = { sizeof(OSVERSIONINFO) };
// default to PS/2, override if USB is specified as a parameter
ledmethod = LED_METHOD_PS2;
if (commandline != NULL && strcmp(commandline, "-usb") == 0)
ledmethod = LED_METHOD_USB;
// force Win9x method if we're on Win 9x
GetVersionEx(&osinfo);
if (osinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
ledmethod = LED_METHOD_WIN9X;
// output the method
switch (ledmethod)
{
case LED_METHOD_PS2:
DEBUG_PRINTF(("Using PS/2 method\n"));
break;
case LED_METHOD_USB:
DEBUG_PRINTF(("Using USB method\n"));
break;
case LED_METHOD_WIN9X:
DEBUG_PRINTF(("Using Win9x method\n"));
break;
}
}
//============================================================
// output_shutdown
//============================================================
static void output_shutdown(void)
{
// nothing to do here
}
//============================================================
// output_mame_start
//============================================================
static void output_mame_start(void)
{
HRESULT error_number;
// initialize the system based on the method
switch (ledmethod)
{
case LED_METHOD_PS2:
if (!DefineDosDevice(DDD_RAW_TARGET_PATH, TEXT("Kbd"), TEXT("\\Device\\KeyboardClass0")))
{
error_number = GetLastError();
fprintf(stderr, "Unable to open the keyboard device. (error %d)\n", (UINT32)error_number);
return;
}
hKbdDev = CreateFile(TEXT("\\\\.\\Kbd"), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hKbdDev == INVALID_HANDLE_VALUE)
{
error_number = GetLastError();
fprintf(stderr, "Unable to open the keyboard device. (error %d)\n", (UINT32)error_number);
return;
}
break;
}
// remember the initial LED states
original_state = current_state = led_get_state();
}
//============================================================
// output_mame_stop
//============================================================
static void output_mame_stop(void)
{
int error_number = 0;
// restore the initial LED states
led_set_state(original_state);
switch (ledmethod)
{
case LED_METHOD_PS2:
if (!DefineDosDevice(DDD_REMOVE_DEFINITION, TEXT("Kbd"), NULL))
{
error_number = GetLastError();
fprintf(stderr, "Unable to close the keyboard device. (error %d)\n", error_number);
return;
}
if (!CloseHandle(hKbdDev))
{
error_number = GetLastError();
fprintf(stderr, "Unable to close the keyboard device. (error %d)\n", error_number);
return;
}
break;
}
}
//============================================================
// output_set_state
//============================================================
static void output_set_state(const char *outname, INT32 state)
{
// look for pause state
if (strcmp(outname, "pause") == 0)
{
if (state)
{
pause_state = led_get_state();
led_set_state(original_state);
}
else
{
original_state = led_get_state();
led_set_state(pause_state);
}
}
// look for LED0/LED1/LED2 states and update accordingly
else if (strcmp(outname, "led0") == 0)
led_set_state((current_state & ~1) | (state & 1));
else if (strcmp(outname, "led1") == 0)
led_set_state((current_state & ~2) | ((state & 1) << 1));
else if (strcmp(outname, "led2") == 0)
led_set_state((current_state & ~4) | ((state & 1) << 2));
}
//============================================================
// led_get_state
//============================================================
static int led_get_state(void)
{
int result = 0;
switch (ledmethod)
{
case LED_METHOD_WIN9X:
case LED_METHOD_USB:
{
BYTE key_states[256];
// get the current state
GetKeyboardState(&key_states[0]);
// set the numlock bit
result |= (key_states[VK_NUMLOCK] & 1);
result |= (key_states[VK_CAPITAL] & 1) << 1;
result |= (key_states[VK_SCROLL] & 1) << 2;
break;
}
case LED_METHOD_PS2:
{
KEYBOARD_INDICATOR_PARAMETERS OutputBuffer; // Output buffer for DeviceIoControl
ULONG DataLength = sizeof(KEYBOARD_INDICATOR_PARAMETERS);
ULONG ReturnedLength; // Number of bytes returned in output buffer
// Address first keyboard
OutputBuffer.UnitId = 0;
DeviceIoControl(hKbdDev, IOCTL_KEYBOARD_QUERY_INDICATORS,
NULL, 0,
&OutputBuffer, DataLength,
&ReturnedLength, NULL);
// Demangle lights to match 95/98
if (OutputBuffer.LedFlags & KEYBOARD_NUM_LOCK_ON) result |= 0x1;
if (OutputBuffer.LedFlags & KEYBOARD_CAPS_LOCK_ON) result |= 0x2;
if (OutputBuffer.LedFlags & KEYBOARD_SCROLL_LOCK_ON) result |= 0x4;
break;
}
}
return result;
}
//============================================================
// led_set_state
//============================================================
static void led_set_state(int state)
{
current_state = state;
switch (ledmethod)
{
case LED_METHOD_WIN9X:
{
// thanks to Lee Taylor for the original version of this code
BYTE key_states[256];
// get the current state
GetKeyboardState(&key_states[0]);
// mask states and set new states
key_states[VK_NUMLOCK] = (key_states[VK_NUMLOCK] & ~1) | ((state >> 0) & 1);
key_states[VK_CAPITAL] = (key_states[VK_CAPITAL] & ~1) | ((state >> 1) & 1);
key_states[VK_SCROLL] = (key_states[VK_SCROLL] & ~1) | ((state >> 2) & 1);
SetKeyboardState(&key_states[0]);
break;
}
case LED_METHOD_USB:
{
static const BYTE vk[3] = { VK_NUMLOCK, VK_CAPITAL, VK_SCROLL };
BYTE keyState[256];
int k;
GetKeyboardState((LPBYTE)&keyState);
for (k = 0; k < 3; k++)
{
if ((((state >> k) & 1) && !(keyState[vk[k]] & 1)) ||
(!((state >> k) & 1) && (keyState[vk[k]] & 1)))
{
// Simulate a key press
keybd_event(vk[k], 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
// Simulate a key release
keybd_event(vk[k], 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
}
}
keyState[VK_NUMLOCK] = (keyState[VK_NUMLOCK] & ~1) | ((state >> 0) & 1);
keyState[VK_CAPITAL] = (keyState[VK_CAPITAL] & ~1) | ((state >> 1) & 1);
keyState[VK_SCROLL] = (keyState[VK_SCROLL] & ~1) | ((state >> 2) & 1);
SetKeyboardState(&keyState[0]);
break;
}
case LED_METHOD_PS2:
{
KEYBOARD_INDICATOR_PARAMETERS InputBuffer; // Input buffer for DeviceIoControl
ULONG DataLength = sizeof(KEYBOARD_INDICATOR_PARAMETERS);
ULONG ReturnedLength; // Number of bytes returned in output buffer
UINT LedFlags=0;
// Demangle lights to match 95/98
if (state & 0x1) LedFlags |= KEYBOARD_NUM_LOCK_ON;
if (state & 0x2) LedFlags |= KEYBOARD_CAPS_LOCK_ON;
if (state & 0x4) LedFlags |= KEYBOARD_SCROLL_LOCK_ON;
// Address first keyboard
InputBuffer.UnitId = 0;
InputBuffer.LedFlags = LedFlags;
DeviceIoControl(hKbdDev, IOCTL_KEYBOARD_SET_INDICATORS,
&InputBuffer, DataLength,
NULL, 0,
&ReturnedLength, NULL);
break;
}
}
}