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 24KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  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. // FLTK can pull in the X11 headers on some systems
  28. #ifndef XK_VoidSymbol
  29. #define XK_MISCELLANY
  30. #define XK_XKB_KEYS
  31. #include <rfb/keysymdef.h>
  32. #endif
  33. #ifndef XF86XK_ModeLock
  34. #include <rfb/XF86keysym.h>
  35. #endif
  36. #ifndef NoSymbol
  37. #define NoSymbol 0
  38. #endif
  39. #include "Viewport.h"
  40. #include "CConn.h"
  41. #include "OptionsDialog.h"
  42. #include "DesktopWindow.h"
  43. #include "i18n.h"
  44. #include "fltk_layout.h"
  45. #include "parameters.h"
  46. #include "keysym2ucs.h"
  47. #include "menukey.h"
  48. #include "vncviewer.h"
  49. #include "PlatformPixelBuffer.h"
  50. #include "FLTKPixelBuffer.h"
  51. #if defined(WIN32)
  52. #include "Win32PixelBuffer.h"
  53. #elif defined(__APPLE__)
  54. #include "OSXPixelBuffer.h"
  55. #else
  56. #include "X11PixelBuffer.h"
  57. #endif
  58. #include <FL/fl_draw.H>
  59. #include <FL/fl_ask.H>
  60. #include <FL/Fl_Menu.H>
  61. #include <FL/Fl_Menu_Button.H>
  62. #ifdef WIN32
  63. #include "win32.h"
  64. #endif
  65. using namespace rfb;
  66. using namespace rdr;
  67. static rfb::LogWriter vlog("Viewport");
  68. // Menu constants
  69. enum { ID_EXIT, ID_FULLSCREEN, ID_RESIZE,
  70. ID_CTRL, ID_ALT, ID_MENUKEY, ID_CTRLALTDEL,
  71. ID_REFRESH, ID_OPTIONS, ID_INFO, ID_ABOUT, ID_DISMISS };
  72. // Fake key presses use this value and above
  73. static const int fakeKeyBase = 0x200;
  74. Viewport::Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_)
  75. : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(NULL),
  76. lastPointerPos(0, 0), lastButtonMask(0),
  77. cursor(NULL), menuCtrlKey(false), menuAltKey(false)
  78. {
  79. // FLTK STR #2636 gives us the ability to monitor clipboard changes
  80. #ifdef HAVE_FLTK_CLIPBOARD
  81. Fl::add_clipboard_notify(handleClipboardChange, this);
  82. #endif
  83. frameBuffer = createFramebuffer(w, h);
  84. assert(frameBuffer);
  85. contextMenu = new Fl_Menu_Button(0, 0, 0, 0);
  86. // Setting box type to FL_NO_BOX prevents it from trying to draw the
  87. // button component (which we don't want)
  88. contextMenu->box(FL_NO_BOX);
  89. // The (invisible) button associated with this widget can mess with
  90. // things like Fl_Scroll so we need to get rid of any parents.
  91. // Unfortunately that's not possible because of STR #2654, but
  92. // reparenting to the current window works for most cases.
  93. window()->add(contextMenu);
  94. setMenuKey();
  95. OptionsDialog::addCallback(handleOptions, this);
  96. // Send a fake pointer event so that the server will stop rendering
  97. // a server-side cursor. Ideally we'd like to send the actual pointer
  98. // position, but we can't really tell when the window manager is done
  99. // placing us so we don't have a good time for that.
  100. handlePointerEvent(Point(w/2, h/2), 0);
  101. }
  102. Viewport::~Viewport()
  103. {
  104. // Unregister all timeouts in case they get a change tro trigger
  105. // again later when this object is already gone.
  106. Fl::remove_timeout(handlePointerTimeout, this);
  107. #ifdef HAVE_FLTK_CLIPBOARD
  108. Fl::remove_clipboard_notify(handleClipboardChange);
  109. #endif
  110. OptionsDialog::removeCallback(handleOptions);
  111. delete frameBuffer;
  112. if (cursor) {
  113. if (!cursor->alloc_array)
  114. delete [] cursor->array;
  115. delete cursor;
  116. }
  117. // FLTK automatically deletes all child widgets, so we shouldn't touch
  118. // them ourselves here
  119. }
  120. const rfb::PixelFormat &Viewport::getPreferredPF()
  121. {
  122. return frameBuffer->getPF();
  123. }
  124. // Copy the areas of the framebuffer that have been changed (damaged)
  125. // to the displayed window.
  126. // FIXME: Make sure this gets called on slow updates
  127. void Viewport::updateWindow()
  128. {
  129. Rect r;
  130. r = frameBuffer->getDamage();
  131. damage(FL_DAMAGE_USER1, r.tl.x + x(), r.tl.y + y(), r.width(), r.height());
  132. }
  133. rfb::ModifiablePixelBuffer* Viewport::getFramebuffer(void)
  134. {
  135. return frameBuffer;
  136. }
  137. #ifdef HAVE_FLTK_CURSOR
  138. static const char * dotcursor_xpm[] = {
  139. "5 5 2 1",
  140. ". c #000000",
  141. " c #FFFFFF",
  142. " ",
  143. " ... ",
  144. " ... ",
  145. " ... ",
  146. " "};
  147. #endif
  148. void Viewport::setCursor(int width, int height, const Point& hotspot,
  149. void* data, void* mask)
  150. {
  151. #ifdef HAVE_FLTK_CURSOR
  152. if (cursor) {
  153. if (!cursor->alloc_array)
  154. delete [] cursor->array;
  155. delete cursor;
  156. }
  157. int mask_len = ((width+7)/8) * height;
  158. int i;
  159. for (i = 0; i < mask_len; i++)
  160. if (((rdr::U8*)mask)[i]) break;
  161. if ((i == mask_len) && dotWhenNoCursor) {
  162. vlog.debug("cursor is empty - using dot");
  163. Fl_Pixmap pxm(dotcursor_xpm);
  164. cursor = new Fl_RGB_Image(&pxm);
  165. cursorHotspot.x = cursorHotspot.y = 2;
  166. } else {
  167. if ((width == 0) || (height == 0)) {
  168. U8 *buffer = new U8[4];
  169. memset(buffer, 0, 4);
  170. cursor = new Fl_RGB_Image(buffer, 1, 1, 4);
  171. cursorHotspot.x = cursorHotspot.y = 0;
  172. } else {
  173. U8 *buffer = new U8[width*height*4];
  174. U8 *i, *o, *m;
  175. int m_width;
  176. const PixelFormat *pf;
  177. pf = &cc->cp.pf();
  178. i = (U8*)data;
  179. o = buffer;
  180. m = (U8*)mask;
  181. m_width = (width+7)/8;
  182. for (int y = 0;y < height;y++) {
  183. for (int x = 0;x < width;x++) {
  184. pf->rgbFromBuffer(o, i, 1);
  185. if (m[(m_width*y)+(x/8)] & 0x80>>(x%8))
  186. o[3] = 255;
  187. else
  188. o[3] = 0;
  189. o += 4;
  190. i += pf->bpp/8;
  191. }
  192. }
  193. cursor = new Fl_RGB_Image(buffer, width, height, 4);
  194. cursorHotspot = hotspot;
  195. }
  196. }
  197. if (Fl::belowmouse() == this)
  198. window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
  199. #endif
  200. }
  201. void Viewport::draw()
  202. {
  203. int X, Y, W, H;
  204. // Check what actually needs updating
  205. fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
  206. if ((W == 0) || (H == 0))
  207. return;
  208. frameBuffer->draw(X - x(), Y - y(), X, Y, W, H);
  209. }
  210. void Viewport::resize(int x, int y, int w, int h)
  211. {
  212. PlatformPixelBuffer* newBuffer;
  213. rfb::Rect rect;
  214. const rdr::U8* data;
  215. int stride;
  216. // FIXME: Resize should probably be a feature of the pixel buffer itself
  217. if ((w == frameBuffer->width()) && (h == frameBuffer->height()))
  218. goto end;
  219. vlog.debug("Resizing framebuffer from %dx%d to %dx%d",
  220. frameBuffer->width(), frameBuffer->height(), w, h);
  221. newBuffer = createFramebuffer(w, h);
  222. assert(newBuffer);
  223. rect.setXYWH(0, 0,
  224. __rfbmin(newBuffer->width(), frameBuffer->width()),
  225. __rfbmin(newBuffer->height(), frameBuffer->height()));
  226. data = frameBuffer->getBuffer(frameBuffer->getRect(), &stride);
  227. newBuffer->imageRect(rect, data, stride);
  228. // Black out any new areas
  229. if (newBuffer->width() > frameBuffer->width()) {
  230. rect.setXYWH(frameBuffer->width(), 0,
  231. newBuffer->width() - frameBuffer->width(),
  232. newBuffer->height());
  233. newBuffer->fillRect(rect, 0);
  234. }
  235. if (newBuffer->height() > frameBuffer->height()) {
  236. rect.setXYWH(0, frameBuffer->height(),
  237. newBuffer->width(),
  238. newBuffer->height() - frameBuffer->height());
  239. newBuffer->fillRect(rect, 0);
  240. }
  241. delete frameBuffer;
  242. frameBuffer = newBuffer;
  243. end:
  244. Fl_Widget::resize(x, y, w, h);
  245. }
  246. int Viewport::handle(int event)
  247. {
  248. char *buffer;
  249. int ret;
  250. int buttonMask, wheelMask;
  251. DownMap::const_iterator iter;
  252. switch (event) {
  253. case FL_PASTE:
  254. buffer = new char[Fl::event_length() + 1];
  255. // This is documented as to ASCII, but actually does to 8859-1
  256. ret = fl_utf8toa(Fl::event_text(), Fl::event_length(), buffer,
  257. Fl::event_length() + 1);
  258. assert(ret < (Fl::event_length() + 1));
  259. vlog.debug("Sending clipboard data (%d bytes)", strlen(buffer));
  260. try {
  261. cc->writer()->clientCutText(buffer, ret);
  262. } catch (rdr::Exception& e) {
  263. vlog.error("%s", e.str());
  264. exit_vncviewer(e.str());
  265. }
  266. delete [] buffer;
  267. return 1;
  268. case FL_ENTER:
  269. // Yes, we would like some pointer events please!
  270. #ifdef HAVE_FLTK_CURSOR
  271. if (cursor)
  272. window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
  273. #endif
  274. return 1;
  275. case FL_LEAVE:
  276. #ifdef HAVE_FLTK_CURSOR
  277. window()->cursor(FL_CURSOR_DEFAULT);
  278. #endif
  279. // Fall through as we want a last move event to help trigger edge stuff
  280. case FL_PUSH:
  281. case FL_RELEASE:
  282. case FL_DRAG:
  283. case FL_MOVE:
  284. case FL_MOUSEWHEEL:
  285. buttonMask = 0;
  286. if (Fl::event_button1())
  287. buttonMask |= 1;
  288. if (Fl::event_button2())
  289. buttonMask |= 2;
  290. if (Fl::event_button3())
  291. buttonMask |= 4;
  292. if (event == FL_MOUSEWHEEL) {
  293. wheelMask = 0;
  294. if (Fl::event_dy() < 0)
  295. wheelMask |= 8;
  296. if (Fl::event_dy() > 0)
  297. wheelMask |= 16;
  298. if (Fl::event_dx() < 0)
  299. wheelMask |= 32;
  300. if (Fl::event_dx() > 0)
  301. wheelMask |= 64;
  302. // A quick press of the wheel "button", followed by a immediate
  303. // release below
  304. handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()),
  305. buttonMask | wheelMask);
  306. }
  307. handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), buttonMask);
  308. return 1;
  309. case FL_FOCUS:
  310. // Yes, we would like some focus please!
  311. return 1;
  312. case FL_UNFOCUS:
  313. // Release all keys that were pressed as that generally makes most
  314. // sense (e.g. Alt+Tab where we only see the Alt press)
  315. while (!downKeySym.empty())
  316. handleKeyRelease(downKeySym.begin()->first);
  317. return 1;
  318. case FL_KEYDOWN:
  319. handleFLTKKeyPress();
  320. return 1;
  321. case FL_KEYUP:
  322. handleKeyRelease(Fl::event_original_key());
  323. return 1;
  324. }
  325. return Fl_Widget::handle(event);
  326. }
  327. PlatformPixelBuffer* Viewport::createFramebuffer(int w, int h)
  328. {
  329. PlatformPixelBuffer *fb;
  330. try {
  331. #if defined(WIN32)
  332. fb = new Win32PixelBuffer(w, h);
  333. #elif defined(__APPLE__)
  334. fb = new OSXPixelBuffer(w, h);
  335. #else
  336. fb = new X11PixelBuffer(w, h);
  337. #endif
  338. } catch (rdr::Exception& e) {
  339. fb = new FLTKPixelBuffer(w, h);
  340. }
  341. return fb;
  342. }
  343. void Viewport::handleClipboardChange(int source, void *data)
  344. {
  345. Viewport *self = (Viewport *)data;
  346. assert(self);
  347. if (!sendPrimary && (source == 0))
  348. return;
  349. Fl::paste(*self, source);
  350. }
  351. void Viewport::handlePointerEvent(const rfb::Point& pos, int buttonMask)
  352. {
  353. if (!viewOnly) {
  354. if (pointerEventInterval == 0 || buttonMask != lastButtonMask) {
  355. try {
  356. cc->writer()->pointerEvent(pos, buttonMask);
  357. } catch (rdr::Exception& e) {
  358. vlog.error("%s", e.str());
  359. exit_vncviewer(e.str());
  360. }
  361. } else {
  362. if (!Fl::has_timeout(handlePointerTimeout, this))
  363. Fl::add_timeout((double)pointerEventInterval/1000.0,
  364. handlePointerTimeout, this);
  365. }
  366. lastPointerPos = pos;
  367. lastButtonMask = buttonMask;
  368. }
  369. }
  370. void Viewport::handlePointerTimeout(void *data)
  371. {
  372. Viewport *self = (Viewport *)data;
  373. assert(self);
  374. try {
  375. self->cc->writer()->pointerEvent(self->lastPointerPos, self->lastButtonMask);
  376. } catch (rdr::Exception& e) {
  377. vlog.error("%s", e.str());
  378. exit_vncviewer(e.str());
  379. }
  380. }
  381. void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym)
  382. {
  383. static bool menuRecursion = false;
  384. // Prevent recursion if the menu wants to send its own
  385. // activation key.
  386. if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) {
  387. menuRecursion = true;
  388. popupContextMenu();
  389. menuRecursion = false;
  390. return;
  391. }
  392. if (viewOnly)
  393. return;
  394. #ifdef __APPLE__
  395. // Alt on OS X behaves more like AltGr on other systems, and to get
  396. // sane behaviour we should translate things in that manner for the
  397. // remote VNC server. However that means we lose the ability to use
  398. // Alt as a shortcut modifier. Do what RealVNC does and hijack the
  399. // left command key as an Alt replacement.
  400. switch (keySym) {
  401. case XK_Super_L:
  402. keySym = XK_Alt_L;
  403. break;
  404. case XK_Super_R:
  405. keySym = XK_Super_L;
  406. break;
  407. case XK_Alt_L:
  408. case XK_Alt_R:
  409. keySym = XK_ISO_Level3_Shift;
  410. break;
  411. }
  412. #endif
  413. #ifdef WIN32
  414. // Ugly hack alert!
  415. //
  416. // Windows doesn't have a proper AltGr, but handles it using fake
  417. // Ctrl+Alt. Unfortunately X11 doesn't generally like the combination
  418. // Ctrl+Alt+AltGr, which we usually end up with when Xvnc tries to
  419. // get everything in the correct state. Cheat and temporarily release
  420. // Ctrl and Alt when we send some other symbol.
  421. bool ctrlPressed, altPressed;
  422. DownMap::iterator iter;
  423. ctrlPressed = false;
  424. altPressed = false;
  425. for (iter = downKeySym.begin();iter != downKeySym.end();++iter) {
  426. if (iter->second == XK_Control_L)
  427. ctrlPressed = true;
  428. else if (iter->second == XK_Alt_R)
  429. altPressed = true;
  430. }
  431. if (ctrlPressed && altPressed) {
  432. vlog.debug("Faking release of AltGr (Ctrl_L+Alt_R)");
  433. try {
  434. cc->writer()->keyEvent(XK_Control_L, false);
  435. cc->writer()->keyEvent(XK_Alt_R, false);
  436. } catch (rdr::Exception& e) {
  437. vlog.error("%s", e.str());
  438. exit_vncviewer(e.str());
  439. }
  440. }
  441. #endif
  442. // Because of the way keyboards work, we cannot expect to have the same
  443. // symbol on release as when pressed. This breaks the VNC protocol however,
  444. // so we need to keep track of what keysym a key _code_ generated on press
  445. // and send the same on release.
  446. downKeySym[keyCode] = keySym;
  447. #if defined(WIN32) || defined(__APPLE__)
  448. vlog.debug("Key pressed: 0x%04x => 0x%04x", keyCode, keySym);
  449. #else
  450. vlog.debug("Key pressed: 0x%04x => XK_%s (0x%04x)",
  451. keyCode, XKeysymToString(keySym), keySym);
  452. #endif
  453. try {
  454. cc->writer()->keyEvent(keySym, true);
  455. } catch (rdr::Exception& e) {
  456. vlog.error("%s", e.str());
  457. exit_vncviewer(e.str());
  458. }
  459. #ifdef WIN32
  460. // Ugly hack continued...
  461. if (ctrlPressed && altPressed) {
  462. vlog.debug("Restoring AltGr state");
  463. try {
  464. cc->writer()->keyEvent(XK_Control_L, true);
  465. cc->writer()->keyEvent(XK_Alt_R, true);
  466. } catch (rdr::Exception& e) {
  467. vlog.error("%s", e.str());
  468. exit_vncviewer(e.str());
  469. }
  470. }
  471. #endif
  472. }
  473. void Viewport::handleKeyRelease(int keyCode)
  474. {
  475. DownMap::iterator iter;
  476. if (viewOnly)
  477. return;
  478. iter = downKeySym.find(keyCode);
  479. if (iter == downKeySym.end()) {
  480. // These occur somewhat frequently so let's not spam them unless
  481. // logging is turned up.
  482. vlog.debug("Unexpected release of key code %d", keyCode);
  483. return;
  484. }
  485. #if defined(WIN32) || defined(__APPLE__)
  486. vlog.debug("Key released: 0x%04x => 0x%04x", keyCode, iter->second);
  487. #else
  488. vlog.debug("Key released: 0x%04x => XK_%s (0x%04x)",
  489. keyCode, XKeysymToString(iter->second), iter->second);
  490. #endif
  491. try {
  492. cc->writer()->keyEvent(iter->second, false);
  493. } catch (rdr::Exception& e) {
  494. vlog.error("%s", e.str());
  495. exit_vncviewer(e.str());
  496. }
  497. downKeySym.erase(iter);
  498. }
  499. rdr::U32 Viewport::translateKeyEvent(void)
  500. {
  501. unsigned ucs;
  502. int keyCode, origKeyCode;
  503. const char *keyText;
  504. int keyTextLen;
  505. keyCode = Fl::event_key();
  506. origKeyCode = Fl::event_original_key();
  507. keyText = Fl::event_text();
  508. keyTextLen = Fl::event_length();
  509. vlog.debug("FLTK key %d (%d) '%s'[%d]", origKeyCode, keyCode, keyText, keyTextLen);
  510. // First check for function keys
  511. if ((keyCode > FL_F) && (keyCode <= FL_F_Last))
  512. return XK_F1 + (keyCode - FL_F - 1);
  513. // Numpad numbers
  514. if ((keyCode >= (FL_KP + '0')) && (keyCode <= (FL_KP + '9')))
  515. return XK_KP_0 + (keyCode - (FL_KP + '0'));
  516. // FLTK does some special remapping of numpad keys when numlock is off
  517. if ((origKeyCode >= FL_KP) && (origKeyCode <= FL_KP_Last)) {
  518. switch (keyCode) {
  519. case FL_F+1:
  520. return XK_KP_F1;
  521. case FL_F+2:
  522. return XK_KP_F2;
  523. case FL_F+3:
  524. return XK_KP_F3;
  525. case FL_F+4:
  526. return XK_KP_F4;
  527. case FL_Home:
  528. return XK_KP_Home;
  529. case FL_Left:
  530. return XK_KP_Left;
  531. case FL_Up:
  532. return XK_KP_Up;
  533. case FL_Right:
  534. return XK_KP_Right;
  535. case FL_Down:
  536. return XK_KP_Down;
  537. case FL_Page_Up:
  538. return XK_KP_Page_Up;
  539. case FL_Page_Down:
  540. return XK_KP_Page_Down;
  541. case FL_End:
  542. return XK_KP_End;
  543. case FL_Insert:
  544. return XK_KP_Insert;
  545. case FL_Delete:
  546. return XK_KP_Delete;
  547. }
  548. }
  549. #if defined(WIN32) || defined(__APPLE__)
  550. // X11 fairly consistently uses XK_KP_Separator for comma and
  551. // XK_KP_Decimal for period. Windows and OS X are a different matter
  552. // though.
  553. //
  554. // OS X will consistently generate the same key code no matter what
  555. // layout is being used.
  556. //
  557. // Windows is terribly inconcistent, and is not something that's
  558. // likely to change:
  559. // http://blogs.msdn.com/michkap/archive/2006/09/13/752377.aspx
  560. //
  561. // To get X11 behaviour, we instead look at the text generated by
  562. // they key.
  563. if ((keyCode == (FL_KP + ',')) || (keyCode == (FL_KP + '.'))) {
  564. switch (keyText[0]) {
  565. case ',':
  566. return XK_KP_Separator;
  567. case '.':
  568. return XK_KP_Decimal;
  569. default:
  570. vlog.error(_("Unknown decimal separator: '%s'"), keyText);
  571. return XK_KP_Decimal;
  572. }
  573. }
  574. #endif
  575. // Then other special keys
  576. switch (keyCode) {
  577. case FL_BackSpace:
  578. return XK_BackSpace;
  579. case FL_Tab:
  580. return XK_Tab;
  581. case FL_Enter:
  582. return XK_Return;
  583. case FL_Pause:
  584. return XK_Pause;
  585. case FL_Scroll_Lock:
  586. return XK_Scroll_Lock;
  587. case FL_Escape:
  588. return XK_Escape;
  589. case FL_Home:
  590. return XK_Home;
  591. case FL_Left:
  592. return XK_Left;
  593. case FL_Up:
  594. return XK_Up;
  595. case FL_Right:
  596. return XK_Right;
  597. case FL_Down:
  598. return XK_Down;
  599. case FL_Page_Up:
  600. return XK_Page_Up;
  601. case FL_Page_Down:
  602. return XK_Page_Down;
  603. case FL_End:
  604. return XK_End;
  605. case FL_Print:
  606. return XK_Print;
  607. case FL_Insert:
  608. return XK_Insert;
  609. case FL_Menu:
  610. return XK_Menu;
  611. case FL_Help:
  612. return XK_Help;
  613. case FL_Num_Lock:
  614. return XK_Num_Lock;
  615. case FL_Shift_L:
  616. return XK_Shift_L;
  617. case FL_Shift_R:
  618. return XK_Shift_R;
  619. case FL_Control_L:
  620. return XK_Control_L;
  621. case FL_Control_R:
  622. return XK_Control_R;
  623. case FL_Caps_Lock:
  624. return XK_Caps_Lock;
  625. case FL_Meta_L:
  626. return XK_Super_L;
  627. case FL_Meta_R:
  628. return XK_Super_R;
  629. case FL_Alt_L:
  630. return XK_Alt_L;
  631. case FL_Alt_R:
  632. return XK_Alt_R;
  633. case FL_Delete:
  634. return XK_Delete;
  635. case FL_KP_Enter:
  636. return XK_KP_Enter;
  637. case FL_KP + '=':
  638. return XK_KP_Equal;
  639. case FL_KP + '*':
  640. return XK_KP_Multiply;
  641. case FL_KP + '+':
  642. return XK_KP_Add;
  643. case FL_KP + ',':
  644. return XK_KP_Separator;
  645. case FL_KP + '-':
  646. return XK_KP_Subtract;
  647. case FL_KP + '.':
  648. return XK_KP_Decimal;
  649. case FL_KP + '/':
  650. return XK_KP_Divide;
  651. #ifdef HAVE_FLTK_MEDIAKEYS
  652. case FL_Volume_Down:
  653. return XF86XK_AudioLowerVolume;
  654. case FL_Volume_Mute:
  655. return XF86XK_AudioMute;
  656. case FL_Volume_Up:
  657. return XF86XK_AudioRaiseVolume;
  658. case FL_Media_Play:
  659. return XF86XK_AudioPlay;
  660. case FL_Media_Stop:
  661. return XF86XK_AudioStop;
  662. case FL_Media_Prev:
  663. return XF86XK_AudioPrev;
  664. case FL_Media_Next:
  665. return XF86XK_AudioNext;
  666. case FL_Home_Page:
  667. return XF86XK_HomePage;
  668. case FL_Mail:
  669. return XF86XK_Mail;
  670. case FL_Search:
  671. return XF86XK_Search;
  672. case FL_Back:
  673. return XF86XK_Back;
  674. case FL_Forward:
  675. return XF86XK_Forward;
  676. case FL_Stop:
  677. return XF86XK_Stop;
  678. case FL_Refresh:
  679. return XF86XK_Refresh;
  680. case FL_Sleep:
  681. return XF86XK_Sleep;
  682. case FL_Favorites:
  683. return XF86XK_Favorites;
  684. #endif
  685. case XK_ISO_Level3_Shift:
  686. // FLTK tends to let this one leak through on X11...
  687. return XK_ISO_Level3_Shift;
  688. case XK_Multi_key:
  689. // Same for this...
  690. return XK_Multi_key;
  691. }
  692. // Unknown special key?
  693. if (keyText[0] == '\0') {
  694. vlog.error(_("Unknown FLTK key code %d (0x%04x)"), keyCode, keyCode);
  695. return NoSymbol;
  696. }
  697. // Look up the symbol the key produces and translate that from Unicode
  698. // to a X11 keysym.
  699. if (fl_utf_nb_char((const unsigned char*)keyText, strlen(keyText)) != 1) {
  700. vlog.error(_("Multiple characters given for key code %d (0x%04x): '%s'"),
  701. keyCode, keyCode, keyText);
  702. return NoSymbol;
  703. }
  704. ucs = fl_utf8decode(keyText, NULL, NULL);
  705. return ucs2keysym(ucs);
  706. }
  707. void Viewport::handleFLTKKeyPress(void)
  708. {
  709. rdr::U32 keySym;
  710. keySym = translateKeyEvent();
  711. if (keySym == NoSymbol)
  712. return;
  713. handleKeyPress(Fl::event_original_key(), keySym);
  714. }
  715. void Viewport::initContextMenu()
  716. {
  717. contextMenu->clear();
  718. contextMenu->add(_("Exit viewer"), 0, NULL, (void*)ID_EXIT, FL_MENU_DIVIDER);
  719. #ifdef HAVE_FLTK_FULLSCREEN
  720. contextMenu->add(_("Full screen"), 0, NULL, (void*)ID_FULLSCREEN,
  721. FL_MENU_TOGGLE | (window()->fullscreen_active()?FL_MENU_VALUE:0));
  722. #endif
  723. contextMenu->add(_("Resize window to session"), 0, NULL, (void*)ID_RESIZE,
  724. #ifdef HAVE_FLTK_FULLSCREEN
  725. (window()->fullscreen_active()?FL_MENU_INACTIVE:0) |
  726. #endif
  727. FL_MENU_DIVIDER);
  728. contextMenu->add(_("Ctrl"), 0, NULL, (void*)ID_CTRL,
  729. FL_MENU_TOGGLE | (menuCtrlKey?FL_MENU_VALUE:0));
  730. contextMenu->add(_("Alt"), 0, NULL, (void*)ID_ALT,
  731. FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0));
  732. if (menuKeySym) {
  733. char sendMenuKey[64];
  734. snprintf(sendMenuKey, 64, _("Send %s"), (const char *)menuKey);
  735. contextMenu->add(sendMenuKey, 0, NULL, (void*)ID_MENUKEY, 0);
  736. contextMenu->add("Secret shortcut menu key", menuKeyCode, NULL, (void*)ID_MENUKEY, FL_MENU_INVISIBLE);
  737. }
  738. contextMenu->add(_("Send Ctrl-Alt-Del"), 0, NULL, (void*)ID_CTRLALTDEL, FL_MENU_DIVIDER);
  739. contextMenu->add(_("Refresh screen"), 0, NULL, (void*)ID_REFRESH, FL_MENU_DIVIDER);
  740. contextMenu->add(_("Options..."), 0, NULL, (void*)ID_OPTIONS, 0);
  741. contextMenu->add(_("Connection info..."), 0, NULL, (void*)ID_INFO, 0);
  742. contextMenu->add(_("About TigerVNC viewer..."), 0, NULL, (void*)ID_ABOUT, FL_MENU_DIVIDER);
  743. contextMenu->add(_("Dismiss menu"), 0, NULL, (void*)ID_DISMISS, 0);
  744. }
  745. void Viewport::popupContextMenu()
  746. {
  747. const Fl_Menu_Item *m;
  748. char buffer[1024];
  749. // Make sure the menu is reset to its initial state between goes or
  750. // it will start up highlighting the previously selected entry.
  751. contextMenu->value(-1);
  752. // initialize context menu before display
  753. initContextMenu();
  754. // Unfortunately FLTK doesn't reliably restore the mouse pointer for
  755. // menus, so we have to help it out.
  756. #ifdef HAVE_FLTK_CURSOR
  757. if (Fl::belowmouse() == this)
  758. window()->cursor(FL_CURSOR_DEFAULT);
  759. #endif
  760. m = contextMenu->popup();
  761. // Back to our proper mouse pointer.
  762. #ifdef HAVE_FLTK_CURSOR
  763. if ((Fl::belowmouse() == this) && cursor)
  764. window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
  765. #endif
  766. if (m == NULL)
  767. return;
  768. switch (m->argument()) {
  769. case ID_EXIT:
  770. exit_vncviewer();
  771. break;
  772. #ifdef HAVE_FLTK_FULLSCREEN
  773. case ID_FULLSCREEN:
  774. if (window()->fullscreen_active())
  775. window()->fullscreen_off();
  776. else
  777. ((DesktopWindow*)window())->fullscreen_on();
  778. break;
  779. #endif
  780. case ID_RESIZE:
  781. #ifdef HAVE_FLTK_FULLSCREEN
  782. if (window()->fullscreen_active())
  783. break;
  784. #endif
  785. window()->size(w(), h());
  786. break;
  787. case ID_CTRL:
  788. if (m->value())
  789. handleKeyPress(fakeKeyBase + 0, XK_Control_L);
  790. else
  791. handleKeyRelease(fakeKeyBase + 0);
  792. menuCtrlKey = !menuCtrlKey;
  793. break;
  794. case ID_ALT:
  795. if (m->value())
  796. handleKeyPress(fakeKeyBase + 1, XK_Alt_L);
  797. else
  798. handleKeyRelease(fakeKeyBase + 1);
  799. menuAltKey = !menuAltKey;
  800. break;
  801. case ID_MENUKEY:
  802. handleKeyPress(fakeKeyBase + 2, menuKeySym);
  803. handleKeyRelease(fakeKeyBase + 2);
  804. break;
  805. case ID_CTRLALTDEL:
  806. handleKeyPress(fakeKeyBase + 3, XK_Control_L);
  807. handleKeyPress(fakeKeyBase + 4, XK_Alt_L);
  808. handleKeyPress(fakeKeyBase + 5, XK_Delete);
  809. handleKeyRelease(fakeKeyBase + 5);
  810. handleKeyRelease(fakeKeyBase + 4);
  811. handleKeyRelease(fakeKeyBase + 3);
  812. break;
  813. case ID_REFRESH:
  814. cc->refreshFramebuffer();
  815. break;
  816. case ID_OPTIONS:
  817. OptionsDialog::showDialog();
  818. break;
  819. case ID_INFO:
  820. if (fltk_escape(cc->connectionInfo(), buffer, sizeof(buffer)) < sizeof(buffer)) {
  821. fl_message_title(_("VNC connection info"));
  822. fl_message("%s", buffer);
  823. }
  824. break;
  825. case ID_ABOUT:
  826. about_vncviewer();
  827. break;
  828. case ID_DISMISS:
  829. // Don't need to do anything
  830. break;
  831. }
  832. }
  833. void Viewport::setMenuKey()
  834. {
  835. getMenuKey(&menuKeyCode, &menuKeySym);
  836. }
  837. void Viewport::handleOptions(void *data)
  838. {
  839. Viewport *self = (Viewport*)data;
  840. self->setMenuKey();
  841. }