blob: aa8a21bc8996fdd64e522de3c7aabadae01de042 [file] [log] [blame]
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19// -=- SInput.cxx
20//
21// A number of routines that accept VNC input event data and perform
22// the appropriate actions under Win32
23
24#define XK_MISCELLANY
25#define XK_LATIN1
26#define XK_CURRENCY
27#include <rfb/keysymdef.h>
28
29#include <tchar.h>
30#include <rfb_win32/SInput.h>
31#include <rfb_win32/MonitorInfo.h>
32#include <rfb_win32/Service.h>
33#include <rfb_win32/OSVersion.h>
34#include <rfb_win32/DynamicFn.h>
35#include <rfb_win32/keymap.h>
36#include <rdr/Exception.h>
37#include <rfb/LogWriter.h>
38
Peter Åstrand9d8f2112008-12-09 10:55:15 +000039#if(defined(INPUT_MOUSE) && defined(RFB_HAVE_MONITORINFO) && defined(MOUSEEVENTF_VIRTUALDESK))
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000040#define RFB_HAVE_SENDINPUT
41#else
42#pragma message(" NOTE: Not building SendInput support.")
43#endif
44
45using namespace rfb;
46
47static LogWriter vlog("SInput");
48
49#ifdef RFB_HAVE_SENDINPUT
50typedef UINT (WINAPI *_SendInput_proto)(UINT, LPINPUT, int);
51static win32::DynamicFn<_SendInput_proto> _SendInput(_T("user32.dll"), "SendInput");
52#endif
53
54//
55// -=- Pointer implementation for Win32
56//
57
58static DWORD buttonDownMapping[8] = {
59 MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_RIGHTDOWN,
60 MOUSEEVENTF_WHEEL, MOUSEEVENTF_WHEEL, 0, 0, 0
61};
62
63static DWORD buttonUpMapping[8] = {
64 MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_RIGHTUP,
65 MOUSEEVENTF_WHEEL, MOUSEEVENTF_WHEEL, 0, 0, 0
66};
67
68static DWORD buttonDataMapping[8] = {
69 0, 0, 0, 120, -120, 0, 0, 0
70};
71
72win32::SPointer::SPointer()
73 : last_buttonmask(0)
74{
75}
76
77void
78win32::SPointer::pointerEvent(const Point& pos, int buttonmask)
79{
80 // - We are specifying absolute coordinates
81 DWORD flags = MOUSEEVENTF_ABSOLUTE;
82
83 // - Has the pointer moved since the last event?
84 if (!last_position.equals(pos))
85 flags |= MOUSEEVENTF_MOVE;
86
87 // - If the system swaps left and right mouse buttons then we must
88 // swap them here to negate the effect, so that we do the actual
89 // action we mean to do
90 if (::GetSystemMetrics(SM_SWAPBUTTON)) {
91 bool leftDown = buttonmask & 1;
92 bool rightDown = buttonmask & 4;
93 buttonmask = (buttonmask & ~(1 | 4));
94 if (leftDown) buttonmask |= 4;
95 if (rightDown) buttonmask |= 1;
96 }
97
98 DWORD data = 0;
99 for (int i = 0; i < 8; i++) {
100 if ((buttonmask & (1<<i)) != (last_buttonmask & (1<<i))) {
101 if (buttonmask & (1<<i)) {
102 flags |= buttonDownMapping[i];
103 if (buttonDataMapping[i]) {
104 if (data) vlog.info("warning - two buttons set mouse_event data field");
105 data = buttonDataMapping[i];
106 }
107 } else {
108 flags |= buttonUpMapping[i];
109 }
110 }
111 }
112
113 last_position = pos;
114 last_buttonmask = buttonmask;
115
116 Rect primaryDisplay(0,0,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN));
117 if (primaryDisplay.contains(pos)) {
118 // mouse_event wants coordinates specified as a proportion of the
119 // primary display's size, scaled to the range 0 to 65535
120 Point scaled;
121 scaled.x = (pos.x * 65535) / (primaryDisplay.width()-1);
122 scaled.y = (pos.y * 65535) / (primaryDisplay.height()-1);
123 ::mouse_event(flags, scaled.x, scaled.y, data, 0);
124 } else {
125 // The event lies outside the primary monitor. Under Win2K, we can just use
126 // SendInput, which allows us to provide coordinates scaled to the virtual desktop.
127 // SendInput is available on all multi-monitor-aware platforms.
128#ifdef RFB_HAVE_SENDINPUT
129 if (osVersion.isPlatformNT) {
130 if (!_SendInput.isValid())
131 throw rdr::Exception("SendInput not available");
132 INPUT evt;
133 evt.type = INPUT_MOUSE;
134 Point vPos(pos.x-GetSystemMetrics(SM_XVIRTUALSCREEN),
135 pos.y-GetSystemMetrics(SM_YVIRTUALSCREEN));
136 evt.mi.dx = (vPos.x * 65535) / (GetSystemMetrics(SM_CXVIRTUALSCREEN)-1);
137 evt.mi.dy = (vPos.y * 65535) / (GetSystemMetrics(SM_CYVIRTUALSCREEN)-1);
138 evt.mi.dwFlags = flags | MOUSEEVENTF_VIRTUALDESK;
139 evt.mi.dwExtraInfo = 0;
140 evt.mi.mouseData = data;
141 evt.mi.time = 0;
142 if ((*_SendInput)(1, &evt, sizeof(evt)) != 1)
143 throw rdr::SystemException("SendInput", GetLastError());
144 } else {
145 // Under Win9x, this is not addressable by either mouse_event or SendInput
146 // *** STUPID KLUDGY HACK ***
147 POINT cursorPos; GetCursorPos(&cursorPos);
148 ULONG oldSpeed, newSpeed = 10;
149 ULONG mouseInfo[3];
150 if (flags & MOUSEEVENTF_MOVE) {
151 flags &= ~MOUSEEVENTF_ABSOLUTE;
152 SystemParametersInfo(SPI_GETMOUSE, 0, &mouseInfo, 0);
153 SystemParametersInfo(SPI_GETMOUSESPEED, 0, &oldSpeed, 0);
154 vlog.debug("SPI_GETMOUSE %d, %d, %d, speed %d", mouseInfo[0], mouseInfo[1], mouseInfo[2], oldSpeed);
155 ULONG idealMouseInfo[] = {10, 0, 0};
156 SystemParametersInfo(SPI_SETMOUSESPEED, 0, &newSpeed, 0);
157 SystemParametersInfo(SPI_SETMOUSE, 0, &idealMouseInfo, 0);
158 }
159 ::mouse_event(flags, pos.x-cursorPos.x, pos.y-cursorPos.y, data, 0);
160 if (flags & MOUSEEVENTF_MOVE) {
161 SystemParametersInfo(SPI_SETMOUSE, 0, &mouseInfo, 0);
162 SystemParametersInfo(SPI_SETMOUSESPEED, 0, &oldSpeed, 0);
163 }
164 }
165#endif
166 }
167}
168
169//
170// -=- Keyboard implementation
171//
172
173BoolParameter rfb::win32::SKeyboard::deadKeyAware("DeadKeyAware",
174 "Whether to assume the viewer has already interpreted dead key sequences "
175 "into latin-1 characters", true);
176
177static bool oneShift;
178
179// The keysymToAscii table transforms a couple of awkward keysyms into their
180// ASCII equivalents.
181struct keysymToAscii_t {
182 rdr::U32 keysym;
183 rdr::U8 ascii;
184};
185
186keysymToAscii_t keysymToAscii[] = {
187 { XK_KP_Space, ' ' },
188 { XK_KP_Equal, '=' },
189};
190
191rdr::U8 latin1DeadChars[] = {
192 XK_grave, XK_acute, XK_asciicircum, XK_diaeresis, XK_degree, XK_cedilla,
193 XK_asciitilde
194};
195
196struct latin1ToDeadChars_t {
197 rdr::U8 latin1Char;
198 rdr::U8 deadChar;
199 rdr::U8 baseChar;
200};
201
202latin1ToDeadChars_t latin1ToDeadChars[] = {
203
204 { XK_Agrave, XK_grave, XK_A },
205 { XK_Egrave, XK_grave, XK_E },
206 { XK_Igrave, XK_grave, XK_I },
207 { XK_Ograve, XK_grave, XK_O },
208 { XK_Ugrave, XK_grave, XK_U },
209 { XK_agrave, XK_grave, XK_a },
210 { XK_egrave, XK_grave, XK_e },
211 { XK_igrave, XK_grave, XK_i },
212 { XK_ograve, XK_grave, XK_o},
213 { XK_ugrave, XK_grave, XK_u },
214
215 { XK_Aacute, XK_acute, XK_A },
216 { XK_Eacute, XK_acute, XK_E },
217 { XK_Iacute, XK_acute, XK_I },
218 { XK_Oacute, XK_acute, XK_O },
219 { XK_Uacute, XK_acute, XK_U },
220 { XK_Yacute, XK_acute, XK_Y },
221 { XK_aacute, XK_acute, XK_a },
222 { XK_eacute, XK_acute, XK_e },
223 { XK_iacute, XK_acute, XK_i },
224 { XK_oacute, XK_acute, XK_o},
225 { XK_uacute, XK_acute, XK_u },
226 { XK_yacute, XK_acute, XK_y },
227
228 { XK_Acircumflex, XK_asciicircum, XK_A },
229 { XK_Ecircumflex, XK_asciicircum, XK_E },
230 { XK_Icircumflex, XK_asciicircum, XK_I },
231 { XK_Ocircumflex, XK_asciicircum, XK_O },
232 { XK_Ucircumflex, XK_asciicircum, XK_U },
233 { XK_acircumflex, XK_asciicircum, XK_a },
234 { XK_ecircumflex, XK_asciicircum, XK_e },
235 { XK_icircumflex, XK_asciicircum, XK_i },
236 { XK_ocircumflex, XK_asciicircum, XK_o},
237 { XK_ucircumflex, XK_asciicircum, XK_u },
238
239 { XK_Adiaeresis, XK_diaeresis, XK_A },
240 { XK_Ediaeresis, XK_diaeresis, XK_E },
241 { XK_Idiaeresis, XK_diaeresis, XK_I },
242 { XK_Odiaeresis, XK_diaeresis, XK_O },
243 { XK_Udiaeresis, XK_diaeresis, XK_U },
244 { XK_adiaeresis, XK_diaeresis, XK_a },
245 { XK_ediaeresis, XK_diaeresis, XK_e },
246 { XK_idiaeresis, XK_diaeresis, XK_i },
247 { XK_odiaeresis, XK_diaeresis, XK_o},
248 { XK_udiaeresis, XK_diaeresis, XK_u },
249 { XK_ydiaeresis, XK_diaeresis, XK_y },
250
251 { XK_Aring, XK_degree, XK_A },
252 { XK_aring, XK_degree, XK_a },
253
254 { XK_Ccedilla, XK_cedilla, XK_C },
255 { XK_ccedilla, XK_cedilla, XK_c },
256
257 { XK_Atilde, XK_asciitilde, XK_A },
258 { XK_Ntilde, XK_asciitilde, XK_N },
259 { XK_Otilde, XK_asciitilde, XK_O },
260 { XK_atilde, XK_asciitilde, XK_a },
261 { XK_ntilde, XK_asciitilde, XK_n },
262 { XK_otilde, XK_asciitilde, XK_o },
263};
264
265// doKeyboardEvent wraps the system keybd_event function and attempts to find
266// the appropriate scancode corresponding to the supplied virtual keycode.
267
268inline void doKeyboardEvent(BYTE vkCode, DWORD flags) {
269 vlog.debug("vkCode 0x%x flags 0x%x", vkCode, flags);
270 keybd_event(vkCode, MapVirtualKey(vkCode, 0), flags, 0);
271}
272
273// KeyStateModifier is a class which helps simplify generating a "fake" press
274// or release of shift, ctrl, alt, etc. An instance of the class is created
275// for every key which may need to be pressed or released. Then either press()
276// or release() may be called to make sure that the corresponding key is in the
277// right state. The destructor of the class automatically reverts to the
278// previous state.
279
280class KeyStateModifier {
281public:
282 KeyStateModifier(int vkCode_, int flags_=0)
283 : vkCode(vkCode_), flags(flags_), pressed(false), released(false)
284 {}
285 void press() {
286 if (!(GetAsyncKeyState(vkCode) & 0x8000)) {
287 doKeyboardEvent(vkCode, flags);
288 pressed = true;
289 }
290 }
291 void release() {
292 if (GetAsyncKeyState(vkCode) & 0x8000) {
293 doKeyboardEvent(vkCode, flags | KEYEVENTF_KEYUP);
294 released = true;
295 }
296 }
297 ~KeyStateModifier() {
298 if (pressed) {
299 doKeyboardEvent(vkCode, flags | KEYEVENTF_KEYUP);
300 } else if (released) {
301 doKeyboardEvent(vkCode, flags);
302 }
303 }
304 int vkCode;
305 int flags;
306 bool pressed;
307 bool released;
308};
309
310
311// doKeyEventWithModifiers() generates a key event having first "pressed" or
312// "released" the shift, ctrl or alt modifiers if necessary.
313
314void doKeyEventWithModifiers(BYTE vkCode, BYTE modifierState, bool down)
315{
316 KeyStateModifier ctrl(VK_CONTROL);
317 KeyStateModifier alt(VK_MENU);
318 KeyStateModifier shift(VK_SHIFT);
319
320 if (down) {
321 if (modifierState & 2) ctrl.press();
322 if (modifierState & 4) alt.press();
323 if (modifierState & 1) {
324 shift.press();
325 } else {
326 shift.release();
327 }
328 }
329 doKeyboardEvent(vkCode, down ? 0 : KEYEVENTF_KEYUP);
330}
331
332
333win32::SKeyboard::SKeyboard()
334{
335 oneShift = rfb::win32::osVersion.isPlatformWindows;
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000336 for (unsigned int i = 0; i < sizeof(keymap) / sizeof(keymap_t); i++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000337 vkMap[keymap[i].keysym] = keymap[i].vk;
338 extendedMap[keymap[i].keysym] = keymap[i].extended;
339 }
340
341 // Find dead characters for the current keyboard layout
342 // XXX how could we handle the keyboard layout changing?
343 BYTE keystate[256];
344 memset(keystate, 0, 256);
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000345 for (unsigned int j = 0; j < sizeof(latin1DeadChars); j++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000346 SHORT s = VkKeyScan(latin1DeadChars[j]);
347 if (s != -1) {
348 BYTE vkCode = LOBYTE(s);
349 BYTE modifierState = HIBYTE(s);
350 keystate[VK_SHIFT] = (modifierState & 1) ? 0x80 : 0;
351 keystate[VK_CONTROL] = (modifierState & 2) ? 0x80 : 0;
352 keystate[VK_MENU] = (modifierState & 4) ? 0x80 : 0;
353 rdr::U8 chars[2];
354 int nchars = ToAscii(vkCode, 0, keystate, (WORD*)&chars, 0);
355 if (nchars < 0) {
356 vlog.debug("Found dead key 0x%x '%c'",
357 latin1DeadChars[j], latin1DeadChars[j]);
358 deadChars.push_back(latin1DeadChars[j]);
359 ToAscii(vkCode, 0, keystate, (WORD*)&chars, 0);
360 }
361 }
362 }
363}
364
365
366void win32::SKeyboard::keyEvent(rdr::U32 keysym, bool down)
367{
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000368 for (unsigned int i = 0; i < sizeof(keysymToAscii) / sizeof(keysymToAscii_t); i++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000369 if (keysymToAscii[i].keysym == keysym) {
370 keysym = keysymToAscii[i].ascii;
371 break;
372 }
373 }
374
375 if ((keysym >= 32 && keysym <= 126) ||
376 (keysym >= 160 && keysym <= 255))
377 {
378 // ordinary Latin-1 character
379
380 if (deadKeyAware) {
381 // Detect dead chars and generate the dead char followed by space so
382 // that we'll end up with the original char.
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000383 for (unsigned int i = 0; i < deadChars.size(); i++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000384 if (keysym == deadChars[i]) {
385 SHORT dc = VkKeyScan(keysym);
386 if (dc != -1) {
387 if (down) {
388 vlog.info("latin-1 dead key: 0x%x vkCode 0x%x mod 0x%x "
389 "followed by space", keysym, LOBYTE(dc), HIBYTE(dc));
390 doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), true);
391 doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), false);
392 doKeyEventWithModifiers(VK_SPACE, 0, true);
393 doKeyEventWithModifiers(VK_SPACE, 0, false);
394 }
395 return;
396 }
397 }
398 }
399 }
400
401 SHORT s = VkKeyScan(keysym);
402 if (s == -1) {
403 if (down) {
404 // not a single keypress - try synthesizing dead chars.
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000405 for (unsigned int j = 0;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000406 j < sizeof(latin1ToDeadChars) / sizeof(latin1ToDeadChars_t);
407 j++) {
408 if (keysym == latin1ToDeadChars[j].latin1Char) {
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000409 for (unsigned int i = 0; i < deadChars.size(); i++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000410 if (deadChars[i] == latin1ToDeadChars[j].deadChar) {
411 SHORT dc = VkKeyScan(latin1ToDeadChars[j].deadChar);
412 SHORT bc = VkKeyScan(latin1ToDeadChars[j].baseChar);
413 if (dc != -1 && bc != -1) {
414 vlog.info("latin-1 key: 0x%x dead key vkCode 0x%x mod 0x%x "
415 "followed by vkCode 0x%x mod 0x%x",
416 keysym, LOBYTE(dc), HIBYTE(dc),
417 LOBYTE(bc), HIBYTE(bc));
418 doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), true);
419 doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), false);
420 doKeyEventWithModifiers(LOBYTE(bc), HIBYTE(bc), true);
421 doKeyEventWithModifiers(LOBYTE(bc), HIBYTE(bc), false);
422 return;
423 }
424 break;
425 }
426 }
427 break;
428 }
429 }
430 vlog.info("ignoring unrecognised Latin-1 keysym 0x%x",keysym);
431 }
432 return;
433 }
434
435 BYTE vkCode = LOBYTE(s);
436 BYTE modifierState = HIBYTE(s);
437 vlog.debug("latin-1 key: 0x%x vkCode 0x%x mod 0x%x down %d",
438 keysym, vkCode, modifierState, down);
439 doKeyEventWithModifiers(vkCode, modifierState, down);
440
441 } else {
442
443 // see if it's a recognised keyboard key, otherwise ignore it
444
445 if (vkMap.find(keysym) == vkMap.end()) {
446 vlog.info("ignoring unknown keysym 0x%x",keysym);
447 return;
448 }
449 BYTE vkCode = vkMap[keysym];
450 DWORD flags = 0;
451 if (extendedMap[keysym]) flags |= KEYEVENTF_EXTENDEDKEY;
452 if (!down) flags |= KEYEVENTF_KEYUP;
453
454 vlog.debug("keyboard key: keysym 0x%x vkCode 0x%x ext %d down %d",
455 keysym, vkCode, extendedMap[keysym], down);
456 if (down && (vkCode == VK_DELETE) &&
457 ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) &&
458 ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0))
459 {
460 rfb::win32::emulateCtrlAltDel();
461 return;
462 }
463
464 doKeyboardEvent(vkCode, flags);
465 }
466}