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