/* * Support for Windows via the WM_INPUT message. * * Please see the file LICENSE.txt in the source's root directory. * * This file written by Ryan C. Gordon. */ #include "manymouse.h" #if (defined(_WIN32) || defined(_WINDOWS) || defined(__CYGWIN__)) /* WinUser.h won't include rawinput stuff without this... */ #if (_WIN32_WINNT < 0x0501) #undef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif #define WIN32_LEAN_AND_MEAN 1 #include #include #include /* needed for alloca(). */ /* Cygwin's headers don't have WM_INPUT right now... */ #ifndef WM_INPUT #define WM_INPUT 0x00FF #endif /* that should be enough, knock on wood. */ #define MAX_MICE 32 /* * Just trying to avoid malloc() here...we statically allocate a buffer * for events and treat it as a ring buffer. */ /* !!! FIXME: tweak this? */ #define MAX_EVENTS 1024 static ManyMouseEvent input_events[MAX_EVENTS]; static volatile int input_events_read = 0; static volatile int input_events_write = 0; static int available_mice = 0; static int did_api_lookup = 0; static HWND raw_hwnd = NULL; static const char *class_name = "ManyMouseRawInputCatcher"; static const char *win_name = "ManyMouseRawInputMsgWindow"; static ATOM class_atom = 0; static CRITICAL_SECTION mutex; typedef struct { HANDLE handle; char name[256]; } MouseStruct; static MouseStruct mice[MAX_MICE]; /* * The RawInput APIs only exist in Windows XP and later, so you want this * to fail gracefully on earlier systems instead of refusing to start the * process due to missing symbols. To this end, we do a symbol lookup on * User32.dll, etc to get the entry points. * * A lot of these are available all the way back to the start of win32 in * Windows 95 and WinNT 3.1, but just so you don't have to track down any * import libraries, I've added those here, too. That fits well with the * idea of just adding the sources to your build and going forward. */ static UINT (WINAPI *pGetRawInputDeviceList)(PRAWINPUTDEVICELIST,PUINT,UINT); /* !!! FIXME: use unicode version */ static UINT (WINAPI *pGetRawInputDeviceInfoA)(HANDLE,UINT,LPVOID,PUINT); static BOOL (WINAPI *pRegisterRawInputDevices)(PCRAWINPUTDEVICE,UINT,UINT); static LRESULT (WINAPI *pDefRawInputProc)(PRAWINPUT *,INT,UINT); static UINT (WINAPI *pGetRawInputBuffer)(PRAWINPUT,PUINT,UINT); static UINT (WINAPI *pGetRawInputData)(HRAWINPUT,UINT,LPVOID,PUINT,UINT); static HWND (WINAPI *pCreateWindowExA)(DWORD,LPCTSTR,LPCTSTR,DWORD,int,int,int,int,HWND,HMENU,HINSTANCE,LPVOID); static ATOM (WINAPI *pRegisterClassExA)(CONST WNDCLASSEX *); static LRESULT (WINAPI *pDefWindowProcA)(HWND,UINT,WPARAM,LPARAM); static BOOL (WINAPI *pUnregisterClassA)(LPCTSTR,HINSTANCE); static HMODULE (WINAPI *pGetModuleHandleA)(LPCTSTR); static BOOL (WINAPI *pPeekMessageA)(LPMSG,HWND,UINT,UINT,UINT); static BOOL (WINAPI *pTranslateMessage)(const MSG *); static LRESULT (WINAPI *pDispatchMessageA)(const MSG *); static BOOL (WINAPI *pDestroyWindow)(HWND); static void (WINAPI *pInitializeCriticalSection)(LPCRITICAL_SECTION); static void (WINAPI *pEnterCriticalSection)(LPCRITICAL_SECTION); static void (WINAPI *pLeaveCriticalSection)(LPCRITICAL_SECTION); static void (WINAPI *pDeleteCriticalSection)(LPCRITICAL_SECTION); static DWORD (WINAPI *pGetLastError)(void); static HDEVINFO (WINAPI *pSetupDiGetClassDevsA)(LPGUID, LPCTSTR, HWND, DWORD); static BOOL (WINAPI *pSetupDiEnumDeviceInfo)(HDEVINFO, DWORD, PSP_DEVINFO_DATA); static BOOL (WINAPI *pSetupDiGetDeviceInstanceIdA)(HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD); static BOOL (WINAPI *pSetupDiGetDeviceRegistryPropertyA)(HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD); static BOOL (WINAPI *pSetupDiDestroyDeviceInfoList)(HDEVINFO); static int symlookup(HMODULE dll, FARPROC *addr, const char *sym) { *addr = GetProcAddress(dll, sym); if (*addr == NULL) { FreeLibrary(dll); return 0; } /* if */ return 1; } /* symlookup */ static int find_api_symbols(void) { HMODULE dll; if (did_api_lookup) return 1; #define LOOKUP(x) { if (!symlookup(dll, (FARPROC *) &p##x, #x)) return 0; } dll = LoadLibrary(TEXT("user32.dll")); if (dll == NULL) return 0; LOOKUP(GetRawInputDeviceInfoA); LOOKUP(RegisterRawInputDevices); LOOKUP(GetRawInputDeviceList); LOOKUP(DefRawInputProc); LOOKUP(GetRawInputBuffer); LOOKUP(GetRawInputData); LOOKUP(CreateWindowExA); LOOKUP(RegisterClassExA); LOOKUP(UnregisterClassA); LOOKUP(DefWindowProcA); LOOKUP(PeekMessageA); LOOKUP(TranslateMessage); LOOKUP(DispatchMessageA); LOOKUP(DestroyWindow); dll = LoadLibrary(TEXT("kernel32.dll")); if (dll == NULL) return 0; LOOKUP(GetModuleHandleA); LOOKUP(GetLastError); LOOKUP(InitializeCriticalSection); LOOKUP(EnterCriticalSection); LOOKUP(LeaveCriticalSection); LOOKUP(DeleteCriticalSection); dll = LoadLibrary(TEXT("setupapi.dll")); if (dll == NULL) return 0; LOOKUP(SetupDiGetClassDevsA); LOOKUP(SetupDiEnumDeviceInfo); LOOKUP(SetupDiGetDeviceInstanceIdA); LOOKUP(SetupDiGetDeviceRegistryPropertyA); LOOKUP(SetupDiDestroyDeviceInfoList); #undef LOOKUP did_api_lookup = 1; return 1; } /* find_api_symbols */ /* Some simple functions to avoid C runtime dependency... */ static char make_upper(const char a) { return ((a >= 'a') && (a <= 'z')) ? (a - ('a' - 'A')) : a; } /* make_upper */ static void make_string_upper(char *str) { char *ptr; for (ptr = str; *ptr; ptr++) *ptr = make_upper(*ptr); } /* make_string_upper */ static int string_compare(const char *a, const char *b) { while (1) { const char cha = *(a++); const char chb = *(b++); if (cha < chb) return -1; else if (cha > chb) return 1; else if (cha == '\0') return 0; } /* while */ return 0; } /* string_compare */ static size_t string_length(const char *a) { size_t retval; for (retval = 0; *(a++); retval++) { /* spin. */ } return retval; } /* string_length */ static void queue_event(const ManyMouseEvent *event) { /* copy the event info. We'll process it in ManyMouse_PollEvent(). */ CopyMemory(&input_events[input_events_write], event, sizeof (ManyMouseEvent)); input_events_write = ((input_events_write + 1) % MAX_EVENTS); /* Ring buffer full? Lose oldest event. */ if (input_events_write == input_events_read) { /* !!! FIXME: we need to not lose mouse buttons here. */ input_events_read = ((input_events_read + 1) % MAX_EVENTS); } /* if */ } /* queue_event */ static void queue_from_rawinput(const RAWINPUT *raw) { int i; const RAWINPUTHEADER *header = &raw->header; const RAWMOUSE *mouse = &raw->data.mouse; ManyMouseEvent event; if (raw->header.dwType != RIM_TYPEMOUSE) return; for (i = 0; i < available_mice; i++) /* find the device for event. */ { if (mice[i].handle == header->hDevice) break; } /* for */ if (i == available_mice) return; /* not found?! */ /* * RAWINPUT packs a bunch of events into one, so we split it up into * a bunch of ManyMouseEvents here and store them in an internal queue. * Then ManyMouse_PollEvent() just shuffles items off that queue * without any complicated processing. */ event.device = i; pEnterCriticalSection(&mutex); if (mouse->usFlags & MOUSE_MOVE_ABSOLUTE) { /* !!! FIXME: How do we get the min and max values for absmotion? */ event.type = MANYMOUSE_EVENT_ABSMOTION; event.item = 0; event.value = mouse->lLastX; queue_event(&event); event.item = 1; event.value = mouse->lLastY; queue_event(&event); } /* if */ else /*if (mouse->usFlags & MOUSE_MOVE_RELATIVE)*/ { event.type = MANYMOUSE_EVENT_RELMOTION; if (mouse->lLastX != 0) { event.item = 0; event.value = mouse->lLastX; queue_event(&event); } /* if */ if (mouse->lLastY != 0) { event.item = 1; event.value = mouse->lLastY; queue_event(&event); } /* if */ } /* else if */ event.type = MANYMOUSE_EVENT_BUTTON; #define QUEUE_BUTTON(x) { \ if (mouse->usButtonFlags & RI_MOUSE_BUTTON_##x##_DOWN) { \ event.item = x-1; \ event.value = 1; \ queue_event(&event); \ } \ if (mouse->usButtonFlags & RI_MOUSE_BUTTON_##x##_UP) { \ event.item = x-1; \ event.value = 0; \ queue_event(&event); \ } \ } QUEUE_BUTTON(1); QUEUE_BUTTON(2); QUEUE_BUTTON(3); QUEUE_BUTTON(4); QUEUE_BUTTON(5); #undef QUEUE_BUTTON if (mouse->usButtonFlags & RI_MOUSE_WHEEL) { if (mouse->usButtonData != 0) /* !!! FIXME: can this ever be zero? */ { event.type = MANYMOUSE_EVENT_SCROLL; event.item = 0; /* !!! FIXME: horizontal wheel? */ event.value = ( ((SHORT) mouse->usButtonData) > 0) ? 1 : -1; queue_event(&event); } /* if */ } /* if */ pLeaveCriticalSection(&mutex); } /* queue_from_rawinput */ static void wminput_handler(WPARAM wParam, LPARAM lParam) { UINT dwSize = 0; LPBYTE lpb; pGetRawInputData((HRAWINPUT) lParam, RID_INPUT, NULL, &dwSize, sizeof (RAWINPUTHEADER)); if (dwSize < sizeof (RAWINPUT)) return; /* unexpected packet? */ lpb = (LPBYTE) alloca(dwSize); if (lpb == NULL) return; if (pGetRawInputData((HRAWINPUT) lParam, RID_INPUT, lpb, &dwSize, sizeof (RAWINPUTHEADER)) != dwSize) return; queue_from_rawinput((RAWINPUT *) lpb); } /* wminput_handler */ static LRESULT CALLBACK RawWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { if (Msg == WM_INPUT) wminput_handler(wParam, lParam); else if (Msg == WM_DESTROY) return 0; return pDefWindowProcA(hWnd, Msg, wParam, lParam); } /* RawWndProc */ static int init_event_queue(void) { HINSTANCE hInstance = pGetModuleHandleA(NULL); WNDCLASSEX wce; RAWINPUTDEVICE rid; ZeroMemory(input_events, sizeof (input_events)); input_events_read = input_events_write = 0; ZeroMemory(&wce, sizeof (wce)); wce.cbSize = sizeof(WNDCLASSEX); wce.lpfnWndProc = RawWndProc; wce.lpszClassName = class_name; wce.hInstance = hInstance; class_atom = pRegisterClassExA(&wce); if (class_atom == 0) return 0; raw_hwnd = pCreateWindowExA(0, class_name, win_name, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_MESSAGE, NULL, hInstance, NULL); if (raw_hwnd == NULL) return 0; pInitializeCriticalSection(&mutex); ZeroMemory(&rid, sizeof (rid)); rid.usUsagePage = 1; /* GenericDesktop page */ rid.usUsage = 2; /* GeneralDestop Mouse usage. */ rid.dwFlags = RIDEV_INPUTSINK; rid.hwndTarget = raw_hwnd; if (!pRegisterRawInputDevices(&rid, 1, sizeof (rid))) { pDeleteCriticalSection(&mutex); return 0; } /* if */ return 1; } /* init_event_queue */ static void cleanup_window(void) { if (raw_hwnd) { MSG Msg; pDestroyWindow(raw_hwnd); while (pPeekMessageA(&Msg, raw_hwnd, 0, 0, PM_REMOVE)) { pTranslateMessage(&Msg); pDispatchMessageA(&Msg); } /* while */ raw_hwnd = 0; } /* if */ if (class_atom) { pUnregisterClassA(class_name, pGetModuleHandleA(NULL)); class_atom = 0; } /* if */ } /* cleanup_window */ static int get_devinfo_data(HDEVINFO devinfo, const char *devinstance, SP_DEVINFO_DATA *data) { DWORD i = 0; const DWORD bufsize = string_length(devinstance) + 1; char *buf = (char *) alloca(bufsize); if (buf == NULL) return 0; while (1) { ZeroMemory(data, sizeof (SP_DEVINFO_DATA)); data->cbSize = sizeof (SP_DEVINFO_DATA); if (!pSetupDiEnumDeviceInfo(devinfo, i++, data)) { if (pGetLastError() == ERROR_NO_MORE_ITEMS) break; else continue; } /* if */ if (!pSetupDiGetDeviceInstanceIdA(devinfo, data, buf, bufsize, NULL)) continue; make_string_upper(buf); if (string_compare(devinstance, buf) == 0) return 1; /* found it! */ } /* while */ return 0; /* not found. */ } /* get_devinfo_data */ static void get_dev_name_by_instance(const char *devinstance, char *name, size_t namesize) { SP_DEVINFO_DATA devdata; const DWORD flags = DIGCF_ALLCLASSES | DIGCF_PRESENT; HDEVINFO devinfo = pSetupDiGetClassDevsA(NULL, NULL, NULL, flags); if (devinfo == INVALID_HANDLE_VALUE) return; if (get_devinfo_data(devinfo, devinstance, &devdata)) { pSetupDiGetDeviceRegistryPropertyA(devinfo, &devdata, SPDRP_DEVICEDESC, NULL, (PBYTE) name, namesize, NULL); } /* if */ pSetupDiDestroyDeviceInfoList(devinfo); } /* get_dev_name_by_instance */ static void get_device_product_name(char *name, size_t namesize, char *devname) { const char default_device_name[] = "Unidentified input device"; *name = '\0'; /* really insane default. */ if (sizeof (default_device_name) >= namesize) return; /* in case we can't stumble upon something better... */ CopyMemory(name, default_device_name, sizeof (default_device_name)); /* okay, we're got the device instance. Now find the data for it. */ get_dev_name_by_instance(devname, name, namesize); } /* get_device_product_name */ static void init_mouse(const RAWINPUTDEVICELIST *dev) { const char rdp_ident[] = "ROOT\\RDP_MOU\\"; MouseStruct *mouse = &mice[available_mice]; char *buf = NULL; char *ptr = NULL; UINT ct = 0; if (dev->dwType != RIM_TYPEMOUSE) return; /* keyboard or some other fruity thing. */ if (pGetRawInputDeviceInfoA(dev->hDevice, RIDI_DEVICENAME, NULL, &ct) < 0) return; /* ct == is chars, not bytes, but we used the ASCII version. */ buf = (char *) alloca(ct+1); if (buf == NULL) return; if (pGetRawInputDeviceInfoA(dev->hDevice, RIDI_DEVICENAME, buf, &ct) < 0) return; buf[ct] = '\0'; /* make sure it's null-terminated. */ /* XP starts these strings with "\\??\\" ... Vista does "\\\\?\\". :/ */ while ((*buf == '?') || (*buf == '\\')) { buf++; ct--; } /* while */ /* This string tap dancing gets us the device instance id. */ for (ptr = buf; *ptr; ptr++) /* convert '#' to '\\' ... */ { char ch = *ptr; if (ch == '#') *ptr = '\\'; else if (ch == '{') /* hit the GUID part of the string. */ { if (*(ptr-1) == '\\') ptr--; break; } /* else if */ } /* for */ *ptr = '\0'; make_string_upper(buf); /* * Apparently there's a fake "RDP" device...I guess this is * "Remote Desktop Protocol" for controlling the system pointer * remotely via Windows Remote Desktop, but that's just a guess. * At any rate, we don't want that device, so skip it if detected. * * Idea for this found here: * http://link.mywwwserver.com/~jstookey/arcade/rawmouse/raw_mouse.c */ /* avoiding memcmp here so we don't get a C runtime dependency... */ if (ct >= sizeof (rdp_ident) - 1) { int i; for (i = 0; i < sizeof (rdp_ident) - 1; i++) { if (buf[i] != rdp_ident[i]) break; } /* for */ if (i == sizeof (rdp_ident) - 1) return; /* this is an RDP thing. Skip this device. */ } /* if */ /* accept this mouse! */ ZeroMemory(mouse, sizeof (MouseStruct)); get_device_product_name(mouse->name, sizeof (mouse->name), buf); mouse->handle = dev->hDevice; available_mice++; } /* init_mouse */ static int windows_wminput_init(void) { RAWINPUTDEVICELIST *devlist = NULL; UINT ct = 0; UINT i; available_mice = 0; if (!find_api_symbols()) /* only supported on WinXP and later. */ return -1; pGetRawInputDeviceList(NULL, &ct, sizeof (RAWINPUTDEVICELIST)); if (ct == 0) /* no devices. */ return 0; devlist = (PRAWINPUTDEVICELIST) alloca(sizeof (RAWINPUTDEVICELIST) * ct); pGetRawInputDeviceList(devlist, &ct, sizeof (RAWINPUTDEVICELIST)); for (i = 0; i < ct; i++) init_mouse(&devlist[i]); if (!init_event_queue()) { cleanup_window(); available_mice = 0; } /* if */ return available_mice; } /* windows_wminput_init */ static void windows_wminput_quit(void) { /* unregister WM_INPUT devices... */ RAWINPUTDEVICE rid; ZeroMemory(&rid, sizeof (rid)); rid.usUsagePage = 1; /* GenericDesktop page */ rid.usUsage = 2; /* GeneralDestop Mouse usage. */ rid.dwFlags |= RIDEV_REMOVE; pRegisterRawInputDevices(&rid, 1, sizeof (rid)); cleanup_window(); available_mice = 0; pDeleteCriticalSection(&mutex); } /* windows_wminput_quit */ static const char *windows_wminput_name(unsigned int index) { return (index < available_mice) ? mice[index].name : NULL; } /* windows_wminput_name */ /* * Windows doesn't send a WM_INPUT event when you unplug a mouse, * so we try to do a basic query by device handle here; if the * query fails, we assume the device has vanished and generate a * disconnect. */ static int check_for_disconnects(ManyMouseEvent *ev) { /* * (i) is static so we iterate through all mice round-robin and check * one mouse per call to ManyMouse_PollEvent(). This makes this test O(1). */ static unsigned int i = 0; MouseStruct *mouse = NULL; if (++i >= available_mice) /* check first in case of redetect */ i = 0; mouse = &mice[i]; if (mouse->handle != NULL) /* not NULL == still plugged in. */ { UINT size = 0; UINT rc = pGetRawInputDeviceInfoA(mouse->handle, RIDI_DEVICEINFO, NULL, &size); if (rc == (UINT) -1) /* failed...probably unplugged... */ { mouse->handle = NULL; ev->type = MANYMOUSE_EVENT_DISCONNECT; ev->device = i; return 1; } /* if */ } /* if */ return 0; /* no disconnect event this time. */ } /* check_for_disconnects */ static int windows_wminput_poll(ManyMouseEvent *ev) { MSG Msg; /* run the queue for WM_INPUT messages, etc ... */ int found = 0; /* ...favor existing events in the queue... */ pEnterCriticalSection(&mutex); if (input_events_read != input_events_write) /* no events if equal. */ { CopyMemory(ev, &input_events[input_events_read], sizeof (*ev)); input_events_read = ((input_events_read + 1) % MAX_EVENTS); found = 1; } /* if */ pLeaveCriticalSection(&mutex); if (!found) { /* pump Windows for new hardware events... */ while (pPeekMessageA(&Msg, raw_hwnd, 0, 0, PM_REMOVE)) { pTranslateMessage(&Msg); pDispatchMessageA(&Msg); } /* while */ /* In case something new came in, give it to the app... */ pEnterCriticalSection(&mutex); if (input_events_read != input_events_write) /* no events if equal. */ { CopyMemory(ev, &input_events[input_events_read], sizeof (*ev)); input_events_read = ((input_events_read + 1) % MAX_EVENTS); found = 1; } /* if */ pLeaveCriticalSection(&mutex); } /* if */ /* * Check for disconnects if queue is totally empty and Windows didn't * report anything new at this time. This ensures that we don't send a * disconnect event through ManyMouse and then later give a valid * event to the app for a device that is now missing. */ if (!found) found = check_for_disconnects(ev); return found; } /* windows_wminput_poll */ static const ManyMouseDriver ManyMouseDriver_interface = { "Windows XP and later WM_INPUT interface", windows_wminput_init, windows_wminput_quit, windows_wminput_name, windows_wminput_poll }; const ManyMouseDriver *ManyMouseDriver_windows = &ManyMouseDriver_interface; #else const ManyMouseDriver *ManyMouseDriver_windows = 0; #endif /* ifdef Windows blocker */ /* end of windows_wminput.c ... */