summaryrefslogtreecommitdiffstats
path: root/video
diff options
context:
space:
mode:
authorJames Ross-Gowan <rossymiles@gmail.com>2014-04-18 01:25:45 +1000
committerwm4 <wm4@nowhere>2014-04-17 22:52:19 +0200
commit0ab3482f73a199b2e839ad4ec0a4b21adc1e75d5 (patch)
treeb1c334c4b65298836516c314795e96c5fc30cdce /video
parent40a072480c5d52d7daa21dd6162dfba40954f053 (diff)
downloadmpv-0ab3482f73a199b2e839ad4ec0a4b21adc1e75d5.tar.bz2
mpv-0ab3482f73a199b2e839ad4ec0a4b21adc1e75d5.tar.xz
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.
Diffstat (limited to 'video')
-rw-r--r--video/out/w32_common.c231
-rw-r--r--video/out/w32_common.h3
2 files changed, 184 insertions, 50 deletions
diff --git a/video/out/w32_common.c b/video/out/w32_common.c
index 3181b424cb..f0862185de 100644
--- a/video/out/w32_common.c
+++ b/video/out/w32_common.c
@@ -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);
}
diff --git a/video/out/w32_common.h b/video/out/w32_common.h
index b8486b538a..afe3fe871a 100644
--- a/video/out/w32_common.h
+++ b/video/out/w32_common.h
@@ -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;