Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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