blob: 4a44f40104cf5b9c7cfb1b7ede10f873df4fcb44 [file] [log] [blame]
Pierre Ossmand50b3d12011-04-15 07:46:56 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +01002 * Copyright 2011-2014 Pierre Ossman for Cendio AB
Pierre Ossmand50b3d12011-04-15 07:46:56 +00003 *
4 * This is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this software; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
18 */
19
Peter Åstrandc359f362011-08-23 12:04:46 +000020#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
Pierre Ossmand50b3d12011-04-15 07:46:56 +000024#include <assert.h>
25#include <stdio.h>
26#include <string.h>
27
Pierre Ossmand50b3d12011-04-15 07:46:56 +000028#include <rfb/CMsgWriter.h>
29#include <rfb/LogWriter.h>
Pierre Ossmand1a853b2014-10-10 13:37:35 +020030#include <rfb/Exception.h>
Pierre Ossman2fa63f82016-12-05 15:26:21 +010031#include <rfb/ledStates.h>
Pierre Ossmand50b3d12011-04-15 07:46:56 +000032
33// FLTK can pull in the X11 headers on some systems
34#ifndef XK_VoidSymbol
Pierre Ossman13a809a2014-08-22 15:09:54 +020035#define XK_LATIN1
Pierre Ossmand50b3d12011-04-15 07:46:56 +000036#define XK_MISCELLANY
37#define XK_XKB_KEYS
38#include <rfb/keysymdef.h>
39#endif
40
Pierre Ossmancb0cffe2011-05-20 14:55:10 +000041#ifndef XF86XK_ModeLock
42#include <rfb/XF86keysym.h>
43#endif
44
Pierre Ossman2fa63f82016-12-05 15:26:21 +010045#if ! (defined(WIN32) || defined(__APPLE__))
46#include <X11/XKBlib.h>
47#endif
48
Pierre Ossman7b8bff62014-07-21 16:17:23 +020049#ifndef NoSymbol
50#define NoSymbol 0
51#endif
52
Pierre Ossman2e9684f2014-07-21 16:46:22 +020053// Missing in at least some versions of MinGW
54#ifndef MAPVK_VK_TO_VSC
55#define MAPVK_VK_TO_VSC 0
56#endif
57
Pierre Ossmand50b3d12011-04-15 07:46:56 +000058#include "Viewport.h"
59#include "CConn.h"
Pierre Ossmand463b572011-05-16 12:04:43 +000060#include "OptionsDialog.h"
Pierre Ossman947b48d2014-01-27 16:52:35 +010061#include "DesktopWindow.h"
Pierre Ossmand50b3d12011-04-15 07:46:56 +000062#include "i18n.h"
Pierre Ossmanc628ba42011-05-23 12:21:21 +000063#include "fltk_layout.h"
Pierre Ossmand50b3d12011-04-15 07:46:56 +000064#include "parameters.h"
65#include "keysym2ucs.h"
Martin Koegler498ef462011-09-04 07:04:43 +000066#include "menukey.h"
Pierre Ossman39ceb502011-07-12 15:54:25 +000067#include "vncviewer.h"
Pierre Ossmand50b3d12011-04-15 07:46:56 +000068
Pierre Ossmanac13abe2014-02-07 14:46:26 +010069#include "PlatformPixelBuffer.h"
Pierre Ossman947b48d2014-01-27 16:52:35 +010070
DRCb65bb932011-06-24 03:17:00 +000071#include <FL/fl_draw.H>
72#include <FL/fl_ask.H>
73
Pierre Ossman769963f2014-01-20 14:43:52 +010074#include <FL/Fl_Menu.H>
75#include <FL/Fl_Menu_Button.H>
76
Pierre Ossman0c158662017-07-13 15:54:11 +020077#if !defined(WIN32) && !defined(__APPLE__)
78#include <X11/XKBlib.h>
79extern const struct _code_map_xkb_to_qnum {
80 const char * from;
81 const unsigned short to;
82} code_map_xkb_to_qnum[];
83extern const unsigned int code_map_xkb_to_qnum_len;
84
85static int code_map_keycode_to_qnum[256];
86#endif
87
Pierre Ossman6b743d02014-07-21 16:48:43 +020088#ifdef __APPLE__
89#include "cocoa.h"
Pierre Ossman0c158662017-07-13 15:54:11 +020090extern const unsigned short code_map_osx_to_qnum[];
91extern const unsigned int code_map_osx_to_qnum_len;
Pierre Ossman6b743d02014-07-21 16:48:43 +020092#endif
93
DRCb65bb932011-06-24 03:17:00 +000094#ifdef WIN32
95#include "win32.h"
96#endif
97
Pierre Ossman6b743d02014-07-21 16:48:43 +020098
Pierre Ossmand50b3d12011-04-15 07:46:56 +000099using namespace rfb;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000100using namespace rdr;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000101
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000102static rfb::LogWriter vlog("Viewport");
103
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000104// Menu constants
105
Joel Teichroeba7494ac2015-07-13 14:46:22 -0700106enum { ID_EXIT, ID_FULLSCREEN, ID_MINIMIZE, ID_RESIZE,
Pierre Ossman8dc8bca2012-07-04 11:37:10 +0000107 ID_CTRL, ID_ALT, ID_MENUKEY, ID_CTRLALTDEL,
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000108 ID_REFRESH, ID_OPTIONS, ID_INFO, ID_ABOUT, ID_DISMISS };
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000109
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100110// Used to detect fake input (0xaa is not a real key)
111#ifdef WIN32
112static const WORD SCAN_FAKE = 0xaa;
113#endif
114
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000115Viewport::Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_)
Pierre Ossman0c9bd4b2014-07-09 16:44:11 +0200116 : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(NULL),
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +0100117 lastPointerPos(0, 0), lastButtonMask(0),
Pierre Ossman51249782018-03-08 14:05:39 +0100118#ifdef WIN32
119 altGrArmed(false),
120#endif
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200121 pendingServerCutText(NULL), pendingClientCutText(NULL),
Pierre Ossmanb1cd6ca2015-03-03 16:37:43 +0100122 menuCtrlKey(false), menuAltKey(false), cursor(NULL)
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000123{
Pierre Ossman0c158662017-07-13 15:54:11 +0200124#if !defined(WIN32) && !defined(__APPLE__)
125 XkbDescPtr xkb;
126 Status status;
127
128 xkb = XkbGetMap(fl_display, 0, XkbUseCoreKbd);
129 if (!xkb)
Pierre Ossman87b9d5f2017-09-18 16:07:12 +0200130 throw rfb::Exception("XkbGetMap");
Pierre Ossman0c158662017-07-13 15:54:11 +0200131
132 status = XkbGetNames(fl_display, XkbKeyNamesMask, xkb);
133 if (status != Success)
Pierre Ossman87b9d5f2017-09-18 16:07:12 +0200134 throw rfb::Exception("XkbGetNames");
Pierre Ossman0c158662017-07-13 15:54:11 +0200135
136 memset(code_map_keycode_to_qnum, 0, sizeof(code_map_keycode_to_qnum));
137 for (KeyCode keycode = xkb->min_key_code;
138 keycode < xkb->max_key_code;
139 keycode++) {
140 const char *keyname = xkb->names->keys[keycode].name;
141 unsigned short rfbcode;
142
143 if (keyname[0] == '\0')
144 continue;
145
146 rfbcode = 0;
147 for (unsigned i = 0;i < code_map_xkb_to_qnum_len;i++) {
148 if (strncmp(code_map_xkb_to_qnum[i].from,
149 keyname, XkbKeyNameLength) == 0) {
150 rfbcode = code_map_xkb_to_qnum[i].to;
151 break;
152 }
153 }
154 if (rfbcode != 0)
155 code_map_keycode_to_qnum[keycode] = rfbcode;
156 else
157 vlog.debug("No key mapping for key %.4s", keyname);
158 }
159
160 XkbFreeKeyboard(xkb, 0, True);
161#endif
162
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000163 Fl::add_clipboard_notify(handleClipboardChange, this);
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000164
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200165 // We need to intercept keyboard events early
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200166 Fl::add_system_handler(handleSystemEvent, this);
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200167
Pierre Ossman403ac272017-01-02 17:00:41 +0100168 frameBuffer = new PlatformPixelBuffer(w, h);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000169 assert(frameBuffer);
Pierre Ossman9f273e92015-11-09 16:34:54 +0100170 cc->setFramebuffer(frameBuffer);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000171
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000172 contextMenu = new Fl_Menu_Button(0, 0, 0, 0);
Pierre Ossmanad9d1ae2011-05-26 14:16:02 +0000173 // Setting box type to FL_NO_BOX prevents it from trying to draw the
174 // button component (which we don't want)
175 contextMenu->box(FL_NO_BOX);
176
Pierre Ossmanb043ad12011-06-01 09:26:57 +0000177 // The (invisible) button associated with this widget can mess with
178 // things like Fl_Scroll so we need to get rid of any parents.
179 // Unfortunately that's not possible because of STR #2654, but
180 // reparenting to the current window works for most cases.
181 window()->add(contextMenu);
182
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000183 setMenuKey();
184
185 OptionsDialog::addCallback(handleOptions, this);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000186}
187
188
189Viewport::~Viewport()
190{
191 // Unregister all timeouts in case they get a change tro trigger
192 // again later when this object is already gone.
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000193 Fl::remove_timeout(handlePointerTimeout, this);
Pierre Ossman51249782018-03-08 14:05:39 +0100194#ifdef WIN32
195 Fl::remove_timeout(handleAltGrTimeout, this);
196#endif
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000197
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200198 Fl::remove_system_handler(handleSystemEvent);
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200199
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000200 Fl::remove_clipboard_notify(handleClipboardChange);
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000201
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000202 OptionsDialog::removeCallback(handleOptions);
203
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000204 if (cursor) {
Pierre Ossmanf741d342011-06-09 08:32:49 +0000205 if (!cursor->alloc_array)
206 delete [] cursor->array;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000207 delete cursor;
208 }
209
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200210 clearPendingClipboard();
211
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000212 // FLTK automatically deletes all child widgets, so we shouldn't touch
213 // them ourselves here
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000214}
215
216
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000217const rfb::PixelFormat &Viewport::getPreferredPF()
218{
Pierre Ossman132b3d02011-06-13 11:19:32 +0000219 return frameBuffer->getPF();
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000220}
221
222
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000223// Copy the areas of the framebuffer that have been changed (damaged)
224// to the displayed window.
225
226void Viewport::updateWindow()
227{
228 Rect r;
229
Pierre Ossman0c9bd4b2014-07-09 16:44:11 +0200230 r = frameBuffer->getDamage();
231 damage(FL_DAMAGE_USER1, r.tl.x + x(), r.tl.y + y(), r.width(), r.height());
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000232}
233
Pierre Ossman4c204232018-03-26 13:32:49 +0200234void Viewport::serverCutText(const char* str, rdr::U32 len)
235{
236 char *buffer;
237 int size, ret;
238
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200239 clearPendingClipboard();
240
Pierre Ossman4c204232018-03-26 13:32:49 +0200241 if (!acceptClipboard)
242 return;
243
244 size = fl_utf8froma(NULL, 0, str, len);
245 if (size <= 0)
246 return;
247
248 size++;
249
250 buffer = new char[size];
251
252 ret = fl_utf8froma(buffer, size, str, len);
253 assert(ret < size);
254
255 vlog.debug("Got clipboard data (%d bytes)", (int)strlen(buffer));
256
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200257 if (!hasFocus()) {
258 pendingServerCutText = buffer;
259 return;
260 }
261
Pierre Ossman4c204232018-03-26 13:32:49 +0200262 // RFB doesn't have separate selection and clipboard concepts, so we
263 // dump the data into both variants.
264 if (setPrimary)
265 Fl::copy(buffer, ret, 0);
266 Fl::copy(buffer, ret, 1);
267
268 delete [] buffer;
269}
270
Pierre Ossmanf741d342011-06-09 08:32:49 +0000271static const char * dotcursor_xpm[] = {
272 "5 5 2 1",
273 ". c #000000",
274 " c #FFFFFF",
275 " ",
276 " ... ",
277 " ... ",
278 " ... ",
279 " "};
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000280
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000281void Viewport::setCursor(int width, int height, const Point& hotspot,
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100282 const rdr::U8* data)
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000283{
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100284 int i;
285
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000286 if (cursor) {
Pierre Ossmanf741d342011-06-09 08:32:49 +0000287 if (!cursor->alloc_array)
288 delete [] cursor->array;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000289 delete cursor;
290 }
291
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100292 for (i = 0; i < width*height; i++)
293 if (data[i*4 + 3] != 0) break;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000294
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100295 if ((i == width*height) && dotWhenNoCursor) {
Pierre Ossmana51574a2011-06-09 08:39:35 +0000296 vlog.debug("cursor is empty - using dot");
Pierre Ossmanf741d342011-06-09 08:32:49 +0000297
298 Fl_Pixmap pxm(dotcursor_xpm);
299 cursor = new Fl_RGB_Image(&pxm);
300 cursorHotspot.x = cursorHotspot.y = 2;
301 } else {
Pierre Ossman17a48f02011-06-09 08:42:04 +0000302 if ((width == 0) || (height == 0)) {
303 U8 *buffer = new U8[4];
304 memset(buffer, 0, 4);
305 cursor = new Fl_RGB_Image(buffer, 1, 1, 4);
306 cursorHotspot.x = cursorHotspot.y = 0;
307 } else {
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100308 U8 *buffer = new U8[width * height * 4];
309 memcpy(buffer, data, width * height * 4);
Pierre Ossman17a48f02011-06-09 08:42:04 +0000310 cursor = new Fl_RGB_Image(buffer, width, height, 4);
Pierre Ossman17a48f02011-06-09 08:42:04 +0000311 cursorHotspot = hotspot;
Pierre Ossmanf741d342011-06-09 08:32:49 +0000312 }
Pierre Ossmanf741d342011-06-09 08:32:49 +0000313 }
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000314
315 if (Fl::belowmouse() == this)
316 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000317}
318
319
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100320void Viewport::setLEDState(unsigned int state)
321{
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100322 vlog.debug("Got server LED state: 0x%08x", state);
323
Pierre Ossman9a747322018-03-26 14:16:43 +0200324 if (!hasFocus())
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100325 return;
326
327#if defined(WIN32)
328 INPUT input[6];
329 UINT count;
330 UINT ret;
331
332 memset(input, 0, sizeof(input));
333 count = 0;
334
335 if (!!(state & ledCapsLock) != !!(GetKeyState(VK_CAPITAL) & 0x1)) {
336 input[count].type = input[count+1].type = INPUT_KEYBOARD;
337 input[count].ki.wVk = input[count+1].ki.wVk = VK_CAPITAL;
338 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
339 input[count].ki.dwFlags = 0;
340 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP;
341 count += 2;
342 }
343
344 if (!!(state & ledNumLock) != !!(GetKeyState(VK_NUMLOCK) & 0x1)) {
345 input[count].type = input[count+1].type = INPUT_KEYBOARD;
346 input[count].ki.wVk = input[count+1].ki.wVk = VK_NUMLOCK;
347 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
348 input[count].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
349 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY;
350 count += 2;
351 }
352
353 if (!!(state & ledScrollLock) != !!(GetKeyState(VK_SCROLL) & 0x1)) {
354 input[count].type = input[count+1].type = INPUT_KEYBOARD;
355 input[count].ki.wVk = input[count+1].ki.wVk = VK_SCROLL;
356 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
357 input[count].ki.dwFlags = 0;
358 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP;
359 count += 2;
360 }
361
362 if (count == 0)
363 return;
364
365 ret = SendInput(count, input, sizeof(*input));
366 if (ret < count)
367 vlog.error(_("Failed to update keyboard LED state: %lu"), GetLastError());
368#elif defined(__APPLE__)
369 int ret;
370
371 ret = cocoa_set_caps_lock_state(state & ledCapsLock);
372 if (ret != 0) {
373 vlog.error(_("Failed to update keyboard LED state: %d"), ret);
374 return;
375 }
376
377 ret = cocoa_set_num_lock_state(state & ledNumLock);
378 if (ret != 0) {
379 vlog.error(_("Failed to update keyboard LED state: %d"), ret);
380 return;
381 }
382
383 // No support for Scroll Lock //
384
385#else
386 unsigned int affect, values;
387 unsigned int mask;
388
389 Bool ret;
390
391 affect = values = 0;
392
393 affect |= LockMask;
394 if (state & ledCapsLock)
395 values |= LockMask;
396
397 mask = getModifierMask(XK_Num_Lock);
398 affect |= mask;
399 if (state & ledNumLock)
400 values |= mask;
401
402 mask = getModifierMask(XK_Scroll_Lock);
403 affect |= mask;
404 if (state & ledScrollLock)
405 values |= mask;
406
407 ret = XkbLockModifiers(fl_display, XkbUseCoreKbd, affect, values);
408 if (!ret)
409 vlog.error(_("Failed to update keyboard LED state"));
410#endif
411}
412
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100413void Viewport::pushLEDState()
414{
415 unsigned int state;
416
417 // Server support?
418 if (cc->cp.ledState() == ledUnknown)
419 return;
420
421 state = 0;
422
423#if defined(WIN32)
424 if (GetKeyState(VK_CAPITAL) & 0x1)
425 state |= ledCapsLock;
426 if (GetKeyState(VK_NUMLOCK) & 0x1)
427 state |= ledNumLock;
428 if (GetKeyState(VK_SCROLL) & 0x1)
429 state |= ledScrollLock;
430#elif defined(__APPLE__)
431 int ret;
432 bool on;
433
434 ret = cocoa_get_caps_lock_state(&on);
435 if (ret != 0) {
436 vlog.error(_("Failed to get keyboard LED state: %d"), ret);
437 return;
438 }
439 if (on)
440 state |= ledCapsLock;
441
442 ret = cocoa_get_num_lock_state(&on);
443 if (ret != 0) {
444 vlog.error(_("Failed to get keyboard LED state: %d"), ret);
445 return;
446 }
447 if (on)
448 state |= ledNumLock;
449
450 // No support for Scroll Lock //
451 state |= (cc->cp.ledState() & ledScrollLock);
452
453#else
454 unsigned int mask;
455
456 Status status;
457 XkbStateRec xkbState;
458
459 status = XkbGetState(fl_display, XkbUseCoreKbd, &xkbState);
460 if (status != Success) {
461 vlog.error(_("Failed to get keyboard LED state: %d"), status);
462 return;
463 }
464
465 if (xkbState.locked_mods & LockMask)
466 state |= ledCapsLock;
467
468 mask = getModifierMask(XK_Num_Lock);
469 if (xkbState.locked_mods & mask)
470 state |= ledNumLock;
471
472 mask = getModifierMask(XK_Scroll_Lock);
473 if (xkbState.locked_mods & mask)
474 state |= ledScrollLock;
475#endif
476
477 if ((state & ledCapsLock) != (cc->cp.ledState() & ledCapsLock)) {
478 vlog.debug("Inserting fake CapsLock to get in sync with server");
Pierre Ossman0c158662017-07-13 15:54:11 +0200479 handleKeyPress(0x3a, XK_Caps_Lock);
480 handleKeyRelease(0x3a);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100481 }
482 if ((state & ledNumLock) != (cc->cp.ledState() & ledNumLock)) {
483 vlog.debug("Inserting fake NumLock to get in sync with server");
Pierre Ossman0c158662017-07-13 15:54:11 +0200484 handleKeyPress(0x45, XK_Num_Lock);
485 handleKeyRelease(0x45);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100486 }
487 if ((state & ledScrollLock) != (cc->cp.ledState() & ledScrollLock)) {
488 vlog.debug("Inserting fake ScrollLock to get in sync with server");
Pierre Ossman0c158662017-07-13 15:54:11 +0200489 handleKeyPress(0x46, XK_Scroll_Lock);
490 handleKeyRelease(0x46);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100491 }
492}
493
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100494
Pierre Ossman3d74d882017-01-02 19:49:52 +0100495void Viewport::draw(Surface* dst)
496{
497 int X, Y, W, H;
498
499 // Check what actually needs updating
500 fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
501 if ((W == 0) || (H == 0))
502 return;
503
504 frameBuffer->draw(dst, X - x(), Y - y(), X, Y, W, H);
505}
506
507
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000508void Viewport::draw()
509{
510 int X, Y, W, H;
511
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000512 // Check what actually needs updating
Pierre Ossmana6e20772011-04-15 11:10:52 +0000513 fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000514 if ((W == 0) || (H == 0))
515 return;
516
Pierre Ossman132b3d02011-06-13 11:19:32 +0000517 frameBuffer->draw(X - x(), Y - y(), X, Y, W, H);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000518}
519
520
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000521void Viewport::resize(int x, int y, int w, int h)
522{
Pierre Ossman9f273e92015-11-09 16:34:54 +0100523 if ((w != frameBuffer->width()) || (h != frameBuffer->height())) {
524 vlog.debug("Resizing framebuffer from %dx%d to %dx%d",
525 frameBuffer->width(), frameBuffer->height(), w, h);
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000526
Pierre Ossman403ac272017-01-02 17:00:41 +0100527 frameBuffer = new PlatformPixelBuffer(w, h);
Pierre Ossman9f273e92015-11-09 16:34:54 +0100528 assert(frameBuffer);
529 cc->setFramebuffer(frameBuffer);
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000530 }
531
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000532 Fl_Widget::resize(x, y, w, h);
533}
534
535
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000536int Viewport::handle(int event)
537{
Pierre Ossman689c4582011-05-26 15:39:41 +0000538 char *buffer;
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000539 int ret;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000540 int buttonMask, wheelMask;
541 DownMap::const_iterator iter;
542
543 switch (event) {
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000544 case FL_PASTE:
Pierre Ossman689c4582011-05-26 15:39:41 +0000545 buffer = new char[Fl::event_length() + 1];
546
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200547 clearPendingClipboard();
548
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000549 // This is documented as to ASCII, but actually does to 8859-1
Pierre Ossman689c4582011-05-26 15:39:41 +0000550 ret = fl_utf8toa(Fl::event_text(), Fl::event_length(), buffer,
551 Fl::event_length() + 1);
552 assert(ret < (Fl::event_length() + 1));
553
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200554 if (!hasFocus()) {
555 pendingClientCutText = buffer;
556 return 1;
557 }
558
Pierre Ossmanfb450fb2015-03-03 16:34:56 +0100559 vlog.debug("Sending clipboard data (%d bytes)", (int)strlen(buffer));
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000560
561 try {
Pierre Ossman59da99f2016-02-05 10:43:12 +0100562 cc->writer()->writeClientCutText(buffer, ret);
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000563 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000564 vlog.error("%s", e.str());
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000565 exit_vncviewer(e.str());
566 }
Pierre Ossman689c4582011-05-26 15:39:41 +0000567
568 delete [] buffer;
569
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000570 return 1;
Pierre Ossman689c4582011-05-26 15:39:41 +0000571
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000572 case FL_ENTER:
Pierre Ossman1f1f6fd2011-06-09 08:33:29 +0000573 if (cursor)
574 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossman56610fb2015-01-27 16:28:15 +0100575 // Yes, we would like some pointer events please!
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000576 return 1;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000577
578 case FL_LEAVE:
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000579 window()->cursor(FL_CURSOR_DEFAULT);
Pierre Ossman164865a2011-11-10 13:30:39 +0000580 // Fall through as we want a last move event to help trigger edge stuff
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000581 case FL_PUSH:
582 case FL_RELEASE:
583 case FL_DRAG:
584 case FL_MOVE:
585 case FL_MOUSEWHEEL:
586 buttonMask = 0;
587 if (Fl::event_button1())
588 buttonMask |= 1;
589 if (Fl::event_button2())
590 buttonMask |= 2;
591 if (Fl::event_button3())
592 buttonMask |= 4;
593
594 if (event == FL_MOUSEWHEEL) {
Pierre Ossmandf0ed9f2011-05-24 11:33:43 +0000595 wheelMask = 0;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000596 if (Fl::event_dy() < 0)
Pierre Ossmandf0ed9f2011-05-24 11:33:43 +0000597 wheelMask |= 8;
598 if (Fl::event_dy() > 0)
599 wheelMask |= 16;
600 if (Fl::event_dx() < 0)
601 wheelMask |= 32;
602 if (Fl::event_dx() > 0)
603 wheelMask |= 64;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000604
605 // A quick press of the wheel "button", followed by a immediate
606 // release below
Pierre Ossman6a464be2011-04-15 12:57:31 +0000607 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()),
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000608 buttonMask | wheelMask);
609 }
610
Pierre Ossman6a464be2011-04-15 12:57:31 +0000611 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), buttonMask);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000612 return 1;
613
614 case FL_FOCUS:
Pierre Ossman48ef54d2014-08-19 14:08:04 +0200615 Fl::disable_im();
Pierre Ossman78236292018-03-26 14:15:40 +0200616
617 try {
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200618 flushPendingClipboard();
619
Pierre Ossman78236292018-03-26 14:15:40 +0200620 // We may have gotten our lock keys out of sync with the server
621 // whilst we didn't have focus. Try to sort this out.
622 pushLEDState();
623 } catch (rdr::Exception& e) {
624 vlog.error("%s", e.str());
625 exit_vncviewer(e.str());
626 }
627
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000628 // Yes, we would like some focus please!
629 return 1;
630
631 case FL_UNFOCUS:
632 // Release all keys that were pressed as that generally makes most
633 // sense (e.g. Alt+Tab where we only see the Alt press)
Pierre Ossmanf8450ca2011-07-12 16:10:16 +0000634 while (!downKeySym.empty())
Pierre Ossman25188c42014-07-21 16:30:08 +0200635 handleKeyRelease(downKeySym.begin()->first);
Pierre Ossman48ef54d2014-08-19 14:08:04 +0200636 Fl::enable_im();
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000637 return 1;
638
639 case FL_KEYDOWN:
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000640 case FL_KEYUP:
Pierre Ossman56610fb2015-01-27 16:28:15 +0100641 // Just ignore these as keys were handled in the event handler
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000642 return 1;
643 }
644
645 return Fl_Widget::handle(event);
646}
647
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100648
Pierre Ossman9a747322018-03-26 14:16:43 +0200649bool Viewport::hasFocus()
650{
651 Fl_Widget* focus;
652
653 focus = Fl::grab();
654 if (!focus)
655 focus = Fl::focus();
656
657 return focus == this;
658}
659
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100660#if ! (defined(WIN32) || defined(__APPLE__))
661unsigned int Viewport::getModifierMask(unsigned int keysym)
662{
663 XkbDescPtr xkb;
664 unsigned int mask, keycode;
665 XkbAction *act;
666
667 mask = 0;
668
669 xkb = XkbGetMap(fl_display, XkbAllComponentsMask, XkbUseCoreKbd);
670 if (xkb == NULL)
671 return 0;
672
673 for (keycode = xkb->min_key_code; keycode <= xkb->max_key_code; keycode++) {
674 unsigned int state_out;
675 KeySym ks;
676
677 XkbTranslateKeyCode(xkb, keycode, 0, &state_out, &ks);
678 if (ks == NoSymbol)
679 continue;
680
681 if (ks == keysym)
682 break;
683 }
684
685 // KeySym not mapped?
686 if (keycode > xkb->max_key_code)
687 goto out;
688
689 act = XkbKeyAction(xkb, keycode, 0);
690 if (act == NULL)
691 goto out;
692 if (act->type != XkbSA_LockMods)
693 goto out;
694
695 if (act->mods.flags & XkbSA_UseModMapMods)
696 mask = xkb->map->modmap[keycode];
697 else
698 mask = act->mods.mask;
699
700out:
701 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
702
703 return mask;
704}
705#endif
706
707
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000708void Viewport::handleClipboardChange(int source, void *data)
709{
710 Viewport *self = (Viewport *)data;
711
712 assert(self);
713
Pierre Ossmana432b582017-01-02 15:16:58 +0100714 if (!sendClipboard)
715 return;
716
Pierre Ossmanf7fef922015-12-10 21:24:08 +0100717#if !defined(WIN32) && !defined(__APPLE__)
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000718 if (!sendPrimary && (source == 0))
719 return;
Pierre Ossmanf7fef922015-12-10 21:24:08 +0100720#endif
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000721
722 Fl::paste(*self, source);
723}
724
725
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200726void Viewport::clearPendingClipboard()
727{
728 delete [] pendingServerCutText;
729 pendingServerCutText = NULL;
730 delete [] pendingClientCutText;
731 pendingClientCutText = NULL;
732}
733
734
735void Viewport::flushPendingClipboard()
736{
737 if (pendingServerCutText) {
738 size_t len = strlen(pendingServerCutText);
739 if (setPrimary)
740 Fl::copy(pendingServerCutText, len, 0);
741 Fl::copy(pendingServerCutText, len, 1);
742 }
743 if (pendingClientCutText) {
744 size_t len = strlen(pendingClientCutText);
745 vlog.debug("Sending pending clipboard data (%d bytes)", (int)len);
Pierre Ossmanfa646bc2018-04-20 13:45:07 +0200746 cc->writer()->writeClientCutText(pendingClientCutText, len);
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200747 }
748
749 clearPendingClipboard();
750}
751
752
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000753void Viewport::handlePointerEvent(const rfb::Point& pos, int buttonMask)
754{
755 if (!viewOnly) {
756 if (pointerEventInterval == 0 || buttonMask != lastButtonMask) {
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000757 try {
Pierre Ossman59da99f2016-02-05 10:43:12 +0100758 cc->writer()->writePointerEvent(pos, buttonMask);
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000759 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000760 vlog.error("%s", e.str());
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000761 exit_vncviewer(e.str());
762 }
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000763 } else {
764 if (!Fl::has_timeout(handlePointerTimeout, this))
765 Fl::add_timeout((double)pointerEventInterval/1000.0,
766 handlePointerTimeout, this);
767 }
768 lastPointerPos = pos;
769 lastButtonMask = buttonMask;
770 }
771}
772
773
774void Viewport::handlePointerTimeout(void *data)
775{
776 Viewport *self = (Viewport *)data;
777
778 assert(self);
779
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000780 try {
Pierre Ossman59da99f2016-02-05 10:43:12 +0100781 self->cc->writer()->writePointerEvent(self->lastPointerPos,
782 self->lastButtonMask);
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000783 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000784 vlog.error("%s", e.str());
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000785 exit_vncviewer(e.str());
786 }
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000787}
788
789
Pierre Ossman25188c42014-07-21 16:30:08 +0200790void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym)
791{
792 static bool menuRecursion = false;
793
794 // Prevent recursion if the menu wants to send its own
795 // activation key.
796 if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) {
797 menuRecursion = true;
798 popupContextMenu();
799 menuRecursion = false;
800 return;
801 }
802
803 if (viewOnly)
804 return;
805
Pierre Ossman0c158662017-07-13 15:54:11 +0200806 if (keyCode == 0) {
807 vlog.error(_("No key code specified on key press"));
808 return;
809 }
810
Pierre Ossman25188c42014-07-21 16:30:08 +0200811#ifdef __APPLE__
812 // Alt on OS X behaves more like AltGr on other systems, and to get
813 // sane behaviour we should translate things in that manner for the
814 // remote VNC server. However that means we lose the ability to use
815 // Alt as a shortcut modifier. Do what RealVNC does and hijack the
816 // left command key as an Alt replacement.
817 switch (keySym) {
818 case XK_Super_L:
819 keySym = XK_Alt_L;
820 break;
821 case XK_Super_R:
822 keySym = XK_Super_L;
823 break;
824 case XK_Alt_L:
Pierre Ossman38fcebb2014-08-21 13:44:28 +0200825 keySym = XK_Mode_switch;
826 break;
Pierre Ossman25188c42014-07-21 16:30:08 +0200827 case XK_Alt_R:
828 keySym = XK_ISO_Level3_Shift;
829 break;
830 }
831#endif
832
Pierre Ossman25188c42014-07-21 16:30:08 +0200833 // Because of the way keyboards work, we cannot expect to have the same
834 // symbol on release as when pressed. This breaks the VNC protocol however,
835 // so we need to keep track of what keysym a key _code_ generated on press
836 // and send the same on release.
837 downKeySym[keyCode] = keySym;
838
839#if defined(WIN32) || defined(__APPLE__)
840 vlog.debug("Key pressed: 0x%04x => 0x%04x", keyCode, keySym);
841#else
842 vlog.debug("Key pressed: 0x%04x => XK_%s (0x%04x)",
843 keyCode, XKeysymToString(keySym), keySym);
844#endif
845
846 try {
Pierre Ossman0c158662017-07-13 15:54:11 +0200847 // Fake keycode?
848 if (keyCode > 0xff)
Pierre Ossman59da99f2016-02-05 10:43:12 +0100849 cc->writer()->writeKeyEvent(keySym, 0, true);
Pierre Ossman0c158662017-07-13 15:54:11 +0200850 else
Pierre Ossman59da99f2016-02-05 10:43:12 +0100851 cc->writer()->writeKeyEvent(keySym, keyCode, true);
Pierre Ossman25188c42014-07-21 16:30:08 +0200852 } catch (rdr::Exception& e) {
853 vlog.error("%s", e.str());
854 exit_vncviewer(e.str());
855 }
Pierre Ossman25188c42014-07-21 16:30:08 +0200856}
857
858
859void Viewport::handleKeyRelease(int keyCode)
860{
861 DownMap::iterator iter;
862
863 if (viewOnly)
864 return;
865
866 iter = downKeySym.find(keyCode);
867 if (iter == downKeySym.end()) {
868 // These occur somewhat frequently so let's not spam them unless
869 // logging is turned up.
870 vlog.debug("Unexpected release of key code %d", keyCode);
871 return;
872 }
873
874#if defined(WIN32) || defined(__APPLE__)
875 vlog.debug("Key released: 0x%04x => 0x%04x", keyCode, iter->second);
876#else
877 vlog.debug("Key released: 0x%04x => XK_%s (0x%04x)",
878 keyCode, XKeysymToString(iter->second), iter->second);
879#endif
880
881 try {
Pierre Ossman0c158662017-07-13 15:54:11 +0200882 if (keyCode > 0xff)
Pierre Ossman59da99f2016-02-05 10:43:12 +0100883 cc->writer()->writeKeyEvent(iter->second, 0, false);
Pierre Ossman0c158662017-07-13 15:54:11 +0200884 else
Pierre Ossman59da99f2016-02-05 10:43:12 +0100885 cc->writer()->writeKeyEvent(iter->second, keyCode, false);
Pierre Ossman25188c42014-07-21 16:30:08 +0200886 } catch (rdr::Exception& e) {
887 vlog.error("%s", e.str());
888 exit_vncviewer(e.str());
889 }
890
891 downKeySym.erase(iter);
892}
893
894
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200895int Viewport::handleSystemEvent(void *event, void *data)
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200896{
897 Viewport *self = (Viewport *)data;
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200898
899 assert(self);
900
Pierre Ossman9a747322018-03-26 14:16:43 +0200901 if (!self->hasFocus())
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200902 return 0;
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200903
904 assert(event);
905
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200906#if defined(WIN32)
907 MSG *msg = (MSG*)event;
908
909 if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN)) {
910 UINT vKey;
911 bool isExtended;
912 int keyCode;
913 rdr::U32 keySym;
914
915 vKey = msg->wParam;
916 isExtended = (msg->lParam & (1 << 24)) != 0;
917
918 keyCode = ((msg->lParam >> 16) & 0xff);
919
Pierre Ossman51249782018-03-08 14:05:39 +0100920 // Windows doesn't have a proper AltGr, but handles it using fake
921 // Ctrl+Alt. However the remote end might not be Windows, so we need
922 // to merge those in to a single AltGr event. We detect this case
923 // by seeing the two key events directly after each other with a very
Pierre Ossman69428fe2018-03-08 17:24:54 +0100924 // short time between them (<50ms) and supress the Ctrl event.
Pierre Ossman51249782018-03-08 14:05:39 +0100925 if (self->altGrArmed) {
926 self->altGrArmed = false;
927 Fl::remove_timeout(handleAltGrTimeout);
928
929 if (isExtended && (keyCode == 0x38) && (vKey == VK_MENU) &&
930 ((msg->time - self->altGrCtrlTime) < 50)) {
Pierre Ossman69428fe2018-03-08 17:24:54 +0100931 // Alt seen, so this is an AltGr sequence
932 } else {
933 // Not Alt, so fire the queued up Ctrl event
934 self->handleKeyPress(0x1d, XK_Control_L);
Pierre Ossman51249782018-03-08 14:05:39 +0100935 }
Pierre Ossman51249782018-03-08 14:05:39 +0100936 }
937
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100938 if (keyCode == SCAN_FAKE) {
939 vlog.debug("Ignoring fake key press (virtual key 0x%02x)", vKey);
940 return 1;
941 }
942
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200943 // Windows sets the scan code to 0x00 for multimedia keys, so we
944 // have to do a reverse lookup based on the vKey.
945 if (keyCode == 0x00) {
946 keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
947 if (keyCode == 0x00) {
Pierre Ossmanf05f3552014-09-22 12:19:52 +0200948 if (isExtended)
949 vlog.error(_("No scan code for extended virtual key 0x%02x"), (int)vKey);
950 else
951 vlog.error(_("No scan code for virtual key 0x%02x"), (int)vKey);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200952 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200953 }
954 }
955
Pierre Ossman0c158662017-07-13 15:54:11 +0200956 if (keyCode & ~0x7f) {
957 vlog.error(_("Invalid scan code 0x%02x"), (int)keyCode);
958 return 1;
959 }
960
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200961 if (isExtended)
Pierre Ossman0c158662017-07-13 15:54:11 +0200962 keyCode |= 0x80;
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200963
Pierre Ossmanf73214c2017-11-13 09:06:03 +0100964
965 // Fortunately RFB and Windows use the same scan code set (mostly),
966 // so there is no conversion needed
967 // (as long as we encode the extended keys with the high bit)
968
969 // However Pause sends a code that conflicts with NumLock, so use
970 // the code most RFB implementations use (part of the sequence for
971 // Ctrl+Pause, i.e. Break)
972 if (keyCode == 0x45)
973 keyCode = 0xc6;
974
975 // And NumLock incorrectly has the extended bit set
976 if (keyCode == 0xc5)
977 keyCode = 0x45;
978
979 // And Alt+PrintScreen (i.e. SysRq) sends a different code than
980 // PrintScreen
981 if (keyCode == 0xb7)
Pierre Ossman0c158662017-07-13 15:54:11 +0200982 keyCode = 0x54;
Pierre Ossmana7d3dc72014-09-30 17:03:28 +0200983
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200984 keySym = win32_vkey_to_keysym(vKey, isExtended);
985 if (keySym == NoSymbol) {
Pierre Ossmanf05f3552014-09-22 12:19:52 +0200986 if (isExtended)
987 vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey);
988 else
989 vlog.error(_("No symbol for virtual key 0x%02x"), (int)vKey);
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200990 }
991
Pierre Ossman30b3f922017-11-13 09:07:59 +0100992 // Windows sends the same vKey for both shifts, so we need to look
993 // at the scan code to tell them apart
994 if ((keySym == XK_Shift_L) && (keyCode == 0x36))
995 keySym = XK_Shift_R;
Pierre Ossman0c158662017-07-13 15:54:11 +0200996
Pierre Ossman69428fe2018-03-08 17:24:54 +0100997 // AltGr handling (see above)
998 if (win32_has_altgr()) {
999 if ((keyCode == 0xb8) && (keySym == XK_Alt_R))
1000 keySym = XK_ISO_Level3_Shift;
1001
1002 // Possible start of AltGr sequence?
1003 if ((keyCode == 0x1d) && (keySym == XK_Control_L)) {
1004 self->altGrArmed = true;
1005 self->altGrCtrlTime = msg->time;
1006 Fl::add_timeout(0.1, handleAltGrTimeout, self);
1007 return 1;
1008 }
Pierre Ossman51249782018-03-08 14:05:39 +01001009 }
1010
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001011 self->handleKeyPress(keyCode, keySym);
1012
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001013 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001014 } else if ((msg->message == WM_KEYUP) || (msg->message == WM_SYSKEYUP)) {
1015 UINT vKey;
1016 bool isExtended;
1017 int keyCode;
1018
1019 vKey = msg->wParam;
1020 isExtended = (msg->lParam & (1 << 24)) != 0;
1021
1022 keyCode = ((msg->lParam >> 16) & 0xff);
Pierre Ossman2fa63f82016-12-05 15:26:21 +01001023
Pierre Ossman51249782018-03-08 14:05:39 +01001024 // We can't get a release in the middle of an AltGr sequence, so
1025 // abort that detection
1026 if (self->altGrArmed) {
1027 self->altGrArmed = false;
1028 Fl::remove_timeout(handleAltGrTimeout);
1029 self->handleKeyPress(0x1d, XK_Control_L);
1030 }
1031
Pierre Ossman2fa63f82016-12-05 15:26:21 +01001032 if (keyCode == SCAN_FAKE) {
1033 vlog.debug("Ignoring fake key release (virtual key 0x%02x)", vKey);
1034 return 1;
1035 }
1036
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001037 if (keyCode == 0x00)
1038 keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
1039 if (isExtended)
Pierre Ossman0c158662017-07-13 15:54:11 +02001040 keyCode |= 0x80;
Pierre Ossmanf73214c2017-11-13 09:06:03 +01001041 if (keyCode == 0x45)
1042 keyCode = 0xc6;
1043 if (keyCode == 0xc5)
1044 keyCode = 0x45;
1045 if (keyCode == 0xb7)
Pierre Ossman0c158662017-07-13 15:54:11 +02001046 keyCode = 0x54;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001047
1048 self->handleKeyRelease(keyCode);
1049
Pierre Ossman30b3f922017-11-13 09:07:59 +01001050 // Windows has a rather nasty bug where it won't send key release
1051 // events for a Shift button if the other Shift is still pressed
1052 if ((keyCode == 0x2a) || (keyCode == 0x36)) {
1053 if (self->downKeySym.count(0x2a))
1054 self->handleKeyRelease(0x2a);
1055 if (self->downKeySym.count(0x36))
1056 self->handleKeyRelease(0x36);
1057 }
1058
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001059 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001060 }
Pierre Ossman6b743d02014-07-21 16:48:43 +02001061#elif defined(__APPLE__)
1062 if (cocoa_is_keyboard_event(event)) {
1063 int keyCode;
1064
1065 keyCode = cocoa_event_keycode(event);
Pierre Ossman0c158662017-07-13 15:54:11 +02001066 if ((unsigned)keyCode >= code_map_osx_to_qnum_len)
1067 keyCode = 0;
1068 else
1069 keyCode = code_map_osx_to_qnum[keyCode];
Pierre Ossman6b743d02014-07-21 16:48:43 +02001070
1071 if (cocoa_is_key_press(event)) {
1072 rdr::U32 keySym;
1073
1074 keySym = cocoa_event_keysym(event);
1075 if (keySym == NoSymbol) {
1076 vlog.error(_("No symbol for key code 0x%02x (in the current state)"),
1077 (int)keyCode);
Pierre Ossman6b743d02014-07-21 16:48:43 +02001078 }
1079
1080 self->handleKeyPress(keyCode, keySym);
1081
1082 // We don't get any release events for CapsLock, so we have to
1083 // send the release right away.
1084 if (keySym == XK_Caps_Lock)
1085 self->handleKeyRelease(keyCode);
1086 } else {
1087 self->handleKeyRelease(keyCode);
1088 }
1089
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001090 return 1;
Pierre Ossman6b743d02014-07-21 16:48:43 +02001091 }
1092#else
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001093 XEvent *xevent = (XEvent*)event;
1094
1095 if (xevent->type == KeyPress) {
Pierre Ossman0c158662017-07-13 15:54:11 +02001096 int keycode;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001097 char str;
1098 KeySym keysym;
1099
Pierre Ossman0c158662017-07-13 15:54:11 +02001100 keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
1101
1102 // Generate a fake keycode just for tracking if we can't figure
1103 // out the proper one
1104 if (keycode == 0)
1105 keycode = 0x100 | xevent->xkey.keycode;
1106
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001107 XLookupString(&xevent->xkey, &str, 1, &keysym, NULL);
1108 if (keysym == NoSymbol) {
1109 vlog.error(_("No symbol for key code %d (in the current state)"),
1110 (int)xevent->xkey.keycode);
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001111 }
1112
1113 switch (keysym) {
1114 // For the first few years, there wasn't a good consensus on what the
1115 // Windows keys should be mapped to for X11. So we need to help out a
1116 // bit and map all variants to the same key...
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001117 case XK_Hyper_L:
1118 keysym = XK_Super_L;
1119 break;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001120 case XK_Hyper_R:
1121 keysym = XK_Super_R;
1122 break;
1123 // There has been several variants for Shift-Tab over the years.
1124 // RFB states that we should always send a normal tab.
1125 case XK_ISO_Left_Tab:
1126 keysym = XK_Tab;
1127 break;
1128 }
1129
Pierre Ossman0c158662017-07-13 15:54:11 +02001130 self->handleKeyPress(keycode, keysym);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001131 return 1;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001132 } else if (xevent->type == KeyRelease) {
Pierre Ossman0c158662017-07-13 15:54:11 +02001133 int keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
1134 if (keycode == 0)
1135 keycode = 0x100 | xevent->xkey.keycode;
1136 self->handleKeyRelease(keycode);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001137 return 1;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001138 }
1139#endif
1140
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001141 return 0;
Pierre Ossman4f3ac692014-08-22 15:10:22 +02001142}
1143
Pierre Ossman51249782018-03-08 14:05:39 +01001144#ifdef WIN32
1145void Viewport::handleAltGrTimeout(void *data)
1146{
1147 Viewport *self = (Viewport *)data;
1148
1149 assert(self);
1150
1151 self->altGrArmed = false;
1152 self->handleKeyPress(0x1d, XK_Control_L);
1153}
1154#endif
1155
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001156void Viewport::initContextMenu()
1157{
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001158 contextMenu->clear();
1159
Pierre Ossmanff626f82015-09-23 16:39:54 +02001160 fltk_menu_add(contextMenu, p_("ContextMenu|", "E&xit viewer"),
1161 0, NULL, (void*)ID_EXIT, FL_MENU_DIVIDER);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001162
Pierre Ossmanff626f82015-09-23 16:39:54 +02001163 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Full screen"),
1164 0, NULL, (void*)ID_FULLSCREEN,
Pierre Ossman245c8022015-02-25 11:27:49 +01001165 FL_MENU_TOGGLE | (window()->fullscreen_active()?FL_MENU_VALUE:0));
Pierre Ossmanff626f82015-09-23 16:39:54 +02001166 fltk_menu_add(contextMenu, p_("ContextMenu|", "Minimi&ze"),
1167 0, NULL, (void*)ID_MINIMIZE, 0);
1168 fltk_menu_add(contextMenu, p_("ContextMenu|", "Resize &window to session"),
1169 0, NULL, (void*)ID_RESIZE,
Pierre Ossman245c8022015-02-25 11:27:49 +01001170 (window()->fullscreen_active()?FL_MENU_INACTIVE:0) |
Pierre Ossman245c8022015-02-25 11:27:49 +01001171 FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001172
Pierre Ossmanff626f82015-09-23 16:39:54 +02001173 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Ctrl"),
1174 0, NULL, (void*)ID_CTRL,
Pierre Ossman245c8022015-02-25 11:27:49 +01001175 FL_MENU_TOGGLE | (menuCtrlKey?FL_MENU_VALUE:0));
Pierre Ossmanff626f82015-09-23 16:39:54 +02001176 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Alt"),
1177 0, NULL, (void*)ID_ALT,
Pierre Ossman245c8022015-02-25 11:27:49 +01001178 FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0));
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001179
1180 if (menuKeySym) {
1181 char sendMenuKey[64];
Pierre Ossmanff626f82015-09-23 16:39:54 +02001182 snprintf(sendMenuKey, 64, p_("ContextMenu|", "Send %s"), (const char *)menuKey);
Pierre Ossman245c8022015-02-25 11:27:49 +01001183 fltk_menu_add(contextMenu, sendMenuKey, 0, NULL, (void*)ID_MENUKEY, 0);
Pierre Ossman0c158662017-07-13 15:54:11 +02001184 fltk_menu_add(contextMenu, "Secret shortcut menu key", menuKeyFLTK, NULL,
Pierre Ossman245c8022015-02-25 11:27:49 +01001185 (void*)ID_MENUKEY, FL_MENU_INVISIBLE);
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001186 }
1187
Pierre Ossmanff626f82015-09-23 16:39:54 +02001188 fltk_menu_add(contextMenu, p_("ContextMenu|", "Send Ctrl-Alt-&Del"),
1189 0, NULL, (void*)ID_CTRLALTDEL, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001190
Pierre Ossmanff626f82015-09-23 16:39:54 +02001191 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Refresh screen"),
1192 0, NULL, (void*)ID_REFRESH, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001193
Pierre Ossmanff626f82015-09-23 16:39:54 +02001194 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Options..."),
1195 0, NULL, (void*)ID_OPTIONS, 0);
1196 fltk_menu_add(contextMenu, p_("ContextMenu|", "Connection &info..."),
1197 0, NULL, (void*)ID_INFO, 0);
1198 fltk_menu_add(contextMenu, p_("ContextMenu|", "About &TigerVNC viewer..."),
1199 0, NULL, (void*)ID_ABOUT, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001200
Pierre Ossmanff626f82015-09-23 16:39:54 +02001201 fltk_menu_add(contextMenu, p_("ContextMenu|", "Dismiss &menu"),
1202 0, NULL, (void*)ID_DISMISS, 0);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001203}
1204
1205
1206void Viewport::popupContextMenu()
1207{
1208 const Fl_Menu_Item *m;
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001209 char buffer[1024];
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001210
Pierre Ossmanb3ff4372011-06-03 11:45:15 +00001211 // Make sure the menu is reset to its initial state between goes or
1212 // it will start up highlighting the previously selected entry.
1213 contextMenu->value(-1);
1214
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001215 // initialize context menu before display
1216 initContextMenu();
1217
Pierre Ossmanfd177f32012-01-05 12:37:04 +00001218 // Unfortunately FLTK doesn't reliably restore the mouse pointer for
1219 // menus, so we have to help it out.
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001220 if (Fl::belowmouse() == this)
1221 window()->cursor(FL_CURSOR_DEFAULT);
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001222
Pierre Ossman78236292018-03-26 14:15:40 +02001223 // FLTK also doesn't switch focus properly for menus
1224 handle(FL_UNFOCUS);
1225
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001226 m = contextMenu->popup();
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001227
Pierre Ossman78236292018-03-26 14:15:40 +02001228 handle(FL_FOCUS);
1229
Pierre Ossmanfd177f32012-01-05 12:37:04 +00001230 // Back to our proper mouse pointer.
Pierre Ossmande1a3b92014-03-17 14:29:49 +01001231 if ((Fl::belowmouse() == this) && cursor)
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001232 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001233
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001234 if (m == NULL)
1235 return;
1236
1237 switch (m->argument()) {
1238 case ID_EXIT:
1239 exit_vncviewer();
1240 break;
Pierre Ossman4c613d32011-05-26 14:14:06 +00001241 case ID_FULLSCREEN:
1242 if (window()->fullscreen_active())
1243 window()->fullscreen_off();
Pierre Ossman275284f2012-07-04 11:37:48 +00001244 else
Pierre Ossmanaae38912012-07-13 11:22:55 +00001245 ((DesktopWindow*)window())->fullscreen_on();
Pierre Ossman4c613d32011-05-26 14:14:06 +00001246 break;
Joel Teichroeba7494ac2015-07-13 14:46:22 -07001247 case ID_MINIMIZE:
1248 window()->iconize();
Joel Teichroeb1f9e45a2015-07-18 07:09:24 -07001249 break;
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001250 case ID_RESIZE:
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001251 if (window()->fullscreen_active())
1252 break;
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001253 window()->size(w(), h());
1254 break;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001255 case ID_CTRL:
Pierre Ossman25188c42014-07-21 16:30:08 +02001256 if (m->value())
Pierre Ossman0c158662017-07-13 15:54:11 +02001257 handleKeyPress(0x1d, XK_Control_L);
Pierre Ossman25188c42014-07-21 16:30:08 +02001258 else
Pierre Ossman0c158662017-07-13 15:54:11 +02001259 handleKeyRelease(0x1d);
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001260 menuCtrlKey = !menuCtrlKey;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001261 break;
1262 case ID_ALT:
Pierre Ossman25188c42014-07-21 16:30:08 +02001263 if (m->value())
Pierre Ossman0c158662017-07-13 15:54:11 +02001264 handleKeyPress(0x38, XK_Alt_L);
Pierre Ossman25188c42014-07-21 16:30:08 +02001265 else
Pierre Ossman0c158662017-07-13 15:54:11 +02001266 handleKeyRelease(0x38);
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001267 menuAltKey = !menuAltKey;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001268 break;
1269 case ID_MENUKEY:
Pierre Ossman0c158662017-07-13 15:54:11 +02001270 handleKeyPress(menuKeyCode, menuKeySym);
1271 handleKeyRelease(menuKeyCode);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001272 break;
1273 case ID_CTRLALTDEL:
Pierre Ossman0c158662017-07-13 15:54:11 +02001274 handleKeyPress(0x1d, XK_Control_L);
1275 handleKeyPress(0x38, XK_Alt_L);
1276 handleKeyPress(0xd3, XK_Delete);
Pierre Ossman991b4fe2011-07-12 16:02:30 +00001277
Pierre Ossman0c158662017-07-13 15:54:11 +02001278 handleKeyRelease(0xd3);
1279 handleKeyRelease(0x38);
1280 handleKeyRelease(0x1d);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001281 break;
Pierre Ossmand4c61ce2011-04-29 11:18:12 +00001282 case ID_REFRESH:
1283 cc->refreshFramebuffer();
1284 break;
Pierre Ossmand463b572011-05-16 12:04:43 +00001285 case ID_OPTIONS:
1286 OptionsDialog::showDialog();
1287 break;
Pierre Ossman2eb1d112011-05-16 12:18:08 +00001288 case ID_INFO:
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001289 if (fltk_escape(cc->connectionInfo(), buffer, sizeof(buffer)) < sizeof(buffer)) {
1290 fl_message_title(_("VNC connection info"));
Pierre Ossmanf52740e2012-04-25 15:43:56 +00001291 fl_message("%s", buffer);
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001292 }
Pierre Ossman2eb1d112011-05-16 12:18:08 +00001293 break;
Pierre Ossmanb8858222011-04-29 11:51:38 +00001294 case ID_ABOUT:
1295 about_vncviewer();
1296 break;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001297 case ID_DISMISS:
1298 // Don't need to do anything
1299 break;
1300 }
1301}
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001302
1303
1304void Viewport::setMenuKey()
1305{
Pierre Ossman0c158662017-07-13 15:54:11 +02001306 getMenuKey(&menuKeyFLTK, &menuKeyCode, &menuKeySym);
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001307}
1308
1309
1310void Viewport::handleOptions(void *data)
1311{
1312 Viewport *self = (Viewport*)data;
1313
1314 self->setMenuKey();
1315}