1 /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 * Copyright 2011-2019 Pierre Ossman for Cendio AB
3 *
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
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <assert.h>
25 #include <stdio.h>
26 #include <string.h>
27
28 #include <rfb/CMsgWriter.h>
29 #include <rfb/LogWriter.h>
30 #include <rfb/Exception.h>
31 #include <rfb/ledStates.h>
32
33 // FLTK can pull in the X11 headers on some systems
34 #ifndef XK_VoidSymbol
35 #define XK_LATIN1
36 #define XK_MISCELLANY
37 #define XK_XKB_KEYS
38 #include <rfb/keysymdef.h>
39 #endif
40
41 #ifndef XF86XK_ModeLock
42 #include <rfb/XF86keysym.h>
43 #endif
44
45 #if ! (defined(WIN32) || defined(__APPLE__))
46 #include <X11/XKBlib.h>
47 #endif
48
49 #ifndef NoSymbol
50 #define NoSymbol 0
51 #endif
52
53 // Missing in at least some versions of MinGW
54 #ifndef MAPVK_VK_TO_VSC
55 #define MAPVK_VK_TO_VSC 0
56 #endif
57
58 #include "Viewport.h"
59 #include "CConn.h"
60 #include "OptionsDialog.h"
61 #include "DesktopWindow.h"
62 #include "i18n.h"
63 #include "fltk_layout.h"
64 #include "parameters.h"
65 #include "keysym2ucs.h"
66 #include "menukey.h"
67 #include "vncviewer.h"
68
69 #include "PlatformPixelBuffer.h"
70
71 #include <FL/fl_draw.H>
72 #include <FL/fl_ask.H>
73
74 #include <FL/Fl_Menu.H>
75 #include <FL/Fl_Menu_Button.H>
76
77 #if !defined(WIN32) && !defined(__APPLE__)
78 #include <X11/XKBlib.h>
79 extern const struct _code_map_xkb_to_qnum {
80 const char * from;
81 const unsigned short to;
82 } code_map_xkb_to_qnum[];
83 extern const unsigned int code_map_xkb_to_qnum_len;
84
85 static int code_map_keycode_to_qnum[256];
86 #endif
87
88 #ifdef __APPLE__
89 #include "cocoa.h"
90 extern const unsigned short code_map_osx_to_qnum[];
91 extern const unsigned int code_map_osx_to_qnum_len;
92 #endif
93
94 #ifdef WIN32
95 #include "win32.h"
96 #endif
97
98
99 using namespace rfb;
100 using namespace rdr;
101
102 static rfb::LogWriter vlog("Viewport");
103
104 // Menu constants
105
106 enum { ID_DISCONNECT, ID_FULLSCREEN, ID_MINIMIZE, ID_RESIZE,
107 ID_CTRL, ID_ALT, ID_MENUKEY, ID_CTRLALTDEL,
108 ID_REFRESH, ID_OPTIONS, ID_INFO, ID_ABOUT };
109
110 // Used to detect fake input (0xaa is not a real key)
111 #ifdef WIN32
112 static const WORD SCAN_FAKE = 0xaa;
113 #endif
114
Viewport(int w,int h,const rfb::PixelFormat & serverPF,CConn * cc_)115 Viewport::Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_)
116 : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(NULL),
117 lastPointerPos(0, 0), lastButtonMask(0),
118 #ifdef WIN32
119 altGrArmed(false),
120 #endif
121 firstLEDState(true),
122 pendingServerClipboard(false), pendingClientClipboard(false),
123 menuCtrlKey(false), menuAltKey(false), cursor(NULL)
124 {
125 #if !defined(WIN32) && !defined(__APPLE__)
126 XkbDescPtr xkb;
127 Status status;
128
129 xkb = XkbGetMap(fl_display, 0, XkbUseCoreKbd);
130 if (!xkb)
131 throw rfb::Exception("XkbGetMap");
132
133 status = XkbGetNames(fl_display, XkbKeyNamesMask, xkb);
134 if (status != Success)
135 throw rfb::Exception("XkbGetNames");
136
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
164 Fl::add_clipboard_notify(handleClipboardChange, this);
165
166 // We need to intercept keyboard events early
167 Fl::add_system_handler(handleSystemEvent, this);
168
169 frameBuffer = new PlatformPixelBuffer(w, h);
170 assert(frameBuffer);
171 cc->setFramebuffer(frameBuffer);
172
173 contextMenu = new Fl_Menu_Button(0, 0, 0, 0);
174 // 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
178 // 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
184 setMenuKey();
185
186 OptionsDialog::addCallback(handleOptions, this);
187
188 // Make sure we have an initial blank cursor set
189 setCursor(0, 0, rfb::Point(0, 0), NULL);
190 }
191
192
~Viewport()193 Viewport::~Viewport()
194 {
195 // Unregister all timeouts in case they get a change tro trigger
196 // again later when this object is already gone.
197 Fl::remove_timeout(handlePointerTimeout, this);
198 #ifdef WIN32
199 Fl::remove_timeout(handleAltGrTimeout, this);
200 #endif
201
202 Fl::remove_system_handler(handleSystemEvent);
203
204 Fl::remove_clipboard_notify(handleClipboardChange);
205
206 OptionsDialog::removeCallback(handleOptions);
207
208 if (cursor) {
209 if (!cursor->alloc_array)
210 delete [] cursor->array;
211 delete cursor;
212 }
213
214 // FLTK automatically deletes all child widgets, so we shouldn't touch
215 // them ourselves here
216 }
217
218
getPreferredPF()219 const rfb::PixelFormat &Viewport::getPreferredPF()
220 {
221 return frameBuffer->getPF();
222 }
223
224
225 // Copy the areas of the framebuffer that have been changed (damaged)
226 // to the displayed window.
227
updateWindow()228 void Viewport::updateWindow()
229 {
230 Rect r;
231
232 r = frameBuffer->getDamage();
233 damage(FL_DAMAGE_USER1, r.tl.x + x(), r.tl.y + y(), r.width(), r.height());
234 }
235
236 static const char * dotcursor_xpm[] = {
237 "5 5 2 1",
238 ". c #000000",
239 " c #FFFFFF",
240 " ",
241 " ... ",
242 " ... ",
243 " ... ",
244 " "};
245
setCursor(int width,int height,const Point & hotspot,const rdr::U8 * data)246 void Viewport::setCursor(int width, int height, const Point& hotspot,
247 const rdr::U8* data)
248 {
249 int i;
250
251 if (cursor) {
252 if (!cursor->alloc_array)
253 delete [] cursor->array;
254 delete cursor;
255 }
256
257 for (i = 0; i < width*height; i++)
258 if (data[i*4 + 3] != 0) break;
259
260 if ((i == width*height) && dotWhenNoCursor) {
261 vlog.debug("cursor is empty - using dot");
262
263 Fl_Pixmap pxm(dotcursor_xpm);
264 cursor = new Fl_RGB_Image(&pxm);
265 cursorHotspot.x = cursorHotspot.y = 2;
266 } else {
267 if ((width == 0) || (height == 0)) {
268 U8 *buffer = new U8[4];
269 memset(buffer, 0, 4);
270 cursor = new Fl_RGB_Image(buffer, 1, 1, 4);
271 cursorHotspot.x = cursorHotspot.y = 0;
272 } else {
273 U8 *buffer = new U8[width * height * 4];
274 memcpy(buffer, data, width * height * 4);
275 cursor = new Fl_RGB_Image(buffer, width, height, 4);
276 cursorHotspot = hotspot;
277 }
278 }
279
280 if (Fl::belowmouse() == this)
281 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
282 }
283
handleClipboardRequest()284 void Viewport::handleClipboardRequest()
285 {
286 Fl::paste(*this, clipboardSource);
287 }
288
handleClipboardAnnounce(bool available)289 void Viewport::handleClipboardAnnounce(bool available)
290 {
291 if (!acceptClipboard)
292 return;
293
294 if (!available) {
295 vlog.debug("Clipboard is no longer available on server");
296 pendingServerClipboard = false;
297 return;
298 }
299
300 pendingClientClipboard = false;
301
302 if (!hasFocus()) {
303 vlog.debug("Got notification of new clipboard on server whilst not focused, will request data later");
304 pendingServerClipboard = true;
305 return;
306 }
307
308 vlog.debug("Got notification of new clipboard on server, requesting data");
309 cc->requestClipboard();
310 }
311
handleClipboardData(const char * data)312 void Viewport::handleClipboardData(const char* data)
313 {
314 size_t len;
315
316 if (!hasFocus())
317 return;
318
319 len = strlen(data);
320
321 vlog.debug("Got clipboard data (%d bytes)", (int)len);
322
323 // RFB doesn't have separate selection and clipboard concepts, so we
324 // dump the data into both variants.
325 #if !defined(WIN32) && !defined(__APPLE__)
326 if (setPrimary)
327 Fl::copy(data, len, 0);
328 #endif
329 Fl::copy(data, len, 1);
330 }
331
setLEDState(unsigned int state)332 void Viewport::setLEDState(unsigned int state)
333 {
334 vlog.debug("Got server LED state: 0x%08x", state);
335
336 // The first message is just considered to be the server announcing
337 // support for this extension. We will push our state to sync up the
338 // server when we get focus. If we already have focus we need to push
339 // it here though.
340 if (firstLEDState) {
341 firstLEDState = false;
342 if (hasFocus())
343 pushLEDState();
344 return;
345 }
346
347 if (!hasFocus())
348 return;
349
350 #if defined(WIN32)
351 INPUT input[6];
352 UINT count;
353 UINT ret;
354
355 memset(input, 0, sizeof(input));
356 count = 0;
357
358 if (!!(state & ledCapsLock) != !!(GetKeyState(VK_CAPITAL) & 0x1)) {
359 input[count].type = input[count+1].type = INPUT_KEYBOARD;
360 input[count].ki.wVk = input[count+1].ki.wVk = VK_CAPITAL;
361 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
362 input[count].ki.dwFlags = 0;
363 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP;
364 count += 2;
365 }
366
367 if (!!(state & ledNumLock) != !!(GetKeyState(VK_NUMLOCK) & 0x1)) {
368 input[count].type = input[count+1].type = INPUT_KEYBOARD;
369 input[count].ki.wVk = input[count+1].ki.wVk = VK_NUMLOCK;
370 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
371 input[count].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
372 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY;
373 count += 2;
374 }
375
376 if (!!(state & ledScrollLock) != !!(GetKeyState(VK_SCROLL) & 0x1)) {
377 input[count].type = input[count+1].type = INPUT_KEYBOARD;
378 input[count].ki.wVk = input[count+1].ki.wVk = VK_SCROLL;
379 input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
380 input[count].ki.dwFlags = 0;
381 input[count+1].ki.dwFlags = KEYEVENTF_KEYUP;
382 count += 2;
383 }
384
385 if (count == 0)
386 return;
387
388 ret = SendInput(count, input, sizeof(*input));
389 if (ret < count)
390 vlog.error(_("Failed to update keyboard LED state: %lu"), GetLastError());
391 #elif defined(__APPLE__)
392 int ret;
393
394 ret = cocoa_set_caps_lock_state(state & ledCapsLock);
395 if (ret != 0) {
396 vlog.error(_("Failed to update keyboard LED state: %d"), ret);
397 return;
398 }
399
400 ret = cocoa_set_num_lock_state(state & ledNumLock);
401 if (ret != 0) {
402 vlog.error(_("Failed to update keyboard LED state: %d"), ret);
403 return;
404 }
405
406 // No support for Scroll Lock //
407
408 #else
409 unsigned int affect, values;
410 unsigned int mask;
411
412 Bool ret;
413
414 affect = values = 0;
415
416 affect |= LockMask;
417 if (state & ledCapsLock)
418 values |= LockMask;
419
420 mask = getModifierMask(XK_Num_Lock);
421 affect |= mask;
422 if (state & ledNumLock)
423 values |= mask;
424
425 mask = getModifierMask(XK_Scroll_Lock);
426 affect |= mask;
427 if (state & ledScrollLock)
428 values |= mask;
429
430 ret = XkbLockModifiers(fl_display, XkbUseCoreKbd, affect, values);
431 if (!ret)
432 vlog.error(_("Failed to update keyboard LED state"));
433 #endif
434 }
435
pushLEDState()436 void Viewport::pushLEDState()
437 {
438 unsigned int state;
439
440 // Server support?
441 if (cc->server.ledState() == ledUnknown)
442 return;
443
444 state = 0;
445
446 #if defined(WIN32)
447 if (GetKeyState(VK_CAPITAL) & 0x1)
448 state |= ledCapsLock;
449 if (GetKeyState(VK_NUMLOCK) & 0x1)
450 state |= ledNumLock;
451 if (GetKeyState(VK_SCROLL) & 0x1)
452 state |= ledScrollLock;
453 #elif defined(__APPLE__)
454 int ret;
455 bool on;
456
457 ret = cocoa_get_caps_lock_state(&on);
458 if (ret != 0) {
459 vlog.error(_("Failed to get keyboard LED state: %d"), ret);
460 return;
461 }
462 if (on)
463 state |= ledCapsLock;
464
465 ret = cocoa_get_num_lock_state(&on);
466 if (ret != 0) {
467 vlog.error(_("Failed to get keyboard LED state: %d"), ret);
468 return;
469 }
470 if (on)
471 state |= ledNumLock;
472
473 // No support for Scroll Lock //
474 state |= (cc->server.ledState() & ledScrollLock);
475
476 #else
477 unsigned int mask;
478
479 Status status;
480 XkbStateRec xkbState;
481
482 status = XkbGetState(fl_display, XkbUseCoreKbd, &xkbState);
483 if (status != Success) {
484 vlog.error(_("Failed to get keyboard LED state: %d"), status);
485 return;
486 }
487
488 if (xkbState.locked_mods & LockMask)
489 state |= ledCapsLock;
490
491 mask = getModifierMask(XK_Num_Lock);
492 if (xkbState.locked_mods & mask)
493 state |= ledNumLock;
494
495 mask = getModifierMask(XK_Scroll_Lock);
496 if (xkbState.locked_mods & mask)
497 state |= ledScrollLock;
498 #endif
499
500 if ((state & ledCapsLock) != (cc->server.ledState() & ledCapsLock)) {
501 vlog.debug("Inserting fake CapsLock to get in sync with server");
502 handleKeyPress(0x3a, XK_Caps_Lock);
503 handleKeyRelease(0x3a);
504 }
505 if ((state & ledNumLock) != (cc->server.ledState() & ledNumLock)) {
506 vlog.debug("Inserting fake NumLock to get in sync with server");
507 handleKeyPress(0x45, XK_Num_Lock);
508 handleKeyRelease(0x45);
509 }
510 if ((state & ledScrollLock) != (cc->server.ledState() & ledScrollLock)) {
511 vlog.debug("Inserting fake ScrollLock to get in sync with server");
512 handleKeyPress(0x46, XK_Scroll_Lock);
513 handleKeyRelease(0x46);
514 }
515 }
516
517
draw(Surface * dst)518 void Viewport::draw(Surface* dst)
519 {
520 int X, Y, W, H;
521
522 // Check what actually needs updating
523 fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
524 if ((W == 0) || (H == 0))
525 return;
526
527 frameBuffer->draw(dst, X - x(), Y - y(), X, Y, W, H);
528 }
529
530
draw()531 void Viewport::draw()
532 {
533 int X, Y, W, H;
534
535 // Check what actually needs updating
536 fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
537 if ((W == 0) || (H == 0))
538 return;
539
540 frameBuffer->draw(X - x(), Y - y(), X, Y, W, H);
541 }
542
543
resize(int x,int y,int w,int h)544 void Viewport::resize(int x, int y, int w, int h)
545 {
546 if ((w != frameBuffer->width()) || (h != frameBuffer->height())) {
547 vlog.debug("Resizing framebuffer from %dx%d to %dx%d",
548 frameBuffer->width(), frameBuffer->height(), w, h);
549
550 frameBuffer = new PlatformPixelBuffer(w, h);
551 assert(frameBuffer);
552 cc->setFramebuffer(frameBuffer);
553 }
554
555 Fl_Widget::resize(x, y, w, h);
556 }
557
558
handle(int event)559 int Viewport::handle(int event)
560 {
561 char *filtered;
562 int buttonMask, wheelMask;
563 DownMap::const_iterator iter;
564
565 switch (event) {
566 case FL_PASTE:
567 filtered = convertLF(Fl::event_text(), Fl::event_length());
568
569 vlog.debug("Sending clipboard data (%d bytes)", (int)strlen(filtered));
570
571 try {
572 cc->sendClipboardData(filtered);
573 } catch (rdr::Exception& e) {
574 vlog.error("%s", e.str());
575 abort_connection(_("An unexpected error occurred when "
576 "communicating with the server:\n\n%s"),
577 e.str());
578 }
579
580 strFree(filtered);
581
582 return 1;
583
584 case FL_ENTER:
585 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
586 // Yes, we would like some pointer events please!
587 return 1;
588
589 case FL_LEAVE:
590 window()->cursor(FL_CURSOR_DEFAULT);
591 // We want a last move event to help trigger edge stuff
592 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), 0);
593 return 1;
594
595 case FL_PUSH:
596 case FL_RELEASE:
597 case FL_DRAG:
598 case FL_MOVE:
599 case FL_MOUSEWHEEL:
600 buttonMask = 0;
601 if (Fl::event_button1())
602 buttonMask |= 1;
603 if (Fl::event_button2())
604 buttonMask |= 2;
605 if (Fl::event_button3())
606 buttonMask |= 4;
607
608 if (event == FL_MOUSEWHEEL) {
609 wheelMask = 0;
610 if (Fl::event_dy() < 0)
611 wheelMask |= 8;
612 if (Fl::event_dy() > 0)
613 wheelMask |= 16;
614 if (Fl::event_dx() < 0)
615 wheelMask |= 32;
616 if (Fl::event_dx() > 0)
617 wheelMask |= 64;
618
619 // A quick press of the wheel "button", followed by a immediate
620 // release below
621 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()),
622 buttonMask | wheelMask);
623 }
624
625 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), buttonMask);
626 return 1;
627
628 case FL_FOCUS:
629 Fl::disable_im();
630
631 flushPendingClipboard();
632
633 // We may have gotten our lock keys out of sync with the server
634 // whilst we didn't have focus. Try to sort this out.
635 pushLEDState();
636
637 // Resend Ctrl/Alt if needed
638 if (menuCtrlKey)
639 handleKeyPress(0x1d, XK_Control_L);
640 if (menuAltKey)
641 handleKeyPress(0x38, XK_Alt_L);
642
643 // Yes, we would like some focus please!
644 return 1;
645
646 case FL_UNFOCUS:
647 // Release all keys that were pressed as that generally makes most
648 // sense (e.g. Alt+Tab where we only see the Alt press)
649 while (!downKeySym.empty())
650 handleKeyRelease(downKeySym.begin()->first);
651 Fl::enable_im();
652 return 1;
653
654 case FL_KEYDOWN:
655 case FL_KEYUP:
656 // Just ignore these as keys were handled in the event handler
657 return 1;
658 }
659
660 return Fl_Widget::handle(event);
661 }
662
sendPointerEvent(const rfb::Point & pos,int buttonMask)663 void Viewport::sendPointerEvent(const rfb::Point& pos, int buttonMask)
664 {
665 if (viewOnly)
666 return;
667
668 if ((pointerEventInterval == 0) || (buttonMask != lastButtonMask)) {
669 try {
670 cc->writer()->writePointerEvent(pos, buttonMask);
671 } catch (rdr::Exception& e) {
672 vlog.error("%s", e.str());
673 abort_connection(_("An unexpected error occurred when "
674 "communicating with the server:\n\n%s"),
675 e.str());
676 }
677 } else {
678 if (!Fl::has_timeout(handlePointerTimeout, this))
679 Fl::add_timeout((double)pointerEventInterval/1000.0,
680 handlePointerTimeout, this);
681 }
682 lastPointerPos = pos;
683 lastButtonMask = buttonMask;
684 }
685
hasFocus()686 bool Viewport::hasFocus()
687 {
688 Fl_Widget* focus;
689
690 focus = Fl::grab();
691 if (!focus)
692 focus = Fl::focus();
693
694 return focus == this;
695 }
696
697 #if ! (defined(WIN32) || defined(__APPLE__))
getModifierMask(unsigned int keysym)698 unsigned int Viewport::getModifierMask(unsigned int keysym)
699 {
700 XkbDescPtr xkb;
701 unsigned int mask, keycode;
702 XkbAction *act;
703
704 mask = 0;
705
706 xkb = XkbGetMap(fl_display, XkbAllComponentsMask, XkbUseCoreKbd);
707 if (xkb == NULL)
708 return 0;
709
710 for (keycode = xkb->min_key_code; keycode <= xkb->max_key_code; keycode++) {
711 unsigned int state_out;
712 KeySym ks;
713
714 XkbTranslateKeyCode(xkb, keycode, 0, &state_out, &ks);
715 if (ks == NoSymbol)
716 continue;
717
718 if (ks == keysym)
719 break;
720 }
721
722 // KeySym not mapped?
723 if (keycode > xkb->max_key_code)
724 goto out;
725
726 act = XkbKeyAction(xkb, keycode, 0);
727 if (act == NULL)
728 goto out;
729 if (act->type != XkbSA_LockMods)
730 goto out;
731
732 if (act->mods.flags & XkbSA_UseModMapMods)
733 mask = xkb->map->modmap[keycode];
734 else
735 mask = act->mods.mask;
736
737 out:
738 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
739
740 return mask;
741 }
742 #endif
743
744
handleClipboardChange(int source,void * data)745 void Viewport::handleClipboardChange(int source, void *data)
746 {
747 Viewport *self = (Viewport *)data;
748
749 assert(self);
750
751 if (!sendClipboard)
752 return;
753
754 #if !defined(WIN32) && !defined(__APPLE__)
755 if (!sendPrimary && (source == 0))
756 return;
757 #endif
758
759 self->clipboardSource = source;
760
761 self->pendingServerClipboard = false;
762
763 if (!self->hasFocus()) {
764 vlog.debug("Local clipboard changed whilst not focused, will notify server later");
765 self->pendingClientClipboard = true;
766 // Clear any older client clipboard from the server
767 self->cc->announceClipboard(false);
768 return;
769 }
770
771 vlog.debug("Local clipboard changed, notifying server");
772 try {
773 self->cc->announceClipboard(true);
774 } catch (rdr::Exception& e) {
775 vlog.error("%s", e.str());
776 abort_connection(_("An unexpected error occurred when "
777 "communicating with the server:\n\n%s"),
778 e.str());
779 }
780 }
781
782
flushPendingClipboard()783 void Viewport::flushPendingClipboard()
784 {
785 if (pendingServerClipboard) {
786 vlog.debug("Focus regained after remote clipboard change, requesting data");
787 try {
788 cc->requestClipboard();
789 } catch (rdr::Exception& e) {
790 vlog.error("%s", e.str());
791 abort_connection(_("An unexpected error occurred when "
792 "communicating with the server:\n\n%s"),
793 e.str());
794 }
795 }
796 if (pendingClientClipboard) {
797 vlog.debug("Focus regained after local clipboard change, notifying server");
798 try {
799 cc->announceClipboard(true);
800 } catch (rdr::Exception& e) {
801 vlog.error("%s", e.str());
802 abort_connection(_("An unexpected error occurred when "
803 "communicating with the server:\n\n%s"),
804 e.str());
805 }
806 }
807
808 pendingServerClipboard = false;
809 pendingClientClipboard = false;
810 }
811
812
handlePointerEvent(const rfb::Point & pos,int buttonMask)813 void Viewport::handlePointerEvent(const rfb::Point& pos, int buttonMask)
814 {
815 filterPointerEvent(pos, buttonMask);
816 }
817
818
handlePointerTimeout(void * data)819 void Viewport::handlePointerTimeout(void *data)
820 {
821 Viewport *self = (Viewport *)data;
822
823 assert(self);
824
825 try {
826 self->cc->writer()->writePointerEvent(self->lastPointerPos,
827 self->lastButtonMask);
828 } catch (rdr::Exception& e) {
829 vlog.error("%s", e.str());
830 abort_connection(_("An unexpected error occurred when "
831 "communicating with the server:\n\n%s"),
832 e.str());
833 }
834 }
835
836
handleKeyPress(int keyCode,rdr::U32 keySym)837 void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym)
838 {
839 static bool menuRecursion = false;
840
841 // Prevent recursion if the menu wants to send its own
842 // activation key.
843 if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) {
844 menuRecursion = true;
845 popupContextMenu();
846 menuRecursion = false;
847 return;
848 }
849
850 if (viewOnly)
851 return;
852
853 if (keyCode == 0) {
854 vlog.error(_("No key code specified on key press"));
855 return;
856 }
857
858 #ifdef __APPLE__
859 // Alt on OS X behaves more like AltGr on other systems, and to get
860 // sane behaviour we should translate things in that manner for the
861 // remote VNC server. However that means we lose the ability to use
862 // Alt as a shortcut modifier. Do what RealVNC does and hijack the
863 // left command key as an Alt replacement.
864 switch (keySym) {
865 case XK_Super_L:
866 keySym = XK_Alt_L;
867 break;
868 case XK_Super_R:
869 keySym = XK_Super_L;
870 break;
871 case XK_Alt_L:
872 keySym = XK_Mode_switch;
873 break;
874 case XK_Alt_R:
875 keySym = XK_ISO_Level3_Shift;
876 break;
877 }
878 #endif
879
880 // Because of the way keyboards work, we cannot expect to have the same
881 // symbol on release as when pressed. This breaks the VNC protocol however,
882 // so we need to keep track of what keysym a key _code_ generated on press
883 // and send the same on release.
884 downKeySym[keyCode] = keySym;
885
886 #if defined(WIN32) || defined(__APPLE__)
887 vlog.debug("Key pressed: 0x%04x => 0x%04x", keyCode, keySym);
888 #else
889 vlog.debug("Key pressed: 0x%04x => XK_%s (0x%04x)",
890 keyCode, XKeysymToString(keySym), keySym);
891 #endif
892
893 try {
894 // Fake keycode?
895 if (keyCode > 0xff)
896 cc->writer()->writeKeyEvent(keySym, 0, true);
897 else
898 cc->writer()->writeKeyEvent(keySym, keyCode, true);
899 } catch (rdr::Exception& e) {
900 vlog.error("%s", e.str());
901 abort_connection(_("An unexpected error occurred when "
902 "communicating with the server:\n\n%s"),
903 e.str());
904 }
905 }
906
907
handleKeyRelease(int keyCode)908 void Viewport::handleKeyRelease(int keyCode)
909 {
910 DownMap::iterator iter;
911
912 if (viewOnly)
913 return;
914
915 iter = downKeySym.find(keyCode);
916 if (iter == downKeySym.end()) {
917 // These occur somewhat frequently so let's not spam them unless
918 // logging is turned up.
919 vlog.debug("Unexpected release of key code %d", keyCode);
920 return;
921 }
922
923 #if defined(WIN32) || defined(__APPLE__)
924 vlog.debug("Key released: 0x%04x => 0x%04x", keyCode, iter->second);
925 #else
926 vlog.debug("Key released: 0x%04x => XK_%s (0x%04x)",
927 keyCode, XKeysymToString(iter->second), iter->second);
928 #endif
929
930 try {
931 if (keyCode > 0xff)
932 cc->writer()->writeKeyEvent(iter->second, 0, false);
933 else
934 cc->writer()->writeKeyEvent(iter->second, keyCode, false);
935 } catch (rdr::Exception& e) {
936 vlog.error("%s", e.str());
937 abort_connection(_("An unexpected error occurred when "
938 "communicating with the server:\n\n%s"),
939 e.str());
940 }
941
942 downKeySym.erase(iter);
943 }
944
945
handleSystemEvent(void * event,void * data)946 int Viewport::handleSystemEvent(void *event, void *data)
947 {
948 Viewport *self = (Viewport *)data;
949
950 assert(self);
951
952 if (!self->hasFocus())
953 return 0;
954
955 assert(event);
956
957 #if defined(WIN32)
958 MSG *msg = (MSG*)event;
959
960 if ((msg->message == WM_MOUSEMOVE) ||
961 (msg->message == WM_LBUTTONDOWN) ||
962 (msg->message == WM_LBUTTONUP) ||
963 (msg->message == WM_RBUTTONDOWN) ||
964 (msg->message == WM_RBUTTONUP) ||
965 (msg->message == WM_MBUTTONDOWN) ||
966 (msg->message == WM_MBUTTONUP) ||
967 (msg->message == WM_MOUSEWHEEL) ||
968 (msg->message == WM_MOUSEHWHEEL)) {
969 // We can't get a mouse event in the middle of an AltGr sequence, so
970 // abort that detection
971 if (self->altGrArmed)
972 self->resolveAltGrDetection(false);
973
974 return 0; // We didn't really consume the mouse event
975 } else if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN)) {
976 UINT vKey;
977 bool isExtended;
978 int keyCode;
979 rdr::U32 keySym;
980
981 vKey = msg->wParam;
982 isExtended = (msg->lParam & (1 << 24)) != 0;
983
984 keyCode = ((msg->lParam >> 16) & 0xff);
985
986 // Windows' touch keyboard doesn't set a scan code for the Alt
987 // portion of the AltGr sequence, so we need to help it out
988 if (!isExtended && (keyCode == 0x00) && (vKey == VK_MENU)) {
989 isExtended = true;
990 keyCode = 0x38;
991 }
992
993 // Windows doesn't have a proper AltGr, but handles it using fake
994 // Ctrl+Alt. However the remote end might not be Windows, so we need
995 // to merge those in to a single AltGr event. We detect this case
996 // by seeing the two key events directly after each other with a very
997 // short time between them (<50ms) and supress the Ctrl event.
998 if (self->altGrArmed) {
999 bool altPressed = isExtended &&
1000 (keyCode == 0x38) &&
1001 (vKey == VK_MENU) &&
1002 ((msg->time - self->altGrCtrlTime) < 50);
1003 self->resolveAltGrDetection(altPressed);
1004 }
1005
1006 if (keyCode == SCAN_FAKE) {
1007 vlog.debug("Ignoring fake key press (virtual key 0x%02x)", vKey);
1008 return 1;
1009 }
1010
1011 // Windows sets the scan code to 0x00 for multimedia keys, so we
1012 // have to do a reverse lookup based on the vKey.
1013 if (keyCode == 0x00) {
1014 keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
1015 if (keyCode == 0x00) {
1016 if (isExtended)
1017 vlog.error(_("No scan code for extended virtual key 0x%02x"), (int)vKey);
1018 else
1019 vlog.error(_("No scan code for virtual key 0x%02x"), (int)vKey);
1020 return 1;
1021 }
1022 }
1023
1024 if (keyCode & ~0x7f) {
1025 vlog.error(_("Invalid scan code 0x%02x"), (int)keyCode);
1026 return 1;
1027 }
1028
1029 if (isExtended)
1030 keyCode |= 0x80;
1031
1032
1033 // Fortunately RFB and Windows use the same scan code set (mostly),
1034 // so there is no conversion needed
1035 // (as long as we encode the extended keys with the high bit)
1036
1037 // However Pause sends a code that conflicts with NumLock, so use
1038 // the code most RFB implementations use (part of the sequence for
1039 // Ctrl+Pause, i.e. Break)
1040 if (keyCode == 0x45)
1041 keyCode = 0xc6;
1042
1043 // And NumLock incorrectly has the extended bit set
1044 if (keyCode == 0xc5)
1045 keyCode = 0x45;
1046
1047 // And Alt+PrintScreen (i.e. SysRq) sends a different code than
1048 // PrintScreen
1049 if (keyCode == 0xb7)
1050 keyCode = 0x54;
1051
1052 keySym = win32_vkey_to_keysym(vKey, isExtended);
1053 if (keySym == NoSymbol) {
1054 if (isExtended)
1055 vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey);
1056 else
1057 vlog.error(_("No symbol for virtual key 0x%02x"), (int)vKey);
1058 }
1059
1060 // Windows sends the same vKey for both shifts, so we need to look
1061 // at the scan code to tell them apart
1062 if ((keySym == XK_Shift_L) && (keyCode == 0x36))
1063 keySym = XK_Shift_R;
1064
1065 // AltGr handling (see above)
1066 if (win32_has_altgr()) {
1067 if ((keyCode == 0xb8) && (keySym == XK_Alt_R))
1068 keySym = XK_ISO_Level3_Shift;
1069
1070 // Possible start of AltGr sequence?
1071 if ((keyCode == 0x1d) && (keySym == XK_Control_L)) {
1072 self->altGrArmed = true;
1073 self->altGrCtrlTime = msg->time;
1074 Fl::add_timeout(0.1, handleAltGrTimeout, self);
1075 return 1;
1076 }
1077 }
1078
1079 self->handleKeyPress(keyCode, keySym);
1080
1081 // We don't get reliable WM_KEYUP for these
1082 switch (keySym) {
1083 case XK_Zenkaku_Hankaku:
1084 case XK_Eisu_toggle:
1085 case XK_Katakana:
1086 case XK_Hiragana:
1087 case XK_Romaji:
1088 self->handleKeyRelease(keyCode);
1089 }
1090
1091 return 1;
1092 } else if ((msg->message == WM_KEYUP) || (msg->message == WM_SYSKEYUP)) {
1093 UINT vKey;
1094 bool isExtended;
1095 int keyCode;
1096
1097 vKey = msg->wParam;
1098 isExtended = (msg->lParam & (1 << 24)) != 0;
1099
1100 keyCode = ((msg->lParam >> 16) & 0xff);
1101
1102 // Touch keyboard AltGr (see above)
1103 if (!isExtended && (keyCode == 0x00) && (vKey == VK_MENU)) {
1104 isExtended = true;
1105 keyCode = 0x38;
1106 }
1107
1108 // We can't get a release in the middle of an AltGr sequence, so
1109 // abort that detection
1110 if (self->altGrArmed)
1111 self->resolveAltGrDetection(false);
1112
1113 if (keyCode == SCAN_FAKE) {
1114 vlog.debug("Ignoring fake key release (virtual key 0x%02x)", vKey);
1115 return 1;
1116 }
1117
1118 if (keyCode == 0x00)
1119 keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
1120 if (isExtended)
1121 keyCode |= 0x80;
1122 if (keyCode == 0x45)
1123 keyCode = 0xc6;
1124 if (keyCode == 0xc5)
1125 keyCode = 0x45;
1126 if (keyCode == 0xb7)
1127 keyCode = 0x54;
1128
1129 self->handleKeyRelease(keyCode);
1130
1131 // Windows has a rather nasty bug where it won't send key release
1132 // events for a Shift button if the other Shift is still pressed
1133 if ((keyCode == 0x2a) || (keyCode == 0x36)) {
1134 if (self->downKeySym.count(0x2a))
1135 self->handleKeyRelease(0x2a);
1136 if (self->downKeySym.count(0x36))
1137 self->handleKeyRelease(0x36);
1138 }
1139
1140 return 1;
1141 }
1142 #elif defined(__APPLE__)
1143 if (cocoa_is_keyboard_event(event)) {
1144 int keyCode;
1145
1146 keyCode = cocoa_event_keycode(event);
1147 if ((unsigned)keyCode >= code_map_osx_to_qnum_len)
1148 keyCode = 0;
1149 else
1150 keyCode = code_map_osx_to_qnum[keyCode];
1151
1152 if (cocoa_is_key_press(event)) {
1153 rdr::U32 keySym;
1154
1155 keySym = cocoa_event_keysym(event);
1156 if (keySym == NoSymbol) {
1157 vlog.error(_("No symbol for key code 0x%02x (in the current state)"),
1158 (int)keyCode);
1159 }
1160
1161 self->handleKeyPress(keyCode, keySym);
1162
1163 // We don't get any release events for CapsLock, so we have to
1164 // send the release right away.
1165 if (keySym == XK_Caps_Lock)
1166 self->handleKeyRelease(keyCode);
1167 } else {
1168 self->handleKeyRelease(keyCode);
1169 }
1170
1171 return 1;
1172 }
1173 #else
1174 XEvent *xevent = (XEvent*)event;
1175
1176 if (xevent->type == KeyPress) {
1177 int keycode;
1178 char str;
1179 KeySym keysym;
1180
1181 keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
1182
1183 // Generate a fake keycode just for tracking if we can't figure
1184 // out the proper one
1185 if (keycode == 0)
1186 keycode = 0x100 | xevent->xkey.keycode;
1187
1188 XLookupString(&xevent->xkey, &str, 1, &keysym, NULL);
1189 if (keysym == NoSymbol) {
1190 vlog.error(_("No symbol for key code %d (in the current state)"),
1191 (int)xevent->xkey.keycode);
1192 }
1193
1194 switch (keysym) {
1195 // For the first few years, there wasn't a good consensus on what the
1196 // Windows keys should be mapped to for X11. So we need to help out a
1197 // bit and map all variants to the same key...
1198 case XK_Hyper_L:
1199 keysym = XK_Super_L;
1200 break;
1201 case XK_Hyper_R:
1202 keysym = XK_Super_R;
1203 break;
1204 // There has been several variants for Shift-Tab over the years.
1205 // RFB states that we should always send a normal tab.
1206 case XK_ISO_Left_Tab:
1207 keysym = XK_Tab;
1208 break;
1209 }
1210
1211 self->handleKeyPress(keycode, keysym);
1212 return 1;
1213 } else if (xevent->type == KeyRelease) {
1214 int keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
1215 if (keycode == 0)
1216 keycode = 0x100 | xevent->xkey.keycode;
1217 self->handleKeyRelease(keycode);
1218 return 1;
1219 }
1220 #endif
1221
1222 return 0;
1223 }
1224
1225 #ifdef WIN32
handleAltGrTimeout(void * data)1226 void Viewport::handleAltGrTimeout(void *data)
1227 {
1228 Viewport *self = (Viewport *)data;
1229
1230 assert(self);
1231
1232 self->altGrArmed = false;
1233 self->handleKeyPress(0x1d, XK_Control_L);
1234 }
1235
resolveAltGrDetection(bool isAltGrSequence)1236 void Viewport::resolveAltGrDetection(bool isAltGrSequence)
1237 {
1238 altGrArmed = false;
1239 Fl::remove_timeout(handleAltGrTimeout);
1240 // when it's not an AltGr sequence we can't supress the Ctrl anymore
1241 if (!isAltGrSequence)
1242 handleKeyPress(0x1d, XK_Control_L);
1243 }
1244 #endif
1245
initContextMenu()1246 void Viewport::initContextMenu()
1247 {
1248 contextMenu->clear();
1249
1250 fltk_menu_add(contextMenu, p_("ContextMenu|", "Dis&connect"),
1251 0, NULL, (void*)ID_DISCONNECT, FL_MENU_DIVIDER);
1252
1253 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Full screen"),
1254 0, NULL, (void*)ID_FULLSCREEN,
1255 FL_MENU_TOGGLE | (window()->fullscreen_active()?FL_MENU_VALUE:0));
1256 fltk_menu_add(contextMenu, p_("ContextMenu|", "Minimi&ze"),
1257 0, NULL, (void*)ID_MINIMIZE, 0);
1258 fltk_menu_add(contextMenu, p_("ContextMenu|", "Resize &window to session"),
1259 0, NULL, (void*)ID_RESIZE,
1260 (window()->fullscreen_active()?FL_MENU_INACTIVE:0) |
1261 FL_MENU_DIVIDER);
1262
1263 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Ctrl"),
1264 0, NULL, (void*)ID_CTRL,
1265 FL_MENU_TOGGLE | (menuCtrlKey?FL_MENU_VALUE:0));
1266 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Alt"),
1267 0, NULL, (void*)ID_ALT,
1268 FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0));
1269
1270 if (menuKeySym) {
1271 char sendMenuKey[64];
1272 snprintf(sendMenuKey, 64, p_("ContextMenu|", "Send %s"), (const char *)menuKey);
1273 fltk_menu_add(contextMenu, sendMenuKey, 0, NULL, (void*)ID_MENUKEY, 0);
1274 fltk_menu_add(contextMenu, "Secret shortcut menu key", menuKeyFLTK, NULL,
1275 (void*)ID_MENUKEY, FL_MENU_INVISIBLE);
1276 }
1277
1278 fltk_menu_add(contextMenu, p_("ContextMenu|", "Send Ctrl-Alt-&Del"),
1279 0, NULL, (void*)ID_CTRLALTDEL, FL_MENU_DIVIDER);
1280
1281 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Refresh screen"),
1282 0, NULL, (void*)ID_REFRESH, FL_MENU_DIVIDER);
1283
1284 fltk_menu_add(contextMenu, p_("ContextMenu|", "&Options..."),
1285 0, NULL, (void*)ID_OPTIONS, 0);
1286 fltk_menu_add(contextMenu, p_("ContextMenu|", "Connection &info..."),
1287 0, NULL, (void*)ID_INFO, 0);
1288 fltk_menu_add(contextMenu, p_("ContextMenu|", "About &TigerVNC viewer..."),
1289 0, NULL, (void*)ID_ABOUT, 0);
1290 }
1291
1292
popupContextMenu()1293 void Viewport::popupContextMenu()
1294 {
1295 const Fl_Menu_Item *m;
1296 char buffer[1024];
1297
1298 // Make sure the menu is reset to its initial state between goes or
1299 // it will start up highlighting the previously selected entry.
1300 contextMenu->value(-1);
1301
1302 // initialize context menu before display
1303 initContextMenu();
1304
1305 // Unfortunately FLTK doesn't reliably restore the mouse pointer for
1306 // menus, so we have to help it out.
1307 if (Fl::belowmouse() == this)
1308 window()->cursor(FL_CURSOR_DEFAULT);
1309
1310 // FLTK also doesn't switch focus properly for menus
1311 handle(FL_UNFOCUS);
1312
1313 m = contextMenu->popup();
1314
1315 handle(FL_FOCUS);
1316
1317 // Back to our proper mouse pointer.
1318 if (Fl::belowmouse())
1319 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
1320
1321 if (m == NULL)
1322 return;
1323
1324 switch (m->argument()) {
1325 case ID_DISCONNECT:
1326 disconnect();
1327 break;
1328 case ID_FULLSCREEN:
1329 if (window()->fullscreen_active())
1330 window()->fullscreen_off();
1331 else
1332 ((DesktopWindow*)window())->fullscreen_on();
1333 break;
1334 case ID_MINIMIZE:
1335 window()->iconize();
1336 break;
1337 case ID_RESIZE:
1338 if (window()->fullscreen_active())
1339 break;
1340 window()->size(w(), h());
1341 break;
1342 case ID_CTRL:
1343 if (m->value())
1344 handleKeyPress(0x1d, XK_Control_L);
1345 else
1346 handleKeyRelease(0x1d);
1347 menuCtrlKey = !menuCtrlKey;
1348 break;
1349 case ID_ALT:
1350 if (m->value())
1351 handleKeyPress(0x38, XK_Alt_L);
1352 else
1353 handleKeyRelease(0x38);
1354 menuAltKey = !menuAltKey;
1355 break;
1356 case ID_MENUKEY:
1357 handleKeyPress(menuKeyCode, menuKeySym);
1358 handleKeyRelease(menuKeyCode);
1359 break;
1360 case ID_CTRLALTDEL:
1361 handleKeyPress(0x1d, XK_Control_L);
1362 handleKeyPress(0x38, XK_Alt_L);
1363 handleKeyPress(0xd3, XK_Delete);
1364
1365 handleKeyRelease(0xd3);
1366 handleKeyRelease(0x38);
1367 handleKeyRelease(0x1d);
1368 break;
1369 case ID_REFRESH:
1370 cc->refreshFramebuffer();
1371 break;
1372 case ID_OPTIONS:
1373 OptionsDialog::showDialog();
1374 break;
1375 case ID_INFO:
1376 if (fltk_escape(cc->connectionInfo(), buffer, sizeof(buffer)) < sizeof(buffer)) {
1377 fl_message_title(_("VNC connection info"));
1378 fl_message("%s", buffer);
1379 }
1380 break;
1381 case ID_ABOUT:
1382 about_vncviewer();
1383 break;
1384 }
1385 }
1386
1387
setMenuKey()1388 void Viewport::setMenuKey()
1389 {
1390 getMenuKey(&menuKeyFLTK, &menuKeyCode, &menuKeySym);
1391 }
1392
1393
handleOptions(void * data)1394 void Viewport::handleOptions(void *data)
1395 {
1396 Viewport *self = (Viewport*)data;
1397
1398 self->setMenuKey();
1399 // FIXME: Need to recheck cursor for dotWhenNoCursor
1400 }
1401