mirror of https://github.com/mpv-player/mpv
w32_common: use ToUnicode to translate key input
This replaces translate_key_input with a solution that gives mpv more control over how keyboard input is converted to unicode. As a result: - Key up/down events are generated the same way for all keys. - Dead keys generate their base character instead of being combined with the following character. - Many Ctrl and Ctrl+Alt key combinations that were previously broken are fixed, since it's possible to discover the base keys. - AltGr doesn't produce special characters when mp_input_use_alt_gr is false. This also fixes some logic to do with detecting AltGr and adds proper UTF-16 decoding.
This commit is contained in:
parent
40a072480c
commit
0ab3482f73
|
@ -40,10 +40,6 @@
|
||||||
|
|
||||||
static const wchar_t classname[] = L"mpv";
|
static const wchar_t classname[] = L"mpv";
|
||||||
|
|
||||||
enum mp_messages {
|
|
||||||
MPM_PUTKEY = WM_USER,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct tagDropTarget {
|
typedef struct tagDropTarget {
|
||||||
IDropTarget iface;
|
IDropTarget iface;
|
||||||
ULONG refCnt;
|
ULONG refCnt;
|
||||||
|
@ -277,15 +273,176 @@ static bool key_state(struct vo *vo, int vk)
|
||||||
static int mod_state(struct vo *vo)
|
static int mod_state(struct vo *vo)
|
||||||
{
|
{
|
||||||
int res = 0;
|
int res = 0;
|
||||||
if (key_state(vo, VK_CONTROL))
|
|
||||||
|
// AltGr is represented as LCONTROL+RMENU on Windows
|
||||||
|
bool alt_gr = mp_input_use_alt_gr(vo->input_ctx) &&
|
||||||
|
key_state(vo, VK_RMENU) && key_state(vo, VK_LCONTROL);
|
||||||
|
|
||||||
|
if (key_state(vo, VK_RCONTROL) || (key_state(vo, VK_LCONTROL) && !alt_gr))
|
||||||
res |= MP_KEY_MODIFIER_CTRL;
|
res |= MP_KEY_MODIFIER_CTRL;
|
||||||
if (key_state(vo, VK_SHIFT))
|
if (key_state(vo, VK_SHIFT))
|
||||||
res |= MP_KEY_MODIFIER_SHIFT;
|
res |= MP_KEY_MODIFIER_SHIFT;
|
||||||
if (key_state(vo, VK_MENU))
|
if (key_state(vo, VK_LMENU) || (key_state(vo, VK_RMENU) && !alt_gr))
|
||||||
res |= MP_KEY_MODIFIER_ALT;
|
res |= MP_KEY_MODIFIER_ALT;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int decode_surrogate_pair(wchar_t lead, wchar_t trail)
|
||||||
|
{
|
||||||
|
return 0x10000 + ((lead & 0x3ff) << 10) | (trail & 0x3ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int decode_utf16(struct vo *vo, wchar_t c)
|
||||||
|
{
|
||||||
|
struct vo_w32_state *w32 = vo->w32;
|
||||||
|
|
||||||
|
// Decode UTF-16, keeping state in w32->high_surrogate
|
||||||
|
if (IS_HIGH_SURROGATE(c)) {
|
||||||
|
w32->high_surrogate = c;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (IS_LOW_SURROGATE(c)) {
|
||||||
|
if (!w32->high_surrogate) {
|
||||||
|
MP_ERR(vo, "Invalid UTF-16 input\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int codepoint = decode_surrogate_pair(w32->high_surrogate, c);
|
||||||
|
w32->high_surrogate = 0;
|
||||||
|
return codepoint;
|
||||||
|
}
|
||||||
|
if (w32->high_surrogate != 0) {
|
||||||
|
w32->high_surrogate = 0;
|
||||||
|
MP_ERR(vo, "Invalid UTF-16 input\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_keyboard_buffer(void)
|
||||||
|
{
|
||||||
|
static const UINT vkey = VK_DECIMAL;
|
||||||
|
static const BYTE keys[256] = { 0 };
|
||||||
|
UINT scancode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC);
|
||||||
|
wchar_t buf[10];
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
// Use the method suggested by Michael Kaplan to clear any pending dead
|
||||||
|
// keys from the current keyboard layout. See:
|
||||||
|
// https://web.archive.org/web/20101004154432/http://blogs.msdn.com/b/michkap/archive/2006/04/06/569632.aspx
|
||||||
|
// https://web.archive.org/web/20100820152419/http://blogs.msdn.com/b/michkap/archive/2007/10/27/5717859.aspx
|
||||||
|
do {
|
||||||
|
ret = ToUnicode(vkey, scancode, keys, buf, MP_ARRAY_SIZE(buf), 0);
|
||||||
|
} while (ret < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int to_unicode(UINT vkey, UINT scancode, const BYTE keys[256])
|
||||||
|
{
|
||||||
|
// This wraps ToUnicode to be stateless and to return only one character
|
||||||
|
|
||||||
|
// Make the buffer 10 code units long to be safe, same as here:
|
||||||
|
// https://web.archive.org/web/20101013215215/http://blogs.msdn.com/b/michkap/archive/2006/03/24/559169.aspx
|
||||||
|
wchar_t buf[10] = { 0 };
|
||||||
|
|
||||||
|
// Dead keys aren't useful for key shortcuts, so clear the keyboard state
|
||||||
|
clear_keyboard_buffer();
|
||||||
|
|
||||||
|
int len = ToUnicode(vkey, scancode, keys, buf, MP_ARRAY_SIZE(buf), 0);
|
||||||
|
|
||||||
|
// Return the last complete UTF-16 code point. A negative return value
|
||||||
|
// indicates a dead key, however there should still be a non-combining
|
||||||
|
// version of the key in the buffer.
|
||||||
|
if (len < 0)
|
||||||
|
len = -len;
|
||||||
|
if (len >= 2 && IS_SURROGATE_PAIR(buf[len - 2], buf[len - 1]))
|
||||||
|
return decode_surrogate_pair(buf[len - 2], buf[len - 1]);
|
||||||
|
if (len >= 1)
|
||||||
|
return buf[len - 1];
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int decode_key(struct vo *vo, UINT vkey, UINT scancode)
|
||||||
|
{
|
||||||
|
BYTE keys[256];
|
||||||
|
GetKeyboardState(keys);
|
||||||
|
|
||||||
|
// If mp_input_use_alt_gr is false, detect and remove AltGr so normal
|
||||||
|
// characters are generated. Note that AltGr is represented as
|
||||||
|
// LCONTROL+RMENU on Windows.
|
||||||
|
if ((keys[VK_RMENU] & 0x80) && (keys[VK_LCONTROL] & 0x80) &&
|
||||||
|
!mp_input_use_alt_gr(vo->input_ctx))
|
||||||
|
{
|
||||||
|
keys[VK_RMENU] = keys[VK_LCONTROL] = 0;
|
||||||
|
keys[VK_MENU] = keys[VK_LMENU];
|
||||||
|
keys[VK_CONTROL] = keys[VK_RCONTROL];
|
||||||
|
}
|
||||||
|
|
||||||
|
int c = to_unicode(vkey, scancode, keys);
|
||||||
|
|
||||||
|
// Some shift states prevent ToUnicode from working or cause it to produce
|
||||||
|
// control characters. If this is detected, remove modifiers until it
|
||||||
|
// starts producing normal characters.
|
||||||
|
if (c < 0x20 && (keys[VK_MENU] & 0x80)) {
|
||||||
|
keys[VK_LMENU] = keys[VK_RMENU] = keys[VK_MENU] = 0;
|
||||||
|
c = to_unicode(vkey, scancode, keys);
|
||||||
|
}
|
||||||
|
if (c < 0x20 && (keys[VK_CONTROL] & 0x80)) {
|
||||||
|
keys[VK_LCONTROL] = keys[VK_RCONTROL] = keys[VK_CONTROL] = 0;
|
||||||
|
c = to_unicode(vkey, scancode, keys);
|
||||||
|
}
|
||||||
|
if (c < 0x20)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Decode lone UTF-16 surrogates (VK_PACKET can generate these)
|
||||||
|
if (c < 0x10000)
|
||||||
|
return decode_utf16(vo, c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_key_down(struct vo *vo, UINT vkey, UINT scancode)
|
||||||
|
{
|
||||||
|
// Ignore key repeat
|
||||||
|
if (scancode & KF_REPEAT)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int mpkey = mp_w32_vkey_to_mpkey(vkey, scancode & KF_EXTENDED);
|
||||||
|
if (!mpkey) {
|
||||||
|
mpkey = decode_key(vo, vkey, scancode & (0xff | KF_EXTENDED));
|
||||||
|
if (!mpkey)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mp_input_put_key(vo->input_ctx, mpkey | mod_state(vo) | MP_KEY_STATE_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_key_up(struct vo *vo, UINT vkey, UINT scancode)
|
||||||
|
{
|
||||||
|
switch (vkey) {
|
||||||
|
case VK_MENU:
|
||||||
|
case VK_CONTROL:
|
||||||
|
case VK_SHIFT:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Releasing all keys on key-up is simpler and ensures no keys can be
|
||||||
|
// get "stuck." This matches the behaviour of other VOs.
|
||||||
|
mp_input_put_key(vo->input_ctx, MP_INPUT_RELEASE_ALL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_char(struct vo *vo, wchar_t wc)
|
||||||
|
{
|
||||||
|
int c = decode_utf16(vo, wc);
|
||||||
|
|
||||||
|
if (c == 0)
|
||||||
|
return true;
|
||||||
|
if (c < 0x20)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
mp_input_put_key(vo->input_ctx, c | mod_state(vo));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||||
LPARAM lParam)
|
LPARAM lParam)
|
||||||
{
|
{
|
||||||
|
@ -361,34 +518,26 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MPM_PUTKEY:
|
|
||||||
mp_input_put_key(vo->input_ctx, wParam | mod_state(vo));
|
|
||||||
break;
|
|
||||||
case WM_SYSKEYDOWN:
|
case WM_SYSKEYDOWN:
|
||||||
|
case WM_KEYDOWN:
|
||||||
|
handle_key_down(vo, wParam, HIWORD(lParam));
|
||||||
|
if (wParam == VK_F10)
|
||||||
|
return 0;
|
||||||
|
break;
|
||||||
|
case WM_SYSKEYUP:
|
||||||
|
case WM_KEYUP:
|
||||||
|
handle_key_up(vo, wParam, HIWORD(lParam));
|
||||||
if (wParam == VK_F10)
|
if (wParam == VK_F10)
|
||||||
return 0;
|
return 0;
|
||||||
break;
|
break;
|
||||||
case WM_CHAR:
|
case WM_CHAR:
|
||||||
case WM_SYSCHAR: {
|
case WM_SYSCHAR:
|
||||||
int mods = mod_state(vo);
|
if (handle_char(vo, wParam))
|
||||||
int code = wParam;
|
|
||||||
// Windows enables Ctrl+Alt when AltGr (VK_RMENU) is pressed.
|
|
||||||
// E.g. AltGr+9 on a German keyboard would yield Ctrl+Alt+[
|
|
||||||
// Warning: wine handles this differently. Don't test this on wine!
|
|
||||||
if (key_state(vo, VK_RMENU) && mp_input_use_alt_gr(vo->input_ctx))
|
|
||||||
mods &= ~(MP_KEY_MODIFIER_CTRL | MP_KEY_MODIFIER_ALT);
|
|
||||||
// Apparently Ctrl+A to Ctrl+Z is special cased, and produces
|
|
||||||
// character codes from 1-26. Work it around.
|
|
||||||
if ((mods & MP_KEY_MODIFIER_CTRL) && code >= 1 && code <= 26)
|
|
||||||
code = code - 1 + (mods & MP_KEY_MODIFIER_SHIFT ? 'A' : 'a');
|
|
||||||
if (code >= 32 && code < (1<<21)) {
|
|
||||||
mp_input_put_key(vo->input_ctx, code | mods);
|
|
||||||
// At least with Alt+char, not calling DefWindowProcW stops
|
|
||||||
// Windows from emitting a beep.
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
case WM_KILLFOCUS:
|
||||||
|
mp_input_put_key(vo->input_ctx, MP_INPUT_RELEASE_ALL);
|
||||||
|
break;
|
||||||
case WM_SETCURSOR:
|
case WM_SETCURSOR:
|
||||||
if (LOWORD(lParam) == HTCLIENT && !w32->cursor_visible) {
|
if (LOWORD(lParam) == HTCLIENT && !w32->cursor_visible) {
|
||||||
SetCursor(NULL);
|
SetCursor(NULL);
|
||||||
|
@ -477,24 +626,10 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||||
return DefWindowProcW(hWnd, message, wParam, lParam);
|
return DefWindowProcW(hWnd, message, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool translate_key_input(MSG *msg)
|
static bool is_key_message(UINT msg)
|
||||||
{
|
{
|
||||||
if (msg->message != WM_KEYDOWN && msg->message != WM_SYSKEYDOWN &&
|
return msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN ||
|
||||||
msg->message != WM_KEYUP && msg->message != WM_SYSKEYUP)
|
msg == WM_KEYUP || msg == WM_SYSKEYUP;
|
||||||
return false;
|
|
||||||
|
|
||||||
bool ext = HIWORD(msg->lParam) & KF_EXTENDED;
|
|
||||||
int mpkey = mp_w32_vkey_to_mpkey(msg->wParam, ext);
|
|
||||||
|
|
||||||
// If we don't want the key, return false so TranslateMessage can convert
|
|
||||||
// it to Unicode
|
|
||||||
if (!mpkey)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (msg->message == WM_KEYDOWN || msg->message == WM_SYSKEYDOWN)
|
|
||||||
SendMessageW(msg->hwnd, MPM_PUTKEY, mpkey, 0);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -517,13 +652,9 @@ int vo_w32_check_events(struct vo *vo)
|
||||||
w32->event_flags = 0;
|
w32->event_flags = 0;
|
||||||
|
|
||||||
while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) {
|
while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) {
|
||||||
// Decode key messages and synthesize MPM_PUTKEY for keys in the
|
// Only send IME messages to TranslateMessage
|
||||||
// keymap table
|
if (is_key_message(msg.message) && msg.wParam == VK_PROCESSKEY)
|
||||||
if (!translate_key_input(&msg)) {
|
|
||||||
// TranslateMessage only needs to generate WM_CHAR for keys that
|
|
||||||
// haven't already been decoded
|
|
||||||
TranslateMessage(&msg);
|
TranslateMessage(&msg);
|
||||||
}
|
|
||||||
DispatchMessageW(&msg);
|
DispatchMessageW(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,9 @@ struct vo_w32_state {
|
||||||
|
|
||||||
int mouse_x;
|
int mouse_x;
|
||||||
int mouse_y;
|
int mouse_y;
|
||||||
|
|
||||||
|
// UTF-16 decoding state for WM_CHAR and VK_PACKET
|
||||||
|
int high_surrogate;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct vo;
|
struct vo;
|
||||||
|
|
Loading…
Reference in New Issue