blob: 713d36486fdcba2ae9ef270b34308cc36a2dc8ed [file] [log] [blame]
Pierre Ossmand50b3d12011-04-15 07:46:56 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Pierre Ossman546b2ad2019-05-02 12:32:03 +02002 * Copyright 2011-2019 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 Ossman609a9c82018-06-07 09:16:02 +0200121 firstLEDState(true),
Pierre Ossman615d16b2019-05-03 10:53:06 +0200122 pendingServerClipboard(false), pendingClientClipboard(false),
Pierre Ossmanb1cd6ca2015-03-03 16:37:43 +0100123 menuCtrlKey(false), menuAltKey(false), cursor(NULL)
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000124{
Pierre Ossman0c158662017-07-13 15:54:11 +0200125#if !defined(WIN32) && !defined(__APPLE__)
126 XkbDescPtr xkb;
127 Status status;
128
129 xkb = XkbGetMap(fl_display, 0, XkbUseCoreKbd);
130 if (!xkb)
Pierre Ossman87b9d5f2017-09-18 16:07:12 +0200131 throw rfb::Exception("XkbGetMap");
Pierre Ossman0c158662017-07-13 15:54:11 +0200132
133 status = XkbGetNames(fl_display, XkbKeyNamesMask, xkb);
134 if (status != Success)
Pierre Ossman87b9d5f2017-09-18 16:07:12 +0200135 throw rfb::Exception("XkbGetNames");
Pierre Ossman0c158662017-07-13 15:54:11 +0200136
137 memset(code_map_keycode_to_qnum, 0, sizeof(code_map_keycode_to_qnum));
138 for (KeyCode keycode = xkb->min_key_code;
139 keycode < xkb->max_key_code;
140 keycode++) {
141 const char *keyname = xkb->names->keys[keycode].name;
142 unsigned short rfbcode;
143
144 if (keyname[0] == '\0')
145 continue;
146
147 rfbcode = 0;
148 for (unsigned i = 0;i < code_map_xkb_to_qnum_len;i++) {
149 if (strncmp(code_map_xkb_to_qnum[i].from,
150 keyname, XkbKeyNameLength) == 0) {
151 rfbcode = code_map_xkb_to_qnum[i].to;
152 break;
153 }
154 }
155 if (rfbcode != 0)
156 code_map_keycode_to_qnum[keycode] = rfbcode;
157 else
158 vlog.debug("No key mapping for key %.4s", keyname);
159 }
160
161 XkbFreeKeyboard(xkb, 0, True);
162#endif
163
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000164 Fl::add_clipboard_notify(handleClipboardChange, this);
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000165
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200166 // We need to intercept keyboard events early
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200167 Fl::add_system_handler(handleSystemEvent, this);
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200168
Pierre Ossman403ac272017-01-02 17:00:41 +0100169 frameBuffer = new PlatformPixelBuffer(w, h);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000170 assert(frameBuffer);
Pierre Ossman9f273e92015-11-09 16:34:54 +0100171 cc->setFramebuffer(frameBuffer);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000172
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000173 contextMenu = new Fl_Menu_Button(0, 0, 0, 0);
Pierre Ossmanad9d1ae2011-05-26 14:16:02 +0000174 // Setting box type to FL_NO_BOX prevents it from trying to draw the
175 // button component (which we don't want)
176 contextMenu->box(FL_NO_BOX);
177
Pierre Ossmanb043ad12011-06-01 09:26:57 +0000178 // The (invisible) button associated with this widget can mess with
179 // things like Fl_Scroll so we need to get rid of any parents.
180 // Unfortunately that's not possible because of STR #2654, but
181 // reparenting to the current window works for most cases.
182 window()->add(contextMenu);
183
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000184 setMenuKey();
185
186 OptionsDialog::addCallback(handleOptions, this);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000187}
188
189
190Viewport::~Viewport()
191{
192 // Unregister all timeouts in case they get a change tro trigger
193 // again later when this object is already gone.
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000194 Fl::remove_timeout(handlePointerTimeout, this);
Pierre Ossman51249782018-03-08 14:05:39 +0100195#ifdef WIN32
196 Fl::remove_timeout(handleAltGrTimeout, this);
197#endif
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000198
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200199 Fl::remove_system_handler(handleSystemEvent);
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200200
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000201 Fl::remove_clipboard_notify(handleClipboardChange);
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000202
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000203 OptionsDialog::removeCallback(handleOptions);
204
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000205 if (cursor) {
Pierre Ossmanf741d342011-06-09 08:32:49 +0000206 if (!cursor->alloc_array)
207 delete [] cursor->array;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000208 delete cursor;
209 }
210
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000211 // FLTK automatically deletes all child widgets, so we shouldn't touch
212 // them ourselves here
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000213}
214
215
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000216const rfb::PixelFormat &Viewport::getPreferredPF()
217{
Pierre Ossman132b3d02011-06-13 11:19:32 +0000218 return frameBuffer->getPF();
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000219}
220
221
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000222// Copy the areas of the framebuffer that have been changed (damaged)
223// to the displayed window.
224
225void Viewport::updateWindow()
226{
227 Rect r;
228
Pierre Ossman0c9bd4b2014-07-09 16:44:11 +0200229 r = frameBuffer->getDamage();
230 damage(FL_DAMAGE_USER1, r.tl.x + x(), r.tl.y + y(), r.width(), r.height());
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000231}
232
Pierre Ossmanf741d342011-06-09 08:32:49 +0000233static const char * dotcursor_xpm[] = {
234 "5 5 2 1",
235 ". c #000000",
236 " c #FFFFFF",
237 " ",
238 " ... ",
239 " ... ",
240 " ... ",
241 " "};
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000242
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000243void Viewport::setCursor(int width, int height, const Point& hotspot,
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100244 const rdr::U8* data)
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000245{
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100246 int i;
247
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000248 if (cursor) {
Pierre Ossmanf741d342011-06-09 08:32:49 +0000249 if (!cursor->alloc_array)
250 delete [] cursor->array;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000251 delete cursor;
252 }
253
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100254 for (i = 0; i < width*height; i++)
255 if (data[i*4 + 3] != 0) break;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000256
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100257 if ((i == width*height) && dotWhenNoCursor) {
Pierre Ossmana51574a2011-06-09 08:39:35 +0000258 vlog.debug("cursor is empty - using dot");
Pierre Ossmanf741d342011-06-09 08:32:49 +0000259
260 Fl_Pixmap pxm(dotcursor_xpm);
261 cursor = new Fl_RGB_Image(&pxm);
262 cursorHotspot.x = cursorHotspot.y = 2;
263 } else {
Pierre Ossman17a48f02011-06-09 08:42:04 +0000264 if ((width == 0) || (height == 0)) {
265 U8 *buffer = new U8[4];
266 memset(buffer, 0, 4);
267 cursor = new Fl_RGB_Image(buffer, 1, 1, 4);
268 cursorHotspot.x = cursorHotspot.y = 0;
269 } else {
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100270 U8 *buffer = new U8[width * height * 4];
271 memcpy(buffer, data, width * height * 4);
Pierre Ossman17a48f02011-06-09 08:42:04 +0000272 cursor = new Fl_RGB_Image(buffer, width, height, 4);
Pierre Ossman17a48f02011-06-09 08:42:04 +0000273 cursorHotspot = hotspot;
Pierre Ossmanf741d342011-06-09 08:32:49 +0000274 }
Pierre Ossmanf741d342011-06-09 08:32:49 +0000275 }
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000276
277 if (Fl::belowmouse() == this)
278 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000279}
280
Pierre Ossman615d16b2019-05-03 10:53:06 +0200281void Viewport::handleClipboardRequest()
282{
283 Fl::paste(*this, clipboardSource);
284}
285
286void Viewport::handleClipboardAnnounce(bool available)
287{
288 if (!acceptClipboard)
289 return;
290
291 if (available)
292 vlog.debug("Got notification of new clipboard on server");
293 else
294 vlog.debug("Clipboard is no longer available on server");
295
296 if (!available) {
297 pendingServerClipboard = false;
298 return;
299 }
300
301 pendingClientClipboard = false;
302
303 if (!hasFocus()) {
304 pendingServerClipboard = true;
305 return;
306 }
307
308 cc->requestClipboard();
309}
310
311void Viewport::handleClipboardData(const char* data)
312{
313 char* buffer;
314 size_t len;
315
316 if (!hasFocus())
317 return;
318
319 buffer = latin1ToUTF8(data);
320 len = strlen(buffer);
321
322 vlog.debug("Got clipboard data (%d bytes)", (int)len);
323
324 // RFB doesn't have separate selection and clipboard concepts, so we
325 // dump the data into both variants.
326#if !defined(WIN32) && !defined(__APPLE__)
327 if (setPrimary)
328 Fl::copy(buffer, len, 0);
329#endif
330 Fl::copy(buffer, len, 1);
331
332 strFree(buffer);
333}
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000334
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100335void Viewport::setLEDState(unsigned int state)
336{
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100337 vlog.debug("Got server LED state: 0x%08x", state);
338
Pierre Ossman609a9c82018-06-07 09:16:02 +0200339 // The first message is just considered to be the server announcing
Pierre Ossman20634392019-02-28 10:57:40 +0100340 // support for this extension. We will push our state to sync up the
341 // server when we get focus. If we already have focus we need to push
342 // it here though.
Pierre Ossman609a9c82018-06-07 09:16:02 +0200343 if (firstLEDState) {
344 firstLEDState = false;
Pierre Ossman20634392019-02-28 10:57:40 +0100345 if (hasFocus())
346 pushLEDState();
Pierre Ossman609a9c82018-06-07 09:16:02 +0200347 return;
348 }
349
Pierre Ossman9a747322018-03-26 14:16:43 +0200350 if (!hasFocus())
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100351 return;
352
353#if defined(WIN32)
354 INPUT input[6];
355 UINT count;
356 UINT ret;
357
358 memset(input, 0, sizeof(input));
359 count = 0;
360
361 if (!!(state & ledCapsLock) != !!(GetKeyState(VK_CAPITAL) & 0x1)) {
362 input[count].type = input[count+1].type = INPUT_KEYBOARD;
363 input[count].ki.wVk = input[count+1].ki.wVk = VK_CAPITAL;
364 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
365 input[count].ki.dwFlags = 0;
366 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP;
367 count += 2;
368 }
369
370 if (!!(state & ledNumLock) != !!(GetKeyState(VK_NUMLOCK) & 0x1)) {
371 input[count].type = input[count+1].type = INPUT_KEYBOARD;
372 input[count].ki.wVk = input[count+1].ki.wVk = VK_NUMLOCK;
373 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
374 input[count].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
375 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY;
376 count += 2;
377 }
378
379 if (!!(state & ledScrollLock) != !!(GetKeyState(VK_SCROLL) & 0x1)) {
380 input[count].type = input[count+1].type = INPUT_KEYBOARD;
381 input[count].ki.wVk = input[count+1].ki.wVk = VK_SCROLL;
382 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
383 input[count].ki.dwFlags = 0;
384 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP;
385 count += 2;
386 }
387
388 if (count == 0)
389 return;
390
391 ret = SendInput(count, input, sizeof(*input));
392 if (ret < count)
393 vlog.error(_("Failed to update keyboard LED state: %lu"), GetLastError());
394#elif defined(__APPLE__)
395 int ret;
396
397 ret = cocoa_set_caps_lock_state(state & ledCapsLock);
398 if (ret != 0) {
399 vlog.error(_("Failed to update keyboard LED state: %d"), ret);
400 return;
401 }
402
403 ret = cocoa_set_num_lock_state(state & ledNumLock);
404 if (ret != 0) {
405 vlog.error(_("Failed to update keyboard LED state: %d"), ret);
406 return;
407 }
408
409 // No support for Scroll Lock //
410
411#else
412 unsigned int affect, values;
413 unsigned int mask;
414
415 Bool ret;
416
417 affect = values = 0;
418
419 affect |= LockMask;
420 if (state & ledCapsLock)
421 values |= LockMask;
422
423 mask = getModifierMask(XK_Num_Lock);
424 affect |= mask;
425 if (state & ledNumLock)
426 values |= mask;
427
428 mask = getModifierMask(XK_Scroll_Lock);
429 affect |= mask;
430 if (state & ledScrollLock)
431 values |= mask;
432
433 ret = XkbLockModifiers(fl_display, XkbUseCoreKbd, affect, values);
434 if (!ret)
435 vlog.error(_("Failed to update keyboard LED state"));
436#endif
437}
438
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100439void Viewport::pushLEDState()
440{
441 unsigned int state;
442
443 // Server support?
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200444 if (cc->server.ledState() == ledUnknown)
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100445 return;
446
447 state = 0;
448
449#if defined(WIN32)
450 if (GetKeyState(VK_CAPITAL) & 0x1)
451 state |= ledCapsLock;
452 if (GetKeyState(VK_NUMLOCK) & 0x1)
453 state |= ledNumLock;
454 if (GetKeyState(VK_SCROLL) & 0x1)
455 state |= ledScrollLock;
456#elif defined(__APPLE__)
457 int ret;
458 bool on;
459
460 ret = cocoa_get_caps_lock_state(&on);
461 if (ret != 0) {
462 vlog.error(_("Failed to get keyboard LED state: %d"), ret);
463 return;
464 }
465 if (on)
466 state |= ledCapsLock;
467
468 ret = cocoa_get_num_lock_state(&on);
469 if (ret != 0) {
470 vlog.error(_("Failed to get keyboard LED state: %d"), ret);
471 return;
472 }
473 if (on)
474 state |= ledNumLock;
475
476 // No support for Scroll Lock //
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200477 state |= (cc->server.ledState() & ledScrollLock);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100478
479#else
480 unsigned int mask;
481
482 Status status;
483 XkbStateRec xkbState;
484
485 status = XkbGetState(fl_display, XkbUseCoreKbd, &xkbState);
486 if (status != Success) {
487 vlog.error(_("Failed to get keyboard LED state: %d"), status);
488 return;
489 }
490
491 if (xkbState.locked_mods & LockMask)
492 state |= ledCapsLock;
493
494 mask = getModifierMask(XK_Num_Lock);
495 if (xkbState.locked_mods & mask)
496 state |= ledNumLock;
497
498 mask = getModifierMask(XK_Scroll_Lock);
499 if (xkbState.locked_mods & mask)
500 state |= ledScrollLock;
501#endif
502
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200503 if ((state & ledCapsLock) != (cc->server.ledState() & ledCapsLock)) {
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100504 vlog.debug("Inserting fake CapsLock to get in sync with server");
Pierre Ossman0c158662017-07-13 15:54:11 +0200505 handleKeyPress(0x3a, XK_Caps_Lock);
506 handleKeyRelease(0x3a);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100507 }
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200508 if ((state & ledNumLock) != (cc->server.ledState() & ledNumLock)) {
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100509 vlog.debug("Inserting fake NumLock to get in sync with server");
Pierre Ossman0c158662017-07-13 15:54:11 +0200510 handleKeyPress(0x45, XK_Num_Lock);
511 handleKeyRelease(0x45);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100512 }
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200513 if ((state & ledScrollLock) != (cc->server.ledState() & ledScrollLock)) {
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100514 vlog.debug("Inserting fake ScrollLock to get in sync with server");
Pierre Ossman0c158662017-07-13 15:54:11 +0200515 handleKeyPress(0x46, XK_Scroll_Lock);
516 handleKeyRelease(0x46);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100517 }
518}
519
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100520
Pierre Ossman3d74d882017-01-02 19:49:52 +0100521void Viewport::draw(Surface* dst)
522{
523 int X, Y, W, H;
524
525 // Check what actually needs updating
526 fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
527 if ((W == 0) || (H == 0))
528 return;
529
530 frameBuffer->draw(dst, X - x(), Y - y(), X, Y, W, H);
531}
532
533
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000534void Viewport::draw()
535{
536 int X, Y, W, H;
537
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000538 // Check what actually needs updating
Pierre Ossmana6e20772011-04-15 11:10:52 +0000539 fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000540 if ((W == 0) || (H == 0))
541 return;
542
Pierre Ossman132b3d02011-06-13 11:19:32 +0000543 frameBuffer->draw(X - x(), Y - y(), X, Y, W, H);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000544}
545
546
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000547void Viewport::resize(int x, int y, int w, int h)
548{
Pierre Ossman9f273e92015-11-09 16:34:54 +0100549 if ((w != frameBuffer->width()) || (h != frameBuffer->height())) {
550 vlog.debug("Resizing framebuffer from %dx%d to %dx%d",
551 frameBuffer->width(), frameBuffer->height(), w, h);
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000552
Pierre Ossman403ac272017-01-02 17:00:41 +0100553 frameBuffer = new PlatformPixelBuffer(w, h);
Pierre Ossman9f273e92015-11-09 16:34:54 +0100554 assert(frameBuffer);
555 cc->setFramebuffer(frameBuffer);
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000556 }
557
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000558 Fl_Widget::resize(x, y, w, h);
559}
560
561
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000562int Viewport::handle(int event)
563{
Pierre Ossman546b2ad2019-05-02 12:32:03 +0200564 char *buffer, *filtered;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000565 int buttonMask, wheelMask;
566 DownMap::const_iterator iter;
567
568 switch (event) {
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000569 case FL_PASTE:
Pierre Ossman56fa7822016-01-22 16:40:59 +0100570 buffer = utf8ToLatin1(Fl::event_text(), Fl::event_length());
571 filtered = convertLF(buffer);
572 strFree(buffer);
Pierre Ossman689c4582011-05-26 15:39:41 +0000573
Pierre Ossman546b2ad2019-05-02 12:32:03 +0200574 vlog.debug("Sending clipboard data (%d bytes)", (int)strlen(filtered));
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000575
576 try {
Pierre Ossman615d16b2019-05-03 10:53:06 +0200577 cc->sendClipboardData(filtered);
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000578 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000579 vlog.error("%s", e.str());
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000580 exit_vncviewer(e.str());
581 }
Pierre Ossman689c4582011-05-26 15:39:41 +0000582
Pierre Ossman546b2ad2019-05-02 12:32:03 +0200583 strFree(filtered);
Pierre Ossman689c4582011-05-26 15:39:41 +0000584
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000585 return 1;
Pierre Ossman689c4582011-05-26 15:39:41 +0000586
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000587 case FL_ENTER:
Pierre Ossman1f1f6fd2011-06-09 08:33:29 +0000588 if (cursor)
589 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossman56610fb2015-01-27 16:28:15 +0100590 // Yes, we would like some pointer events please!
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000591 return 1;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000592
593 case FL_LEAVE:
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000594 window()->cursor(FL_CURSOR_DEFAULT);
Pierre Ossmanbe882932018-07-31 16:06:59 +0200595 // We want a last move event to help trigger edge stuff
596 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), 0);
597 return 1;
598
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000599 case FL_PUSH:
600 case FL_RELEASE:
601 case FL_DRAG:
602 case FL_MOVE:
603 case FL_MOUSEWHEEL:
604 buttonMask = 0;
605 if (Fl::event_button1())
606 buttonMask |= 1;
607 if (Fl::event_button2())
608 buttonMask |= 2;
609 if (Fl::event_button3())
610 buttonMask |= 4;
611
612 if (event == FL_MOUSEWHEEL) {
Pierre Ossmandf0ed9f2011-05-24 11:33:43 +0000613 wheelMask = 0;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000614 if (Fl::event_dy() < 0)
Pierre Ossmandf0ed9f2011-05-24 11:33:43 +0000615 wheelMask |= 8;
616 if (Fl::event_dy() > 0)
617 wheelMask |= 16;
618 if (Fl::event_dx() < 0)
619 wheelMask |= 32;
620 if (Fl::event_dx() > 0)
621 wheelMask |= 64;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000622
623 // A quick press of the wheel "button", followed by a immediate
624 // release below
Pierre Ossman6a464be2011-04-15 12:57:31 +0000625 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()),
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000626 buttonMask | wheelMask);
627 }
628
Pierre Ossman6a464be2011-04-15 12:57:31 +0000629 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), buttonMask);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000630 return 1;
631
632 case FL_FOCUS:
Pierre Ossman48ef54d2014-08-19 14:08:04 +0200633 Fl::disable_im();
Pierre Ossman78236292018-03-26 14:15:40 +0200634
Pierre Ossmane370e1c2018-07-11 13:05:11 +0200635 flushPendingClipboard();
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200636
Pierre Ossmane370e1c2018-07-11 13:05:11 +0200637 // We may have gotten our lock keys out of sync with the server
638 // whilst we didn't have focus. Try to sort this out.
639 pushLEDState();
Pierre Ossman78236292018-03-26 14:15:40 +0200640
Dominique Martinet9f831802018-07-08 02:15:43 +0900641 // Resend Ctrl/Alt if needed
642 if (menuCtrlKey)
643 handleKeyPress(0x1d, XK_Control_L);
644 if (menuAltKey)
645 handleKeyPress(0x38, XK_Alt_L);
646
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000647 // Yes, we would like some focus please!
648 return 1;
649
650 case FL_UNFOCUS:
651 // Release all keys that were pressed as that generally makes most
652 // sense (e.g. Alt+Tab where we only see the Alt press)
Pierre Ossmanf8450ca2011-07-12 16:10:16 +0000653 while (!downKeySym.empty())
Pierre Ossman25188c42014-07-21 16:30:08 +0200654 handleKeyRelease(downKeySym.begin()->first);
Pierre Ossman48ef54d2014-08-19 14:08:04 +0200655 Fl::enable_im();
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000656 return 1;
657
658 case FL_KEYDOWN:
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000659 case FL_KEYUP:
Pierre Ossman56610fb2015-01-27 16:28:15 +0100660 // Just ignore these as keys were handled in the event handler
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000661 return 1;
662 }
663
664 return Fl_Widget::handle(event);
665}
666
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100667
Pierre Ossman9a747322018-03-26 14:16:43 +0200668bool Viewport::hasFocus()
669{
670 Fl_Widget* focus;
671
672 focus = Fl::grab();
673 if (!focus)
674 focus = Fl::focus();
675
676 return focus == this;
677}
678
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100679#if ! (defined(WIN32) || defined(__APPLE__))
680unsigned int Viewport::getModifierMask(unsigned int keysym)
681{
682 XkbDescPtr xkb;
683 unsigned int mask, keycode;
684 XkbAction *act;
685
686 mask = 0;
687
688 xkb = XkbGetMap(fl_display, XkbAllComponentsMask, XkbUseCoreKbd);
689 if (xkb == NULL)
690 return 0;
691
692 for (keycode = xkb->min_key_code; keycode <= xkb->max_key_code; keycode++) {
693 unsigned int state_out;
694 KeySym ks;
695
696 XkbTranslateKeyCode(xkb, keycode, 0, &state_out, &ks);
697 if (ks == NoSymbol)
698 continue;
699
700 if (ks == keysym)
701 break;
702 }
703
704 // KeySym not mapped?
705 if (keycode > xkb->max_key_code)
706 goto out;
707
708 act = XkbKeyAction(xkb, keycode, 0);
709 if (act == NULL)
710 goto out;
711 if (act->type != XkbSA_LockMods)
712 goto out;
713
714 if (act->mods.flags & XkbSA_UseModMapMods)
715 mask = xkb->map->modmap[keycode];
716 else
717 mask = act->mods.mask;
718
719out:
720 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
721
722 return mask;
723}
724#endif
725
726
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000727void Viewport::handleClipboardChange(int source, void *data)
728{
729 Viewport *self = (Viewport *)data;
730
731 assert(self);
732
Pierre Ossmana432b582017-01-02 15:16:58 +0100733 if (!sendClipboard)
734 return;
735
Pierre Ossmanf7fef922015-12-10 21:24:08 +0100736#if !defined(WIN32) && !defined(__APPLE__)
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000737 if (!sendPrimary && (source == 0))
738 return;
Pierre Ossmanf7fef922015-12-10 21:24:08 +0100739#endif
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000740
Pierre Ossman615d16b2019-05-03 10:53:06 +0200741 self->clipboardSource = source;
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000742
Pierre Ossman615d16b2019-05-03 10:53:06 +0200743 self->pendingServerClipboard = false;
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000744
Pierre Ossman615d16b2019-05-03 10:53:06 +0200745 if (!self->hasFocus()) {
746 self->pendingClientClipboard = true;
747 // Clear any older client clipboard from the server
748 self->cc->announceClipboard(false);
749 return;
750 }
751
752 try {
753 self->cc->announceClipboard(true);
754 } catch (rdr::Exception& e) {
755 vlog.error("%s", e.str());
756 exit_vncviewer(e.str());
757 }
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200758}
759
760
761void Viewport::flushPendingClipboard()
762{
Pierre Ossman615d16b2019-05-03 10:53:06 +0200763 if (pendingServerClipboard) {
Pierre Ossmane370e1c2018-07-11 13:05:11 +0200764 try {
Pierre Ossman615d16b2019-05-03 10:53:06 +0200765 cc->requestClipboard();
766 } catch (rdr::Exception& e) {
767 vlog.error("%s", e.str());
768 exit_vncviewer(e.str());
769 }
770 }
771 if (pendingClientClipboard) {
772 try {
773 cc->announceClipboard(true);
Pierre Ossmane370e1c2018-07-11 13:05:11 +0200774 } catch (rdr::Exception& e) {
775 vlog.error("%s", e.str());
776 exit_vncviewer(e.str());
777 }
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200778 }
779
Pierre Ossman615d16b2019-05-03 10:53:06 +0200780 pendingServerClipboard = false;
781 pendingClientClipboard = false;
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200782}
783
784
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000785void Viewport::handlePointerEvent(const rfb::Point& pos, int buttonMask)
786{
787 if (!viewOnly) {
788 if (pointerEventInterval == 0 || buttonMask != lastButtonMask) {
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000789 try {
Pierre Ossman59da99f2016-02-05 10:43:12 +0100790 cc->writer()->writePointerEvent(pos, buttonMask);
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000791 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000792 vlog.error("%s", e.str());
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000793 exit_vncviewer(e.str());
794 }
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000795 } else {
796 if (!Fl::has_timeout(handlePointerTimeout, this))
797 Fl::add_timeout((double)pointerEventInterval/1000.0,
798 handlePointerTimeout, this);
799 }
800 lastPointerPos = pos;
801 lastButtonMask = buttonMask;
802 }
803}
804
805
806void Viewport::handlePointerTimeout(void *data)
807{
808 Viewport *self = (Viewport *)data;
809
810 assert(self);
811
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000812 try {
Pierre Ossman59da99f2016-02-05 10:43:12 +0100813 self->cc->writer()->writePointerEvent(self->lastPointerPos,
814 self->lastButtonMask);
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000815 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000816 vlog.error("%s", e.str());
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000817 exit_vncviewer(e.str());
818 }
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000819}
820
821
Pierre Ossman25188c42014-07-21 16:30:08 +0200822void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym)
823{
824 static bool menuRecursion = false;
825
826 // Prevent recursion if the menu wants to send its own
827 // activation key.
828 if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) {
829 menuRecursion = true;
830 popupContextMenu();
831 menuRecursion = false;
832 return;
833 }
834
835 if (viewOnly)
836 return;
837
Pierre Ossman0c158662017-07-13 15:54:11 +0200838 if (keyCode == 0) {
839 vlog.error(_("No key code specified on key press"));
840 return;
841 }
842
Pierre Ossman25188c42014-07-21 16:30:08 +0200843#ifdef __APPLE__
844 // Alt on OS X behaves more like AltGr on other systems, and to get
845 // sane behaviour we should translate things in that manner for the
846 // remote VNC server. However that means we lose the ability to use
847 // Alt as a shortcut modifier. Do what RealVNC does and hijack the
848 // left command key as an Alt replacement.
849 switch (keySym) {
850 case XK_Super_L:
851 keySym = XK_Alt_L;
852 break;
853 case XK_Super_R:
854 keySym = XK_Super_L;
855 break;
856 case XK_Alt_L:
Pierre Ossman38fcebb2014-08-21 13:44:28 +0200857 keySym = XK_Mode_switch;
858 break;
Pierre Ossman25188c42014-07-21 16:30:08 +0200859 case XK_Alt_R:
860 keySym = XK_ISO_Level3_Shift;
861 break;
862 }
863#endif
864
Pierre Ossman25188c42014-07-21 16:30:08 +0200865 // Because of the way keyboards work, we cannot expect to have the same
866 // symbol on release as when pressed. This breaks the VNC protocol however,
867 // so we need to keep track of what keysym a key _code_ generated on press
868 // and send the same on release.
869 downKeySym[keyCode] = keySym;
870
871#if defined(WIN32) || defined(__APPLE__)
872 vlog.debug("Key pressed: 0x%04x => 0x%04x", keyCode, keySym);
873#else
874 vlog.debug("Key pressed: 0x%04x => XK_%s (0x%04x)",
875 keyCode, XKeysymToString(keySym), keySym);
876#endif
877
878 try {
Pierre Ossman0c158662017-07-13 15:54:11 +0200879 // Fake keycode?
880 if (keyCode > 0xff)
Pierre Ossman59da99f2016-02-05 10:43:12 +0100881 cc->writer()->writeKeyEvent(keySym, 0, true);
Pierre Ossman0c158662017-07-13 15:54:11 +0200882 else
Pierre Ossman59da99f2016-02-05 10:43:12 +0100883 cc->writer()->writeKeyEvent(keySym, keyCode, true);
Pierre Ossman25188c42014-07-21 16:30:08 +0200884 } catch (rdr::Exception& e) {
885 vlog.error("%s", e.str());
886 exit_vncviewer(e.str());
887 }
Pierre Ossman25188c42014-07-21 16:30:08 +0200888}
889
890
891void Viewport::handleKeyRelease(int keyCode)
892{
893 DownMap::iterator iter;
894
895 if (viewOnly)
896 return;
897
898 iter = downKeySym.find(keyCode);
899 if (iter == downKeySym.end()) {
900 // These occur somewhat frequently so let's not spam them unless
901 // logging is turned up.
902 vlog.debug("Unexpected release of key code %d", keyCode);
903 return;
904 }
905
906#if defined(WIN32) || defined(__APPLE__)
907 vlog.debug("Key released: 0x%04x => 0x%04x", keyCode, iter->second);
908#else
909 vlog.debug("Key released: 0x%04x => XK_%s (0x%04x)",
910 keyCode, XKeysymToString(iter->second), iter->second);
911#endif
912
913 try {
Pierre Ossman0c158662017-07-13 15:54:11 +0200914 if (keyCode > 0xff)
Pierre Ossman59da99f2016-02-05 10:43:12 +0100915 cc->writer()->writeKeyEvent(iter->second, 0, false);
Pierre Ossman0c158662017-07-13 15:54:11 +0200916 else
Pierre Ossman59da99f2016-02-05 10:43:12 +0100917 cc->writer()->writeKeyEvent(iter->second, keyCode, false);
Pierre Ossman25188c42014-07-21 16:30:08 +0200918 } catch (rdr::Exception& e) {
919 vlog.error("%s", e.str());
920 exit_vncviewer(e.str());
921 }
922
923 downKeySym.erase(iter);
924}
925
926
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200927int Viewport::handleSystemEvent(void *event, void *data)
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200928{
929 Viewport *self = (Viewport *)data;
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200930
931 assert(self);
932
Pierre Ossman9a747322018-03-26 14:16:43 +0200933 if (!self->hasFocus())
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200934 return 0;
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200935
936 assert(event);
937
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200938#if defined(WIN32)
939 MSG *msg = (MSG*)event;
940
941 if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN)) {
942 UINT vKey;
943 bool isExtended;
944 int keyCode;
945 rdr::U32 keySym;
946
947 vKey = msg->wParam;
948 isExtended = (msg->lParam & (1 << 24)) != 0;
949
950 keyCode = ((msg->lParam >> 16) & 0xff);
951
Pierre Ossman42d0f5d2018-11-06 17:31:11 +0100952 // Windows' touch keyboard doesn't set a scan code for the Alt
953 // portion of the AltGr sequence, so we need to help it out
954 if (!isExtended && (keyCode == 0x00) && (vKey == VK_MENU)) {
955 isExtended = true;
956 keyCode = 0x38;
957 }
958
Pierre Ossman51249782018-03-08 14:05:39 +0100959 // Windows doesn't have a proper AltGr, but handles it using fake
960 // Ctrl+Alt. However the remote end might not be Windows, so we need
961 // to merge those in to a single AltGr event. We detect this case
962 // by seeing the two key events directly after each other with a very
Pierre Ossman69428fe2018-03-08 17:24:54 +0100963 // short time between them (<50ms) and supress the Ctrl event.
Pierre Ossman51249782018-03-08 14:05:39 +0100964 if (self->altGrArmed) {
965 self->altGrArmed = false;
966 Fl::remove_timeout(handleAltGrTimeout);
967
968 if (isExtended && (keyCode == 0x38) && (vKey == VK_MENU) &&
969 ((msg->time - self->altGrCtrlTime) < 50)) {
Pierre Ossman69428fe2018-03-08 17:24:54 +0100970 // Alt seen, so this is an AltGr sequence
971 } else {
972 // Not Alt, so fire the queued up Ctrl event
973 self->handleKeyPress(0x1d, XK_Control_L);
Pierre Ossman51249782018-03-08 14:05:39 +0100974 }
Pierre Ossman51249782018-03-08 14:05:39 +0100975 }
976
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100977 if (keyCode == SCAN_FAKE) {
978 vlog.debug("Ignoring fake key press (virtual key 0x%02x)", vKey);
979 return 1;
980 }
981
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200982 // Windows sets the scan code to 0x00 for multimedia keys, so we
983 // have to do a reverse lookup based on the vKey.
984 if (keyCode == 0x00) {
985 keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
986 if (keyCode == 0x00) {
Pierre Ossmanf05f3552014-09-22 12:19:52 +0200987 if (isExtended)
988 vlog.error(_("No scan code for extended virtual key 0x%02x"), (int)vKey);
989 else
990 vlog.error(_("No scan code for virtual key 0x%02x"), (int)vKey);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200991 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200992 }
993 }
994
Pierre Ossman0c158662017-07-13 15:54:11 +0200995 if (keyCode & ~0x7f) {
996 vlog.error(_("Invalid scan code 0x%02x"), (int)keyCode);
997 return 1;
998 }
999
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001000 if (isExtended)
Pierre Ossman0c158662017-07-13 15:54:11 +02001001 keyCode |= 0x80;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001002
Pierre Ossmanf73214c2017-11-13 09:06:03 +01001003
1004 // Fortunately RFB and Windows use the same scan code set (mostly),
1005 // so there is no conversion needed
1006 // (as long as we encode the extended keys with the high bit)
1007
1008 // However Pause sends a code that conflicts with NumLock, so use
1009 // the code most RFB implementations use (part of the sequence for
1010 // Ctrl+Pause, i.e. Break)
1011 if (keyCode == 0x45)
1012 keyCode = 0xc6;
1013
1014 // And NumLock incorrectly has the extended bit set
1015 if (keyCode == 0xc5)
1016 keyCode = 0x45;
1017
1018 // And Alt+PrintScreen (i.e. SysRq) sends a different code than
1019 // PrintScreen
1020 if (keyCode == 0xb7)
Pierre Ossman0c158662017-07-13 15:54:11 +02001021 keyCode = 0x54;
Pierre Ossmana7d3dc72014-09-30 17:03:28 +02001022
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001023 keySym = win32_vkey_to_keysym(vKey, isExtended);
1024 if (keySym == NoSymbol) {
Pierre Ossmanf05f3552014-09-22 12:19:52 +02001025 if (isExtended)
1026 vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey);
1027 else
1028 vlog.error(_("No symbol for virtual key 0x%02x"), (int)vKey);
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001029 }
1030
Pierre Ossman30b3f922017-11-13 09:07:59 +01001031 // Windows sends the same vKey for both shifts, so we need to look
1032 // at the scan code to tell them apart
1033 if ((keySym == XK_Shift_L) && (keyCode == 0x36))
1034 keySym = XK_Shift_R;
Pierre Ossman0c158662017-07-13 15:54:11 +02001035
Pierre Ossman69428fe2018-03-08 17:24:54 +01001036 // AltGr handling (see above)
1037 if (win32_has_altgr()) {
1038 if ((keyCode == 0xb8) && (keySym == XK_Alt_R))
1039 keySym = XK_ISO_Level3_Shift;
1040
1041 // Possible start of AltGr sequence?
1042 if ((keyCode == 0x1d) && (keySym == XK_Control_L)) {
1043 self->altGrArmed = true;
1044 self->altGrCtrlTime = msg->time;
1045 Fl::add_timeout(0.1, handleAltGrTimeout, self);
1046 return 1;
1047 }
Pierre Ossman51249782018-03-08 14:05:39 +01001048 }
1049
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001050 self->handleKeyPress(keyCode, keySym);
1051
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001052 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001053 } else if ((msg->message == WM_KEYUP) || (msg->message == WM_SYSKEYUP)) {
1054 UINT vKey;
1055 bool isExtended;
1056 int keyCode;
1057
1058 vKey = msg->wParam;
1059 isExtended = (msg->lParam & (1 << 24)) != 0;
1060
1061 keyCode = ((msg->lParam >> 16) & 0xff);
Pierre Ossman2fa63f82016-12-05 15:26:21 +01001062
Pierre Ossman42d0f5d2018-11-06 17:31:11 +01001063 // Touch keyboard AltGr (see above)
1064 if (!isExtended && (keyCode == 0x00) && (vKey == VK_MENU)) {
1065 isExtended = true;
1066 keyCode = 0x38;
1067 }
1068
Pierre Ossman51249782018-03-08 14:05:39 +01001069 // We can't get a release in the middle of an AltGr sequence, so
1070 // abort that detection
1071 if (self->altGrArmed) {
1072 self->altGrArmed = false;
1073 Fl::remove_timeout(handleAltGrTimeout);
1074 self->handleKeyPress(0x1d, XK_Control_L);
1075 }
1076
Pierre Ossman2fa63f82016-12-05 15:26:21 +01001077 if (keyCode == SCAN_FAKE) {
1078 vlog.debug("Ignoring fake key release (virtual key 0x%02x)", vKey);
1079 return 1;
1080 }
1081
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001082 if (keyCode == 0x00)
1083 keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
1084 if (isExtended)
Pierre Ossman0c158662017-07-13 15:54:11 +02001085 keyCode |= 0x80;
Pierre Ossmanf73214c2017-11-13 09:06:03 +01001086 if (keyCode == 0x45)
1087 keyCode = 0xc6;
1088 if (keyCode == 0xc5)
1089 keyCode = 0x45;
1090 if (keyCode == 0xb7)
Pierre Ossman0c158662017-07-13 15:54:11 +02001091 keyCode = 0x54;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001092
1093 self->handleKeyRelease(keyCode);
1094
Pierre Ossman30b3f922017-11-13 09:07:59 +01001095 // Windows has a rather nasty bug where it won't send key release
1096 // events for a Shift button if the other Shift is still pressed
1097 if ((keyCode == 0x2a) || (keyCode == 0x36)) {
1098 if (self->downKeySym.count(0x2a))
1099 self->handleKeyRelease(0x2a);
1100 if (self->downKeySym.count(0x36))
1101 self->handleKeyRelease(0x36);
1102 }
1103
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001104 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001105 }
Pierre Ossman6b743d02014-07-21 16:48:43 +02001106#elif defined(__APPLE__)
1107 if (cocoa_is_keyboard_event(event)) {
1108 int keyCode;
1109
1110 keyCode = cocoa_event_keycode(event);
Pierre Ossman0c158662017-07-13 15:54:11 +02001111 if ((unsigned)keyCode >= code_map_osx_to_qnum_len)
1112 keyCode = 0;
1113 else
1114 keyCode = code_map_osx_to_qnum[keyCode];
Pierre Ossman6b743d02014-07-21 16:48:43 +02001115
1116 if (cocoa_is_key_press(event)) {
1117 rdr::U32 keySym;
1118
1119 keySym = cocoa_event_keysym(event);
1120 if (keySym == NoSymbol) {
1121 vlog.error(_("No symbol for key code 0x%02x (in the current state)"),
1122 (int)keyCode);
Pierre Ossman6b743d02014-07-21 16:48:43 +02001123 }
1124
1125 self->handleKeyPress(keyCode, keySym);
1126
1127 // We don't get any release events for CapsLock, so we have to
1128 // send the release right away.
1129 if (keySym == XK_Caps_Lock)
1130 self->handleKeyRelease(keyCode);
1131 } else {
1132 self->handleKeyRelease(keyCode);
1133 }
1134
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001135 return 1;
Pierre Ossman6b743d02014-07-21 16:48:43 +02001136 }
1137#else
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001138 XEvent *xevent = (XEvent*)event;
1139
1140 if (xevent->type == KeyPress) {
Pierre Ossman0c158662017-07-13 15:54:11 +02001141 int keycode;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001142 char str;
1143 KeySym keysym;
1144
Pierre Ossman0c158662017-07-13 15:54:11 +02001145 keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
1146
1147 // Generate a fake keycode just for tracking if we can't figure
1148 // out the proper one
1149 if (keycode == 0)
1150 keycode = 0x100 | xevent->xkey.keycode;
1151
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001152 XLookupString(&xevent->xkey, &str, 1, &keysym, NULL);
1153 if (keysym == NoSymbol) {
1154 vlog.error(_("No symbol for key code %d (in the current state)"),
1155 (int)xevent->xkey.keycode);
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001156 }
1157
1158 switch (keysym) {
1159 // For the first few years, there wasn't a good consensus on what the
1160 // Windows keys should be mapped to for X11. So we need to help out a
1161 // bit and map all variants to the same key...
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001162 case XK_Hyper_L:
1163 keysym = XK_Super_L;
1164 break;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001165 case XK_Hyper_R:
1166 keysym = XK_Super_R;
1167 break;
1168 // There has been several variants for Shift-Tab over the years.
1169 // RFB states that we should always send a normal tab.
1170 case XK_ISO_Left_Tab:
1171 keysym = XK_Tab;
1172 break;
1173 }
1174
Pierre Ossman0c158662017-07-13 15:54:11 +02001175 self->handleKeyPress(keycode, keysym);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001176 return 1;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001177 } else if (xevent->type == KeyRelease) {
Pierre Ossman0c158662017-07-13 15:54:11 +02001178 int keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
1179 if (keycode == 0)
1180 keycode = 0x100 | xevent->xkey.keycode;
1181 self->handleKeyRelease(keycode);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001182 return 1;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001183 }
1184#endif
1185
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001186 return 0;
Pierre Ossman4f3ac692014-08-22 15:10:22 +02001187}
1188
Pierre Ossman51249782018-03-08 14:05:39 +01001189#ifdef WIN32
1190void Viewport::handleAltGrTimeout(void *data)
1191{
1192 Viewport *self = (Viewport *)data;
1193
1194 assert(self);
1195
1196 self->altGrArmed = false;
1197 self->handleKeyPress(0x1d, XK_Control_L);
1198}
1199#endif
1200
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001201void Viewport::initContextMenu()
1202{
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001203 contextMenu->clear();
1204
Pierre Ossmanff626f82015-09-23 16:39:54 +02001205 fltk_menu_add(contextMenu, p_("ContextMenu|", "E&xit viewer"),
1206 0, NULL, (void*)ID_EXIT, FL_MENU_DIVIDER);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001207
Pierre Ossmanff626f82015-09-23 16:39:54 +02001208 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Full screen"),
1209 0, NULL, (void*)ID_FULLSCREEN,
Pierre Ossman245c8022015-02-25 11:27:49 +01001210 FL_MENU_TOGGLE | (window()->fullscreen_active()?FL_MENU_VALUE:0));
Pierre Ossmanff626f82015-09-23 16:39:54 +02001211 fltk_menu_add(contextMenu, p_("ContextMenu|", "Minimi&ze"),
1212 0, NULL, (void*)ID_MINIMIZE, 0);
1213 fltk_menu_add(contextMenu, p_("ContextMenu|", "Resize &window to session"),
1214 0, NULL, (void*)ID_RESIZE,
Pierre Ossman245c8022015-02-25 11:27:49 +01001215 (window()->fullscreen_active()?FL_MENU_INACTIVE:0) |
Pierre Ossman245c8022015-02-25 11:27:49 +01001216 FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001217
Pierre Ossmanff626f82015-09-23 16:39:54 +02001218 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Ctrl"),
1219 0, NULL, (void*)ID_CTRL,
Pierre Ossman245c8022015-02-25 11:27:49 +01001220 FL_MENU_TOGGLE | (menuCtrlKey?FL_MENU_VALUE:0));
Pierre Ossmanff626f82015-09-23 16:39:54 +02001221 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Alt"),
1222 0, NULL, (void*)ID_ALT,
Pierre Ossman245c8022015-02-25 11:27:49 +01001223 FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0));
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001224
1225 if (menuKeySym) {
1226 char sendMenuKey[64];
Pierre Ossmanff626f82015-09-23 16:39:54 +02001227 snprintf(sendMenuKey, 64, p_("ContextMenu|", "Send %s"), (const char *)menuKey);
Pierre Ossman245c8022015-02-25 11:27:49 +01001228 fltk_menu_add(contextMenu, sendMenuKey, 0, NULL, (void*)ID_MENUKEY, 0);
Pierre Ossman0c158662017-07-13 15:54:11 +02001229 fltk_menu_add(contextMenu, "Secret shortcut menu key", menuKeyFLTK, NULL,
Pierre Ossman245c8022015-02-25 11:27:49 +01001230 (void*)ID_MENUKEY, FL_MENU_INVISIBLE);
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001231 }
1232
Pierre Ossmanff626f82015-09-23 16:39:54 +02001233 fltk_menu_add(contextMenu, p_("ContextMenu|", "Send Ctrl-Alt-&Del"),
1234 0, NULL, (void*)ID_CTRLALTDEL, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001235
Pierre Ossmanff626f82015-09-23 16:39:54 +02001236 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Refresh screen"),
1237 0, NULL, (void*)ID_REFRESH, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001238
Pierre Ossmanff626f82015-09-23 16:39:54 +02001239 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Options..."),
1240 0, NULL, (void*)ID_OPTIONS, 0);
1241 fltk_menu_add(contextMenu, p_("ContextMenu|", "Connection &info..."),
1242 0, NULL, (void*)ID_INFO, 0);
1243 fltk_menu_add(contextMenu, p_("ContextMenu|", "About &TigerVNC viewer..."),
1244 0, NULL, (void*)ID_ABOUT, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001245
Pierre Ossmanff626f82015-09-23 16:39:54 +02001246 fltk_menu_add(contextMenu, p_("ContextMenu|", "Dismiss &menu"),
1247 0, NULL, (void*)ID_DISMISS, 0);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001248}
1249
1250
1251void Viewport::popupContextMenu()
1252{
1253 const Fl_Menu_Item *m;
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001254 char buffer[1024];
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001255
Pierre Ossmanb3ff4372011-06-03 11:45:15 +00001256 // Make sure the menu is reset to its initial state between goes or
1257 // it will start up highlighting the previously selected entry.
1258 contextMenu->value(-1);
1259
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001260 // initialize context menu before display
1261 initContextMenu();
1262
Pierre Ossmanfd177f32012-01-05 12:37:04 +00001263 // Unfortunately FLTK doesn't reliably restore the mouse pointer for
1264 // menus, so we have to help it out.
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001265 if (Fl::belowmouse() == this)
1266 window()->cursor(FL_CURSOR_DEFAULT);
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001267
Pierre Ossman78236292018-03-26 14:15:40 +02001268 // FLTK also doesn't switch focus properly for menus
1269 handle(FL_UNFOCUS);
1270
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001271 m = contextMenu->popup();
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001272
Pierre Ossman78236292018-03-26 14:15:40 +02001273 handle(FL_FOCUS);
1274
Pierre Ossmanfd177f32012-01-05 12:37:04 +00001275 // Back to our proper mouse pointer.
Pierre Ossmande1a3b92014-03-17 14:29:49 +01001276 if ((Fl::belowmouse() == this) && cursor)
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001277 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001278
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001279 if (m == NULL)
1280 return;
1281
1282 switch (m->argument()) {
1283 case ID_EXIT:
1284 exit_vncviewer();
1285 break;
Pierre Ossman4c613d32011-05-26 14:14:06 +00001286 case ID_FULLSCREEN:
1287 if (window()->fullscreen_active())
1288 window()->fullscreen_off();
Pierre Ossman275284f2012-07-04 11:37:48 +00001289 else
Pierre Ossmanaae38912012-07-13 11:22:55 +00001290 ((DesktopWindow*)window())->fullscreen_on();
Pierre Ossman4c613d32011-05-26 14:14:06 +00001291 break;
Joel Teichroeba7494ac2015-07-13 14:46:22 -07001292 case ID_MINIMIZE:
1293 window()->iconize();
Joel Teichroeb1f9e45a2015-07-18 07:09:24 -07001294 break;
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001295 case ID_RESIZE:
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001296 if (window()->fullscreen_active())
1297 break;
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001298 window()->size(w(), h());
1299 break;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001300 case ID_CTRL:
Pierre Ossman25188c42014-07-21 16:30:08 +02001301 if (m->value())
Pierre Ossman0c158662017-07-13 15:54:11 +02001302 handleKeyPress(0x1d, XK_Control_L);
Pierre Ossman25188c42014-07-21 16:30:08 +02001303 else
Pierre Ossman0c158662017-07-13 15:54:11 +02001304 handleKeyRelease(0x1d);
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001305 menuCtrlKey = !menuCtrlKey;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001306 break;
1307 case ID_ALT:
Pierre Ossman25188c42014-07-21 16:30:08 +02001308 if (m->value())
Pierre Ossman0c158662017-07-13 15:54:11 +02001309 handleKeyPress(0x38, XK_Alt_L);
Pierre Ossman25188c42014-07-21 16:30:08 +02001310 else
Pierre Ossman0c158662017-07-13 15:54:11 +02001311 handleKeyRelease(0x38);
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001312 menuAltKey = !menuAltKey;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001313 break;
1314 case ID_MENUKEY:
Pierre Ossman0c158662017-07-13 15:54:11 +02001315 handleKeyPress(menuKeyCode, menuKeySym);
1316 handleKeyRelease(menuKeyCode);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001317 break;
1318 case ID_CTRLALTDEL:
Pierre Ossman0c158662017-07-13 15:54:11 +02001319 handleKeyPress(0x1d, XK_Control_L);
1320 handleKeyPress(0x38, XK_Alt_L);
1321 handleKeyPress(0xd3, XK_Delete);
Pierre Ossman991b4fe2011-07-12 16:02:30 +00001322
Pierre Ossman0c158662017-07-13 15:54:11 +02001323 handleKeyRelease(0xd3);
1324 handleKeyRelease(0x38);
1325 handleKeyRelease(0x1d);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001326 break;
Pierre Ossmand4c61ce2011-04-29 11:18:12 +00001327 case ID_REFRESH:
1328 cc->refreshFramebuffer();
1329 break;
Pierre Ossmand463b572011-05-16 12:04:43 +00001330 case ID_OPTIONS:
1331 OptionsDialog::showDialog();
1332 break;
Pierre Ossman2eb1d112011-05-16 12:18:08 +00001333 case ID_INFO:
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001334 if (fltk_escape(cc->connectionInfo(), buffer, sizeof(buffer)) < sizeof(buffer)) {
1335 fl_message_title(_("VNC connection info"));
Pierre Ossmanf52740e2012-04-25 15:43:56 +00001336 fl_message("%s", buffer);
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001337 }
Pierre Ossman2eb1d112011-05-16 12:18:08 +00001338 break;
Pierre Ossmanb8858222011-04-29 11:51:38 +00001339 case ID_ABOUT:
1340 about_vncviewer();
1341 break;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001342 case ID_DISMISS:
1343 // Don't need to do anything
1344 break;
1345 }
1346}
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001347
1348
1349void Viewport::setMenuKey()
1350{
Pierre Ossman0c158662017-07-13 15:54:11 +02001351 getMenuKey(&menuKeyFLTK, &menuKeyCode, &menuKeySym);
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001352}
1353
1354
1355void Viewport::handleOptions(void *data)
1356{
1357 Viewport *self = (Viewport*)data;
1358
1359 self->setMenuKey();
1360}