blob: 6c10f3572f516e5cc8384a8cdf12b6b876dffe79 [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 {
562 cc->writer()->clientCutText(buffer, ret);
563 } 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);
746 cc->writer()->clientCutText(pendingClientCutText, len);
747 }
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 {
758 cc->writer()->pointerEvent(pos, buttonMask);
759 } 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 {
781 self->cc->writer()->pointerEvent(self->lastPointerPos, self->lastButtonMask);
782 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000783 vlog.error("%s", e.str());
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000784 exit_vncviewer(e.str());
785 }
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000786}
787
788
Pierre Ossman25188c42014-07-21 16:30:08 +0200789void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym)
790{
791 static bool menuRecursion = false;
792
793 // Prevent recursion if the menu wants to send its own
794 // activation key.
795 if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) {
796 menuRecursion = true;
797 popupContextMenu();
798 menuRecursion = false;
799 return;
800 }
801
802 if (viewOnly)
803 return;
804
Pierre Ossman0c158662017-07-13 15:54:11 +0200805 if (keyCode == 0) {
806 vlog.error(_("No key code specified on key press"));
807 return;
808 }
809
Pierre Ossman25188c42014-07-21 16:30:08 +0200810#ifdef __APPLE__
811 // Alt on OS X behaves more like AltGr on other systems, and to get
812 // sane behaviour we should translate things in that manner for the
813 // remote VNC server. However that means we lose the ability to use
814 // Alt as a shortcut modifier. Do what RealVNC does and hijack the
815 // left command key as an Alt replacement.
816 switch (keySym) {
817 case XK_Super_L:
818 keySym = XK_Alt_L;
819 break;
820 case XK_Super_R:
821 keySym = XK_Super_L;
822 break;
823 case XK_Alt_L:
Pierre Ossman38fcebb2014-08-21 13:44:28 +0200824 keySym = XK_Mode_switch;
825 break;
Pierre Ossman25188c42014-07-21 16:30:08 +0200826 case XK_Alt_R:
827 keySym = XK_ISO_Level3_Shift;
828 break;
829 }
830#endif
831
Pierre Ossman25188c42014-07-21 16:30:08 +0200832 // Because of the way keyboards work, we cannot expect to have the same
833 // symbol on release as when pressed. This breaks the VNC protocol however,
834 // so we need to keep track of what keysym a key _code_ generated on press
835 // and send the same on release.
836 downKeySym[keyCode] = keySym;
837
838#if defined(WIN32) || defined(__APPLE__)
839 vlog.debug("Key pressed: 0x%04x => 0x%04x", keyCode, keySym);
840#else
841 vlog.debug("Key pressed: 0x%04x => XK_%s (0x%04x)",
842 keyCode, XKeysymToString(keySym), keySym);
843#endif
844
845 try {
Pierre Ossman0c158662017-07-13 15:54:11 +0200846 // Fake keycode?
847 if (keyCode > 0xff)
848 cc->writer()->keyEvent(keySym, 0, true);
849 else
850 cc->writer()->keyEvent(keySym, keyCode, true);
Pierre Ossman25188c42014-07-21 16:30:08 +0200851 } catch (rdr::Exception& e) {
852 vlog.error("%s", e.str());
853 exit_vncviewer(e.str());
854 }
Pierre Ossman25188c42014-07-21 16:30:08 +0200855}
856
857
858void Viewport::handleKeyRelease(int keyCode)
859{
860 DownMap::iterator iter;
861
862 if (viewOnly)
863 return;
864
865 iter = downKeySym.find(keyCode);
866 if (iter == downKeySym.end()) {
867 // These occur somewhat frequently so let's not spam them unless
868 // logging is turned up.
869 vlog.debug("Unexpected release of key code %d", keyCode);
870 return;
871 }
872
873#if defined(WIN32) || defined(__APPLE__)
874 vlog.debug("Key released: 0x%04x => 0x%04x", keyCode, iter->second);
875#else
876 vlog.debug("Key released: 0x%04x => XK_%s (0x%04x)",
877 keyCode, XKeysymToString(iter->second), iter->second);
878#endif
879
880 try {
Pierre Ossman0c158662017-07-13 15:54:11 +0200881 if (keyCode > 0xff)
882 cc->writer()->keyEvent(iter->second, 0, false);
883 else
884 cc->writer()->keyEvent(iter->second, keyCode, false);
Pierre Ossman25188c42014-07-21 16:30:08 +0200885 } catch (rdr::Exception& e) {
886 vlog.error("%s", e.str());
887 exit_vncviewer(e.str());
888 }
889
890 downKeySym.erase(iter);
891}
892
893
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200894int Viewport::handleSystemEvent(void *event, void *data)
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200895{
896 Viewport *self = (Viewport *)data;
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200897
898 assert(self);
899
Pierre Ossman9a747322018-03-26 14:16:43 +0200900 if (!self->hasFocus())
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200901 return 0;
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200902
903 assert(event);
904
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200905#if defined(WIN32)
906 MSG *msg = (MSG*)event;
907
908 if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN)) {
909 UINT vKey;
910 bool isExtended;
911 int keyCode;
912 rdr::U32 keySym;
913
914 vKey = msg->wParam;
915 isExtended = (msg->lParam & (1 << 24)) != 0;
916
917 keyCode = ((msg->lParam >> 16) & 0xff);
918
Pierre Ossman51249782018-03-08 14:05:39 +0100919 // Windows doesn't have a proper AltGr, but handles it using fake
920 // Ctrl+Alt. However the remote end might not be Windows, so we need
921 // to merge those in to a single AltGr event. We detect this case
922 // by seeing the two key events directly after each other with a very
Pierre Ossman69428fe2018-03-08 17:24:54 +0100923 // short time between them (<50ms) and supress the Ctrl event.
Pierre Ossman51249782018-03-08 14:05:39 +0100924 if (self->altGrArmed) {
925 self->altGrArmed = false;
926 Fl::remove_timeout(handleAltGrTimeout);
927
928 if (isExtended && (keyCode == 0x38) && (vKey == VK_MENU) &&
929 ((msg->time - self->altGrCtrlTime) < 50)) {
Pierre Ossman69428fe2018-03-08 17:24:54 +0100930 // Alt seen, so this is an AltGr sequence
931 } else {
932 // Not Alt, so fire the queued up Ctrl event
933 self->handleKeyPress(0x1d, XK_Control_L);
Pierre Ossman51249782018-03-08 14:05:39 +0100934 }
Pierre Ossman51249782018-03-08 14:05:39 +0100935 }
936
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100937 if (keyCode == SCAN_FAKE) {
938 vlog.debug("Ignoring fake key press (virtual key 0x%02x)", vKey);
939 return 1;
940 }
941
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200942 // Windows sets the scan code to 0x00 for multimedia keys, so we
943 // have to do a reverse lookup based on the vKey.
944 if (keyCode == 0x00) {
945 keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
946 if (keyCode == 0x00) {
Pierre Ossmanf05f3552014-09-22 12:19:52 +0200947 if (isExtended)
948 vlog.error(_("No scan code for extended virtual key 0x%02x"), (int)vKey);
949 else
950 vlog.error(_("No scan code for virtual key 0x%02x"), (int)vKey);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200951 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200952 }
953 }
954
Pierre Ossman0c158662017-07-13 15:54:11 +0200955 if (keyCode & ~0x7f) {
956 vlog.error(_("Invalid scan code 0x%02x"), (int)keyCode);
957 return 1;
958 }
959
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200960 if (isExtended)
Pierre Ossman0c158662017-07-13 15:54:11 +0200961 keyCode |= 0x80;
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200962
Pierre Ossmanf73214c2017-11-13 09:06:03 +0100963
964 // Fortunately RFB and Windows use the same scan code set (mostly),
965 // so there is no conversion needed
966 // (as long as we encode the extended keys with the high bit)
967
968 // However Pause sends a code that conflicts with NumLock, so use
969 // the code most RFB implementations use (part of the sequence for
970 // Ctrl+Pause, i.e. Break)
971 if (keyCode == 0x45)
972 keyCode = 0xc6;
973
974 // And NumLock incorrectly has the extended bit set
975 if (keyCode == 0xc5)
976 keyCode = 0x45;
977
978 // And Alt+PrintScreen (i.e. SysRq) sends a different code than
979 // PrintScreen
980 if (keyCode == 0xb7)
Pierre Ossman0c158662017-07-13 15:54:11 +0200981 keyCode = 0x54;
Pierre Ossmana7d3dc72014-09-30 17:03:28 +0200982
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200983 keySym = win32_vkey_to_keysym(vKey, isExtended);
984 if (keySym == NoSymbol) {
Pierre Ossmanf05f3552014-09-22 12:19:52 +0200985 if (isExtended)
986 vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey);
987 else
988 vlog.error(_("No symbol for virtual key 0x%02x"), (int)vKey);
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200989 }
990
Pierre Ossman30b3f922017-11-13 09:07:59 +0100991 // Windows sends the same vKey for both shifts, so we need to look
992 // at the scan code to tell them apart
993 if ((keySym == XK_Shift_L) && (keyCode == 0x36))
994 keySym = XK_Shift_R;
Pierre Ossman0c158662017-07-13 15:54:11 +0200995
Pierre Ossman69428fe2018-03-08 17:24:54 +0100996 // AltGr handling (see above)
997 if (win32_has_altgr()) {
998 if ((keyCode == 0xb8) && (keySym == XK_Alt_R))
999 keySym = XK_ISO_Level3_Shift;
1000
1001 // Possible start of AltGr sequence?
1002 if ((keyCode == 0x1d) && (keySym == XK_Control_L)) {
1003 self->altGrArmed = true;
1004 self->altGrCtrlTime = msg->time;
1005 Fl::add_timeout(0.1, handleAltGrTimeout, self);
1006 return 1;
1007 }
Pierre Ossman51249782018-03-08 14:05:39 +01001008 }
1009
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001010 self->handleKeyPress(keyCode, keySym);
1011
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001012 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001013 } else if ((msg->message == WM_KEYUP) || (msg->message == WM_SYSKEYUP)) {
1014 UINT vKey;
1015 bool isExtended;
1016 int keyCode;
1017
1018 vKey = msg->wParam;
1019 isExtended = (msg->lParam & (1 << 24)) != 0;
1020
1021 keyCode = ((msg->lParam >> 16) & 0xff);
Pierre Ossman2fa63f82016-12-05 15:26:21 +01001022
Pierre Ossman51249782018-03-08 14:05:39 +01001023 // We can't get a release in the middle of an AltGr sequence, so
1024 // abort that detection
1025 if (self->altGrArmed) {
1026 self->altGrArmed = false;
1027 Fl::remove_timeout(handleAltGrTimeout);
1028 self->handleKeyPress(0x1d, XK_Control_L);
1029 }
1030
Pierre Ossman2fa63f82016-12-05 15:26:21 +01001031 if (keyCode == SCAN_FAKE) {
1032 vlog.debug("Ignoring fake key release (virtual key 0x%02x)", vKey);
1033 return 1;
1034 }
1035
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001036 if (keyCode == 0x00)
1037 keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
1038 if (isExtended)
Pierre Ossman0c158662017-07-13 15:54:11 +02001039 keyCode |= 0x80;
Pierre Ossmanf73214c2017-11-13 09:06:03 +01001040 if (keyCode == 0x45)
1041 keyCode = 0xc6;
1042 if (keyCode == 0xc5)
1043 keyCode = 0x45;
1044 if (keyCode == 0xb7)
Pierre Ossman0c158662017-07-13 15:54:11 +02001045 keyCode = 0x54;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001046
1047 self->handleKeyRelease(keyCode);
1048
Pierre Ossman30b3f922017-11-13 09:07:59 +01001049 // Windows has a rather nasty bug where it won't send key release
1050 // events for a Shift button if the other Shift is still pressed
1051 if ((keyCode == 0x2a) || (keyCode == 0x36)) {
1052 if (self->downKeySym.count(0x2a))
1053 self->handleKeyRelease(0x2a);
1054 if (self->downKeySym.count(0x36))
1055 self->handleKeyRelease(0x36);
1056 }
1057
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001058 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001059 }
Pierre Ossman6b743d02014-07-21 16:48:43 +02001060#elif defined(__APPLE__)
1061 if (cocoa_is_keyboard_event(event)) {
1062 int keyCode;
1063
1064 keyCode = cocoa_event_keycode(event);
Pierre Ossman0c158662017-07-13 15:54:11 +02001065 if ((unsigned)keyCode >= code_map_osx_to_qnum_len)
1066 keyCode = 0;
1067 else
1068 keyCode = code_map_osx_to_qnum[keyCode];
Pierre Ossman6b743d02014-07-21 16:48:43 +02001069
1070 if (cocoa_is_key_press(event)) {
1071 rdr::U32 keySym;
1072
1073 keySym = cocoa_event_keysym(event);
1074 if (keySym == NoSymbol) {
1075 vlog.error(_("No symbol for key code 0x%02x (in the current state)"),
1076 (int)keyCode);
Pierre Ossman6b743d02014-07-21 16:48:43 +02001077 }
1078
1079 self->handleKeyPress(keyCode, keySym);
1080
1081 // We don't get any release events for CapsLock, so we have to
1082 // send the release right away.
1083 if (keySym == XK_Caps_Lock)
1084 self->handleKeyRelease(keyCode);
1085 } else {
1086 self->handleKeyRelease(keyCode);
1087 }
1088
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001089 return 1;
Pierre Ossman6b743d02014-07-21 16:48:43 +02001090 }
1091#else
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001092 XEvent *xevent = (XEvent*)event;
1093
1094 if (xevent->type == KeyPress) {
Pierre Ossman0c158662017-07-13 15:54:11 +02001095 int keycode;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001096 char str;
1097 KeySym keysym;
1098
Pierre Ossman0c158662017-07-13 15:54:11 +02001099 keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
1100
1101 // Generate a fake keycode just for tracking if we can't figure
1102 // out the proper one
1103 if (keycode == 0)
1104 keycode = 0x100 | xevent->xkey.keycode;
1105
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001106 XLookupString(&xevent->xkey, &str, 1, &keysym, NULL);
1107 if (keysym == NoSymbol) {
1108 vlog.error(_("No symbol for key code %d (in the current state)"),
1109 (int)xevent->xkey.keycode);
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001110 }
1111
1112 switch (keysym) {
1113 // For the first few years, there wasn't a good consensus on what the
1114 // Windows keys should be mapped to for X11. So we need to help out a
1115 // bit and map all variants to the same key...
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001116 case XK_Hyper_L:
1117 keysym = XK_Super_L;
1118 break;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001119 case XK_Hyper_R:
1120 keysym = XK_Super_R;
1121 break;
1122 // There has been several variants for Shift-Tab over the years.
1123 // RFB states that we should always send a normal tab.
1124 case XK_ISO_Left_Tab:
1125 keysym = XK_Tab;
1126 break;
1127 }
1128
Pierre Ossman0c158662017-07-13 15:54:11 +02001129 self->handleKeyPress(keycode, keysym);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001130 return 1;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001131 } else if (xevent->type == KeyRelease) {
Pierre Ossman0c158662017-07-13 15:54:11 +02001132 int keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
1133 if (keycode == 0)
1134 keycode = 0x100 | xevent->xkey.keycode;
1135 self->handleKeyRelease(keycode);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001136 return 1;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001137 }
1138#endif
1139
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001140 return 0;
Pierre Ossman4f3ac692014-08-22 15:10:22 +02001141}
1142
Pierre Ossman51249782018-03-08 14:05:39 +01001143#ifdef WIN32
1144void Viewport::handleAltGrTimeout(void *data)
1145{
1146 Viewport *self = (Viewport *)data;
1147
1148 assert(self);
1149
1150 self->altGrArmed = false;
1151 self->handleKeyPress(0x1d, XK_Control_L);
1152}
1153#endif
1154
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001155void Viewport::initContextMenu()
1156{
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001157 contextMenu->clear();
1158
Pierre Ossmanff626f82015-09-23 16:39:54 +02001159 fltk_menu_add(contextMenu, p_("ContextMenu|", "E&xit viewer"),
1160 0, NULL, (void*)ID_EXIT, FL_MENU_DIVIDER);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001161
Pierre Ossmanff626f82015-09-23 16:39:54 +02001162 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Full screen"),
1163 0, NULL, (void*)ID_FULLSCREEN,
Pierre Ossman245c8022015-02-25 11:27:49 +01001164 FL_MENU_TOGGLE | (window()->fullscreen_active()?FL_MENU_VALUE:0));
Pierre Ossmanff626f82015-09-23 16:39:54 +02001165 fltk_menu_add(contextMenu, p_("ContextMenu|", "Minimi&ze"),
1166 0, NULL, (void*)ID_MINIMIZE, 0);
1167 fltk_menu_add(contextMenu, p_("ContextMenu|", "Resize &window to session"),
1168 0, NULL, (void*)ID_RESIZE,
Pierre Ossman245c8022015-02-25 11:27:49 +01001169 (window()->fullscreen_active()?FL_MENU_INACTIVE:0) |
Pierre Ossman245c8022015-02-25 11:27:49 +01001170 FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001171
Pierre Ossmanff626f82015-09-23 16:39:54 +02001172 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Ctrl"),
1173 0, NULL, (void*)ID_CTRL,
Pierre Ossman245c8022015-02-25 11:27:49 +01001174 FL_MENU_TOGGLE | (menuCtrlKey?FL_MENU_VALUE:0));
Pierre Ossmanff626f82015-09-23 16:39:54 +02001175 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Alt"),
1176 0, NULL, (void*)ID_ALT,
Pierre Ossman245c8022015-02-25 11:27:49 +01001177 FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0));
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001178
1179 if (menuKeySym) {
1180 char sendMenuKey[64];
Pierre Ossmanff626f82015-09-23 16:39:54 +02001181 snprintf(sendMenuKey, 64, p_("ContextMenu|", "Send %s"), (const char *)menuKey);
Pierre Ossman245c8022015-02-25 11:27:49 +01001182 fltk_menu_add(contextMenu, sendMenuKey, 0, NULL, (void*)ID_MENUKEY, 0);
Pierre Ossman0c158662017-07-13 15:54:11 +02001183 fltk_menu_add(contextMenu, "Secret shortcut menu key", menuKeyFLTK, NULL,
Pierre Ossman245c8022015-02-25 11:27:49 +01001184 (void*)ID_MENUKEY, FL_MENU_INVISIBLE);
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001185 }
1186
Pierre Ossmanff626f82015-09-23 16:39:54 +02001187 fltk_menu_add(contextMenu, p_("ContextMenu|", "Send Ctrl-Alt-&Del"),
1188 0, NULL, (void*)ID_CTRLALTDEL, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001189
Pierre Ossmanff626f82015-09-23 16:39:54 +02001190 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Refresh screen"),
1191 0, NULL, (void*)ID_REFRESH, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001192
Pierre Ossmanff626f82015-09-23 16:39:54 +02001193 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Options..."),
1194 0, NULL, (void*)ID_OPTIONS, 0);
1195 fltk_menu_add(contextMenu, p_("ContextMenu|", "Connection &info..."),
1196 0, NULL, (void*)ID_INFO, 0);
1197 fltk_menu_add(contextMenu, p_("ContextMenu|", "About &TigerVNC viewer..."),
1198 0, NULL, (void*)ID_ABOUT, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001199
Pierre Ossmanff626f82015-09-23 16:39:54 +02001200 fltk_menu_add(contextMenu, p_("ContextMenu|", "Dismiss &menu"),
1201 0, NULL, (void*)ID_DISMISS, 0);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001202}
1203
1204
1205void Viewport::popupContextMenu()
1206{
1207 const Fl_Menu_Item *m;
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001208 char buffer[1024];
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001209
Pierre Ossmanb3ff4372011-06-03 11:45:15 +00001210 // Make sure the menu is reset to its initial state between goes or
1211 // it will start up highlighting the previously selected entry.
1212 contextMenu->value(-1);
1213
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001214 // initialize context menu before display
1215 initContextMenu();
1216
Pierre Ossmanfd177f32012-01-05 12:37:04 +00001217 // Unfortunately FLTK doesn't reliably restore the mouse pointer for
1218 // menus, so we have to help it out.
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001219 if (Fl::belowmouse() == this)
1220 window()->cursor(FL_CURSOR_DEFAULT);
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001221
Pierre Ossman78236292018-03-26 14:15:40 +02001222 // FLTK also doesn't switch focus properly for menus
1223 handle(FL_UNFOCUS);
1224
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001225 m = contextMenu->popup();
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001226
Pierre Ossman78236292018-03-26 14:15:40 +02001227 handle(FL_FOCUS);
1228
Pierre Ossmanfd177f32012-01-05 12:37:04 +00001229 // Back to our proper mouse pointer.
Pierre Ossmande1a3b92014-03-17 14:29:49 +01001230 if ((Fl::belowmouse() == this) && cursor)
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001231 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001232
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001233 if (m == NULL)
1234 return;
1235
1236 switch (m->argument()) {
1237 case ID_EXIT:
1238 exit_vncviewer();
1239 break;
Pierre Ossman4c613d32011-05-26 14:14:06 +00001240 case ID_FULLSCREEN:
1241 if (window()->fullscreen_active())
1242 window()->fullscreen_off();
Pierre Ossman275284f2012-07-04 11:37:48 +00001243 else
Pierre Ossmanaae38912012-07-13 11:22:55 +00001244 ((DesktopWindow*)window())->fullscreen_on();
Pierre Ossman4c613d32011-05-26 14:14:06 +00001245 break;
Joel Teichroeba7494ac2015-07-13 14:46:22 -07001246 case ID_MINIMIZE:
1247 window()->iconize();
Joel Teichroeb1f9e45a2015-07-18 07:09:24 -07001248 break;
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001249 case ID_RESIZE:
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001250 if (window()->fullscreen_active())
1251 break;
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001252 window()->size(w(), h());
1253 break;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001254 case ID_CTRL:
Pierre Ossman25188c42014-07-21 16:30:08 +02001255 if (m->value())
Pierre Ossman0c158662017-07-13 15:54:11 +02001256 handleKeyPress(0x1d, XK_Control_L);
Pierre Ossman25188c42014-07-21 16:30:08 +02001257 else
Pierre Ossman0c158662017-07-13 15:54:11 +02001258 handleKeyRelease(0x1d);
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001259 menuCtrlKey = !menuCtrlKey;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001260 break;
1261 case ID_ALT:
Pierre Ossman25188c42014-07-21 16:30:08 +02001262 if (m->value())
Pierre Ossman0c158662017-07-13 15:54:11 +02001263 handleKeyPress(0x38, XK_Alt_L);
Pierre Ossman25188c42014-07-21 16:30:08 +02001264 else
Pierre Ossman0c158662017-07-13 15:54:11 +02001265 handleKeyRelease(0x38);
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001266 menuAltKey = !menuAltKey;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001267 break;
1268 case ID_MENUKEY:
Pierre Ossman0c158662017-07-13 15:54:11 +02001269 handleKeyPress(menuKeyCode, menuKeySym);
1270 handleKeyRelease(menuKeyCode);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001271 break;
1272 case ID_CTRLALTDEL:
Pierre Ossman0c158662017-07-13 15:54:11 +02001273 handleKeyPress(0x1d, XK_Control_L);
1274 handleKeyPress(0x38, XK_Alt_L);
1275 handleKeyPress(0xd3, XK_Delete);
Pierre Ossman991b4fe2011-07-12 16:02:30 +00001276
Pierre Ossman0c158662017-07-13 15:54:11 +02001277 handleKeyRelease(0xd3);
1278 handleKeyRelease(0x38);
1279 handleKeyRelease(0x1d);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001280 break;
Pierre Ossmand4c61ce2011-04-29 11:18:12 +00001281 case ID_REFRESH:
1282 cc->refreshFramebuffer();
1283 break;
Pierre Ossmand463b572011-05-16 12:04:43 +00001284 case ID_OPTIONS:
1285 OptionsDialog::showDialog();
1286 break;
Pierre Ossman2eb1d112011-05-16 12:18:08 +00001287 case ID_INFO:
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001288 if (fltk_escape(cc->connectionInfo(), buffer, sizeof(buffer)) < sizeof(buffer)) {
1289 fl_message_title(_("VNC connection info"));
Pierre Ossmanf52740e2012-04-25 15:43:56 +00001290 fl_message("%s", buffer);
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001291 }
Pierre Ossman2eb1d112011-05-16 12:18:08 +00001292 break;
Pierre Ossmanb8858222011-04-29 11:51:38 +00001293 case ID_ABOUT:
1294 about_vncviewer();
1295 break;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001296 case ID_DISMISS:
1297 // Don't need to do anything
1298 break;
1299 }
1300}
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001301
1302
1303void Viewport::setMenuKey()
1304{
Pierre Ossman0c158662017-07-13 15:54:11 +02001305 getMenuKey(&menuKeyFLTK, &menuKeyCode, &menuKeySym);
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001306}
1307
1308
1309void Viewport::handleOptions(void *data)
1310{
1311 Viewport *self = (Viewport*)data;
1312
1313 self->setMenuKey();
1314}