blob: 408e483af7de88435bd55b18daac3d037e24da6a [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#include <map>
20
21#define XK_MISCELLANY
22#define XK_LATIN1
23#define XK_CURRENCY
24#include <rfb/keysymdef.h>
25
26#include <rfb_win32/CKeyboard.h>
27#include <rfb/LogWriter.h>
28#include <rfb_win32/OSVersion.h>
29#include "keymap.h"
30
31using namespace rfb;
32
33static LogWriter vlog("CKeyboard");
34
35
36// Client-side RFB keyboard event sythesis
37
38class CKeymapper {
39
40public:
41 CKeymapper()
42 {
Peter Åstrand6ac5b732010-02-10 07:22:55 +000043 for (unsigned int i = 0; i < sizeof(keymap) / sizeof(keymap_t); i++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000044 int extendedVkey = keymap[i].vk + (keymap[i].extended ? 256 : 0);
45 if (keysymMap.find(extendedVkey) == keysymMap.end()) {
46 keysymMap[extendedVkey] = keymap[i].keysym;
47 }
48 }
49 }
50
51 // lookup() tries to find a match for vkey with the extended flag. We check
52 // first for an exact match including the extended flag, then try without the
53 // extended flag.
54 rdr::U32 lookup(int extendedVkey) {
Peter Åstrand6978fc02009-01-15 11:51:39 +000055
56 // There's no real definition of the meaning of
57 // VK_SEPARATOR/XK_KP_Separator or VK_DECIMAL/XK_KP_Decimal. As
58 // http://blogs.msdn.com/michkap/archive/2006/09/13/752377.aspx
59 // puts it: "As for what is actually assigned to VK_DECIMAL, that
60 // is something that for every keyboard is either defined in a
61 // standard or decided by the person/people who submitted the
62 // keyboard layout. It may match the locale's preferences or it
63 // may not". In a VNC context, we are considering a SEPARATOR to
64 // be a comma and a DECIMAL to be a dot.
65 if (extendedVkey == VK_DECIMAL || extendedVkey == VK_SEPARATOR) {
66 char buf[4];
Adam Tkacae1ac972010-06-24 20:08:14 +000067 if (!GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, (LPTSTR) buf,
68 sizeof(buf) / sizeof(TCHAR))) {
Peter Åstrand6978fc02009-01-15 11:51:39 +000069 vlog.debug("failed to retrieve LOCALE_SDECIMAL");
70 } else {
71 switch (buf[0]) {
72 case ',':
73 extendedVkey = VK_SEPARATOR;
74 break;
75 case '.':
76 extendedVkey = VK_DECIMAL;
77 break;
78 }
79 }
80 }
81
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000082 if (keysymMap.find(extendedVkey) != keysymMap.end())
83 return keysymMap[extendedVkey];
84 if (keysymMap.find(extendedVkey ^ 256) != keysymMap.end())
85 return keysymMap[extendedVkey ^ 256];
86 return 0;
87 }
88
89private:
90 std::map<int,rdr::U32> keysymMap;
91} ckeymapper;
92
93
94class ModifierKeyReleaser {
95public:
96 ModifierKeyReleaser(InputHandler* writer_, int vkCode, bool extended)
97 : writer(writer_), extendedVkey(vkCode + (extended ? 256 : 0)),
98 keysym(0)
99 {}
100 void release(std::map<int,rdr::U32>* downKeysym) {
101 if (downKeysym->find(extendedVkey) != downKeysym->end()) {
102 keysym = (*downKeysym)[extendedVkey];
103 vlog.debug("fake release extendedVkey 0x%x, keysym 0x%x",
104 extendedVkey, keysym);
105 writer->keyEvent(keysym, false);
106 }
107 }
108 ~ModifierKeyReleaser() {
109 if (keysym) {
110 vlog.debug("fake press extendedVkey 0x%x, keysym 0x%x",
111 extendedVkey, keysym);
112 writer->keyEvent(keysym, true);
113 }
114 }
115 InputHandler* writer;
116 int extendedVkey;
117 rdr::U32 keysym;
118};
119
120// IS_PRINTABLE_LATIN1 tests if a character is either a printable latin1
121// character, or 128, which is the Euro symbol on Windows.
122#define IS_PRINTABLE_LATIN1(c) (((c) >= 32 && (c) <= 126) || (c) == 128 || \
123 ((c) >= 160 && (c) <= 255))
124
125void win32::CKeyboard::keyEvent(InputHandler* writer, rdr::U8 vkey,
126 rdr::U32 flags, bool down)
127{
128 bool extended = (flags & 0x1000000);
129 int extendedVkey = vkey + (extended ? 256 : 0);
130
131 // If it's a release, just release whichever keysym corresponded to the same
132 // key being pressed, regardless of how it would be interpreted in the
133 // current keyboard state.
134 if (!down) {
135 releaseKey(writer, extendedVkey);
136 return;
137 }
138
139 // We should always pass every down event to ToAscii() otherwise it can get
140 // out of sync.
141
142 // XXX should we pass CapsLock, ScrollLock or NumLock to ToAscii - they
143 // actually alter the lock state on the keyboard?
144
145 BYTE keystate[256];
146 GetKeyboardState(keystate);
147 rdr::U8 chars[2];
148
149 int nchars = ToAscii(vkey, 0, keystate, (WORD*)&chars, 0);
150
151 // See if it's in the Windows VK code -> X keysym map. We do this before
152 // looking at the result of ToAscii so that e.g. we recognise that it's
153 // XK_KP_Add rather than '+'.
154
155 rdr::U32 keysym = ckeymapper.lookup(extendedVkey);
156 if (keysym) {
157 vlog.debug("mapped key: extendedVkey 0x%x", extendedVkey);
158 pressKey(writer, extendedVkey, keysym);
159 return;
160 }
161
162 if (nchars < 0) {
163 // Dead key - the next call to ToAscii() will give us either the accented
164 // character or two characters.
165 vlog.debug("ToAscii dead key (1): extendedVkey 0x%x", extendedVkey);
166 return;
167 }
168
169 if (nchars > 0 && IS_PRINTABLE_LATIN1(chars[0])) {
170 // Got a printable latin1 character. We must release Control and Alt
171 // (AltGr) if they were both pressed, so that the latin1 character is seen
172 // without them by the VNC server.
173 ModifierKeyReleaser lctrl(writer, VK_CONTROL, 0);
174 ModifierKeyReleaser rctrl(writer, VK_CONTROL, 1);
175 ModifierKeyReleaser lalt(writer, VK_MENU, 0);
176 ModifierKeyReleaser ralt(writer, VK_MENU, 1);
177
178 if ((keystate[VK_CONTROL] & 0x80) && (keystate[VK_MENU] & 0x80)) {
179 lctrl.release(&downKeysym);
180 rctrl.release(&downKeysym);
181 lalt.release(&downKeysym);
182 ralt.release(&downKeysym);
183 }
184
185 for (int i = 0; i < nchars; i++) {
186 vlog.debug("ToAscii key (1): extendedVkey 0x%x", extendedVkey);
187 if (chars[i] == 128) { // special hack for euro!
188 pressKey(writer, extendedVkey, XK_EuroSign);
189 } else {
190 pressKey(writer, extendedVkey, chars[i]);
191 }
192 }
193 return;
194 }
195
196 // Either no chars were generated, or something outside the printable
197 // character range. Try ToAscii() without the Control and Alt keys down to
198 // see if that yields an ordinary character.
199
200 keystate[VK_CONTROL] = keystate[VK_LCONTROL] = keystate[VK_RCONTROL] = 0;
201 keystate[VK_MENU] = keystate[VK_LMENU] = keystate[VK_RMENU] = 0;
202
203 nchars = ToAscii(vkey, 0, keystate, (WORD*)&chars, 0);
204
205 if (nchars < 0) {
206 // So it would be a dead key if neither control nor alt were pressed.
207 // However, we know that at least one of control and alt must be pressed.
208 // We can't leave it at this stage otherwise the next call to ToAscii()
209 // with a valid character will get wrongly interpreted in the context of
210 // this bogus dead key. Working on the assumption that a dead key followed
211 // by space usually returns the dead character itself, try calling ToAscii
212 // with VK_SPACE.
213 vlog.debug("ToAscii dead key (2): extendedVkey 0x%x", extendedVkey);
214 nchars = ToAscii(VK_SPACE, 0, keystate, (WORD*)&chars, 0);
215 if (nchars < 0) {
216 vlog.debug("ToAscii dead key (3): extendedVkey 0x%x - giving up!",
217 extendedVkey);
218 return;
219 }
220 }
221
222 if (nchars > 0 && IS_PRINTABLE_LATIN1(chars[0])) {
223 for (int i = 0; i < nchars; i++) {
224 vlog.debug("ToAscii key (2) (no ctrl/alt): extendedVkey 0x%x",
225 extendedVkey);
226 if (chars[i] == 128) { // special hack for euro!
227 pressKey(writer, extendedVkey, XK_EuroSign);
228 } else {
229 pressKey(writer, extendedVkey, chars[i]);
230 }
231 }
232 return;
233 }
234
235 vlog.debug("no chars regardless of control and alt: extendedVkey 0x%x",
236 extendedVkey);
237}
238
239// releaseAllKeys() - write key release events to the server for all keys
240// that are currently regarded as being down.
241void win32::CKeyboard::releaseAllKeys(InputHandler* writer) {
242 std::map<int,rdr::U32>::iterator i, next_i;
243 for (i=downKeysym.begin(); i!=downKeysym.end(); i=next_i) {
244 next_i = i; next_i++;
245 writer->keyEvent((*i).second, false);
246 downKeysym.erase(i);
247 }
248}
249
250// releaseKey() - write a key up event to the server, but only if we've
251// actually sent a key down event for the given key. The key up event always
252// contains the same keysym we used in the key down event, regardless of what
253// it would look up as using the current keyboard state.
254void win32::CKeyboard::releaseKey(InputHandler* writer, int extendedVkey)
255{
256 if (downKeysym.find(extendedVkey) != downKeysym.end()) {
257 vlog.debug("release extendedVkey 0x%x, keysym 0x%x",
258 extendedVkey, downKeysym[extendedVkey]);
259 writer->keyEvent(downKeysym[extendedVkey], false);
260 downKeysym.erase(extendedVkey);
261 }
262}
263
264// pressKey() - write a key down event to the server, and record which keysym
265// was sent as corresponding to the given extendedVkey. The only tricky bit is
266// that if we are trying to press an extendedVkey which is already marked as
267// down but with a different keysym, then we need to release the old keysym
268// first. This can happen in two cases: (a) when a single key press results in
269// more than one character, and (b) when shift is released while another key is
270// autorepeating.
271void win32::CKeyboard::pressKey(InputHandler* writer, int extendedVkey,
272 rdr::U32 keysym)
273{
274 if (downKeysym.find(extendedVkey) != downKeysym.end()) {
275 if (downKeysym[extendedVkey] != keysym) {
276 vlog.debug("release extendedVkey 0x%x, keysym 0x%x",
277 extendedVkey, downKeysym[extendedVkey]);
278 writer->keyEvent(downKeysym[extendedVkey], false);
279 }
280 }
281 vlog.debug("press extendedVkey 0x%x, keysym 0x%x",
282 extendedVkey, keysym);
283 writer->keyEvent(keysym, true);
284 downKeysym[extendedVkey] = keysym;
285}