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