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

Viewport.cxx 24KB

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