Files
RetroArch/input/drivers/winraw_input.c
T
LibretroAdmin b9be38fd55 strl: copy string literals without scanning them at runtime
strlcpy() begins with strlen() of its source. It lives in another
translation unit, so with no LTO that scan happens at runtime even when
the source is a literal whose length the compiler already measured, and
the call itself cannot be folded away either.

strlcpy_lit() passes the length over as sizeof(). When the destination
size is also a constant -- sizeof(buf), the usual case -- nothing is
left but the stores memcpy() compiles to:

  strlcpy      "core_options"  sizeof(buf)   7.01 ns
  strlcpy_lit  "core_options"  sizeof(buf)   0.42 ns
  strlcpy      41-char literal sizeof(buf)   7.74 ns
  strlcpy_lit  41-char literal sizeof(buf)   0.71 ns
  strlcpy      "core_options"  runtime len   7.11 ns
  strlcpy_lit  "core_options"  runtime len   0.70 ns

The destination bound is kept. When the literal does not fit it defers
to strlcpy(), so the copy truncates as before rather than overrunning;
a form that took only the literal would have turned every one of these
sites into an overflow waiting for a small buffer. The literal is
concatenated with "" so a non-literal source is a compile error rather
than a sizeof() that silently measures a pointer.

818 sites across 104 files were converted by parsing arguments rather
than by pattern-matching text: only a three-argument call whose second
argument is a bare string literal is rewritten, so a macro that happens
to expand to a literal is left alone. Line endings are preserved, so
the CRLF files carry only their own changed lines.

test/compat/test_strl.c compares the two against each other over every
destination size from 0 past the literal's length, for literals that
are empty, single-character, path-shaped, adjacently concatenated and
escape-bearing, checking the return, the terminator and the whole
buffer so an overrun past the terminator is caught. ASan+UBSan+LSan and
TSan both 4/4. The full menu_displaylist_build_list walk over every
displaylist type is byte-identical to before the change
(md5 a2a081bcac09cdc8d72d67efd14ac40e).
2026-08-30 02:10:20 +02:00

1253 lines
39 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifdef CXX_BUILD
extern "C" {
#endif
#include <hidsdi.h>
#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500 /* 2K */
#include <dbt.h>
#endif
#ifdef CXX_BUILD
}
#endif
#include <compat/strl.h>
#include <string/stdstring.h>
#ifndef _XBOX
#include "../../gfx/common/win32_common.h"
#endif
#ifdef HAVE_MENU
#include "../../menu/menu_driver.h"
#endif
#include <queues/task_queue.h>
#include "../input_keymaps.h"
#include "../../configuration.h"
#include "../../retroarch.h"
#include "../../verbosity.h"
enum winraw_mouse_flags
{
WRAW_MOUSE_FLG_BTN_L = (1 << 0),
WRAW_MOUSE_FLG_BTN_M = (1 << 1),
WRAW_MOUSE_FLG_BTN_R = (1 << 2),
WRAW_MOUSE_FLG_BTN_B4 = (1 << 3),
WRAW_MOUSE_FLG_BTN_B5 = (1 << 4)
};
typedef struct
{
HANDLE hnd;
LONG x, y, dlt_x, dlt_y;
LONG whl_u, whl_d;
int device;
uint8_t flags;
} winraw_mouse_t;
struct winraw_pointer_status
{
struct winraw_pointer_status *next;
int pointer_id;
int pointer_x;
int pointer_y;
};
enum winraw_input_flags
{
WRAW_INP_FLG_MOUSE_GRAB = (1 << 0),
WRAW_INP_FLG_MOUSE_XY_MAPPING_READY = (1 << 1),
WRAW_INP_FLG_KB_PAUSE = (1 << 2)
};
typedef struct
{
double view_abs_ratio_x;
double view_abs_ratio_y;
HWND window;
/* Dummy head for easier iteration */
struct winraw_pointer_status pointer_head;
RECT active_rect; /* Needed for checking for a windows size change */
RECT prev_rect; /* Needed for checking for a windows size change */
int rect_delay; /* Needed to delay resize of window */
winraw_mouse_t *mice;
unsigned mouse_cnt;
uint8_t kb_keys[SC_LAST];
uint8_t flags;
bool last_focus;
bool kb_clear_pending;
} winraw_input_t;
/* TODO/FIXME - static globals */
static winraw_mouse_t *g_mice = NULL;
static bool winraw_focus = false;
/* Sync internal mouse coordinates with the OS cursor position.
* Used after events such as window mode, size, and focus changes */
static bool winraw_sync_mouse_to_cursor(winraw_input_t *wr)
{
HWND wnd;
POINT p;
unsigned i;
if (!wr || !wr->mouse_cnt)
return false;
wnd = (HWND)video_driver_window_get();
if (!wnd || !GetCursorPos(&p))
return false;
ScreenToClient(wnd, &p);
for (i = 0; i < wr->mouse_cnt; ++i)
{
g_mice[i].x = (LONG)p.x;
g_mice[i].y = (LONG)p.y;
}
return true;
}
#define WINRAW_KEYBOARD_PRESSED(wr, key) (wr->kb_keys[rarch_keysym_lut[(enum retro_key)(key)]])
static HWND winraw_create_window(WNDPROC wnd_proc)
{
HWND wnd;
WNDCLASSA wc = {0};
if (!(wc.hInstance = GetModuleHandleA(NULL)))
return NULL;
wc.lpfnWndProc = wnd_proc;
wc.lpszClassName = "winraw-input";
if ( !RegisterClassA(&wc)
&& GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
return NULL;
if (!(wnd = CreateWindowExA(0, wc.lpszClassName,
NULL, 0, 0, 0, 0, 0,
HWND_MESSAGE, NULL, NULL, NULL)))
{
UnregisterClassA(wc.lpszClassName, NULL);
return NULL;
}
return wnd;
}
/* Deferred mouse display-name resolution.
*
* Resolving a friendly mouse name requires
* GetRawInputDeviceInfoA(RIDI_DEVICENAME) followed by CreateFile() +
* HidD_GetProductString() on the HID interface. Opening the HID
* interface of e.g. a Bluetooth mouse right after a fresh boot can
* block for seconds while the device stack is still coming up, and
* the result is purely cosmetic (menu display names / log lines), so
* this work runs on the task queue instead of the input driver init
* path. Win32 queries happen in the task handler; publishing the
* names to global input config state happens in the task callback,
* which runs on the main thread (same split as the joypad
* autoconfig task). */
typedef struct
{
HANDLE hnd; /* raw input device handle; used for queries only */
char name[256];
} winraw_mouse_name_entry_t;
typedef struct
{
winraw_mouse_name_entry_t *entries;
unsigned count;
} winraw_mouse_names_handle_t;
static void winraw_mouse_names_free(retro_task_t *task)
{
winraw_mouse_names_handle_t *h = NULL;
if (!task)
return;
if ((h = (winraw_mouse_names_handle_t*)task->state))
{
free(h->entries);
free(h);
}
task->state = NULL;
}
static void winraw_mouse_names_handler(retro_task_t *task)
{
unsigned i;
winraw_mouse_names_handle_t *h = NULL;
if (!task)
return;
if ((h = (winraw_mouse_names_handle_t*)task->state))
{
for (i = 0; i < h->count; ++i)
{
char *name = h->entries[i].name;
/* Reset the in/out size argument every iteration -
* GetRawInputDeviceInfoA() may modify it. */
UINT name_size = sizeof(h->entries[i].name);
UINT r = GetRawInputDeviceInfoA(h->entries[i].hnd,
RIDI_DEVICENAME, name, &name_size);
if (r == (UINT)-1 || r == 0)
name[0] = '\0';
if (name[0])
{
HANDLE hhid = CreateFile(name,
0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hhid != INVALID_HANDLE_VALUE)
{
wchar_t prod_buf[128];
prod_buf[0] = '\0';
if (HidD_GetProductString(hhid, prod_buf, sizeof(prod_buf)))
wcstombs(name, prod_buf, sizeof(h->entries[i].name));
CloseHandle(hhid);
}
}
if (!name[0])
strlcpy_lit(name, "<name not found>",
sizeof(h->entries[i].name));
}
}
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
}
static void winraw_mouse_names_cb(retro_task_t *task,
void *task_data, void *user_data, const char *err)
{
unsigned i;
winraw_mouse_names_handle_t *h = NULL;
if (!task)
return;
if (!(h = (winraw_mouse_names_handle_t*)task->state))
return;
/* input_config_set_mouse_display_name() writes global input
* config state, so it must run here on the main thread. */
for (i = 0; i < h->count; ++i)
{
input_config_set_mouse_display_name(i, h->entries[i].name);
RARCH_LOG("[WinRaw] Found mouse #%u: \"%s\".\n",
i + 1, h->entries[i].name);
}
}
static void winraw_push_mouse_names_task(
winraw_mouse_t *mice, unsigned mouse_cnt)
{
unsigned i;
retro_task_t *task = NULL;
winraw_mouse_names_handle_t *h = NULL;
if (!mouse_cnt)
return;
if (!(h = (winraw_mouse_names_handle_t*)calloc(1, sizeof(*h))))
return;
if (!(h->entries = (winraw_mouse_name_entry_t*)calloc(
mouse_cnt, sizeof(*h->entries))))
{
free(h);
return;
}
h->count = mouse_cnt;
for (i = 0; i < mouse_cnt; ++i)
h->entries[i].hnd = mice[i].hnd;
if (!(task = task_init()))
{
free(h->entries);
free(h);
return;
}
task->handler = winraw_mouse_names_handler;
task->state = h;
task->title = NULL;
task->callback = winraw_mouse_names_cb;
task->cleanup = winraw_mouse_names_free;
task->flags |= RETRO_TASK_FLG_MUTE;
task_queue_push(task);
}
static bool winraw_init_devices(winraw_mouse_t **mice, unsigned *mouse_cnt)
{
UINT i;
POINT crs_pos;
winraw_mouse_t *mice_r = NULL;
unsigned mouse_cnt_r = 0;
RAWINPUTDEVICELIST *devs = NULL;
UINT dev_cnt = 0;
UINT r = GetRawInputDeviceList(
NULL, &dev_cnt, sizeof(RAWINPUTDEVICELIST));
if (r == (UINT)-1)
goto error;
if (!(devs = (RAWINPUTDEVICELIST*)malloc(
dev_cnt * sizeof(RAWINPUTDEVICELIST))))
goto error;
if ((dev_cnt = GetRawInputDeviceList(devs,
&dev_cnt, sizeof(RAWINPUTDEVICELIST))) == (UINT)-1)
goto error;
for (i = 0; i < dev_cnt; ++i)
mouse_cnt_r += devs[i].dwType == RIM_TYPEMOUSE ? 1 : 0;
if (mouse_cnt_r)
{
if (!(mice_r = (winraw_mouse_t*)calloc(
1, mouse_cnt_r * sizeof(winraw_mouse_t))))
goto error;
if (!GetCursorPos(&crs_pos))
goto error;
for (i = 0; i < mouse_cnt_r; ++i)
{
mice_r[i].x = crs_pos.x;
mice_r[i].y = crs_pos.y;
}
}
*mouse_cnt = mouse_cnt_r;
/* count is already checked, so this is safe */
for (i = mouse_cnt_r = 0; i < dev_cnt; ++i)
{
if (devs[i].dwType == RIM_TYPEMOUSE)
{
mouse_cnt_r++;
mice_r[*mouse_cnt - mouse_cnt_r].hnd = devs[i].hDevice;
}
}
*mice = mice_r;
winraw_push_mouse_names_task(mice_r, mouse_cnt_r);
free(devs);
return true;
error:
free(devs);
free(mice_r);
*mice = NULL;
*mouse_cnt = 0;
return false;
}
static int16_t winraw_lightgun_aiming_state(winraw_input_t *wr,
winraw_mouse_t *mouse,
unsigned port, unsigned id)
{
struct video_viewport vp = {0};
int16_t res_x = 0;
int16_t res_y = 0;
int16_t res_screen_x = 0;
int16_t res_screen_y = 0;
if ((video_driver_translate_coord_viewport_wrap(
&vp, mouse->x, mouse->y,
&res_x, &res_y, &res_screen_x, &res_screen_y)))
{
switch (id)
{
case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X:
return res_x;
case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y:
return res_y;
case RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN:
return input_driver_pointer_is_offscreen(res_x, res_y);
default:
break;
}
}
return 0;
}
static bool winraw_mouse_button_pressed(
winraw_input_t *wr,
winraw_mouse_t *mouse,
unsigned port, unsigned key)
{
switch (key)
{
case RETRO_DEVICE_ID_MOUSE_LEFT:
return ((mouse->flags & WRAW_MOUSE_FLG_BTN_L) > 0);
case RETRO_DEVICE_ID_MOUSE_RIGHT:
return ((mouse->flags & WRAW_MOUSE_FLG_BTN_R) > 0);
case RETRO_DEVICE_ID_MOUSE_MIDDLE:
return ((mouse->flags & WRAW_MOUSE_FLG_BTN_M) > 0);
case RETRO_DEVICE_ID_MOUSE_BUTTON_4:
return ((mouse->flags & WRAW_MOUSE_FLG_BTN_B4) > 0);
case RETRO_DEVICE_ID_MOUSE_BUTTON_5:
return ((mouse->flags & WRAW_MOUSE_FLG_BTN_B5) > 0);
case RETRO_DEVICE_ID_MOUSE_WHEELUP:
return mouse->whl_u;
case RETRO_DEVICE_ID_MOUSE_WHEELDOWN:
return mouse->whl_d;
}
return false;
}
static void winraw_init_mouse_xy_mapping(winraw_input_t *wr)
{
struct video_viewport viewport;
int mouse_x;
int mouse_y;
unsigned i;
if (!video_driver_get_viewport_info(&viewport))
return;
/* Default fallback: center of the viewport */
mouse_x = viewport.x + viewport.width / 2;
mouse_y = viewport.y + viewport.height / 2;
/* Sync to OS cursor position; fall back to center if it fails */
if (!winraw_sync_mouse_to_cursor(wr))
{
for (i = 0; i < wr->mouse_cnt; i++)
{
g_mice[i].x = mouse_x;
g_mice[i].y = mouse_y;
}
}
wr->view_abs_ratio_x = (double)viewport.full_width / 65535.0;
wr->view_abs_ratio_y = (double)viewport.full_height / 65535.0;
wr->flags |= WRAW_INP_FLG_MOUSE_XY_MAPPING_READY;
}
static void winraw_update_mouse_state(winraw_input_t *wr,
winraw_mouse_t *mouse, RAWMOUSE *state)
{
POINT crs_pos;
bool swap_mouse_buttons = (g_win32_flags & WIN32_CMN_FLAG_SWAP_MOUSE_BTNS) ? true : false;
/* Used for fixing coordinates after switching resolutions */
GetClientRect((HWND)video_driver_window_get(), &wr->prev_rect);
if (!EqualRect(&wr->active_rect, &wr->prev_rect))
{
if (wr->rect_delay < 10)
{
winraw_init_mouse_xy_mapping(wr); /* Triggering fewer times seems to fix the issue. Forcing resize while resolution is changing */
wr->rect_delay ++;
}
else
{
wr->active_rect = wr->prev_rect;
winraw_init_mouse_xy_mapping(wr);
wr->rect_delay = 0;
}
}
if (state->usFlags & MOUSE_MOVE_ABSOLUTE)
{
if ((wr->flags & WRAW_INP_FLG_MOUSE_XY_MAPPING_READY) > 0)
{
state->lLastX = (LONG)(wr->view_abs_ratio_x * state->lLastX);
state->lLastY = (LONG)(wr->view_abs_ratio_y * state->lLastY);
InterlockedExchangeAdd(&mouse->dlt_x, state->lLastX - mouse->x);
InterlockedExchangeAdd(&mouse->dlt_y, state->lLastY - mouse->y);
mouse->x = state->lLastX;
mouse->y = state->lLastY;
}
else
winraw_init_mouse_xy_mapping(wr);
}
else if (state->lLastX || state->lLastY)
{
/* Menu and pointer require GetCursorPos() for
* positioning, but using that always will
* break multiple mice positions */
bool getcursorpos = (mouse->device == RETRO_DEVICE_POINTER) ? true : false;
#ifdef HAVE_MENU
if (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE)
getcursorpos = true;
#endif
/* Input overlay with mouse cursor must also use GetCursorPos() */
if (!getcursorpos)
{
settings_t *settings = config_get_ptr();
if ( settings->bools.input_overlay_enable
&& *settings->paths.path_overlay)
getcursorpos = true;
}
if (getcursorpos)
{
InterlockedExchangeAdd(&mouse->dlt_x, state->lLastX);
InterlockedExchangeAdd(&mouse->dlt_y, state->lLastY);
if (!GetCursorPos(&crs_pos))
RARCH_DBG("[WinRaw] GetCursorPos failed with error %lu.\n", GetLastError());
else if (!ScreenToClient((HWND)video_driver_window_get(), &crs_pos))
RARCH_DBG("[WinRaw] ScreenToClient failed with error %lu.\n", GetLastError());
}
else
{
/* Handle different sensitivity for lightguns */
if (mouse->device == RETRO_DEVICE_LIGHTGUN)
{
InterlockedExchange(&mouse->dlt_x, state->lLastX);
InterlockedExchange(&mouse->dlt_y, state->lLastY);
}
else
{
InterlockedExchangeAdd(&mouse->dlt_x, state->lLastX);
InterlockedExchangeAdd(&mouse->dlt_y, state->lLastY);
}
crs_pos.x = mouse->x + mouse->dlt_x;
crs_pos.y = mouse->y + mouse->dlt_y;
/* Prevent travel outside active window */
if (crs_pos.x < wr->active_rect.left)
crs_pos.x = wr->active_rect.left;
else if (crs_pos.x > wr->active_rect.right)
crs_pos.x = wr->active_rect.right;
if (crs_pos.y < wr->active_rect.top)
crs_pos.y = wr->active_rect.top;
else if (crs_pos.y > wr->active_rect.bottom)
crs_pos.y = wr->active_rect.bottom;
}
mouse->x = crs_pos.x;
mouse->y = crs_pos.y;
}
if (swap_mouse_buttons)
{
if (state->usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN)
mouse->flags |= (WRAW_MOUSE_FLG_BTN_R);
else if (state->usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP)
mouse->flags &= ~(WRAW_MOUSE_FLG_BTN_R);
if (state->usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN)
mouse->flags |= (WRAW_MOUSE_FLG_BTN_L);
else if (state->usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP)
mouse->flags &= ~(WRAW_MOUSE_FLG_BTN_L);
}
else
{
if (state->usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN)
mouse->flags |= (WRAW_MOUSE_FLG_BTN_L);
else if (state->usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP)
mouse->flags &= ~(WRAW_MOUSE_FLG_BTN_L);
if (state->usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN)
mouse->flags |= (WRAW_MOUSE_FLG_BTN_R);
else if (state->usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP)
mouse->flags &= ~(WRAW_MOUSE_FLG_BTN_R);
}
if (state->usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN)
mouse->flags |= (WRAW_MOUSE_FLG_BTN_M);
else if (state->usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP)
mouse->flags &= ~(WRAW_MOUSE_FLG_BTN_M);
if (state->usButtonFlags & RI_MOUSE_BUTTON_4_DOWN)
mouse->flags |= (WRAW_MOUSE_FLG_BTN_B4);
else if (state->usButtonFlags & RI_MOUSE_BUTTON_4_UP)
mouse->flags &= ~(WRAW_MOUSE_FLG_BTN_B4);
if (state->usButtonFlags & RI_MOUSE_BUTTON_5_DOWN)
mouse->flags |= (WRAW_MOUSE_FLG_BTN_B5);
else if (state->usButtonFlags & RI_MOUSE_BUTTON_5_UP)
mouse->flags &= ~(WRAW_MOUSE_FLG_BTN_B5);
if (state->usButtonFlags & RI_MOUSE_WHEEL)
{
if ((SHORT)state->usButtonData > 0)
InterlockedExchange(&mouse->whl_u, 1);
else if ((SHORT)state->usButtonData < 0)
InterlockedExchange(&mouse->whl_d, 1);
}
}
static LRESULT CALLBACK winraw_callback(
HWND wnd, UINT msg, WPARAM wpar, LPARAM lpar)
{
static uint8_t data[1024];
RAWINPUT *ri = (RAWINPUT*)data;
UINT size = sizeof(data);
if (msg != WM_INPUT)
return DefWindowProcA(wnd, msg, wpar, lpar);
if (!(
GET_RAWINPUT_CODE_WPARAM(wpar) != RIM_INPUT /* app is in the background */
|| GetRawInputData((HRAWINPUT)lpar, RID_INPUT,
data, &size, sizeof(RAWINPUTHEADER)) == (UINT)-1))
{
unsigned i;
unsigned mcode, flags, down, mod;
winraw_input_t *wr = (winraw_input_t*)(LONG_PTR)
GetWindowLongPtr(wnd, GWLP_USERDATA);
switch (ri->header.dwType)
{
case RIM_TYPEKEYBOARD:
mcode = ri->data.keyboard.MakeCode;
flags = ri->data.keyboard.Flags;
down = (flags & RI_KEY_BREAK) ? 0 : 1;
mod = 0;
/* Extended scancodes */
if (flags & RI_KEY_E0)
mcode |= 0xE000;
else if (flags & RI_KEY_E1)
mcode |= 0xE100;
/* Special pause-key handling due to
* scancode 0xE11D45 incoming separately */
if ((wr->flags & WRAW_INP_FLG_KB_PAUSE) > 0)
{
wr->flags &= ~(WRAW_INP_FLG_KB_PAUSE);
if (mcode == SC_NUMLOCK)
mcode = SC_PAUSE;
}
else if (mcode == 0xE11D)
wr->flags |= (WRAW_INP_FLG_KB_PAUSE);
/* Ignored scancodes */
switch (mcode)
{
case RETROK_UNKNOWN:
case 0xE11D:
case 0xE02A:
case 0xE036:
case 0xE0AA:
case 0xE0B6:
return 0;
}
if (GetKeyState(VK_SHIFT) & 0x80)
mod |= RETROKMOD_SHIFT;
if (GetKeyState(VK_CONTROL) & 0x80)
mod |= RETROKMOD_CTRL;
if (GetKeyState(VK_MENU) & 0x80)
mod |= RETROKMOD_ALT;
if (GetKeyState(VK_CAPITAL) & 0x81)
mod |= RETROKMOD_CAPSLOCK;
if (GetKeyState(VK_SCROLL) & 0x81)
mod |= RETROKMOD_SCROLLOCK;
if (GetKeyState(VK_NUMLOCK) & 0x81)
mod |= RETROKMOD_NUMLOCK;
if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x80)
mod |= RETROKMOD_META;
wr->kb_keys[mcode] = down;
input_keyboard_event(down,
input_keymaps_translate_keysym_to_rk(mcode),
0, mod, RETRO_DEVICE_KEYBOARD);
break;
case RIM_TYPEMOUSE:
for (i = 0; i < wr->mouse_cnt; ++i)
{
if (g_mice[i].hnd == ri->header.hDevice)
{
winraw_update_mouse_state(wr,
&g_mice[i], &ri->data.mouse);
break;
}
}
break;
}
}
DefWindowProcA(wnd, msg, wpar, lpar);
return 0;
}
static void *winraw_init(const char *joypad_driver)
{
RAWINPUTDEVICE rid;
bool input_nowinkey_enable = config_get_ptr()->bools.input_nowinkey_enable;
winraw_input_t *wr = (winraw_input_t *)
calloc(1, sizeof(winraw_input_t));
if (!wr)
return NULL;
input_keymaps_init_keyboard_lut(rarch_key_map_winraw);
if (!(wr->window = winraw_create_window(winraw_callback)))
goto error;
if (!winraw_init_devices(&g_mice, &wr->mouse_cnt))
goto error;
if (wr->mouse_cnt)
{
if (!(wr->mice = (winraw_mouse_t*)
malloc(wr->mouse_cnt * sizeof(winraw_mouse_t))))
goto error;
memcpy(wr->mice, g_mice, wr->mouse_cnt * sizeof(winraw_mouse_t));
}
rid.dwFlags = (wr->window) ? 0 : RIDEV_REMOVE;
rid.hwndTarget = wr->window;
rid.usUsagePage = 0x01; /* Generic desktop */
rid.usUsage = 0x06; /* Keyboard */
if (input_nowinkey_enable)
rid.dwFlags |= RIDEV_NOHOTKEYS; /* Disable win keys while focused */
if (!RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)))
goto error;
rid.dwFlags = wr->window ? 0 : RIDEV_REMOVE;
rid.hwndTarget = wr->window;
rid.usUsagePage = 0x01; /* generic desktop */
rid.usUsage = 0x02; /* mouse */
if (!RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)))
goto error;
SetWindowLongPtr(wr->window, GWLP_USERDATA, (LONG_PTR)wr);
return wr;
error:
if (wr && wr->window)
{
rid.dwFlags = RIDEV_REMOVE;
rid.hwndTarget = NULL;
rid.usUsagePage = 0x01; /* generic desktop */
rid.usUsage = 0x02; /* mouse */
RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
rid.dwFlags = RIDEV_REMOVE;
rid.hwndTarget = NULL;
rid.usUsagePage = 0x01; /* Generic desktop */
rid.usUsage = 0x06; /* Keyboard */
if (input_nowinkey_enable)
rid.dwFlags |= RIDEV_NOHOTKEYS; /* Disable win keys while focused */
RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
DestroyWindow(wr->window);
UnregisterClassA("winraw-input", NULL);
}
free(g_mice);
if (wr)
free(wr->mice);
free(wr);
return NULL;
}
static void winraw_poll(void *data)
{
unsigned i;
winraw_input_t *wr = (winraw_input_t*)data;
/* Sync coordinates when window regains focus */
if (winraw_focus && !wr->last_focus)
winraw_sync_mouse_to_cursor(wr);
/* Release any keys still held at the moment focus is lost.
*
* The wndproc rejects background input (it returns early when
* GET_RAWINPUT_CODE_WPARAM() is not RIM_INPUT), so no key-up
* ever arrives for a key that was down when the window was
* deactivated, and it would stay latched indefinitely.
*
* The clear is deferred while Alt is still physically down,
* which is the case throughout an Alt-Tab. Latch it instead of
* testing the focus-loss edge directly, otherwise the edge is
* missed on exactly the Alt-Tab case that needs it and the
* keys stay latched until focus returns. */
if (!winraw_focus && wr->last_focus)
wr->kb_clear_pending = true;
if (wr->kb_clear_pending && !(GetKeyState(VK_MENU) & 0x8000))
{
/* LAlt is released through input_keyboard_event() rather
* than just zeroed, so the keyboard layer observes the
* transition and does not treat Alt as held afterwards. */
if (wr->kb_keys[SC_LALT])
input_keyboard_event(0,
input_keymaps_translate_keysym_to_rk(SC_LALT),
0, 0, RETRO_DEVICE_KEYBOARD);
memset(wr->kb_keys, 0, SC_LAST);
wr->kb_clear_pending = false;
}
wr->last_focus = winraw_focus;
for (i = 0; i < wr->mouse_cnt; ++i)
{
/* Clear buttons when not focused */
if (!winraw_focus)
g_mice[i].flags = 0;
wr->mice[i].x = g_mice[i].x;
wr->mice[i].y = g_mice[i].y;
wr->mice[i].dlt_x = InterlockedExchange(&g_mice[i].dlt_x, 0);
wr->mice[i].dlt_y = InterlockedExchange(&g_mice[i].dlt_y, 0);
wr->mice[i].whl_u = InterlockedExchange(&g_mice[i].whl_u, 0);
wr->mice[i].whl_d = InterlockedExchange(&g_mice[i].whl_d, 0);
wr->mice[i].flags = g_mice[i].flags;
}
}
static int16_t winraw_input_state(
void *data,
const input_device_driver_t *joypad,
const input_device_driver_t *sec_joypad,
rarch_joypad_info_t *joypad_info,
const retro_keybind_set *binds,
bool keyboard_mapping_blocked,
unsigned port,
unsigned device,
unsigned idx,
unsigned id)
{
winraw_mouse_t *mouse = NULL;
if (port < MAX_USERS)
{
int16_t ret = 0;
winraw_input_t *wr = (winraw_input_t*)data;
bool process_mouse =
(device == RETRO_DEVICE_JOYPAD)
|| (device == RETRO_DEVICE_MOUSE)
|| (device == RARCH_DEVICE_MOUSE_SCREEN)
|| (device == RETRO_DEVICE_LIGHTGUN)
|| (device == RETRO_DEVICE_POINTER)
|| (device == RARCH_DEVICE_POINTER_SCREEN);
if (process_mouse)
{
unsigned i;
settings_t *settings = config_get_ptr();
for (i = 0; i < wr->mouse_cnt; ++i)
{
if (i == settings->uints.input_mouse_index[port])
{
mouse = &wr->mice[i];
if (mouse && device > RETRO_DEVICE_JOYPAD)
g_mice[i].device = device;
break;
}
}
}
switch (device)
{
case RETRO_DEVICE_JOYPAD:
if (id == RETRO_DEVICE_ID_JOYPAD_MASK)
{
unsigned i;
if (mouse)
{
for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
{
if (binds[port][i].valid)
{
if (winraw_mouse_button_pressed(wr, mouse, port, binds[port][i].mbutton))
ret |= (1 << i);
}
}
}
if (!keyboard_mapping_blocked)
{
for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
{
if (binds[port][i].valid)
{
if ( (binds[port][i].key && binds[port][i].key < RETROK_LAST)
&& WINRAW_KEYBOARD_PRESSED(wr, binds[port][i].key))
ret |= (1 << i);
}
}
}
return ret;
}
if (id < RARCH_BIND_LIST_END)
{
if (binds[port][id].valid)
{
if ( (binds[port][id].key && binds[port][id].key < RETROK_LAST)
&& WINRAW_KEYBOARD_PRESSED(wr, binds[port][id].key)
&& (id == RARCH_GAME_FOCUS_TOGGLE || !keyboard_mapping_blocked)
)
return 1;
else if (mouse && winraw_mouse_button_pressed(wr, mouse, port, binds[port][id].mbutton))
return 1;
}
}
break;
case RETRO_DEVICE_ANALOG:
{
int id_minus_key = 0;
int id_plus_key = 0;
unsigned id_minus = 0;
unsigned id_plus = 0;
bool id_plus_valid = false;
bool id_minus_valid = false;
input_conv_analog_id_to_bind_id(idx, id, id_minus, id_plus);
id_minus_valid = binds[port][id_minus].valid;
id_plus_valid = binds[port][id_plus].valid;
id_minus_key = binds[port][id_minus].key;
id_plus_key = binds[port][id_plus].key;
if (id_plus_valid && id_plus_key && id_plus_key < RETROK_LAST)
{
if (WINRAW_KEYBOARD_PRESSED(wr, id_plus_key))
ret = 0x7fff;
}
if (id_minus_valid && id_minus_key && id_minus_key < RETROK_LAST)
{
if (WINRAW_KEYBOARD_PRESSED(wr, id_minus_key))
ret += -0x7fff;
}
}
return ret;
case RETRO_DEVICE_KEYBOARD:
return (id && id < RETROK_LAST) && WINRAW_KEYBOARD_PRESSED(wr, id);
case RETRO_DEVICE_MOUSE:
case RARCH_DEVICE_MOUSE_SCREEN:
if (mouse)
{
bool abs = (device == RARCH_DEVICE_MOUSE_SCREEN);
switch (id)
{
case RETRO_DEVICE_ID_MOUSE_X:
return abs ? mouse->x : mouse->dlt_x;
case RETRO_DEVICE_ID_MOUSE_Y:
return abs ? mouse->y : mouse->dlt_y;
case RETRO_DEVICE_ID_MOUSE_LEFT:
if ((mouse->flags & WRAW_MOUSE_FLG_BTN_L) > 0)
return 1;
break;
case RETRO_DEVICE_ID_MOUSE_RIGHT:
if ((mouse->flags & WRAW_MOUSE_FLG_BTN_R) > 0)
return 1;
break;
case RETRO_DEVICE_ID_MOUSE_WHEELUP:
if (mouse->whl_u)
return 1;
break;
case RETRO_DEVICE_ID_MOUSE_WHEELDOWN:
if (mouse->whl_d)
return 1;
break;
case RETRO_DEVICE_ID_MOUSE_MIDDLE:
if ((mouse->flags & WRAW_MOUSE_FLG_BTN_M) > 0)
return 1;
break;
case RETRO_DEVICE_ID_MOUSE_BUTTON_4:
if ((mouse->flags & WRAW_MOUSE_FLG_BTN_B4) > 0)
return 1;
break;
case RETRO_DEVICE_ID_MOUSE_BUTTON_5:
if ((mouse->flags & WRAW_MOUSE_FLG_BTN_B5) > 0)
return 1;
break;
}
}
break;
case RETRO_DEVICE_POINTER:
case RARCH_DEVICE_POINTER_SCREEN:
{
struct video_viewport vp = {0};
bool pointer_down = false;
int x = 0;
int y = 0;
int16_t res_x = 0;
int16_t res_y = 0;
int16_t res_screen_x = 0;
int16_t res_screen_y = 0;
unsigned num = 0;
struct winraw_pointer_status *
check_pos = wr->pointer_head.next;
while (check_pos && num < idx)
{
num++;
check_pos = check_pos->next;
}
if (!check_pos && idx > 0) /* idx = 0 has mouse fallback. */
return 0;
if (mouse)
{
x = mouse->x;
y = mouse->y;
pointer_down = (mouse->flags & (WRAW_MOUSE_FLG_BTN_L)) > 0;
}
if (check_pos)
{
x = check_pos->pointer_x;
y = check_pos->pointer_y;
pointer_down = true;
}
if (!(video_driver_translate_coord_viewport_confined_wrap(&vp, x, y,
&res_x, &res_y, &res_screen_x, &res_screen_y)))
return 0;
if (device == RARCH_DEVICE_POINTER_SCREEN)
{
res_x = res_screen_x;
res_y = res_screen_y;
}
switch (id)
{
case RETRO_DEVICE_ID_POINTER_X:
return res_x;
case RETRO_DEVICE_ID_POINTER_Y:
return res_y;
case RETRO_DEVICE_ID_POINTER_PRESSED:
return pointer_down;
case RETRO_DEVICE_ID_POINTER_IS_OFFSCREEN:
return input_driver_pointer_is_offscreen(res_x, res_y);
default:
break;
}
}
break;
case RETRO_DEVICE_LIGHTGUN:
switch (id)
{
case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X:
case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y:
case RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN:
if (mouse)
return winraw_lightgun_aiming_state(wr, mouse, port, id);
break;
case RETRO_DEVICE_ID_LIGHTGUN_TRIGGER:
case RETRO_DEVICE_ID_LIGHTGUN_RELOAD:
case RETRO_DEVICE_ID_LIGHTGUN_AUX_A:
case RETRO_DEVICE_ID_LIGHTGUN_AUX_B:
case RETRO_DEVICE_ID_LIGHTGUN_AUX_C:
case RETRO_DEVICE_ID_LIGHTGUN_START:
case RETRO_DEVICE_ID_LIGHTGUN_SELECT:
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP:
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN:
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT:
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT:
case RETRO_DEVICE_ID_LIGHTGUN_PAUSE: /* deprecated */
{
unsigned new_id = input_driver_lightgun_id_convert(id);
const uint64_t bind_joykey = input_config_binds[port][new_id].joykey;
const uint64_t bind_joyaxis = input_config_binds[port][new_id].joyaxis;
const uint64_t autobind_joykey = input_autoconf_binds[port][new_id].joykey;
const uint64_t autobind_joyaxis= input_autoconf_binds[port][new_id].joyaxis;
uint16_t joyport = joypad_info->joy_idx;
float axis_threshold = joypad_info->axis_threshold;
const uint64_t joykey = (bind_joykey != NO_BTN)
? bind_joykey : autobind_joykey;
const uint32_t joyaxis = (bind_joyaxis != AXIS_NONE)
? bind_joyaxis : autobind_joyaxis;
if (binds[port][new_id].valid)
{
if ((uint16_t)joykey != NO_BTN && joypad->button(
joyport, (uint16_t)joykey))
return 1;
if (joyaxis != AXIS_NONE &&
((float)abs(joypad->axis(joyport, joyaxis))
/ 0x8000) > axis_threshold)
return 1;
else if ((binds[port][new_id].key && binds[port][new_id].key < RETROK_LAST)
&& !keyboard_mapping_blocked
&& WINRAW_KEYBOARD_PRESSED(wr, binds[port][new_id].key)
)
return 1;
else if (mouse)
{
if (winraw_mouse_button_pressed(wr, mouse, port, binds[port][new_id].mbutton))
return 1;
}
}
}
break;
/*deprecated*/
case RETRO_DEVICE_ID_LIGHTGUN_X:
if (mouse)
return mouse->dlt_x;
break;
case RETRO_DEVICE_ID_LIGHTGUN_Y:
if (mouse)
return mouse->dlt_y;
break;
}
break;
}
}
return 0;
}
#if !defined(_XBOX)
bool winraw_handle_message(UINT msg,
WPARAM wpar, LPARAM lpar)
{
switch (msg)
{
case WM_SETFOCUS:
winraw_focus = true;
break;
case WM_KILLFOCUS:
winraw_focus = false;
break;
case WM_DEVICECHANGE:
#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500 /* 2K */
if ( (wpar == DBT_DEVICEARRIVAL)
|| (wpar == DBT_DEVICEREMOVECOMPLETE))
{
PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lpar;
if (pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
settings_t *settings = config_get_ptr();
/* Name the joypad driver to reinitialise. Passing NULL
* here makes input_joypad_init_driver() skip the branch
* that honours the configured driver - it is guarded by
* 'if (ident && *ident)' - and fall through to
* input_joypad_init_first(), which takes whichever entry
* of joypad_drivers[] initialises first. The configured
* driver is never tried, so a device change silently
* swaps it for one earlier in that list. */
joypad_driver_reinit(NULL,
settings ? settings->arrays.input_joypad_driver : NULL);
}
}
#endif
break;
}
return false;
}
#endif
static void winraw_free(void *data)
{
RAWINPUTDEVICE rid;
winraw_input_t *wr = (winraw_input_t*)data;
bool input_nowinkey_enable = config_get_ptr()->bools.input_nowinkey_enable;
rid.dwFlags = RIDEV_REMOVE;
rid.hwndTarget = NULL;
rid.usUsagePage = 0x01; /* generic desktop */
rid.usUsage = 0x02; /* mouse */
RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
rid.dwFlags = RIDEV_REMOVE;
rid.hwndTarget = NULL;
rid.usUsagePage = 0x01; /* Generic desktop */
rid.usUsage = 0x06; /* Keyboard */
if (input_nowinkey_enable)
rid.dwFlags |= RIDEV_NOHOTKEYS; /* Disable win keys while focused */
RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
SetWindowLongPtr(wr->window, GWLP_USERDATA, 0);
if (wr->window)
{
DestroyWindow(wr->window);
UnregisterClassA("winraw-input", NULL);
}
free(g_mice);
free(wr->mice);
free(data);
}
static uint64_t winraw_get_capabilities(void *u)
{
return (1 << RETRO_DEVICE_KEYBOARD)
| (1 << RETRO_DEVICE_MOUSE)
| (1 << RETRO_DEVICE_JOYPAD)
| (1 << RETRO_DEVICE_ANALOG)
| (1 << RETRO_DEVICE_POINTER)
| (1 << RETRO_DEVICE_LIGHTGUN);
}
static void winraw_grab_mouse(void *d, bool state)
{
RAWINPUTDEVICE rid;
winraw_input_t *wr = (winraw_input_t*)d;
bool curr_state = (wr->flags & WRAW_INP_FLG_MOUSE_GRAB) > 0;
if (curr_state == state)
return;
rid.dwFlags = (wr->window) ? 0 : RIDEV_REMOVE;
rid.hwndTarget = wr->window;
rid.usUsagePage = 0x01; /* generic desktop */
rid.usUsage = 0x02; /* mouse */
if (RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)))
{
if (state)
wr->flags |= WRAW_INP_FLG_MOUSE_GRAB;
else
wr->flags &= ~WRAW_INP_FLG_MOUSE_GRAB;
#ifndef _XBOX
win32_clip_window(state);
#endif
}
}
input_driver_t input_winraw = {
winraw_init,
winraw_poll,
winraw_input_state,
winraw_free,
NULL,
NULL,
winraw_get_capabilities,
"raw",
winraw_grab_mouse,
NULL,
NULL
};