mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-04 17:11:06 +01:00
91b224d0c4
Signed-off-by: Ross Lagerwall <ross.lagerwall@citrix.com>
279 lines
9.6 KiB
C#
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);
|
|
|
|
}
|
|
}
|