Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

Viewport.cxx 24KB

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