/* 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 { /// /// Translates C# Keys (i.e. virtual key codes) into X11 keysyms. /// public class KeyMap { private static Dictionary map = new Dictionary(); 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); } }