xenadmin/XenAdmin/VNC/KeyMap.cs
Ross Lagerwall 91b224d0c4 CP-41775: Fix using Hanja/Hangeul keys over VNC
Signed-off-by: Ross Lagerwall <ross.lagerwall@citrix.com>
2023-03-06 15:59:48 +00:00

279 lines
9.6 KiB
C#

/* Copyright (c) Cloud Software Group, Inc.
*
* Redistribution and use in source and binary forms,
* with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Resources;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using XenCenterLib;
namespace DotNetVnc
{
/// <summary>
/// Translates C# Keys (i.e. virtual key codes) into X11 keysyms.
/// </summary>
public class KeyMap
{
private static Dictionary<Keys, int> map = new Dictionary<Keys, int>();
static KeyMap()
{
ResourceManager resources = new ResourceManager("DotNetVnc.KeyMap", typeof(KeyMap).Assembly);
foreach (Keys key in Enum.GetValues(typeof(Keys)))
{
int sym = parse_keysym(resources.GetString(Enum.GetName(typeof(Keys), key)));
if (sym != -1)
map[key] = sym;
}
}
private static int parse_keysym(string s)
{
if (s != null)
{
NumberStyles style;
if (s.StartsWith("0x") || s.StartsWith("0X"))
{
s = s.Substring(2);
style = NumberStyles.HexNumber;
}
else
{
style = NumberStyles.Integer;
}
int keysym;
if (int.TryParse(s, style, null, out keysym))
{
return keysym;
}
}
return -1;
}
public static int translateKey(Keys key)
{
return IsMapped(key) ? map[key] : UnicodeOfKey(key);
}
public static bool IsMapped(Keys key)
{
return map.ContainsKey(key);
}
private static IntPtr keyboard_state = Marshal.AllocHGlobal(256);
private static StringBuilder char_buffer = new StringBuilder(Win32.TO_UNICODE_BUFFER_SIZE);
private static int UnicodeOfKey(Keys key)
{
try
{
Win32.GetKeyboardState(keyboard_state);
int n = Win32.ToUnicode((uint)key, 0, keyboard_state, char_buffer, Win32.TO_UNICODE_BUFFER_SIZE, 0);
if (n == 1)
{
int k = char_buffer[0];
if (k < 0x20)
{
return k + 0x60;
}
else
{
return k;
}
}
else
{
return -1;
}
}
catch
{
return -1;
}
}
}
public unsafe class InterceptKeys
{
private static bool bubble = false;
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WM_SYSKEYDOWN = 0x0104;
private const int FLAG_EXTENDED = 0x01;
private delegate int LowLevelKeyboardProc(
int nCode, int wParam, KBDLLHOOKSTRUCT* lParam);
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public delegate void KeyEvent(bool down, int scancode, int keysym);
private static KeyEvent keyEvent = null;
#pragma warning disable 0649
[StructLayout(LayoutKind.Sequential)]
private struct KBDLLHOOKSTRUCT
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}
#pragma warning restore 0649
public static void grabKeys(KeyEvent keyEvent, bool bubble)
{
InterceptKeys.bubble = bubble;
if (InterceptKeys.keyEvent == null)
{
InterceptKeys.keyEvent = keyEvent;
_hookID = SetHook(_proc);
}
}
public static void releaseKeys()
{
if (InterceptKeys.keyEvent != null)
{
InterceptKeys.keyEvent = null;
UnhookWindowsHookEx(_hookID);
}
}
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
Win32.GetModuleHandle(curModule.ModuleName), 0);
}
}
private const int RIGHT_SHIFT_SCAN = 54;
private const int NUM_LOCK_SCAN = 69;
private const int HANJA_SCAN = 0xf1;
private const int HANGEUL_SCAN = 0xf2;
private static int HookCallback(int nCode, int wParam, KBDLLHOOKSTRUCT* lParam)
{
if (nCode < 0)
{
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
else
{
KBDLLHOOKSTRUCT kbStruct = *lParam;
bool extended = (kbStruct.flags & FLAG_EXTENDED) == FLAG_EXTENDED;
bool down = (wParam == WM_KEYDOWN) || (wParam == WM_SYSKEYDOWN);
int scanCode = kbStruct.scanCode;
int keySym = KeyMap.translateKey((Keys)kbStruct.vkCode);
/* kbStruct.scanCode for NUM_LOCK and PAUSE are the same (69).
* But NUM_LOCK is an extended key, where as PAUSE is not.
* QEMU doesn't support PAUSE and expects NUM_LOCK scanCode
* to be sent as 69
*/
switch (scanCode)
{
/* Although RIGHT_SHIFT, NUMS_LOCK are extended keys,
* scan code for these keys are not prefixed with 0xe0.
*/
case RIGHT_SHIFT_SCAN:
case NUM_LOCK_SCAN:
break;
/* QEMU expects "QNum" scancodes on the wire and these
* are different for the Hanja and Hangeul keys. Update
* accordingly.
*/
case HANJA_SCAN:
scanCode = 0x71;
break;
case HANGEUL_SCAN:
scanCode = 0x72;
break;
default:
/* 128 is added to scanCode to differentiate
* an extended key. Scan code for all extended keys
* needs to be prefixed with 0xe0, so adding 128
* or ( | 0x80) will give a hint to qemu that this
* scanCode is an extended one and qemu can then prefix
* scanCode with 0xe0
*/
scanCode += (extended ? 128 : 0);
break;
}
if (InterceptKeys.keyEvent != null)
{
InterceptKeys.keyEvent(down, scanCode, keySym);
}
if (bubble || scanCode == NUM_LOCK_SCAN)
{
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
else
{
return 1; // Prevent the message being passed on.
}
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int CallNextHookEx(IntPtr hhk, int nCode,
int wParam, KBDLLHOOKSTRUCT * lParam);
}
}