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

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