You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Viewport.cxx 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
  2. * Copyright 2011-2014 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. // FLTK can pull in the X11 headers on some systems
  29. #ifndef XK_VoidSymbol
  30. #define XK_LATIN1
  31. #define XK_MISCELLANY
  32. #define XK_XKB_KEYS
  33. #include <rfb/keysymdef.h>
  34. #endif
  35. #ifndef XF86XK_ModeLock
  36. #include <rfb/XF86keysym.h>
  37. #endif
  38. #ifndef NoSymbol
  39. #define NoSymbol 0
  40. #endif
  41. // Missing in at least some versions of MinGW
  42. #ifndef MAPVK_VK_TO_VSC
  43. #define MAPVK_VK_TO_VSC 0
  44. #endif
  45. #include "Viewport.h"
  46. #include "CConn.h"
  47. #include "OptionsDialog.h"
  48. #include "DesktopWindow.h"
  49. #include "i18n.h"
  50. #include "fltk_layout.h"
  51. #include "parameters.h"
  52. #include "keysym2ucs.h"
  53. #include "menukey.h"
  54. #include "vncviewer.h"
  55. #include "PlatformPixelBuffer.h"
  56. #include <FL/fl_draw.H>
  57. #include <FL/fl_ask.H>
  58. #include <FL/Fl_Menu.H>
  59. #include <FL/Fl_Menu_Button.H>
  60. #ifdef __APPLE__
  61. #include "cocoa.h"
  62. #endif
  63. #ifdef WIN32
  64. #include "win32.h"
  65. #endif
  66. using namespace rfb;
  67. using namespace rdr;
  68. static rfb::LogWriter vlog("Viewport");
  69. // Menu constants
  70. enum { ID_EXIT, ID_FULLSCREEN, ID_MINIMIZE, ID_RESIZE,
  71. ID_CTRL, ID_ALT, ID_MENUKEY, ID_CTRLALTDEL,
  72. ID_REFRESH, ID_OPTIONS, ID_INFO, ID_ABOUT, ID_DISMISS };
  73. // Fake key presses use this value and above
  74. static const int fakeKeyBase = 0x200;
  75. Viewport::Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_)
  76. : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(NULL),
  77. lastPointerPos(0, 0), lastButtonMask(0),
  78. menuCtrlKey(false), menuAltKey(false), cursor(NULL)
  79. {
  80. Fl::add_clipboard_notify(handleClipboardChange, this);
  81. // We need to intercept keyboard events early
  82. Fl::add_system_handler(handleSystemEvent, this);
  83. frameBuffer = new PlatformPixelBuffer(w, h);
  84. assert(frameBuffer);
  85. cc->setFramebuffer(frameBuffer);
  86. contextMenu = new Fl_Menu_Button(0, 0, 0, 0);
  87. // Setting box type to FL_NO_BOX prevents it from trying to draw the
  88. // button component (which we don't want)
  89. contextMenu->box(FL_NO_BOX);
  90. // The (invisible) button associated with this widget can mess with
  91. // things like Fl_Scroll so we need to get rid of any parents.
  92. // Unfortunately that's not possible because of STR #2654, but
  93. // reparenting to the current window works for most cases.
  94. window()->add(contextMenu);
  95. setMenuKey();
  96. OptionsDialog::addCallback(handleOptions, this);
  97. // Send a fake pointer event so that the server will stop rendering
  98. // a server-side cursor. Ideally we'd like to send the actual pointer
  99. // position, but we can't really tell when the window manager is done
  100. // placing us so we don't have a good time for that.
  101. handlePointerEvent(Point(w/2, h/2), 0);
  102. }
  103. Viewport::~Viewport()
  104. {
  105. // Unregister all timeouts in case they get a change tro trigger
  106. // again later when this object is already gone.
  107. Fl::remove_timeout(handlePointerTimeout, this);
  108. Fl::remove_system_handler(handleSystemEvent);
  109. Fl::remove_clipboard_notify(handleClipboardChange);
  110. OptionsDialog::removeCallback(handleOptions);
  111. if (cursor) {
  112. if (!cursor->alloc_array)
  113. delete [] cursor->array;
  114. delete cursor;
  115. }
  116. // FLTK automatically deletes all child widgets, so we shouldn't touch
  117. // them ourselves here
  118. }
  119. const rfb::PixelFormat &Viewport::getPreferredPF()
  120. {
  121. return frameBuffer->getPF();
  122. }
  123. // Copy the areas of the framebuffer that have been changed (damaged)
  124. // to the displayed window.
  125. void Viewport::updateWindow()
  126. {
  127. Rect r;
  128. r = frameBuffer->getDamage();
  129. damage(FL_DAMAGE_USER1, r.tl.x + x(), r.tl.y + y(), r.width(), r.height());
  130. }
  131. static const char * dotcursor_xpm[] = {
  132. "5 5 2 1",
  133. ". c #000000",
  134. " c #FFFFFF",
  135. " ",
  136. " ... ",
  137. " ... ",
  138. " ... ",
  139. " "};
  140. void Viewport::setCursor(int width, int height, const Point& hotspot,
  141. void* data, void* mask)
  142. {
  143. if (cursor) {
  144. if (!cursor->alloc_array)
  145. delete [] cursor->array;
  146. delete cursor;
  147. }
  148. int mask_len = ((width+7)/8) * height;
  149. int i;
  150. for (i = 0; i < mask_len; i++)
  151. if (((rdr::U8*)mask)[i]) break;
  152. if ((i == mask_len) && dotWhenNoCursor) {
  153. vlog.debug("cursor is empty - using dot");
  154. Fl_Pixmap pxm(dotcursor_xpm);
  155. cursor = new Fl_RGB_Image(&pxm);
  156. cursorHotspot.x = cursorHotspot.y = 2;
  157. } else {
  158. if ((width == 0) || (height == 0)) {
  159. U8 *buffer = new U8[4];
  160. memset(buffer, 0, 4);
  161. cursor = new Fl_RGB_Image(buffer, 1, 1, 4);
  162. cursorHotspot.x = cursorHotspot.y = 0;
  163. } else {
  164. U8 *buffer = new U8[width*height*4];
  165. U8 *i, *o, *m;
  166. int m_width;
  167. const PixelFormat *pf;
  168. pf = &cc->cp.pf();
  169. i = (U8*)data;
  170. o = buffer;
  171. m = (U8*)mask;
  172. m_width = (width+7)/8;
  173. for (int y = 0;y < height;y++) {
  174. for (int x = 0;x < width;x++) {
  175. pf->rgbFromBuffer(o, i, 1);
  176. if (m[(m_width*y)+(x/8)] & 0x80>>(x%8))
  177. o[3] = 255;
  178. else
  179. o[3] = 0;
  180. o += 4;
  181. i += pf->bpp/8;
  182. }
  183. }
  184. cursor = new Fl_RGB_Image(buffer, width, height, 4);
  185. cursorHotspot = hotspot;
  186. }
  187. }
  188. if (Fl::belowmouse() == this)
  189. window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
  190. }
  191. void Viewport::draw()
  192. {
  193. int X, Y, W, H;
  194. // Check what actually needs updating
  195. fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
  196. if ((W == 0) || (H == 0))
  197. return;
  198. frameBuffer->draw(X - x(), Y - y(), X, Y, W, H);
  199. }
  200. void Viewport::resize(int x, int y, int w, int h)
  201. {
  202. if ((w != frameBuffer->width()) || (h != frameBuffer->height())) {
  203. vlog.debug("Resizing framebuffer from %dx%d to %dx%d",
  204. frameBuffer->width(), frameBuffer->height(), w, h);
  205. frameBuffer = new PlatformPixelBuffer(w, h);
  206. assert(frameBuffer);
  207. cc->setFramebuffer(frameBuffer);
  208. }
  209. Fl_Widget::resize(x, y, w, h);
  210. }
  211. int Viewport::handle(int event)
  212. {
  213. char *buffer;
  214. int ret;
  215. int buttonMask, wheelMask;
  216. DownMap::const_iterator iter;
  217. switch (event) {
  218. case FL_PASTE:
  219. buffer = new char[Fl::event_length() + 1];
  220. // This is documented as to ASCII, but actually does to 8859-1
  221. ret = fl_utf8toa(Fl::event_text(), Fl::event_length(), buffer,
  222. Fl::event_length() + 1);
  223. assert(ret < (Fl::event_length() + 1));
  224. vlog.debug("Sending clipboard data (%d bytes)", (int)strlen(buffer));
  225. try {
  226. cc->writer()->clientCutText(buffer, ret);
  227. } catch (rdr::Exception& e) {
  228. vlog.error("%s", e.str());
  229. exit_vncviewer(e.str());
  230. }
  231. delete [] buffer;
  232. return 1;
  233. case FL_ENTER:
  234. if (cursor)
  235. window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
  236. // Yes, we would like some pointer events please!
  237. return 1;
  238. case FL_LEAVE:
  239. window()->cursor(FL_CURSOR_DEFAULT);
  240. // Fall through as we want a last move event to help trigger edge stuff
  241. case FL_PUSH:
  242. case FL_RELEASE:
  243. case FL_DRAG:
  244. case FL_MOVE:
  245. case FL_MOUSEWHEEL:
  246. buttonMask = 0;
  247. if (Fl::event_button1())
  248. buttonMask |= 1;
  249. if (Fl::event_button2())
  250. buttonMask |= 2;
  251. if (Fl::event_button3())
  252. buttonMask |= 4;
  253. if (event == FL_MOUSEWHEEL) {
  254. wheelMask = 0;
  255. if (Fl::event_dy() < 0)
  256. wheelMask |= 8;
  257. if (Fl::event_dy() > 0)
  258. wheelMask |= 16;
  259. if (Fl::event_dx() < 0)
  260. wheelMask |= 32;
  261. if (Fl::event_dx() > 0)
  262. wheelMask |= 64;
  263. // A quick press of the wheel "button", followed by a immediate
  264. // release below
  265. handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()),
  266. buttonMask | wheelMask);
  267. }
  268. handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), buttonMask);
  269. return 1;
  270. case FL_FOCUS:
  271. Fl::disable_im();
  272. // Yes, we would like some focus please!
  273. return 1;
  274. case FL_UNFOCUS:
  275. // Release all keys that were pressed as that generally makes most
  276. // sense (e.g. Alt+Tab where we only see the Alt press)
  277. while (!downKeySym.empty())
  278. handleKeyRelease(downKeySym.begin()->first);
  279. Fl::enable_im();
  280. return 1;
  281. case FL_KEYDOWN:
  282. case FL_KEYUP:
  283. // Just ignore these as keys were handled in the event handler
  284. return 1;
  285. }
  286. return Fl_Widget::handle(event);
  287. }
  288. void Viewport::handleClipboardChange(int source, void *data)
  289. {
  290. Viewport *self = (Viewport *)data;
  291. assert(self);
  292. if (!sendClipboard)
  293. return;
  294. #if !defined(WIN32) && !defined(__APPLE__)
  295. if (!sendPrimary && (source == 0))
  296. return;
  297. #endif
  298. Fl::paste(*self, source);
  299. }
  300. void Viewport::handlePointerEvent(const rfb::Point& pos, int buttonMask)
  301. {
  302. if (!viewOnly) {
  303. if (pointerEventInterval == 0 || buttonMask != lastButtonMask) {
  304. try {
  305. cc->writer()->pointerEvent(pos, buttonMask);
  306. } catch (rdr::Exception& e) {
  307. vlog.error("%s", e.str());
  308. exit_vncviewer(e.str());
  309. }
  310. } else {
  311. if (!Fl::has_timeout(handlePointerTimeout, this))
  312. Fl::add_timeout((double)pointerEventInterval/1000.0,
  313. handlePointerTimeout, this);
  314. }
  315. lastPointerPos = pos;
  316. lastButtonMask = buttonMask;
  317. }
  318. }
  319. void Viewport::handlePointerTimeout(void *data)
  320. {
  321. Viewport *self = (Viewport *)data;
  322. assert(self);
  323. try {
  324. self->cc->writer()->pointerEvent(self->lastPointerPos, self->lastButtonMask);
  325. } catch (rdr::Exception& e) {
  326. vlog.error("%s", e.str());
  327. exit_vncviewer(e.str());
  328. }
  329. }
  330. void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym)
  331. {
  332. static bool menuRecursion = false;
  333. // Prevent recursion if the menu wants to send its own
  334. // activation key.
  335. if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) {
  336. menuRecursion = true;
  337. popupContextMenu();
  338. menuRecursion = false;
  339. return;
  340. }
  341. if (viewOnly)
  342. return;
  343. #ifdef __APPLE__
  344. // Alt on OS X behaves more like AltGr on other systems, and to get
  345. // sane behaviour we should translate things in that manner for the
  346. // remote VNC server. However that means we lose the ability to use
  347. // Alt as a shortcut modifier. Do what RealVNC does and hijack the
  348. // left command key as an Alt replacement.
  349. switch (keySym) {
  350. case XK_Super_L:
  351. keySym = XK_Alt_L;
  352. break;
  353. case XK_Super_R:
  354. keySym = XK_Super_L;
  355. break;
  356. case XK_Alt_L:
  357. keySym = XK_Mode_switch;
  358. break;
  359. case XK_Alt_R:
  360. keySym = XK_ISO_Level3_Shift;
  361. break;
  362. }
  363. #endif
  364. #ifdef WIN32
  365. // Ugly hack alert!
  366. //
  367. // Windows doesn't have a proper AltGr, but handles it using fake
  368. // Ctrl+Alt. Unfortunately X11 doesn't generally like the combination
  369. // Ctrl+Alt+AltGr, which we usually end up with when Xvnc tries to
  370. // get everything in the correct state. Cheat and temporarily release
  371. // Ctrl and Alt when we send some other symbol.
  372. bool ctrlPressed, altPressed;
  373. DownMap::iterator iter;
  374. ctrlPressed = false;
  375. altPressed = false;
  376. for (iter = downKeySym.begin();iter != downKeySym.end();++iter) {
  377. if (iter->second == XK_Control_L)
  378. ctrlPressed = true;
  379. else if (iter->second == XK_Alt_R)
  380. altPressed = true;
  381. }
  382. if (ctrlPressed && altPressed) {
  383. vlog.debug("Faking release of AltGr (Ctrl_L+Alt_R)");
  384. try {
  385. cc->writer()->keyEvent(XK_Control_L, false);
  386. cc->writer()->keyEvent(XK_Alt_R, false);
  387. } catch (rdr::Exception& e) {
  388. vlog.error("%s", e.str());
  389. exit_vncviewer(e.str());
  390. }
  391. }
  392. #endif
  393. // Because of the way keyboards work, we cannot expect to have the same
  394. // symbol on release as when pressed. This breaks the VNC protocol however,
  395. // so we need to keep track of what keysym a key _code_ generated on press
  396. // and send the same on release.
  397. downKeySym[keyCode] = keySym;
  398. #if defined(WIN32) || defined(__APPLE__)
  399. vlog.debug("Key pressed: 0x%04x => 0x%04x", keyCode, keySym);
  400. #else
  401. vlog.debug("Key pressed: 0x%04x => XK_%s (0x%04x)",
  402. keyCode, XKeysymToString(keySym), keySym);
  403. #endif
  404. try {
  405. cc->writer()->keyEvent(keySym, true);
  406. } catch (rdr::Exception& e) {
  407. vlog.error("%s", e.str());
  408. exit_vncviewer(e.str());
  409. }
  410. #ifdef WIN32
  411. // Ugly hack continued...
  412. if (ctrlPressed && altPressed) {
  413. vlog.debug("Restoring AltGr state");
  414. try {
  415. cc->writer()->keyEvent(XK_Control_L, true);
  416. cc->writer()->keyEvent(XK_Alt_R, true);
  417. } catch (rdr::Exception& e) {
  418. vlog.error("%s", e.str());
  419. exit_vncviewer(e.str());
  420. }
  421. }
  422. #endif
  423. }
  424. void Viewport::handleKeyRelease(int keyCode)
  425. {
  426. DownMap::iterator iter;
  427. if (viewOnly)
  428. return;
  429. iter = downKeySym.find(keyCode);
  430. if (iter == downKeySym.end()) {
  431. // These occur somewhat frequently so let's not spam them unless
  432. // logging is turned up.
  433. vlog.debug("Unexpected release of key code %d", keyCode);
  434. return;
  435. }
  436. #if defined(WIN32) || defined(__APPLE__)
  437. vlog.debug("Key released: 0x%04x => 0x%04x", keyCode, iter->second);
  438. #else
  439. vlog.debug("Key released: 0x%04x => XK_%s (0x%04x)",
  440. keyCode, XKeysymToString(iter->second), iter->second);
  441. #endif
  442. try {
  443. cc->writer()->keyEvent(iter->second, false);
  444. } catch (rdr::Exception& e) {
  445. vlog.error("%s", e.str());
  446. exit_vncviewer(e.str());
  447. }
  448. downKeySym.erase(iter);
  449. }
  450. int Viewport::handleSystemEvent(void *event, void *data)
  451. {
  452. Viewport *self = (Viewport *)data;
  453. Fl_Widget *focus;
  454. assert(self);
  455. focus = Fl::grab();
  456. if (!focus)
  457. focus = Fl::focus();
  458. if (!focus)
  459. return 0;
  460. if (focus != self)
  461. return 0;
  462. assert(event);
  463. #if defined(WIN32)
  464. MSG *msg = (MSG*)event;
  465. if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN)) {
  466. UINT vKey;
  467. bool isExtended;
  468. int keyCode;
  469. rdr::U32 keySym;
  470. vKey = msg->wParam;
  471. isExtended = (msg->lParam & (1 << 24)) != 0;
  472. keyCode = ((msg->lParam >> 16) & 0xff);
  473. // Windows sets the scan code to 0x00 for multimedia keys, so we
  474. // have to do a reverse lookup based on the vKey.
  475. if (keyCode == 0x00) {
  476. keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
  477. if (keyCode == 0x00) {
  478. if (isExtended)
  479. vlog.error(_("No scan code for extended virtual key 0x%02x"), (int)vKey);
  480. else
  481. vlog.error(_("No scan code for virtual key 0x%02x"), (int)vKey);
  482. return 1;
  483. }
  484. }
  485. if (isExtended)
  486. keyCode |= 0x100;
  487. // VK_SNAPSHOT sends different scan codes depending on the state of
  488. // Alt. This means that we can get different scan codes on press and
  489. // release. Force it to be something standard.
  490. if (vKey == VK_SNAPSHOT)
  491. keyCode = 0x137;
  492. keySym = win32_vkey_to_keysym(vKey, isExtended);
  493. if (keySym == NoSymbol) {
  494. if (isExtended)
  495. vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey);
  496. else
  497. vlog.error(_("No symbol for virtual key 0x%02x"), (int)vKey);
  498. return 1;
  499. }
  500. self->handleKeyPress(keyCode, keySym);
  501. return 1;
  502. } else if ((msg->message == WM_KEYUP) || (msg->message == WM_SYSKEYUP)) {
  503. UINT vKey;
  504. bool isExtended;
  505. int keyCode;
  506. vKey = msg->wParam;
  507. isExtended = (msg->lParam & (1 << 24)) != 0;
  508. keyCode = ((msg->lParam >> 16) & 0xff);
  509. if (keyCode == 0x00)
  510. keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
  511. if (isExtended)
  512. keyCode |= 0x100;
  513. if (vKey == VK_SNAPSHOT)
  514. keyCode = 0x137;
  515. self->handleKeyRelease(keyCode);
  516. return 1;
  517. }
  518. #elif defined(__APPLE__)
  519. if (cocoa_is_keyboard_event(event)) {
  520. int keyCode;
  521. keyCode = cocoa_event_keycode(event);
  522. if (cocoa_is_key_press(event)) {
  523. rdr::U32 keySym;
  524. keySym = cocoa_event_keysym(event);
  525. if (keySym == NoSymbol) {
  526. vlog.error(_("No symbol for key code 0x%02x (in the current state)"),
  527. (int)keyCode);
  528. return 1;
  529. }
  530. self->handleKeyPress(keyCode, keySym);
  531. // We don't get any release events for CapsLock, so we have to
  532. // send the release right away.
  533. if (keySym == XK_Caps_Lock)
  534. self->handleKeyRelease(keyCode);
  535. } else {
  536. self->handleKeyRelease(keyCode);
  537. }
  538. return 1;
  539. }
  540. #else
  541. XEvent *xevent = (XEvent*)event;
  542. if (xevent->type == KeyPress) {
  543. char str;
  544. KeySym keysym;
  545. XLookupString(&xevent->xkey, &str, 1, &keysym, NULL);
  546. if (keysym == NoSymbol) {
  547. vlog.error(_("No symbol for key code %d (in the current state)"),
  548. (int)xevent->xkey.keycode);
  549. return 1;
  550. }
  551. switch (keysym) {
  552. // For the first few years, there wasn't a good consensus on what the
  553. // Windows keys should be mapped to for X11. So we need to help out a
  554. // bit and map all variants to the same key...
  555. case XK_Hyper_L:
  556. keysym = XK_Super_L;
  557. break;
  558. case XK_Hyper_R:
  559. keysym = XK_Super_R;
  560. break;
  561. // There has been several variants for Shift-Tab over the years.
  562. // RFB states that we should always send a normal tab.
  563. case XK_ISO_Left_Tab:
  564. keysym = XK_Tab;
  565. break;
  566. }
  567. self->handleKeyPress(xevent->xkey.keycode, keysym);
  568. return 1;
  569. } else if (xevent->type == KeyRelease) {
  570. self->handleKeyRelease(xevent->xkey.keycode);
  571. return 1;
  572. }
  573. #endif
  574. return 0;
  575. }
  576. void Viewport::initContextMenu()
  577. {
  578. contextMenu->clear();
  579. fltk_menu_add(contextMenu, p_("ContextMenu|", "E&xit viewer"),
  580. 0, NULL, (void*)ID_EXIT, FL_MENU_DIVIDER);
  581. fltk_menu_add(contextMenu, p_("ContextMenu|", "&Full screen"),
  582. 0, NULL, (void*)ID_FULLSCREEN,
  583. FL_MENU_TOGGLE | (window()->fullscreen_active()?FL_MENU_VALUE:0));
  584. fltk_menu_add(contextMenu, p_("ContextMenu|", "Minimi&ze"),
  585. 0, NULL, (void*)ID_MINIMIZE, 0);
  586. fltk_menu_add(contextMenu, p_("ContextMenu|", "Resize &window to session"),
  587. 0, NULL, (void*)ID_RESIZE,
  588. (window()->fullscreen_active()?FL_MENU_INACTIVE:0) |
  589. FL_MENU_DIVIDER);
  590. fltk_menu_add(contextMenu, p_("ContextMenu|", "&Ctrl"),
  591. 0, NULL, (void*)ID_CTRL,
  592. FL_MENU_TOGGLE | (menuCtrlKey?FL_MENU_VALUE:0));
  593. fltk_menu_add(contextMenu, p_("ContextMenu|", "&Alt"),
  594. 0, NULL, (void*)ID_ALT,
  595. FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0));
  596. if (menuKeySym) {
  597. char sendMenuKey[64];
  598. snprintf(sendMenuKey, 64, p_("ContextMenu|", "Send %s"), (const char *)menuKey);
  599. fltk_menu_add(contextMenu, sendMenuKey, 0, NULL, (void*)ID_MENUKEY, 0);
  600. fltk_menu_add(contextMenu, "Secret shortcut menu key", menuKeyCode, NULL,
  601. (void*)ID_MENUKEY, FL_MENU_INVISIBLE);
  602. }
  603. fltk_menu_add(contextMenu, p_("ContextMenu|", "Send Ctrl-Alt-&Del"),
  604. 0, NULL, (void*)ID_CTRLALTDEL, FL_MENU_DIVIDER);
  605. fltk_menu_add(contextMenu, p_("ContextMenu|", "&Refresh screen"),
  606. 0, NULL, (void*)ID_REFRESH, FL_MENU_DIVIDER);
  607. fltk_menu_add(contextMenu, p_("ContextMenu|", "&Options..."),
  608. 0, NULL, (void*)ID_OPTIONS, 0);
  609. fltk_menu_add(contextMenu, p_("ContextMenu|", "Connection &info..."),
  610. 0, NULL, (void*)ID_INFO, 0);
  611. fltk_menu_add(contextMenu, p_("ContextMenu|", "About &TigerVNC viewer..."),
  612. 0, NULL, (void*)ID_ABOUT, FL_MENU_DIVIDER);
  613. fltk_menu_add(contextMenu, p_("ContextMenu|", "Dismiss &menu"),
  614. 0, NULL, (void*)ID_DISMISS, 0);
  615. }
  616. void Viewport::popupContextMenu()
  617. {
  618. const Fl_Menu_Item *m;
  619. char buffer[1024];
  620. // Make sure the menu is reset to its initial state between goes or
  621. // it will start up highlighting the previously selected entry.
  622. contextMenu->value(-1);
  623. // initialize context menu before display
  624. initContextMenu();
  625. // Unfortunately FLTK doesn't reliably restore the mouse pointer for
  626. // menus, so we have to help it out.
  627. if (Fl::belowmouse() == this)
  628. window()->cursor(FL_CURSOR_DEFAULT);
  629. m = contextMenu->popup();
  630. // Back to our proper mouse pointer.
  631. if ((Fl::belowmouse() == this) && cursor)
  632. window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
  633. if (m == NULL)
  634. return;
  635. switch (m->argument()) {
  636. case ID_EXIT:
  637. exit_vncviewer();
  638. break;
  639. case ID_FULLSCREEN:
  640. if (window()->fullscreen_active())
  641. window()->fullscreen_off();
  642. else
  643. ((DesktopWindow*)window())->fullscreen_on();
  644. break;
  645. case ID_MINIMIZE:
  646. window()->iconize();
  647. break;
  648. case ID_RESIZE:
  649. if (window()->fullscreen_active())
  650. break;
  651. window()->size(w(), h());
  652. break;
  653. case ID_CTRL:
  654. if (m->value())
  655. handleKeyPress(fakeKeyBase + 0, XK_Control_L);
  656. else
  657. handleKeyRelease(fakeKeyBase + 0);
  658. menuCtrlKey = !menuCtrlKey;
  659. break;
  660. case ID_ALT:
  661. if (m->value())
  662. handleKeyPress(fakeKeyBase + 1, XK_Alt_L);
  663. else
  664. handleKeyRelease(fakeKeyBase + 1);
  665. menuAltKey = !menuAltKey;
  666. break;
  667. case ID_MENUKEY:
  668. handleKeyPress(fakeKeyBase + 2, menuKeySym);
  669. handleKeyRelease(fakeKeyBase + 2);
  670. break;
  671. case ID_CTRLALTDEL:
  672. handleKeyPress(fakeKeyBase + 3, XK_Control_L);
  673. handleKeyPress(fakeKeyBase + 4, XK_Alt_L);
  674. handleKeyPress(fakeKeyBase + 5, XK_Delete);
  675. handleKeyRelease(fakeKeyBase + 5);
  676. handleKeyRelease(fakeKeyBase + 4);
  677. handleKeyRelease(fakeKeyBase + 3);
  678. break;
  679. case ID_REFRESH:
  680. cc->refreshFramebuffer();
  681. break;
  682. case ID_OPTIONS:
  683. OptionsDialog::showDialog();
  684. break;
  685. case ID_INFO:
  686. if (fltk_escape(cc->connectionInfo(), buffer, sizeof(buffer)) < sizeof(buffer)) {
  687. fl_message_title(_("VNC connection info"));
  688. fl_message("%s", buffer);
  689. }
  690. break;
  691. case ID_ABOUT:
  692. about_vncviewer();
  693. break;
  694. case ID_DISMISS:
  695. // Don't need to do anything
  696. break;
  697. }
  698. }
  699. void Viewport::setMenuKey()
  700. {
  701. getMenuKey(&menuKeyCode, &menuKeySym);
  702. }
  703. void Viewport::handleOptions(void *data)
  704. {
  705. Viewport *self = (Viewport*)data;
  706. self->setMenuKey();
  707. }