blob: 151ecb47b009319f0c9397edc46fa4f78bcd88e9 [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 Ossmanbe6909b2018-03-26 14:17:04 +0200122 pendingServerCutText(NULL), pendingClientCutText(NULL),
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 Ossmanbe6909b2018-03-26 14:17:04 +0200211 clearPendingClipboard();
212
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000213 // FLTK automatically deletes all child widgets, so we shouldn't touch
214 // them ourselves here
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000215}
216
217
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000218const rfb::PixelFormat &Viewport::getPreferredPF()
219{
Pierre Ossman132b3d02011-06-13 11:19:32 +0000220 return frameBuffer->getPF();
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000221}
222
223
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000224// Copy the areas of the framebuffer that have been changed (damaged)
225// to the displayed window.
226
227void Viewport::updateWindow()
228{
229 Rect r;
230
Pierre Ossman0c9bd4b2014-07-09 16:44:11 +0200231 r = frameBuffer->getDamage();
232 damage(FL_DAMAGE_USER1, r.tl.x + x(), r.tl.y + y(), r.width(), r.height());
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000233}
234
Pierre Ossman66f1db52019-05-02 12:32:03 +0200235void Viewport::serverCutText(const char* str)
Pierre Ossman4c204232018-03-26 13:32:49 +0200236{
237 char *buffer;
Pierre Ossman56fa7822016-01-22 16:40:59 +0100238 size_t len;
Pierre Ossman4c204232018-03-26 13:32:49 +0200239
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200240 clearPendingClipboard();
241
Pierre Ossman4c204232018-03-26 13:32:49 +0200242 if (!acceptClipboard)
243 return;
244
Pierre Ossman56fa7822016-01-22 16:40:59 +0100245 buffer = latin1ToUTF8(str);
246 len = strlen(buffer);
Pierre Ossman4c204232018-03-26 13:32:49 +0200247
Pierre Ossman56fa7822016-01-22 16:40:59 +0100248 vlog.debug("Got clipboard data (%d bytes)", (int)len);
Pierre Ossman4c204232018-03-26 13:32:49 +0200249
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200250 if (!hasFocus()) {
251 pendingServerCutText = buffer;
252 return;
253 }
254
Pierre Ossman4c204232018-03-26 13:32:49 +0200255 // RFB doesn't have separate selection and clipboard concepts, so we
256 // dump the data into both variants.
Pierre Ossman5cd3ff62019-04-01 14:24:27 +0200257#if !defined(WIN32) && !defined(__APPLE__)
Pierre Ossman4c204232018-03-26 13:32:49 +0200258 if (setPrimary)
Pierre Ossman56fa7822016-01-22 16:40:59 +0100259 Fl::copy(buffer, len, 0);
Pierre Ossman5cd3ff62019-04-01 14:24:27 +0200260#endif
Pierre Ossman56fa7822016-01-22 16:40:59 +0100261 Fl::copy(buffer, len, 1);
Pierre Ossman4c204232018-03-26 13:32:49 +0200262
Pierre Ossman56fa7822016-01-22 16:40:59 +0100263 strFree(buffer);
Pierre Ossman4c204232018-03-26 13:32:49 +0200264}
265
Pierre Ossmanf741d342011-06-09 08:32:49 +0000266static const char * dotcursor_xpm[] = {
267 "5 5 2 1",
268 ". c #000000",
269 " c #FFFFFF",
270 " ",
271 " ... ",
272 " ... ",
273 " ... ",
274 " "};
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000275
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000276void Viewport::setCursor(int width, int height, const Point& hotspot,
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100277 const rdr::U8* data)
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000278{
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100279 int i;
280
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000281 if (cursor) {
Pierre Ossmanf741d342011-06-09 08:32:49 +0000282 if (!cursor->alloc_array)
283 delete [] cursor->array;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000284 delete cursor;
285 }
286
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100287 for (i = 0; i < width*height; i++)
288 if (data[i*4 + 3] != 0) break;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000289
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100290 if ((i == width*height) && dotWhenNoCursor) {
Pierre Ossmana51574a2011-06-09 08:39:35 +0000291 vlog.debug("cursor is empty - using dot");
Pierre Ossmanf741d342011-06-09 08:32:49 +0000292
293 Fl_Pixmap pxm(dotcursor_xpm);
294 cursor = new Fl_RGB_Image(&pxm);
295 cursorHotspot.x = cursorHotspot.y = 2;
296 } else {
Pierre Ossman17a48f02011-06-09 08:42:04 +0000297 if ((width == 0) || (height == 0)) {
298 U8 *buffer = new U8[4];
299 memset(buffer, 0, 4);
300 cursor = new Fl_RGB_Image(buffer, 1, 1, 4);
301 cursorHotspot.x = cursorHotspot.y = 0;
302 } else {
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100303 U8 *buffer = new U8[width * height * 4];
304 memcpy(buffer, data, width * height * 4);
Pierre Ossman17a48f02011-06-09 08:42:04 +0000305 cursor = new Fl_RGB_Image(buffer, width, height, 4);
Pierre Ossman17a48f02011-06-09 08:42:04 +0000306 cursorHotspot = hotspot;
Pierre Ossmanf741d342011-06-09 08:32:49 +0000307 }
Pierre Ossmanf741d342011-06-09 08:32:49 +0000308 }
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000309
310 if (Fl::belowmouse() == this)
311 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000312}
313
314
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100315void Viewport::setLEDState(unsigned int state)
316{
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100317 vlog.debug("Got server LED state: 0x%08x", state);
318
Pierre Ossman609a9c82018-06-07 09:16:02 +0200319 // The first message is just considered to be the server announcing
Pierre Ossman20634392019-02-28 10:57:40 +0100320 // support for this extension. We will push our state to sync up the
321 // server when we get focus. If we already have focus we need to push
322 // it here though.
Pierre Ossman609a9c82018-06-07 09:16:02 +0200323 if (firstLEDState) {
324 firstLEDState = false;
Pierre Ossman20634392019-02-28 10:57:40 +0100325 if (hasFocus())
326 pushLEDState();
Pierre Ossman609a9c82018-06-07 09:16:02 +0200327 return;
328 }
329
Pierre Ossman9a747322018-03-26 14:16:43 +0200330 if (!hasFocus())
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100331 return;
332
333#if defined(WIN32)
334 INPUT input[6];
335 UINT count;
336 UINT ret;
337
338 memset(input, 0, sizeof(input));
339 count = 0;
340
341 if (!!(state & ledCapsLock) != !!(GetKeyState(VK_CAPITAL) & 0x1)) {
342 input[count].type = input[count+1].type = INPUT_KEYBOARD;
343 input[count].ki.wVk = input[count+1].ki.wVk = VK_CAPITAL;
344 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
345 input[count].ki.dwFlags = 0;
346 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP;
347 count += 2;
348 }
349
350 if (!!(state & ledNumLock) != !!(GetKeyState(VK_NUMLOCK) & 0x1)) {
351 input[count].type = input[count+1].type = INPUT_KEYBOARD;
352 input[count].ki.wVk = input[count+1].ki.wVk = VK_NUMLOCK;
353 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
354 input[count].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
355 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY;
356 count += 2;
357 }
358
359 if (!!(state & ledScrollLock) != !!(GetKeyState(VK_SCROLL) & 0x1)) {
360 input[count].type = input[count+1].type = INPUT_KEYBOARD;
361 input[count].ki.wVk = input[count+1].ki.wVk = VK_SCROLL;
362 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
363 input[count].ki.dwFlags = 0;
364 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP;
365 count += 2;
366 }
367
368 if (count == 0)
369 return;
370
371 ret = SendInput(count, input, sizeof(*input));
372 if (ret < count)
373 vlog.error(_("Failed to update keyboard LED state: %lu"), GetLastError());
374#elif defined(__APPLE__)
375 int ret;
376
377 ret = cocoa_set_caps_lock_state(state & ledCapsLock);
378 if (ret != 0) {
379 vlog.error(_("Failed to update keyboard LED state: %d"), ret);
380 return;
381 }
382
383 ret = cocoa_set_num_lock_state(state & ledNumLock);
384 if (ret != 0) {
385 vlog.error(_("Failed to update keyboard LED state: %d"), ret);
386 return;
387 }
388
389 // No support for Scroll Lock //
390
391#else
392 unsigned int affect, values;
393 unsigned int mask;
394
395 Bool ret;
396
397 affect = values = 0;
398
399 affect |= LockMask;
400 if (state & ledCapsLock)
401 values |= LockMask;
402
403 mask = getModifierMask(XK_Num_Lock);
404 affect |= mask;
405 if (state & ledNumLock)
406 values |= mask;
407
408 mask = getModifierMask(XK_Scroll_Lock);
409 affect |= mask;
410 if (state & ledScrollLock)
411 values |= mask;
412
413 ret = XkbLockModifiers(fl_display, XkbUseCoreKbd, affect, values);
414 if (!ret)
415 vlog.error(_("Failed to update keyboard LED state"));
416#endif
417}
418
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100419void Viewport::pushLEDState()
420{
421 unsigned int state;
422
423 // Server support?
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200424 if (cc->server.ledState() == ledUnknown)
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100425 return;
426
427 state = 0;
428
429#if defined(WIN32)
430 if (GetKeyState(VK_CAPITAL) & 0x1)
431 state |= ledCapsLock;
432 if (GetKeyState(VK_NUMLOCK) & 0x1)
433 state |= ledNumLock;
434 if (GetKeyState(VK_SCROLL) & 0x1)
435 state |= ledScrollLock;
436#elif defined(__APPLE__)
437 int ret;
438 bool on;
439
440 ret = cocoa_get_caps_lock_state(&on);
441 if (ret != 0) {
442 vlog.error(_("Failed to get keyboard LED state: %d"), ret);
443 return;
444 }
445 if (on)
446 state |= ledCapsLock;
447
448 ret = cocoa_get_num_lock_state(&on);
449 if (ret != 0) {
450 vlog.error(_("Failed to get keyboard LED state: %d"), ret);
451 return;
452 }
453 if (on)
454 state |= ledNumLock;
455
456 // No support for Scroll Lock //
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200457 state |= (cc->server.ledState() & ledScrollLock);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100458
459#else
460 unsigned int mask;
461
462 Status status;
463 XkbStateRec xkbState;
464
465 status = XkbGetState(fl_display, XkbUseCoreKbd, &xkbState);
466 if (status != Success) {
467 vlog.error(_("Failed to get keyboard LED state: %d"), status);
468 return;
469 }
470
471 if (xkbState.locked_mods & LockMask)
472 state |= ledCapsLock;
473
474 mask = getModifierMask(XK_Num_Lock);
475 if (xkbState.locked_mods & mask)
476 state |= ledNumLock;
477
478 mask = getModifierMask(XK_Scroll_Lock);
479 if (xkbState.locked_mods & mask)
480 state |= ledScrollLock;
481#endif
482
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200483 if ((state & ledCapsLock) != (cc->server.ledState() & ledCapsLock)) {
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100484 vlog.debug("Inserting fake CapsLock to get in sync with server");
Pierre Ossman0c158662017-07-13 15:54:11 +0200485 handleKeyPress(0x3a, XK_Caps_Lock);
486 handleKeyRelease(0x3a);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100487 }
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200488 if ((state & ledNumLock) != (cc->server.ledState() & ledNumLock)) {
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100489 vlog.debug("Inserting fake NumLock to get in sync with server");
Pierre Ossman0c158662017-07-13 15:54:11 +0200490 handleKeyPress(0x45, XK_Num_Lock);
491 handleKeyRelease(0x45);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100492 }
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200493 if ((state & ledScrollLock) != (cc->server.ledState() & ledScrollLock)) {
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100494 vlog.debug("Inserting fake ScrollLock to get in sync with server");
Pierre Ossman0c158662017-07-13 15:54:11 +0200495 handleKeyPress(0x46, XK_Scroll_Lock);
496 handleKeyRelease(0x46);
Pierre Ossman1668cfa2016-12-10 17:13:40 +0100497 }
498}
499
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100500
Pierre Ossman3d74d882017-01-02 19:49:52 +0100501void Viewport::draw(Surface* dst)
502{
503 int X, Y, W, H;
504
505 // Check what actually needs updating
506 fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
507 if ((W == 0) || (H == 0))
508 return;
509
510 frameBuffer->draw(dst, X - x(), Y - y(), X, Y, W, H);
511}
512
513
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000514void Viewport::draw()
515{
516 int X, Y, W, H;
517
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000518 // Check what actually needs updating
Pierre Ossmana6e20772011-04-15 11:10:52 +0000519 fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000520 if ((W == 0) || (H == 0))
521 return;
522
Pierre Ossman132b3d02011-06-13 11:19:32 +0000523 frameBuffer->draw(X - x(), Y - y(), X, Y, W, H);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000524}
525
526
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000527void Viewport::resize(int x, int y, int w, int h)
528{
Pierre Ossman9f273e92015-11-09 16:34:54 +0100529 if ((w != frameBuffer->width()) || (h != frameBuffer->height())) {
530 vlog.debug("Resizing framebuffer from %dx%d to %dx%d",
531 frameBuffer->width(), frameBuffer->height(), w, h);
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000532
Pierre Ossman403ac272017-01-02 17:00:41 +0100533 frameBuffer = new PlatformPixelBuffer(w, h);
Pierre Ossman9f273e92015-11-09 16:34:54 +0100534 assert(frameBuffer);
535 cc->setFramebuffer(frameBuffer);
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000536 }
537
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000538 Fl_Widget::resize(x, y, w, h);
539}
540
541
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000542int Viewport::handle(int event)
543{
Pierre Ossman546b2ad2019-05-02 12:32:03 +0200544 char *buffer, *filtered;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000545 int buttonMask, wheelMask;
546 DownMap::const_iterator iter;
547
548 switch (event) {
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000549 case FL_PASTE:
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200550 clearPendingClipboard();
551
Pierre Ossman56fa7822016-01-22 16:40:59 +0100552 buffer = utf8ToLatin1(Fl::event_text(), Fl::event_length());
553 filtered = convertLF(buffer);
554 strFree(buffer);
Pierre Ossman689c4582011-05-26 15:39:41 +0000555
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200556 if (!hasFocus()) {
Pierre Ossman56fa7822016-01-22 16:40:59 +0100557 pendingClientCutText = filtered;
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200558 return 1;
559 }
560
Pierre Ossman546b2ad2019-05-02 12:32:03 +0200561 vlog.debug("Sending clipboard data (%d bytes)", (int)strlen(filtered));
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000562
563 try {
Pierre Ossman66f1db52019-05-02 12:32:03 +0200564 cc->writer()->writeClientCutText(filtered);
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000565 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000566 vlog.error("%s", e.str());
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000567 exit_vncviewer(e.str());
568 }
Pierre Ossman689c4582011-05-26 15:39:41 +0000569
Pierre Ossman546b2ad2019-05-02 12:32:03 +0200570 strFree(filtered);
Pierre Ossman689c4582011-05-26 15:39:41 +0000571
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000572 return 1;
Pierre Ossman689c4582011-05-26 15:39:41 +0000573
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000574 case FL_ENTER:
Pierre Ossman1f1f6fd2011-06-09 08:33:29 +0000575 if (cursor)
576 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossman56610fb2015-01-27 16:28:15 +0100577 // Yes, we would like some pointer events please!
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000578 return 1;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000579
580 case FL_LEAVE:
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000581 window()->cursor(FL_CURSOR_DEFAULT);
Pierre Ossmanbe882932018-07-31 16:06:59 +0200582 // We want a last move event to help trigger edge stuff
583 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), 0);
584 return 1;
585
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000586 case FL_PUSH:
587 case FL_RELEASE:
588 case FL_DRAG:
589 case FL_MOVE:
590 case FL_MOUSEWHEEL:
591 buttonMask = 0;
592 if (Fl::event_button1())
593 buttonMask |= 1;
594 if (Fl::event_button2())
595 buttonMask |= 2;
596 if (Fl::event_button3())
597 buttonMask |= 4;
598
599 if (event == FL_MOUSEWHEEL) {
Pierre Ossmandf0ed9f2011-05-24 11:33:43 +0000600 wheelMask = 0;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000601 if (Fl::event_dy() < 0)
Pierre Ossmandf0ed9f2011-05-24 11:33:43 +0000602 wheelMask |= 8;
603 if (Fl::event_dy() > 0)
604 wheelMask |= 16;
605 if (Fl::event_dx() < 0)
606 wheelMask |= 32;
607 if (Fl::event_dx() > 0)
608 wheelMask |= 64;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000609
610 // A quick press of the wheel "button", followed by a immediate
611 // release below
Pierre Ossman6a464be2011-04-15 12:57:31 +0000612 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()),
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000613 buttonMask | wheelMask);
614 }
615
Pierre Ossman6a464be2011-04-15 12:57:31 +0000616 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), buttonMask);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000617 return 1;
618
619 case FL_FOCUS:
Pierre Ossman48ef54d2014-08-19 14:08:04 +0200620 Fl::disable_im();
Pierre Ossman78236292018-03-26 14:15:40 +0200621
Pierre Ossmane370e1c2018-07-11 13:05:11 +0200622 flushPendingClipboard();
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200623
Pierre Ossmane370e1c2018-07-11 13:05:11 +0200624 // We may have gotten our lock keys out of sync with the server
625 // whilst we didn't have focus. Try to sort this out.
626 pushLEDState();
Pierre Ossman78236292018-03-26 14:15:40 +0200627
Dominique Martinet9f831802018-07-08 02:15:43 +0900628 // Resend Ctrl/Alt if needed
629 if (menuCtrlKey)
630 handleKeyPress(0x1d, XK_Control_L);
631 if (menuAltKey)
632 handleKeyPress(0x38, XK_Alt_L);
633
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000634 // Yes, we would like some focus please!
635 return 1;
636
637 case FL_UNFOCUS:
638 // Release all keys that were pressed as that generally makes most
639 // sense (e.g. Alt+Tab where we only see the Alt press)
Pierre Ossmanf8450ca2011-07-12 16:10:16 +0000640 while (!downKeySym.empty())
Pierre Ossman25188c42014-07-21 16:30:08 +0200641 handleKeyRelease(downKeySym.begin()->first);
Pierre Ossman48ef54d2014-08-19 14:08:04 +0200642 Fl::enable_im();
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000643 return 1;
644
645 case FL_KEYDOWN:
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000646 case FL_KEYUP:
Pierre Ossman56610fb2015-01-27 16:28:15 +0100647 // Just ignore these as keys were handled in the event handler
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000648 return 1;
649 }
650
651 return Fl_Widget::handle(event);
652}
653
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100654
Pierre Ossman9a747322018-03-26 14:16:43 +0200655bool Viewport::hasFocus()
656{
657 Fl_Widget* focus;
658
659 focus = Fl::grab();
660 if (!focus)
661 focus = Fl::focus();
662
663 return focus == this;
664}
665
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100666#if ! (defined(WIN32) || defined(__APPLE__))
667unsigned int Viewport::getModifierMask(unsigned int keysym)
668{
669 XkbDescPtr xkb;
670 unsigned int mask, keycode;
671 XkbAction *act;
672
673 mask = 0;
674
675 xkb = XkbGetMap(fl_display, XkbAllComponentsMask, XkbUseCoreKbd);
676 if (xkb == NULL)
677 return 0;
678
679 for (keycode = xkb->min_key_code; keycode <= xkb->max_key_code; keycode++) {
680 unsigned int state_out;
681 KeySym ks;
682
683 XkbTranslateKeyCode(xkb, keycode, 0, &state_out, &ks);
684 if (ks == NoSymbol)
685 continue;
686
687 if (ks == keysym)
688 break;
689 }
690
691 // KeySym not mapped?
692 if (keycode > xkb->max_key_code)
693 goto out;
694
695 act = XkbKeyAction(xkb, keycode, 0);
696 if (act == NULL)
697 goto out;
698 if (act->type != XkbSA_LockMods)
699 goto out;
700
701 if (act->mods.flags & XkbSA_UseModMapMods)
702 mask = xkb->map->modmap[keycode];
703 else
704 mask = act->mods.mask;
705
706out:
707 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
708
709 return mask;
710}
711#endif
712
713
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000714void Viewport::handleClipboardChange(int source, void *data)
715{
716 Viewport *self = (Viewport *)data;
717
718 assert(self);
719
Pierre Ossmana432b582017-01-02 15:16:58 +0100720 if (!sendClipboard)
721 return;
722
Pierre Ossmanf7fef922015-12-10 21:24:08 +0100723#if !defined(WIN32) && !defined(__APPLE__)
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000724 if (!sendPrimary && (source == 0))
725 return;
Pierre Ossmanf7fef922015-12-10 21:24:08 +0100726#endif
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000727
728 Fl::paste(*self, source);
729}
730
731
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200732void Viewport::clearPendingClipboard()
733{
Pierre Ossman56fa7822016-01-22 16:40:59 +0100734 strFree(pendingServerCutText);
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200735 pendingServerCutText = NULL;
Pierre Ossman56fa7822016-01-22 16:40:59 +0100736 strFree(pendingClientCutText);
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200737 pendingClientCutText = NULL;
738}
739
740
741void Viewport::flushPendingClipboard()
742{
743 if (pendingServerCutText) {
744 size_t len = strlen(pendingServerCutText);
Pierre Ossman5cd3ff62019-04-01 14:24:27 +0200745#if !defined(WIN32) && !defined(__APPLE__)
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200746 if (setPrimary)
747 Fl::copy(pendingServerCutText, len, 0);
Pierre Ossman5cd3ff62019-04-01 14:24:27 +0200748#endif
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200749 Fl::copy(pendingServerCutText, len, 1);
750 }
751 if (pendingClientCutText) {
752 size_t len = strlen(pendingClientCutText);
753 vlog.debug("Sending pending clipboard data (%d bytes)", (int)len);
Pierre Ossmane370e1c2018-07-11 13:05:11 +0200754 try {
Pierre Ossman66f1db52019-05-02 12:32:03 +0200755 cc->writer()->writeClientCutText(pendingClientCutText);
Pierre Ossmane370e1c2018-07-11 13:05:11 +0200756 } catch (rdr::Exception& e) {
757 vlog.error("%s", e.str());
758 exit_vncviewer(e.str());
759 }
Pierre Ossmanbe6909b2018-03-26 14:17:04 +0200760 }
761
762 clearPendingClipboard();
763}
764
765
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000766void Viewport::handlePointerEvent(const rfb::Point& pos, int buttonMask)
767{
768 if (!viewOnly) {
769 if (pointerEventInterval == 0 || buttonMask != lastButtonMask) {
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000770 try {
Pierre Ossman59da99f2016-02-05 10:43:12 +0100771 cc->writer()->writePointerEvent(pos, buttonMask);
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000772 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000773 vlog.error("%s", e.str());
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000774 exit_vncviewer(e.str());
775 }
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000776 } else {
777 if (!Fl::has_timeout(handlePointerTimeout, this))
778 Fl::add_timeout((double)pointerEventInterval/1000.0,
779 handlePointerTimeout, this);
780 }
781 lastPointerPos = pos;
782 lastButtonMask = buttonMask;
783 }
784}
785
786
787void Viewport::handlePointerTimeout(void *data)
788{
789 Viewport *self = (Viewport *)data;
790
791 assert(self);
792
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000793 try {
Pierre Ossman59da99f2016-02-05 10:43:12 +0100794 self->cc->writer()->writePointerEvent(self->lastPointerPos,
795 self->lastButtonMask);
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000796 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000797 vlog.error("%s", e.str());
Pierre Ossman829cdeb2011-07-12 16:58:12 +0000798 exit_vncviewer(e.str());
799 }
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000800}
801
802
Pierre Ossman25188c42014-07-21 16:30:08 +0200803void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym)
804{
805 static bool menuRecursion = false;
806
807 // Prevent recursion if the menu wants to send its own
808 // activation key.
809 if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) {
810 menuRecursion = true;
811 popupContextMenu();
812 menuRecursion = false;
813 return;
814 }
815
816 if (viewOnly)
817 return;
818
Pierre Ossman0c158662017-07-13 15:54:11 +0200819 if (keyCode == 0) {
820 vlog.error(_("No key code specified on key press"));
821 return;
822 }
823
Pierre Ossman25188c42014-07-21 16:30:08 +0200824#ifdef __APPLE__
825 // Alt on OS X behaves more like AltGr on other systems, and to get
826 // sane behaviour we should translate things in that manner for the
827 // remote VNC server. However that means we lose the ability to use
828 // Alt as a shortcut modifier. Do what RealVNC does and hijack the
829 // left command key as an Alt replacement.
830 switch (keySym) {
831 case XK_Super_L:
832 keySym = XK_Alt_L;
833 break;
834 case XK_Super_R:
835 keySym = XK_Super_L;
836 break;
837 case XK_Alt_L:
Pierre Ossman38fcebb2014-08-21 13:44:28 +0200838 keySym = XK_Mode_switch;
839 break;
Pierre Ossman25188c42014-07-21 16:30:08 +0200840 case XK_Alt_R:
841 keySym = XK_ISO_Level3_Shift;
842 break;
843 }
844#endif
845
Pierre Ossman25188c42014-07-21 16:30:08 +0200846 // Because of the way keyboards work, we cannot expect to have the same
847 // symbol on release as when pressed. This breaks the VNC protocol however,
848 // so we need to keep track of what keysym a key _code_ generated on press
849 // and send the same on release.
850 downKeySym[keyCode] = keySym;
851
852#if defined(WIN32) || defined(__APPLE__)
853 vlog.debug("Key pressed: 0x%04x => 0x%04x", keyCode, keySym);
854#else
855 vlog.debug("Key pressed: 0x%04x => XK_%s (0x%04x)",
856 keyCode, XKeysymToString(keySym), keySym);
857#endif
858
859 try {
Pierre Ossman0c158662017-07-13 15:54:11 +0200860 // Fake keycode?
861 if (keyCode > 0xff)
Pierre Ossman59da99f2016-02-05 10:43:12 +0100862 cc->writer()->writeKeyEvent(keySym, 0, true);
Pierre Ossman0c158662017-07-13 15:54:11 +0200863 else
Pierre Ossman59da99f2016-02-05 10:43:12 +0100864 cc->writer()->writeKeyEvent(keySym, keyCode, true);
Pierre Ossman25188c42014-07-21 16:30:08 +0200865 } catch (rdr::Exception& e) {
866 vlog.error("%s", e.str());
867 exit_vncviewer(e.str());
868 }
Pierre Ossman25188c42014-07-21 16:30:08 +0200869}
870
871
872void Viewport::handleKeyRelease(int keyCode)
873{
874 DownMap::iterator iter;
875
876 if (viewOnly)
877 return;
878
879 iter = downKeySym.find(keyCode);
880 if (iter == downKeySym.end()) {
881 // These occur somewhat frequently so let's not spam them unless
882 // logging is turned up.
883 vlog.debug("Unexpected release of key code %d", keyCode);
884 return;
885 }
886
887#if defined(WIN32) || defined(__APPLE__)
888 vlog.debug("Key released: 0x%04x => 0x%04x", keyCode, iter->second);
889#else
890 vlog.debug("Key released: 0x%04x => XK_%s (0x%04x)",
891 keyCode, XKeysymToString(iter->second), iter->second);
892#endif
893
894 try {
Pierre Ossman0c158662017-07-13 15:54:11 +0200895 if (keyCode > 0xff)
Pierre Ossman59da99f2016-02-05 10:43:12 +0100896 cc->writer()->writeKeyEvent(iter->second, 0, false);
Pierre Ossman0c158662017-07-13 15:54:11 +0200897 else
Pierre Ossman59da99f2016-02-05 10:43:12 +0100898 cc->writer()->writeKeyEvent(iter->second, keyCode, false);
Pierre Ossman25188c42014-07-21 16:30:08 +0200899 } catch (rdr::Exception& e) {
900 vlog.error("%s", e.str());
901 exit_vncviewer(e.str());
902 }
903
904 downKeySym.erase(iter);
905}
906
907
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200908int Viewport::handleSystemEvent(void *event, void *data)
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200909{
910 Viewport *self = (Viewport *)data;
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200911
912 assert(self);
913
Pierre Ossman9a747322018-03-26 14:16:43 +0200914 if (!self->hasFocus())
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200915 return 0;
Pierre Ossman4f3ac692014-08-22 15:10:22 +0200916
917 assert(event);
918
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200919#if defined(WIN32)
920 MSG *msg = (MSG*)event;
921
922 if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN)) {
923 UINT vKey;
924 bool isExtended;
925 int keyCode;
926 rdr::U32 keySym;
927
928 vKey = msg->wParam;
929 isExtended = (msg->lParam & (1 << 24)) != 0;
930
931 keyCode = ((msg->lParam >> 16) & 0xff);
932
Pierre Ossman42d0f5d2018-11-06 17:31:11 +0100933 // Windows' touch keyboard doesn't set a scan code for the Alt
934 // portion of the AltGr sequence, so we need to help it out
935 if (!isExtended && (keyCode == 0x00) && (vKey == VK_MENU)) {
936 isExtended = true;
937 keyCode = 0x38;
938 }
939
Pierre Ossman51249782018-03-08 14:05:39 +0100940 // Windows doesn't have a proper AltGr, but handles it using fake
941 // Ctrl+Alt. However the remote end might not be Windows, so we need
942 // to merge those in to a single AltGr event. We detect this case
943 // by seeing the two key events directly after each other with a very
Pierre Ossman69428fe2018-03-08 17:24:54 +0100944 // short time between them (<50ms) and supress the Ctrl event.
Pierre Ossman51249782018-03-08 14:05:39 +0100945 if (self->altGrArmed) {
946 self->altGrArmed = false;
947 Fl::remove_timeout(handleAltGrTimeout);
948
949 if (isExtended && (keyCode == 0x38) && (vKey == VK_MENU) &&
950 ((msg->time - self->altGrCtrlTime) < 50)) {
Pierre Ossman69428fe2018-03-08 17:24:54 +0100951 // Alt seen, so this is an AltGr sequence
952 } else {
953 // Not Alt, so fire the queued up Ctrl event
954 self->handleKeyPress(0x1d, XK_Control_L);
Pierre Ossman51249782018-03-08 14:05:39 +0100955 }
Pierre Ossman51249782018-03-08 14:05:39 +0100956 }
957
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100958 if (keyCode == SCAN_FAKE) {
959 vlog.debug("Ignoring fake key press (virtual key 0x%02x)", vKey);
960 return 1;
961 }
962
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200963 // Windows sets the scan code to 0x00 for multimedia keys, so we
964 // have to do a reverse lookup based on the vKey.
965 if (keyCode == 0x00) {
966 keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
967 if (keyCode == 0x00) {
Pierre Ossmanf05f3552014-09-22 12:19:52 +0200968 if (isExtended)
969 vlog.error(_("No scan code for extended virtual key 0x%02x"), (int)vKey);
970 else
971 vlog.error(_("No scan code for virtual key 0x%02x"), (int)vKey);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +0200972 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200973 }
974 }
975
Pierre Ossman0c158662017-07-13 15:54:11 +0200976 if (keyCode & ~0x7f) {
977 vlog.error(_("Invalid scan code 0x%02x"), (int)keyCode);
978 return 1;
979 }
980
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200981 if (isExtended)
Pierre Ossman0c158662017-07-13 15:54:11 +0200982 keyCode |= 0x80;
Pierre Ossman2e9684f2014-07-21 16:46:22 +0200983
Pierre Ossmanf73214c2017-11-13 09:06:03 +0100984
985 // Fortunately RFB and Windows use the same scan code set (mostly),
986 // so there is no conversion needed
987 // (as long as we encode the extended keys with the high bit)
988
989 // However Pause sends a code that conflicts with NumLock, so use
990 // the code most RFB implementations use (part of the sequence for
991 // Ctrl+Pause, i.e. Break)
992 if (keyCode == 0x45)
993 keyCode = 0xc6;
994
995 // And NumLock incorrectly has the extended bit set
996 if (keyCode == 0xc5)
997 keyCode = 0x45;
998
999 // And Alt+PrintScreen (i.e. SysRq) sends a different code than
1000 // PrintScreen
1001 if (keyCode == 0xb7)
Pierre Ossman0c158662017-07-13 15:54:11 +02001002 keyCode = 0x54;
Pierre Ossmana7d3dc72014-09-30 17:03:28 +02001003
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001004 keySym = win32_vkey_to_keysym(vKey, isExtended);
1005 if (keySym == NoSymbol) {
Pierre Ossmanf05f3552014-09-22 12:19:52 +02001006 if (isExtended)
1007 vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey);
1008 else
1009 vlog.error(_("No symbol for virtual key 0x%02x"), (int)vKey);
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001010 }
1011
Pierre Ossman30b3f922017-11-13 09:07:59 +01001012 // Windows sends the same vKey for both shifts, so we need to look
1013 // at the scan code to tell them apart
1014 if ((keySym == XK_Shift_L) && (keyCode == 0x36))
1015 keySym = XK_Shift_R;
Pierre Ossman0c158662017-07-13 15:54:11 +02001016
Pierre Ossman69428fe2018-03-08 17:24:54 +01001017 // AltGr handling (see above)
1018 if (win32_has_altgr()) {
1019 if ((keyCode == 0xb8) && (keySym == XK_Alt_R))
1020 keySym = XK_ISO_Level3_Shift;
1021
1022 // Possible start of AltGr sequence?
1023 if ((keyCode == 0x1d) && (keySym == XK_Control_L)) {
1024 self->altGrArmed = true;
1025 self->altGrCtrlTime = msg->time;
1026 Fl::add_timeout(0.1, handleAltGrTimeout, self);
1027 return 1;
1028 }
Pierre Ossman51249782018-03-08 14:05:39 +01001029 }
1030
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001031 self->handleKeyPress(keyCode, keySym);
1032
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001033 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001034 } else if ((msg->message == WM_KEYUP) || (msg->message == WM_SYSKEYUP)) {
1035 UINT vKey;
1036 bool isExtended;
1037 int keyCode;
1038
1039 vKey = msg->wParam;
1040 isExtended = (msg->lParam & (1 << 24)) != 0;
1041
1042 keyCode = ((msg->lParam >> 16) & 0xff);
Pierre Ossman2fa63f82016-12-05 15:26:21 +01001043
Pierre Ossman42d0f5d2018-11-06 17:31:11 +01001044 // Touch keyboard AltGr (see above)
1045 if (!isExtended && (keyCode == 0x00) && (vKey == VK_MENU)) {
1046 isExtended = true;
1047 keyCode = 0x38;
1048 }
1049
Pierre Ossman51249782018-03-08 14:05:39 +01001050 // We can't get a release in the middle of an AltGr sequence, so
1051 // abort that detection
1052 if (self->altGrArmed) {
1053 self->altGrArmed = false;
1054 Fl::remove_timeout(handleAltGrTimeout);
1055 self->handleKeyPress(0x1d, XK_Control_L);
1056 }
1057
Pierre Ossman2fa63f82016-12-05 15:26:21 +01001058 if (keyCode == SCAN_FAKE) {
1059 vlog.debug("Ignoring fake key release (virtual key 0x%02x)", vKey);
1060 return 1;
1061 }
1062
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001063 if (keyCode == 0x00)
1064 keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
1065 if (isExtended)
Pierre Ossman0c158662017-07-13 15:54:11 +02001066 keyCode |= 0x80;
Pierre Ossmanf73214c2017-11-13 09:06:03 +01001067 if (keyCode == 0x45)
1068 keyCode = 0xc6;
1069 if (keyCode == 0xc5)
1070 keyCode = 0x45;
1071 if (keyCode == 0xb7)
Pierre Ossman0c158662017-07-13 15:54:11 +02001072 keyCode = 0x54;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001073
1074 self->handleKeyRelease(keyCode);
1075
Pierre Ossman30b3f922017-11-13 09:07:59 +01001076 // Windows has a rather nasty bug where it won't send key release
1077 // events for a Shift button if the other Shift is still pressed
1078 if ((keyCode == 0x2a) || (keyCode == 0x36)) {
1079 if (self->downKeySym.count(0x2a))
1080 self->handleKeyRelease(0x2a);
1081 if (self->downKeySym.count(0x36))
1082 self->handleKeyRelease(0x36);
1083 }
1084
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001085 return 1;
Pierre Ossman2e9684f2014-07-21 16:46:22 +02001086 }
Pierre Ossman6b743d02014-07-21 16:48:43 +02001087#elif defined(__APPLE__)
1088 if (cocoa_is_keyboard_event(event)) {
1089 int keyCode;
1090
1091 keyCode = cocoa_event_keycode(event);
Pierre Ossman0c158662017-07-13 15:54:11 +02001092 if ((unsigned)keyCode >= code_map_osx_to_qnum_len)
1093 keyCode = 0;
1094 else
1095 keyCode = code_map_osx_to_qnum[keyCode];
Pierre Ossman6b743d02014-07-21 16:48:43 +02001096
1097 if (cocoa_is_key_press(event)) {
1098 rdr::U32 keySym;
1099
1100 keySym = cocoa_event_keysym(event);
1101 if (keySym == NoSymbol) {
1102 vlog.error(_("No symbol for key code 0x%02x (in the current state)"),
1103 (int)keyCode);
Pierre Ossman6b743d02014-07-21 16:48:43 +02001104 }
1105
1106 self->handleKeyPress(keyCode, keySym);
1107
1108 // We don't get any release events for CapsLock, so we have to
1109 // send the release right away.
1110 if (keySym == XK_Caps_Lock)
1111 self->handleKeyRelease(keyCode);
1112 } else {
1113 self->handleKeyRelease(keyCode);
1114 }
1115
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001116 return 1;
Pierre Ossman6b743d02014-07-21 16:48:43 +02001117 }
1118#else
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001119 XEvent *xevent = (XEvent*)event;
1120
1121 if (xevent->type == KeyPress) {
Pierre Ossman0c158662017-07-13 15:54:11 +02001122 int keycode;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001123 char str;
1124 KeySym keysym;
1125
Pierre Ossman0c158662017-07-13 15:54:11 +02001126 keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
1127
1128 // Generate a fake keycode just for tracking if we can't figure
1129 // out the proper one
1130 if (keycode == 0)
1131 keycode = 0x100 | xevent->xkey.keycode;
1132
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001133 XLookupString(&xevent->xkey, &str, 1, &keysym, NULL);
1134 if (keysym == NoSymbol) {
1135 vlog.error(_("No symbol for key code %d (in the current state)"),
1136 (int)xevent->xkey.keycode);
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001137 }
1138
1139 switch (keysym) {
1140 // For the first few years, there wasn't a good consensus on what the
1141 // Windows keys should be mapped to for X11. So we need to help out a
1142 // bit and map all variants to the same key...
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001143 case XK_Hyper_L:
1144 keysym = XK_Super_L;
1145 break;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001146 case XK_Hyper_R:
1147 keysym = XK_Super_R;
1148 break;
1149 // There has been several variants for Shift-Tab over the years.
1150 // RFB states that we should always send a normal tab.
1151 case XK_ISO_Left_Tab:
1152 keysym = XK_Tab;
1153 break;
1154 }
1155
Pierre Ossman0c158662017-07-13 15:54:11 +02001156 self->handleKeyPress(keycode, keysym);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001157 return 1;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001158 } else if (xevent->type == KeyRelease) {
Pierre Ossman0c158662017-07-13 15:54:11 +02001159 int keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
1160 if (keycode == 0)
1161 keycode = 0x100 | xevent->xkey.keycode;
1162 self->handleKeyRelease(keycode);
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001163 return 1;
Pierre Ossman6b9622d2014-07-21 16:42:12 +02001164 }
1165#endif
1166
Pierre Ossman64ff1ca2014-09-11 10:48:29 +02001167 return 0;
Pierre Ossman4f3ac692014-08-22 15:10:22 +02001168}
1169
Pierre Ossman51249782018-03-08 14:05:39 +01001170#ifdef WIN32
1171void Viewport::handleAltGrTimeout(void *data)
1172{
1173 Viewport *self = (Viewport *)data;
1174
1175 assert(self);
1176
1177 self->altGrArmed = false;
1178 self->handleKeyPress(0x1d, XK_Control_L);
1179}
1180#endif
1181
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001182void Viewport::initContextMenu()
1183{
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001184 contextMenu->clear();
1185
Pierre Ossmanff626f82015-09-23 16:39:54 +02001186 fltk_menu_add(contextMenu, p_("ContextMenu|", "E&xit viewer"),
1187 0, NULL, (void*)ID_EXIT, FL_MENU_DIVIDER);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001188
Pierre Ossmanff626f82015-09-23 16:39:54 +02001189 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Full screen"),
1190 0, NULL, (void*)ID_FULLSCREEN,
Pierre Ossman245c8022015-02-25 11:27:49 +01001191 FL_MENU_TOGGLE | (window()->fullscreen_active()?FL_MENU_VALUE:0));
Pierre Ossmanff626f82015-09-23 16:39:54 +02001192 fltk_menu_add(contextMenu, p_("ContextMenu|", "Minimi&ze"),
1193 0, NULL, (void*)ID_MINIMIZE, 0);
1194 fltk_menu_add(contextMenu, p_("ContextMenu|", "Resize &window to session"),
1195 0, NULL, (void*)ID_RESIZE,
Pierre Ossman245c8022015-02-25 11:27:49 +01001196 (window()->fullscreen_active()?FL_MENU_INACTIVE:0) |
Pierre Ossman245c8022015-02-25 11:27:49 +01001197 FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001198
Pierre Ossmanff626f82015-09-23 16:39:54 +02001199 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Ctrl"),
1200 0, NULL, (void*)ID_CTRL,
Pierre Ossman245c8022015-02-25 11:27:49 +01001201 FL_MENU_TOGGLE | (menuCtrlKey?FL_MENU_VALUE:0));
Pierre Ossmanff626f82015-09-23 16:39:54 +02001202 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Alt"),
1203 0, NULL, (void*)ID_ALT,
Pierre Ossman245c8022015-02-25 11:27:49 +01001204 FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0));
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001205
1206 if (menuKeySym) {
1207 char sendMenuKey[64];
Pierre Ossmanff626f82015-09-23 16:39:54 +02001208 snprintf(sendMenuKey, 64, p_("ContextMenu|", "Send %s"), (const char *)menuKey);
Pierre Ossman245c8022015-02-25 11:27:49 +01001209 fltk_menu_add(contextMenu, sendMenuKey, 0, NULL, (void*)ID_MENUKEY, 0);
Pierre Ossman0c158662017-07-13 15:54:11 +02001210 fltk_menu_add(contextMenu, "Secret shortcut menu key", menuKeyFLTK, NULL,
Pierre Ossman245c8022015-02-25 11:27:49 +01001211 (void*)ID_MENUKEY, FL_MENU_INVISIBLE);
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001212 }
1213
Pierre Ossmanff626f82015-09-23 16:39:54 +02001214 fltk_menu_add(contextMenu, p_("ContextMenu|", "Send Ctrl-Alt-&Del"),
1215 0, NULL, (void*)ID_CTRLALTDEL, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001216
Pierre Ossmanff626f82015-09-23 16:39:54 +02001217 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Refresh screen"),
1218 0, NULL, (void*)ID_REFRESH, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001219
Pierre Ossmanff626f82015-09-23 16:39:54 +02001220 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Options..."),
1221 0, NULL, (void*)ID_OPTIONS, 0);
1222 fltk_menu_add(contextMenu, p_("ContextMenu|", "Connection &info..."),
1223 0, NULL, (void*)ID_INFO, 0);
1224 fltk_menu_add(contextMenu, p_("ContextMenu|", "About &TigerVNC viewer..."),
1225 0, NULL, (void*)ID_ABOUT, FL_MENU_DIVIDER);
Pierre Ossmanb5b0ea52015-02-25 10:35:03 +01001226
Pierre Ossmanff626f82015-09-23 16:39:54 +02001227 fltk_menu_add(contextMenu, p_("ContextMenu|", "Dismiss &menu"),
1228 0, NULL, (void*)ID_DISMISS, 0);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001229}
1230
1231
1232void Viewport::popupContextMenu()
1233{
1234 const Fl_Menu_Item *m;
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001235 char buffer[1024];
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001236
Pierre Ossmanb3ff4372011-06-03 11:45:15 +00001237 // Make sure the menu is reset to its initial state between goes or
1238 // it will start up highlighting the previously selected entry.
1239 contextMenu->value(-1);
1240
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001241 // initialize context menu before display
1242 initContextMenu();
1243
Pierre Ossmanfd177f32012-01-05 12:37:04 +00001244 // Unfortunately FLTK doesn't reliably restore the mouse pointer for
1245 // menus, so we have to help it out.
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001246 if (Fl::belowmouse() == this)
1247 window()->cursor(FL_CURSOR_DEFAULT);
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001248
Pierre Ossman78236292018-03-26 14:15:40 +02001249 // FLTK also doesn't switch focus properly for menus
1250 handle(FL_UNFOCUS);
1251
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001252 m = contextMenu->popup();
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001253
Pierre Ossman78236292018-03-26 14:15:40 +02001254 handle(FL_FOCUS);
1255
Pierre Ossmanfd177f32012-01-05 12:37:04 +00001256 // Back to our proper mouse pointer.
Pierre Ossmande1a3b92014-03-17 14:29:49 +01001257 if ((Fl::belowmouse() == this) && cursor)
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001258 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossmanbfbdb102012-01-05 12:32:03 +00001259
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001260 if (m == NULL)
1261 return;
1262
1263 switch (m->argument()) {
1264 case ID_EXIT:
1265 exit_vncviewer();
1266 break;
Pierre Ossman4c613d32011-05-26 14:14:06 +00001267 case ID_FULLSCREEN:
1268 if (window()->fullscreen_active())
1269 window()->fullscreen_off();
Pierre Ossman275284f2012-07-04 11:37:48 +00001270 else
Pierre Ossmanaae38912012-07-13 11:22:55 +00001271 ((DesktopWindow*)window())->fullscreen_on();
Pierre Ossman4c613d32011-05-26 14:14:06 +00001272 break;
Joel Teichroeba7494ac2015-07-13 14:46:22 -07001273 case ID_MINIMIZE:
1274 window()->iconize();
Joel Teichroeb1f9e45a2015-07-18 07:09:24 -07001275 break;
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001276 case ID_RESIZE:
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001277 if (window()->fullscreen_active())
1278 break;
Pierre Ossman8dc8bca2012-07-04 11:37:10 +00001279 window()->size(w(), h());
1280 break;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001281 case ID_CTRL:
Pierre Ossman25188c42014-07-21 16:30:08 +02001282 if (m->value())
Pierre Ossman0c158662017-07-13 15:54:11 +02001283 handleKeyPress(0x1d, XK_Control_L);
Pierre Ossman25188c42014-07-21 16:30:08 +02001284 else
Pierre Ossman0c158662017-07-13 15:54:11 +02001285 handleKeyRelease(0x1d);
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001286 menuCtrlKey = !menuCtrlKey;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001287 break;
1288 case ID_ALT:
Pierre Ossman25188c42014-07-21 16:30:08 +02001289 if (m->value())
Pierre Ossman0c158662017-07-13 15:54:11 +02001290 handleKeyPress(0x38, XK_Alt_L);
Pierre Ossman25188c42014-07-21 16:30:08 +02001291 else
Pierre Ossman0c158662017-07-13 15:54:11 +02001292 handleKeyRelease(0x38);
Henrik Anderssonabd70ce2011-09-14 06:31:06 +00001293 menuAltKey = !menuAltKey;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001294 break;
1295 case ID_MENUKEY:
Pierre Ossman0c158662017-07-13 15:54:11 +02001296 handleKeyPress(menuKeyCode, menuKeySym);
1297 handleKeyRelease(menuKeyCode);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001298 break;
1299 case ID_CTRLALTDEL:
Pierre Ossman0c158662017-07-13 15:54:11 +02001300 handleKeyPress(0x1d, XK_Control_L);
1301 handleKeyPress(0x38, XK_Alt_L);
1302 handleKeyPress(0xd3, XK_Delete);
Pierre Ossman991b4fe2011-07-12 16:02:30 +00001303
Pierre Ossman0c158662017-07-13 15:54:11 +02001304 handleKeyRelease(0xd3);
1305 handleKeyRelease(0x38);
1306 handleKeyRelease(0x1d);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001307 break;
Pierre Ossmand4c61ce2011-04-29 11:18:12 +00001308 case ID_REFRESH:
1309 cc->refreshFramebuffer();
1310 break;
Pierre Ossmand463b572011-05-16 12:04:43 +00001311 case ID_OPTIONS:
1312 OptionsDialog::showDialog();
1313 break;
Pierre Ossman2eb1d112011-05-16 12:18:08 +00001314 case ID_INFO:
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001315 if (fltk_escape(cc->connectionInfo(), buffer, sizeof(buffer)) < sizeof(buffer)) {
1316 fl_message_title(_("VNC connection info"));
Pierre Ossmanf52740e2012-04-25 15:43:56 +00001317 fl_message("%s", buffer);
Pierre Ossmanc628ba42011-05-23 12:21:21 +00001318 }
Pierre Ossman2eb1d112011-05-16 12:18:08 +00001319 break;
Pierre Ossmanb8858222011-04-29 11:51:38 +00001320 case ID_ABOUT:
1321 about_vncviewer();
1322 break;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +00001323 case ID_DISMISS:
1324 // Don't need to do anything
1325 break;
1326 }
1327}
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001328
1329
1330void Viewport::setMenuKey()
1331{
Pierre Ossman0c158662017-07-13 15:54:11 +02001332 getMenuKey(&menuKeyFLTK, &menuKeyCode, &menuKeySym);
Pierre Ossman4e7271e2011-05-24 12:47:12 +00001333}
1334
1335
1336void Viewport::handleOptions(void *data)
1337{
1338 Viewport *self = (Viewport*)data;
1339
1340 self->setMenuKey();
1341}