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

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