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:
James Ross-Gowan 2014-04-18 01:25:45 +10:00 committed by wm4
parent 40a072480c
commit 0ab3482f73
2 changed files with 184 additions and 50 deletions

View File

@ -40,10 +40,6 @@
static const wchar_t classname[] = L"mpv";
enum mp_messages {
MPM_PUTKEY = WM_USER,
};
typedef struct tagDropTarget {
IDropTarget iface;
ULONG refCnt;
@ -277,15 +273,176 @@ static bool key_state(struct vo *vo, int vk)
static int mod_state(struct vo *vo)
{
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;
if (key_state(vo, VK_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;
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,
LPARAM lParam)
{
@ -361,34 +518,26 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
break;
}
break;
case MPM_PUTKEY:
mp_input_put_key(vo->input_ctx, wParam | mod_state(vo));
break;
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)
return 0;
break;
case WM_CHAR:
case WM_SYSCHAR: {
int mods = mod_state(vo);
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.
case WM_SYSCHAR:
if (handle_char(vo, wParam))
return 0;
}
break;
}
case WM_KILLFOCUS:
mp_input_put_key(vo->input_ctx, MP_INPUT_RELEASE_ALL);
break;
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT && !w32->cursor_visible) {
SetCursor(NULL);
@ -477,24 +626,10 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
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 &&
msg->message != WM_KEYUP && msg->message != 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;
return msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN ||
msg == WM_KEYUP || msg == WM_SYSKEYUP;
}
/**
@ -517,13 +652,9 @@ int vo_w32_check_events(struct vo *vo)
w32->event_flags = 0;
while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) {
// Decode key messages and synthesize MPM_PUTKEY for keys in the
// keymap table
if (!translate_key_input(&msg)) {
// TranslateMessage only needs to generate WM_CHAR for keys that
// haven't already been decoded
// Only send IME messages to TranslateMessage
if (is_key_message(msg.message) && msg.wParam == VK_PROCESSKEY)
TranslateMessage(&msg);
}
DispatchMessageW(&msg);
}

View File

@ -55,6 +55,9 @@ struct vo_w32_state {
int mouse_x;
int mouse_y;
// UTF-16 decoding state for WM_CHAR and VK_PACKET
int high_surrogate;
};
struct vo;