blob: e6da9b8498386baa4a9efae6cc8a4445d7edc4ee [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>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000033#include <rfb_win32/keymap.h>
34#include <rdr/Exception.h>
35#include <rfb/LogWriter.h>
36
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000037using namespace rfb;
38
39static LogWriter vlog("SInput");
40
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000041//
42// -=- Pointer implementation for Win32
43//
44
45static DWORD buttonDownMapping[8] = {
46 MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_RIGHTDOWN,
47 MOUSEEVENTF_WHEEL, MOUSEEVENTF_WHEEL, 0, 0, 0
48};
49
50static DWORD buttonUpMapping[8] = {
51 MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_RIGHTUP,
52 MOUSEEVENTF_WHEEL, MOUSEEVENTF_WHEEL, 0, 0, 0
53};
54
55static DWORD buttonDataMapping[8] = {
Pierre Ossmana9af1f12015-06-17 10:47:28 +020056 0, 0, 0, 120, (DWORD)-120, 0, 0, 0
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000057};
58
59win32::SPointer::SPointer()
60 : last_buttonmask(0)
61{
62}
63
64void
65win32::SPointer::pointerEvent(const Point& pos, int buttonmask)
66{
67 // - We are specifying absolute coordinates
68 DWORD flags = MOUSEEVENTF_ABSOLUTE;
69
70 // - Has the pointer moved since the last event?
71 if (!last_position.equals(pos))
72 flags |= MOUSEEVENTF_MOVE;
73
74 // - If the system swaps left and right mouse buttons then we must
75 // swap them here to negate the effect, so that we do the actual
76 // action we mean to do
77 if (::GetSystemMetrics(SM_SWAPBUTTON)) {
78 bool leftDown = buttonmask & 1;
79 bool rightDown = buttonmask & 4;
80 buttonmask = (buttonmask & ~(1 | 4));
81 if (leftDown) buttonmask |= 4;
82 if (rightDown) buttonmask |= 1;
83 }
84
85 DWORD data = 0;
86 for (int i = 0; i < 8; i++) {
87 if ((buttonmask & (1<<i)) != (last_buttonmask & (1<<i))) {
88 if (buttonmask & (1<<i)) {
89 flags |= buttonDownMapping[i];
90 if (buttonDataMapping[i]) {
91 if (data) vlog.info("warning - two buttons set mouse_event data field");
92 data = buttonDataMapping[i];
93 }
94 } else {
95 flags |= buttonUpMapping[i];
96 }
97 }
98 }
99
100 last_position = pos;
101 last_buttonmask = buttonmask;
102
103 Rect primaryDisplay(0,0,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN));
104 if (primaryDisplay.contains(pos)) {
105 // mouse_event wants coordinates specified as a proportion of the
106 // primary display's size, scaled to the range 0 to 65535
107 Point scaled;
108 scaled.x = (pos.x * 65535) / (primaryDisplay.width()-1);
109 scaled.y = (pos.y * 65535) / (primaryDisplay.height()-1);
110 ::mouse_event(flags, scaled.x, scaled.y, data, 0);
111 } else {
112 // The event lies outside the primary monitor. Under Win2K, we can just use
113 // SendInput, which allows us to provide coordinates scaled to the virtual desktop.
114 // SendInput is available on all multi-monitor-aware platforms.
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100115 INPUT evt;
116 evt.type = INPUT_MOUSE;
117 Point vPos(pos.x-GetSystemMetrics(SM_XVIRTUALSCREEN),
118 pos.y-GetSystemMetrics(SM_YVIRTUALSCREEN));
119 evt.mi.dx = (vPos.x * 65535) / (GetSystemMetrics(SM_CXVIRTUALSCREEN)-1);
120 evt.mi.dy = (vPos.y * 65535) / (GetSystemMetrics(SM_CYVIRTUALSCREEN)-1);
121 evt.mi.dwFlags = flags | MOUSEEVENTF_VIRTUALDESK;
122 evt.mi.dwExtraInfo = 0;
123 evt.mi.mouseData = data;
124 evt.mi.time = 0;
125 if (SendInput(1, &evt, sizeof(evt)) != 1)
126 throw rdr::SystemException("SendInput", GetLastError());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000127 }
128}
129
130//
131// -=- Keyboard implementation
132//
133
134BoolParameter rfb::win32::SKeyboard::deadKeyAware("DeadKeyAware",
135 "Whether to assume the viewer has already interpreted dead key sequences "
136 "into latin-1 characters", true);
Rahul Kale74234172017-07-13 00:35:58 +0200137BoolParameter rfb::win32::SKeyboard::rawKeyboard("RawKeyboard",
138 "Send keyboard events straight through and avoid mapping them to the "
139 "current keyboard layout", false);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000140
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000141// The keysymToAscii table transforms a couple of awkward keysyms into their
142// ASCII equivalents.
143struct keysymToAscii_t {
144 rdr::U32 keysym;
145 rdr::U8 ascii;
146};
147
148keysymToAscii_t keysymToAscii[] = {
149 { XK_KP_Space, ' ' },
150 { XK_KP_Equal, '=' },
151};
152
153rdr::U8 latin1DeadChars[] = {
154 XK_grave, XK_acute, XK_asciicircum, XK_diaeresis, XK_degree, XK_cedilla,
155 XK_asciitilde
156};
157
158struct latin1ToDeadChars_t {
159 rdr::U8 latin1Char;
160 rdr::U8 deadChar;
161 rdr::U8 baseChar;
162};
163
164latin1ToDeadChars_t latin1ToDeadChars[] = {
165
166 { XK_Agrave, XK_grave, XK_A },
167 { XK_Egrave, XK_grave, XK_E },
168 { XK_Igrave, XK_grave, XK_I },
169 { XK_Ograve, XK_grave, XK_O },
170 { XK_Ugrave, XK_grave, XK_U },
171 { XK_agrave, XK_grave, XK_a },
172 { XK_egrave, XK_grave, XK_e },
173 { XK_igrave, XK_grave, XK_i },
174 { XK_ograve, XK_grave, XK_o},
175 { XK_ugrave, XK_grave, XK_u },
176
177 { XK_Aacute, XK_acute, XK_A },
178 { XK_Eacute, XK_acute, XK_E },
179 { XK_Iacute, XK_acute, XK_I },
180 { XK_Oacute, XK_acute, XK_O },
181 { XK_Uacute, XK_acute, XK_U },
182 { XK_Yacute, XK_acute, XK_Y },
183 { XK_aacute, XK_acute, XK_a },
184 { XK_eacute, XK_acute, XK_e },
185 { XK_iacute, XK_acute, XK_i },
186 { XK_oacute, XK_acute, XK_o},
187 { XK_uacute, XK_acute, XK_u },
188 { XK_yacute, XK_acute, XK_y },
189
190 { XK_Acircumflex, XK_asciicircum, XK_A },
191 { XK_Ecircumflex, XK_asciicircum, XK_E },
192 { XK_Icircumflex, XK_asciicircum, XK_I },
193 { XK_Ocircumflex, XK_asciicircum, XK_O },
194 { XK_Ucircumflex, XK_asciicircum, XK_U },
195 { XK_acircumflex, XK_asciicircum, XK_a },
196 { XK_ecircumflex, XK_asciicircum, XK_e },
197 { XK_icircumflex, XK_asciicircum, XK_i },
198 { XK_ocircumflex, XK_asciicircum, XK_o},
199 { XK_ucircumflex, XK_asciicircum, XK_u },
200
201 { XK_Adiaeresis, XK_diaeresis, XK_A },
202 { XK_Ediaeresis, XK_diaeresis, XK_E },
203 { XK_Idiaeresis, XK_diaeresis, XK_I },
204 { XK_Odiaeresis, XK_diaeresis, XK_O },
205 { XK_Udiaeresis, XK_diaeresis, XK_U },
206 { XK_adiaeresis, XK_diaeresis, XK_a },
207 { XK_ediaeresis, XK_diaeresis, XK_e },
208 { XK_idiaeresis, XK_diaeresis, XK_i },
209 { XK_odiaeresis, XK_diaeresis, XK_o},
210 { XK_udiaeresis, XK_diaeresis, XK_u },
211 { XK_ydiaeresis, XK_diaeresis, XK_y },
212
213 { XK_Aring, XK_degree, XK_A },
214 { XK_aring, XK_degree, XK_a },
215
216 { XK_Ccedilla, XK_cedilla, XK_C },
217 { XK_ccedilla, XK_cedilla, XK_c },
218
219 { XK_Atilde, XK_asciitilde, XK_A },
220 { XK_Ntilde, XK_asciitilde, XK_N },
221 { XK_Otilde, XK_asciitilde, XK_O },
222 { XK_atilde, XK_asciitilde, XK_a },
223 { XK_ntilde, XK_asciitilde, XK_n },
224 { XK_otilde, XK_asciitilde, XK_o },
225};
226
227// doKeyboardEvent wraps the system keybd_event function and attempts to find
228// the appropriate scancode corresponding to the supplied virtual keycode.
229
230inline void doKeyboardEvent(BYTE vkCode, DWORD flags) {
Pierre Ossmanfb450fb2015-03-03 16:34:56 +0100231 vlog.debug("vkCode 0x%x flags 0x%lx", vkCode, flags);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000232 keybd_event(vkCode, MapVirtualKey(vkCode, 0), flags, 0);
233}
234
Rahul Kale74234172017-07-13 00:35:58 +0200235inline void doScanCodeEvent(BYTE scancode, bool down) {
236 INPUT evt;
237
238 evt.type = INPUT_KEYBOARD;
239 evt.ki.wVk = 0;
240 evt.ki.dwFlags = KEYEVENTF_SCANCODE;
241
242 if (!down)
243 evt.ki.dwFlags |= KEYEVENTF_KEYUP;
244
245 if (scancode & 0x80) {
246 evt.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
247 scancode &= ~0x80;
248 }
249
250 evt.ki.wScan = scancode;
251 evt.ki.dwExtraInfo = 0;
252 evt.ki.time = 0;
253 vlog.debug("SendInput ScanCode: 0x%x Flags: 0x%lx %s", scancode,
254 evt.ki.dwFlags, down ? "Down" : "Up");
255
Pierre Ossmanf73214c2017-11-13 09:06:03 +0100256 // Windows has some bug where it doesn't look up scan code 0x45
257 // properly, so we need to help it out
258 if (evt.ki.wScan == 0x45) {
259 evt.ki.dwFlags &= ~KEYEVENTF_SCANCODE;
260 if (evt.ki.dwFlags & KEYEVENTF_EXTENDEDKEY)
261 evt.ki.wVk = VK_NUMLOCK;
262 else
263 evt.ki.wVk = VK_PAUSE;
264 }
265
Rahul Kale74234172017-07-13 00:35:58 +0200266 if (SendInput(1, &evt, sizeof(evt)) != 1)
267 vlog.error("SendInput %lu", GetLastError());
268}
269
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000270// KeyStateModifier is a class which helps simplify generating a "fake" press
271// or release of shift, ctrl, alt, etc. An instance of the class is created
272// for every key which may need to be pressed or released. Then either press()
273// or release() may be called to make sure that the corresponding key is in the
274// right state. The destructor of the class automatically reverts to the
275// previous state.
276
277class KeyStateModifier {
278public:
279 KeyStateModifier(int vkCode_, int flags_=0)
280 : vkCode(vkCode_), flags(flags_), pressed(false), released(false)
281 {}
282 void press() {
283 if (!(GetAsyncKeyState(vkCode) & 0x8000)) {
284 doKeyboardEvent(vkCode, flags);
285 pressed = true;
286 }
287 }
288 void release() {
289 if (GetAsyncKeyState(vkCode) & 0x8000) {
290 doKeyboardEvent(vkCode, flags | KEYEVENTF_KEYUP);
291 released = true;
292 }
293 }
294 ~KeyStateModifier() {
295 if (pressed) {
296 doKeyboardEvent(vkCode, flags | KEYEVENTF_KEYUP);
297 } else if (released) {
298 doKeyboardEvent(vkCode, flags);
299 }
300 }
301 int vkCode;
302 int flags;
303 bool pressed;
304 bool released;
305};
306
307
308// doKeyEventWithModifiers() generates a key event having first "pressed" or
309// "released" the shift, ctrl or alt modifiers if necessary.
310
311void doKeyEventWithModifiers(BYTE vkCode, BYTE modifierState, bool down)
312{
313 KeyStateModifier ctrl(VK_CONTROL);
314 KeyStateModifier alt(VK_MENU);
315 KeyStateModifier shift(VK_SHIFT);
316
317 if (down) {
318 if (modifierState & 2) ctrl.press();
319 if (modifierState & 4) alt.press();
320 if (modifierState & 1) {
321 shift.press();
322 } else {
323 shift.release();
324 }
325 }
326 doKeyboardEvent(vkCode, down ? 0 : KEYEVENTF_KEYUP);
327}
328
329
330win32::SKeyboard::SKeyboard()
331{
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000332 for (unsigned int i = 0; i < sizeof(keymap) / sizeof(keymap_t); i++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000333 vkMap[keymap[i].keysym] = keymap[i].vk;
334 extendedMap[keymap[i].keysym] = keymap[i].extended;
335 }
336
337 // Find dead characters for the current keyboard layout
338 // XXX how could we handle the keyboard layout changing?
339 BYTE keystate[256];
340 memset(keystate, 0, 256);
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000341 for (unsigned int j = 0; j < sizeof(latin1DeadChars); j++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000342 SHORT s = VkKeyScan(latin1DeadChars[j]);
343 if (s != -1) {
344 BYTE vkCode = LOBYTE(s);
345 BYTE modifierState = HIBYTE(s);
346 keystate[VK_SHIFT] = (modifierState & 1) ? 0x80 : 0;
347 keystate[VK_CONTROL] = (modifierState & 2) ? 0x80 : 0;
348 keystate[VK_MENU] = (modifierState & 4) ? 0x80 : 0;
349 rdr::U8 chars[2];
350 int nchars = ToAscii(vkCode, 0, keystate, (WORD*)&chars, 0);
351 if (nchars < 0) {
352 vlog.debug("Found dead key 0x%x '%c'",
353 latin1DeadChars[j], latin1DeadChars[j]);
354 deadChars.push_back(latin1DeadChars[j]);
355 ToAscii(vkCode, 0, keystate, (WORD*)&chars, 0);
356 }
357 }
358 }
359}
360
361
Pierre Ossman5ae28212017-05-16 14:30:38 +0200362void win32::SKeyboard::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000363{
Rahul Kale74234172017-07-13 00:35:58 +0200364 // If scan code is available use that directly as windows uses
365 // compatible scancodes
366 if (keycode && rawKeyboard) {
Pierre Ossmanf73214c2017-11-13 09:06:03 +0100367 // However NumLock incorrectly has the extended bit set
368 if (keycode == 0x45)
369 keycode = 0xc5;
370
371 // And Pause uses NumLock's proper code, except when Control is
372 // also pressed (i.e. when it is generating Break)
373 if ((keycode == 0xc6) && !(GetAsyncKeyState(VK_CONTROL) & 0x8000))
374 keycode = 0x45;
375
376 // And PrintScreen uses a different code than Alt+PrintScreen (SysRq)
377 if ((keycode == 0x54) && !(GetAsyncKeyState(VK_MENU) & 0x8000))
378 keycode = 0xb7;
379
Rahul Kale74234172017-07-13 00:35:58 +0200380 doScanCodeEvent(keycode, down);
381 return;
382 }
383
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000384 for (unsigned int i = 0; i < sizeof(keysymToAscii) / sizeof(keysymToAscii_t); i++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000385 if (keysymToAscii[i].keysym == keysym) {
386 keysym = keysymToAscii[i].ascii;
387 break;
388 }
389 }
390
391 if ((keysym >= 32 && keysym <= 126) ||
392 (keysym >= 160 && keysym <= 255))
393 {
394 // ordinary Latin-1 character
395
396 if (deadKeyAware) {
397 // Detect dead chars and generate the dead char followed by space so
398 // that we'll end up with the original char.
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000399 for (unsigned int i = 0; i < deadChars.size(); i++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000400 if (keysym == deadChars[i]) {
401 SHORT dc = VkKeyScan(keysym);
402 if (dc != -1) {
403 if (down) {
404 vlog.info("latin-1 dead key: 0x%x vkCode 0x%x mod 0x%x "
405 "followed by space", keysym, LOBYTE(dc), HIBYTE(dc));
406 doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), true);
407 doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), false);
408 doKeyEventWithModifiers(VK_SPACE, 0, true);
409 doKeyEventWithModifiers(VK_SPACE, 0, false);
410 }
411 return;
412 }
413 }
414 }
415 }
416
417 SHORT s = VkKeyScan(keysym);
418 if (s == -1) {
419 if (down) {
420 // not a single keypress - try synthesizing dead chars.
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000421 for (unsigned int j = 0;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000422 j < sizeof(latin1ToDeadChars) / sizeof(latin1ToDeadChars_t);
423 j++) {
424 if (keysym == latin1ToDeadChars[j].latin1Char) {
Peter Åstrand6820f6a2010-02-10 10:20:11 +0000425 for (unsigned int i = 0; i < deadChars.size(); i++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000426 if (deadChars[i] == latin1ToDeadChars[j].deadChar) {
427 SHORT dc = VkKeyScan(latin1ToDeadChars[j].deadChar);
428 SHORT bc = VkKeyScan(latin1ToDeadChars[j].baseChar);
429 if (dc != -1 && bc != -1) {
430 vlog.info("latin-1 key: 0x%x dead key vkCode 0x%x mod 0x%x "
431 "followed by vkCode 0x%x mod 0x%x",
432 keysym, LOBYTE(dc), HIBYTE(dc),
433 LOBYTE(bc), HIBYTE(bc));
434 doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), true);
435 doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), false);
436 doKeyEventWithModifiers(LOBYTE(bc), HIBYTE(bc), true);
437 doKeyEventWithModifiers(LOBYTE(bc), HIBYTE(bc), false);
438 return;
439 }
440 break;
441 }
442 }
443 break;
444 }
445 }
446 vlog.info("ignoring unrecognised Latin-1 keysym 0x%x",keysym);
447 }
448 return;
449 }
450
451 BYTE vkCode = LOBYTE(s);
452 BYTE modifierState = HIBYTE(s);
453 vlog.debug("latin-1 key: 0x%x vkCode 0x%x mod 0x%x down %d",
454 keysym, vkCode, modifierState, down);
455 doKeyEventWithModifiers(vkCode, modifierState, down);
456
457 } else {
458
459 // see if it's a recognised keyboard key, otherwise ignore it
460
461 if (vkMap.find(keysym) == vkMap.end()) {
462 vlog.info("ignoring unknown keysym 0x%x",keysym);
463 return;
464 }
465 BYTE vkCode = vkMap[keysym];
466 DWORD flags = 0;
467 if (extendedMap[keysym]) flags |= KEYEVENTF_EXTENDEDKEY;
468 if (!down) flags |= KEYEVENTF_KEYUP;
469
470 vlog.debug("keyboard key: keysym 0x%x vkCode 0x%x ext %d down %d",
471 keysym, vkCode, extendedMap[keysym], down);
472 if (down && (vkCode == VK_DELETE) &&
473 ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) &&
474 ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0))
475 {
476 rfb::win32::emulateCtrlAltDel();
477 return;
478 }
479
480 doKeyboardEvent(vkCode, flags);
481 }
482}