blob: 139eb9a450906fe513bce8ad500876440a0dd000 [file] [log] [blame]
Pierre Ossmand50b3d12011-04-15 07:46:56 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
3 *
4 * This is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this software; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
18 */
19
20#include <assert.h>
21#include <stdio.h>
22#include <string.h>
23
24#include <FL/fl_draw.H>
Pierre Ossman2eb1d112011-05-16 12:18:08 +000025#include <FL/fl_ask.H>
Pierre Ossmand50b3d12011-04-15 07:46:56 +000026
27#include <rfb/CMsgWriter.h>
28#include <rfb/LogWriter.h>
29
30// FLTK can pull in the X11 headers on some systems
31#ifndef XK_VoidSymbol
32#define XK_MISCELLANY
33#define XK_XKB_KEYS
34#include <rfb/keysymdef.h>
35#endif
36
Pierre Ossmancb0cffe2011-05-20 14:55:10 +000037#ifndef XF86XK_ModeLock
38#include <rfb/XF86keysym.h>
39#endif
40
Pierre Ossmand50b3d12011-04-15 07:46:56 +000041#include "Viewport.h"
42#include "CConn.h"
Pierre Ossmand463b572011-05-16 12:04:43 +000043#include "OptionsDialog.h"
Pierre Ossmand50b3d12011-04-15 07:46:56 +000044#include "i18n.h"
Pierre Ossmanc628ba42011-05-23 12:21:21 +000045#include "fltk_layout.h"
Pierre Ossmand50b3d12011-04-15 07:46:56 +000046#include "parameters.h"
47#include "keysym2ucs.h"
48
Pierre Ossmand50b3d12011-04-15 07:46:56 +000049using namespace rfb;
Pierre Ossman835b4ef2011-06-08 17:02:36 +000050using namespace rdr;
Pierre Ossmand50b3d12011-04-15 07:46:56 +000051
Pierre Ossmand50b3d12011-04-15 07:46:56 +000052extern void exit_vncviewer();
Pierre Ossmanb8858222011-04-29 11:51:38 +000053extern void about_vncviewer();
Pierre Ossmand50b3d12011-04-15 07:46:56 +000054
55static rfb::LogWriter vlog("Viewport");
56
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +000057// Menu constants
58
Pierre Ossman4c613d32011-05-26 14:14:06 +000059enum { ID_EXIT, ID_FULLSCREEN, ID_CTRL, ID_ALT, ID_MENUKEY, ID_CTRLALTDEL,
Pierre Ossman2eb1d112011-05-16 12:18:08 +000060 ID_REFRESH, ID_OPTIONS, ID_INFO, ID_ABOUT, ID_DISMISS };
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +000061
Pierre Ossmand50b3d12011-04-15 07:46:56 +000062Viewport::Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_)
63 : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(NULL), pixelTrans(NULL),
Pierre Ossman835b4ef2011-06-08 17:02:36 +000064 lastPointerPos(0, 0), lastButtonMask(0), cursor(NULL)
Pierre Ossmand50b3d12011-04-15 07:46:56 +000065{
Pierre Ossmanf8c5ef62011-05-13 12:47:54 +000066// FLTK STR #2599 must be fixed for proper dead keys support
67#ifdef HAVE_FLTK_DEAD_KEYS
68 set_simple_keyboard();
69#endif
70
Pierre Ossman4a6be4a2011-05-19 14:49:18 +000071// FLTK STR #2636 gives us the ability to monitor clipboard changes
72#ifdef HAVE_FLTK_CLIPBOARD
73 Fl::add_clipboard_notify(handleClipboardChange, this);
74#endif
75
Pierre Ossman132b3d02011-06-13 11:19:32 +000076 frameBuffer = new PlatformPixelBuffer(w, h);
Pierre Ossmand50b3d12011-04-15 07:46:56 +000077 assert(frameBuffer);
78
79 setServerPF(serverPF);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +000080
81 contextMenu = new Fl_Menu_Button(0, 0, 0, 0);
Pierre Ossmanad9d1ae2011-05-26 14:16:02 +000082 // Setting box type to FL_NO_BOX prevents it from trying to draw the
83 // button component (which we don't want)
84 contextMenu->box(FL_NO_BOX);
85
Pierre Ossmanb043ad12011-06-01 09:26:57 +000086 // The (invisible) button associated with this widget can mess with
87 // things like Fl_Scroll so we need to get rid of any parents.
88 // Unfortunately that's not possible because of STR #2654, but
89 // reparenting to the current window works for most cases.
90 window()->add(contextMenu);
91
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +000092 initContextMenu();
Pierre Ossman4e7271e2011-05-24 12:47:12 +000093
94 setMenuKey();
95
96 OptionsDialog::addCallback(handleOptions, this);
Pierre Ossmand50b3d12011-04-15 07:46:56 +000097}
98
99
100Viewport::~Viewport()
101{
102 // Unregister all timeouts in case they get a change tro trigger
103 // again later when this object is already gone.
104 Fl::remove_timeout(handleUpdateTimeout, this);
105 Fl::remove_timeout(handleColourMap, this);
106 Fl::remove_timeout(handlePointerTimeout, this);
107
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000108#ifdef HAVE_FLTK_CLIPBOARD
109 Fl::remove_clipboard_notify(handleClipboardChange);
110#endif
111
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000112 OptionsDialog::removeCallback(handleOptions);
113
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000114 delete frameBuffer;
115
116 if (pixelTrans)
117 delete pixelTrans;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000118
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000119 if (cursor) {
Pierre Ossmanf741d342011-06-09 08:32:49 +0000120 if (!cursor->alloc_array)
121 delete [] cursor->array;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000122 delete cursor;
123 }
124
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000125 // FLTK automatically deletes all child widgets, so we shouldn't touch
126 // them ourselves here
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000127}
128
129
130void Viewport::setServerPF(const rfb::PixelFormat& pf)
131{
132 if (pixelTrans)
133 delete pixelTrans;
134 pixelTrans = NULL;
135
136 if (pf.equal(getPreferredPF()))
137 return;
138
139 pixelTrans = new PixelTransformer();
140 pixelTrans->init(pf, &colourMap, getPreferredPF());
141}
142
143
144const rfb::PixelFormat &Viewport::getPreferredPF()
145{
Pierre Ossman132b3d02011-06-13 11:19:32 +0000146 return frameBuffer->getPF();
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000147}
148
149
150// setColourMapEntries() changes some of the entries in the colourmap.
151// Unfortunately these messages are often sent one at a time, so we delay the
152// settings taking effect by 100ms. This is because recalculating the internal
153// translation table can be expensive.
154void Viewport::setColourMapEntries(int firstColour, int nColours,
155 rdr::U16* rgbs)
156{
157 for (int i = 0; i < nColours; i++)
158 colourMap.set(firstColour+i, rgbs[i*3], rgbs[i*3+1], rgbs[i*3+2]);
159
160 if (!Fl::has_timeout(handleColourMap, this))
161 Fl::add_timeout(0.100, handleColourMap, this);
162}
163
164
165// Copy the areas of the framebuffer that have been changed (damaged)
166// to the displayed window.
167
168void Viewport::updateWindow()
169{
170 Rect r;
171
172 Fl::remove_timeout(handleUpdateTimeout, this);
173
174 r = damage.get_bounding_rect();
Pierre Ossmana6e20772011-04-15 11:10:52 +0000175 Fl_Widget::damage(FL_DAMAGE_USER1, r.tl.x + x(), r.tl.y + y(), r.width(), r.height());
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000176
177 damage.clear();
178}
179
Pierre Ossmanf741d342011-06-09 08:32:49 +0000180#ifdef HAVE_FLTK_CURSOR
181static const char * dotcursor_xpm[] = {
182 "5 5 2 1",
183 ". c #000000",
184 " c #FFFFFF",
185 " ",
186 " ... ",
187 " ... ",
188 " ... ",
189 " "};
190#endif
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000191
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000192void Viewport::setCursor(int width, int height, const Point& hotspot,
193 void* data, void* mask)
194{
195#ifdef HAVE_FLTK_CURSOR
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000196 if (cursor) {
Pierre Ossmanf741d342011-06-09 08:32:49 +0000197 if (!cursor->alloc_array)
198 delete [] cursor->array;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000199 delete cursor;
200 }
201
Pierre Ossmanf741d342011-06-09 08:32:49 +0000202 int mask_len = ((width+7)/8) * height;
203 int i;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000204
Pierre Ossmanf741d342011-06-09 08:32:49 +0000205 for (i = 0; i < mask_len; i++)
206 if (((rdr::U8*)mask)[i]) break;
207
Pierre Ossmana51574a2011-06-09 08:39:35 +0000208 if ((i == mask_len) && dotWhenNoCursor) {
209 vlog.debug("cursor is empty - using dot");
Pierre Ossmanf741d342011-06-09 08:32:49 +0000210
211 Fl_Pixmap pxm(dotcursor_xpm);
212 cursor = new Fl_RGB_Image(&pxm);
213 cursorHotspot.x = cursorHotspot.y = 2;
214 } else {
Pierre Ossman17a48f02011-06-09 08:42:04 +0000215 if ((width == 0) || (height == 0)) {
216 U8 *buffer = new U8[4];
217 memset(buffer, 0, 4);
218 cursor = new Fl_RGB_Image(buffer, 1, 1, 4);
219 cursorHotspot.x = cursorHotspot.y = 0;
220 } else {
221 U8 *buffer = new U8[width*height*4];
222 U8 *i, *o, *m;
223 int m_width;
Pierre Ossmanf741d342011-06-09 08:32:49 +0000224
Pierre Ossman17a48f02011-06-09 08:42:04 +0000225 const PixelFormat &pf = frameBuffer->getPF();
Pierre Ossmanf741d342011-06-09 08:32:49 +0000226
Pierre Ossman17a48f02011-06-09 08:42:04 +0000227 i = (U8*)data;
228 o = buffer;
229 m = (U8*)mask;
230 m_width = (width+7)/8;
231 for (int y = 0;y < height;y++) {
232 for (int x = 0;x < width;x++) {
233 pf.rgbFromBuffer(o, i, 1, &colourMap);
Pierre Ossmanf741d342011-06-09 08:32:49 +0000234
Pierre Ossman17a48f02011-06-09 08:42:04 +0000235 if (m[(m_width*y)+(x/8)] & 0x80>>(x%8))
236 o[3] = 255;
237 else
238 o[3] = 0;
Pierre Ossmanf741d342011-06-09 08:32:49 +0000239
Pierre Ossman17a48f02011-06-09 08:42:04 +0000240 o += 4;
241 i += pf.bpp/8;
242 }
Pierre Ossmanf741d342011-06-09 08:32:49 +0000243 }
Pierre Ossman17a48f02011-06-09 08:42:04 +0000244
245 cursor = new Fl_RGB_Image(buffer, width, height, 4);
246
247 cursorHotspot = hotspot;
Pierre Ossmanf741d342011-06-09 08:32:49 +0000248 }
Pierre Ossmanf741d342011-06-09 08:32:49 +0000249 }
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000250
251 if (Fl::belowmouse() == this)
252 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
253#endif
254}
255
256
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000257void Viewport::draw()
258{
259 int X, Y, W, H;
260
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000261 // Check what actually needs updating
Pierre Ossmana6e20772011-04-15 11:10:52 +0000262 fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000263 if ((W == 0) || (H == 0))
264 return;
265
Pierre Ossman132b3d02011-06-13 11:19:32 +0000266 frameBuffer->draw(X - x(), Y - y(), X, Y, W, H);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000267}
268
269
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000270void Viewport::resize(int x, int y, int w, int h)
271{
Pierre Ossman132b3d02011-06-13 11:19:32 +0000272 PlatformPixelBuffer* newBuffer;
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000273 rfb::Rect rect;
274
Pierre Ossman132b3d02011-06-13 11:19:32 +0000275 // FIXME: Resize should probably be a feature of the pixel buffer itself
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000276
277 if ((w == frameBuffer->width()) && (h == frameBuffer->height()))
278 goto end;
279
Pierre Ossman132b3d02011-06-13 11:19:32 +0000280 newBuffer = new PlatformPixelBuffer(w, h);
Pierre Ossmane00f0aa2011-06-01 09:31:53 +0000281 assert(newBuffer);
282
283 rect.setXYWH(0, 0,
284 __rfbmin(newBuffer->width(), frameBuffer->width()),
285 __rfbmin(newBuffer->height(), frameBuffer->height()));
286 newBuffer->imageRect(rect, frameBuffer->data, frameBuffer->getStride());
287
288 // Black out any new areas
289
290 if (newBuffer->width() > frameBuffer->width()) {
291 rect.setXYWH(frameBuffer->width(), 0,
292 newBuffer->width() - frameBuffer->width(),
293 newBuffer->height());
294 newBuffer->fillRect(rect, 0);
295 }
296
297 if (newBuffer->height() > frameBuffer->height()) {
298 rect.setXYWH(0, frameBuffer->height(),
299 newBuffer->width(),
300 newBuffer->height() - frameBuffer->height());
301 newBuffer->fillRect(rect, 0);
302 }
303
304 delete frameBuffer;
305 frameBuffer = newBuffer;
306
307end:
308 Fl_Widget::resize(x, y, w, h);
309}
310
311
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000312int Viewport::handle(int event)
313{
Pierre Ossman689c4582011-05-26 15:39:41 +0000314 char *buffer;
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000315 int ret;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000316 int buttonMask, wheelMask;
317 DownMap::const_iterator iter;
318
319 switch (event) {
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000320 case FL_PASTE:
Pierre Ossman689c4582011-05-26 15:39:41 +0000321 buffer = new char[Fl::event_length() + 1];
322
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000323 // This is documented as to ASCII, but actually does to 8859-1
Pierre Ossman689c4582011-05-26 15:39:41 +0000324 ret = fl_utf8toa(Fl::event_text(), Fl::event_length(), buffer,
325 Fl::event_length() + 1);
326 assert(ret < (Fl::event_length() + 1));
327
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000328 vlog.debug("Sending clipboard data: '%s'", buffer);
329 cc->writer()->clientCutText(buffer, ret);
Pierre Ossman689c4582011-05-26 15:39:41 +0000330
331 delete [] buffer;
332
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000333 return 1;
Pierre Ossman689c4582011-05-26 15:39:41 +0000334
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000335 case FL_ENTER:
336 // Yes, we would like some pointer events please!
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000337#ifdef HAVE_FLTK_CURSOR
Pierre Ossman1f1f6fd2011-06-09 08:33:29 +0000338 if (cursor)
339 window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y);
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000340#endif
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000341 return 1;
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000342
343 case FL_LEAVE:
344#ifdef HAVE_FLTK_CURSOR
345 window()->cursor(FL_CURSOR_DEFAULT);
346#endif
347 return 1;
348
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000349 case FL_PUSH:
350 case FL_RELEASE:
351 case FL_DRAG:
352 case FL_MOVE:
353 case FL_MOUSEWHEEL:
354 buttonMask = 0;
355 if (Fl::event_button1())
356 buttonMask |= 1;
357 if (Fl::event_button2())
358 buttonMask |= 2;
359 if (Fl::event_button3())
360 buttonMask |= 4;
361
362 if (event == FL_MOUSEWHEEL) {
Pierre Ossmandf0ed9f2011-05-24 11:33:43 +0000363 wheelMask = 0;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000364 if (Fl::event_dy() < 0)
Pierre Ossmandf0ed9f2011-05-24 11:33:43 +0000365 wheelMask |= 8;
366 if (Fl::event_dy() > 0)
367 wheelMask |= 16;
368 if (Fl::event_dx() < 0)
369 wheelMask |= 32;
370 if (Fl::event_dx() > 0)
371 wheelMask |= 64;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000372
373 // A quick press of the wheel "button", followed by a immediate
374 // release below
Pierre Ossman6a464be2011-04-15 12:57:31 +0000375 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()),
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000376 buttonMask | wheelMask);
377 }
378
Pierre Ossman6a464be2011-04-15 12:57:31 +0000379 handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), buttonMask);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000380 return 1;
381
382 case FL_FOCUS:
383 // Yes, we would like some focus please!
384 return 1;
385
386 case FL_UNFOCUS:
387 // Release all keys that were pressed as that generally makes most
388 // sense (e.g. Alt+Tab where we only see the Alt press)
Pierre Ossman71f295a2011-05-19 14:55:12 +0000389 for (iter = downKeySym.begin();iter != downKeySym.end();++iter) {
390 vlog.debug("Key released: 0x%04x => 0x%04x", iter->first, iter->second);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000391 cc->writer()->keyEvent(iter->second, false);
Pierre Ossman71f295a2011-05-19 14:55:12 +0000392 }
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000393 downKeySym.clear();
394 return 1;
395
396 case FL_KEYDOWN:
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000397 if (menuKeyCode && (Fl::event_key() == menuKeyCode)) {
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000398 popupContextMenu();
399 return 1;
400 }
401
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000402 handleKeyEvent(Fl::event_key(), Fl::event_original_key(),
403 Fl::event_text(), true);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000404 return 1;
405
406 case FL_KEYUP:
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000407 if (menuKeyCode && (Fl::event_key() == menuKeyCode))
408 return 1;
409
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000410 handleKeyEvent(Fl::event_key(), Fl::event_original_key(),
411 Fl::event_text(), false);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000412 return 1;
413 }
414
415 return Fl_Widget::handle(event);
416}
417
418
419void Viewport::handleUpdateTimeout(void *data)
420{
421 Viewport *self = (Viewport *)data;
422
423 assert(self);
424
425 self->updateWindow();
426}
427
428
429void Viewport::handleColourMap(void *data)
430{
431 Viewport *self = (Viewport *)data;
432
433 assert(self);
434
435 if (self->pixelTrans != NULL)
436 self->pixelTrans->setColourMapEntries(0, 0);
437
438 self->Fl_Widget::damage(FL_DAMAGE_ALL);
439}
440
441
Pierre Ossman4a6be4a2011-05-19 14:49:18 +0000442void Viewport::handleClipboardChange(int source, void *data)
443{
444 Viewport *self = (Viewport *)data;
445
446 assert(self);
447
448 if (!sendPrimary && (source == 0))
449 return;
450
451 Fl::paste(*self, source);
452}
453
454
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000455void Viewport::handlePointerEvent(const rfb::Point& pos, int buttonMask)
456{
457 if (!viewOnly) {
458 if (pointerEventInterval == 0 || buttonMask != lastButtonMask) {
459 cc->writer()->pointerEvent(pos, buttonMask);
460 } else {
461 if (!Fl::has_timeout(handlePointerTimeout, this))
462 Fl::add_timeout((double)pointerEventInterval/1000.0,
463 handlePointerTimeout, this);
464 }
465 lastPointerPos = pos;
466 lastButtonMask = buttonMask;
467 }
468}
469
470
471void Viewport::handlePointerTimeout(void *data)
472{
473 Viewport *self = (Viewport *)data;
474
475 assert(self);
476
477 self->cc->writer()->pointerEvent(self->lastPointerPos, self->lastButtonMask);
478}
479
480
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000481rdr::U32 Viewport::translateKeyEvent(int keyCode, int origKeyCode, const char *keyText)
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000482{
483 unsigned ucs;
484
485 // First check for function keys
486 if ((keyCode > FL_F) && (keyCode <= FL_F_Last))
487 return XK_F1 + (keyCode - FL_F - 1);
488
489 // Numpad numbers
490 if ((keyCode >= (FL_KP + '0')) && (keyCode <= (FL_KP + '9')))
491 return XK_KP_0 + (keyCode - (FL_KP + '0'));
492
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000493 // FLTK does some special remapping of numpad keys when numlock is off
494 if ((origKeyCode >= FL_KP) && (origKeyCode <= FL_KP_Last)) {
495 switch (keyCode) {
496 case FL_F+1:
497 return XK_KP_F1;
498 case FL_F+2:
499 return XK_KP_F2;
500 case FL_F+3:
501 return XK_KP_F3;
502 case FL_F+4:
503 return XK_KP_F4;
504 case FL_Home:
505 return XK_KP_Home;
506 case FL_Left:
507 return XK_KP_Left;
508 case FL_Up:
509 return XK_KP_Up;
510 case FL_Right:
511 return XK_KP_Right;
512 case FL_Down:
513 return XK_KP_Down;
514 case FL_Page_Up:
515 return XK_KP_Page_Up;
516 case FL_Page_Down:
517 return XK_KP_Page_Down;
518 case FL_End:
519 return XK_KP_End;
520 case FL_Insert:
521 return XK_KP_Insert;
522 case FL_Delete:
523 return XK_KP_Delete;
524 }
525 }
526
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000527 // Then other special keys
528 switch (keyCode) {
529 case FL_BackSpace:
530 return XK_BackSpace;
531 case FL_Tab:
532 return XK_Tab;
533 case FL_Enter:
534 return XK_Return;
535 case FL_Pause:
536 return XK_Pause;
537 case FL_Scroll_Lock:
538 return XK_Scroll_Lock;
539 case FL_Escape:
540 return XK_Escape;
541 case FL_Home:
542 return XK_Home;
543 case FL_Left:
544 return XK_Left;
545 case FL_Up:
546 return XK_Up;
547 case FL_Right:
548 return XK_Right;
549 case FL_Down:
550 return XK_Down;
551 case FL_Page_Up:
552 return XK_Page_Up;
553 case FL_Page_Down:
554 return XK_Page_Down;
555 case FL_End:
556 return XK_End;
557 case FL_Print:
558 return XK_Print;
559 case FL_Insert:
560 return XK_Insert;
561 case FL_Menu:
562 return XK_Menu;
563 case FL_Help:
564 return XK_Help;
565 case FL_Num_Lock:
566 return XK_Num_Lock;
567 case FL_Shift_L:
568 return XK_Shift_L;
569 case FL_Shift_R:
570 return XK_Shift_R;
571 case FL_Control_L:
572 return XK_Control_L;
573 case FL_Control_R:
574 return XK_Control_R;
575 case FL_Caps_Lock:
576 return XK_Caps_Lock;
577 case FL_Meta_L:
578 return XK_Super_L;
579 case FL_Meta_R:
580 return XK_Super_R;
581 case FL_Alt_L:
582 return XK_Alt_L;
583 case FL_Alt_R:
584 return XK_Alt_R;
585 case FL_Delete:
586 return XK_Delete;
587 case FL_KP_Enter:
588 return XK_KP_Enter;
589 case FL_KP + '=':
590 return XK_KP_Equal;
591 case FL_KP + '*':
592 return XK_KP_Multiply;
593 case FL_KP + '+':
594 return XK_KP_Add;
595 case FL_KP + ',':
596 return XK_KP_Separator;
597 case FL_KP + '-':
598 return XK_KP_Subtract;
599 case FL_KP + '.':
600 return XK_KP_Decimal;
601 case FL_KP + '/':
602 return XK_KP_Divide;
Pierre Ossmancb0cffe2011-05-20 14:55:10 +0000603#ifdef HAVE_FLTK_MEDIAKEYS
604 case FL_Volume_Down:
605 return XF86XK_AudioLowerVolume;
606 case FL_Volume_Mute:
607 return XF86XK_AudioMute;
608 case FL_Volume_Up:
609 return XF86XK_AudioRaiseVolume;
610 case FL_Media_Play:
611 return XF86XK_AudioPlay;
612 case FL_Media_Stop:
613 return XF86XK_AudioStop;
614 case FL_Media_Prev:
615 return XF86XK_AudioPrev;
616 case FL_Media_Next:
617 return XF86XK_AudioNext;
618 case FL_Home_Page:
619 return XF86XK_HomePage;
620 case FL_Mail:
621 return XF86XK_Mail;
622 case FL_Search:
623 return XF86XK_Search;
624 case FL_Back:
625 return XF86XK_Back;
626 case FL_Forward:
627 return XF86XK_Forward;
628 case FL_Stop:
629 return XF86XK_Stop;
630 case FL_Refresh:
631 return XF86XK_Refresh;
632 case FL_Sleep:
633 return XF86XK_Sleep;
634 case FL_Favorites:
635 return XF86XK_Favorites;
636#endif
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000637 case XK_ISO_Level3_Shift:
638 // FLTK tends to let this one leak through on X11...
639 return XK_ISO_Level3_Shift;
Pierre Ossmana75f8f82011-04-29 11:12:02 +0000640 case XK_Multi_key:
641 // Same for this...
642 return XK_Multi_key;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000643 }
644
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000645 // Unknown special key?
646 if (keyText[0] == '\0') {
647 vlog.error(_("Unknown FLTK key code %d (0x%04x)"), keyCode, keyCode);
648 return XK_VoidSymbol;
649 }
650
651 // Look up the symbol the key produces and translate that from Unicode
652 // to a X11 keysym.
653 if (fl_utf_nb_char((const unsigned char*)keyText, strlen(keyText)) != 1) {
654 vlog.error(_("Multiple characters given for key code %d (0x%04x): '%s'"),
655 keyCode, keyCode, keyText);
656 return XK_VoidSymbol;
657 }
658
659 ucs = fl_utf8decode(keyText, NULL, NULL);
660 return ucs2keysym(ucs);
661}
662
663
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000664void Viewport::handleKeyEvent(int keyCode, int origKeyCode, const char *keyText, bool down)
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000665{
666 rdr::U32 keySym;
667
668 if (viewOnly)
669 return;
670
671 // Because of the way keyboards work, we cannot expect to have the same
672 // symbol on release as when pressed. This breaks the VNC protocol however,
673 // so we need to keep track of what keysym a key _code_ generated on press
674 // and send the same on release.
675 if (!down) {
676 DownMap::iterator iter;
677
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000678 iter = downKeySym.find(origKeyCode);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000679 if (iter == downKeySym.end()) {
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000680 vlog.error(_("Unexpected release of FLTK key code %d (0x%04x)"),
681 origKeyCode, origKeyCode);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000682 return;
683 }
684
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000685 vlog.debug("Key released: 0x%04x => 0x%04x", origKeyCode, iter->second);
Pierre Ossman71f295a2011-05-19 14:55:12 +0000686
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000687 cc->writer()->keyEvent(iter->second, false);
688
689 downKeySym.erase(iter);
690
691 return;
692 }
693
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000694 keySym = translateKeyEvent(keyCode, origKeyCode, keyText);
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000695 if (keySym == XK_VoidSymbol)
696 return;
697
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000698 vlog.debug("Key pressed: 0x%04x (0x%04x) '%s' => 0x%04x",
699 origKeyCode, keyCode, keyText, keySym);
Pierre Ossman71f295a2011-05-19 14:55:12 +0000700
Pierre Ossmancd6ddef2011-05-20 12:05:20 +0000701 downKeySym[origKeyCode] = keySym;
Pierre Ossmand50b3d12011-04-15 07:46:56 +0000702 cc->writer()->keyEvent(keySym, down);
703}
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000704
705
706void Viewport::initContextMenu()
707{
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000708 contextMenu->clear();
709
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000710 contextMenu->add(_("Exit viewer"), 0, NULL, (void*)ID_EXIT, FL_MENU_DIVIDER);
711
Pierre Ossman4c613d32011-05-26 14:14:06 +0000712#ifdef HAVE_FLTK_FULLSCREEN
713 contextMenu->add(_("Full screen"), 0, NULL, (void*)ID_FULLSCREEN, FL_MENU_DIVIDER);
714#endif
715
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000716 contextMenu->add(_("Ctrl"), 0, NULL, (void*)ID_CTRL, FL_MENU_TOGGLE);
717 contextMenu->add(_("Alt"), 0, NULL, (void*)ID_ALT, FL_MENU_TOGGLE);
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000718
719 if (menuKeyCode) {
720 char sendMenuKey[64];
721 snprintf(sendMenuKey, 64, _("Send %s"), (const char *)menuKey);
722 contextMenu->add(sendMenuKey, 0, NULL, (void*)ID_MENUKEY, 0);
723 contextMenu->add("Secret shortcut menu key", menuKeyCode, NULL, (void*)ID_MENUKEY, FL_MENU_INVISIBLE);
724 }
725
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000726 contextMenu->add(_("Send Ctrl-Alt-Del"), 0, NULL, (void*)ID_CTRLALTDEL, FL_MENU_DIVIDER);
727
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000728 contextMenu->add(_("Refresh screen"), 0, NULL, (void*)ID_REFRESH, FL_MENU_DIVIDER);
729
Pierre Ossmand463b572011-05-16 12:04:43 +0000730 contextMenu->add(_("Options..."), 0, NULL, (void*)ID_OPTIONS, 0);
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000731 contextMenu->add(_("Connection info..."), 0, NULL, (void*)ID_INFO, 0);
Pierre Ossmanb8858222011-04-29 11:51:38 +0000732 contextMenu->add(_("About TigerVNC viewer..."), 0, NULL, (void*)ID_ABOUT, FL_MENU_DIVIDER);
733
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000734 contextMenu->add(_("Dismiss menu"), 0, NULL, (void*)ID_DISMISS, 0);
735}
736
737
738void Viewport::popupContextMenu()
739{
740 const Fl_Menu_Item *m;
Pierre Ossmanc628ba42011-05-23 12:21:21 +0000741 char buffer[1024];
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000742
Pierre Ossman407a5c32011-05-26 14:48:29 +0000743 // FIXME: Hacky workaround for the fact that menus grab and ungrab the
744 // keyboard. Releasing focus here triggers some events on ungrab
745 // that DesktopWindow can catch and trigger some voodoo.
746 // See DesktopWindow::handle().
747 if (window()->contains(Fl::focus()))
748 Fl::focus(NULL);
749
Pierre Ossmanb3ff4372011-06-03 11:45:15 +0000750 // Make sure the menu is reset to its initial state between goes or
751 // it will start up highlighting the previously selected entry.
752 contextMenu->value(-1);
753
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000754 m = contextMenu->popup();
755 if (m == NULL)
756 return;
757
758 switch (m->argument()) {
759 case ID_EXIT:
760 exit_vncviewer();
761 break;
Pierre Ossman4c613d32011-05-26 14:14:06 +0000762#ifdef HAVE_FLTK_FULLSCREEN
763 case ID_FULLSCREEN:
764 if (window()->fullscreen_active())
765 window()->fullscreen_off();
766 else
767 window()->fullscreen();
768 break;
769#endif
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000770 case ID_CTRL:
771 if (!viewOnly)
772 cc->writer()->keyEvent(XK_Control_L, m->value());
773 break;
774 case ID_ALT:
775 if (!viewOnly)
776 cc->writer()->keyEvent(XK_Alt_L, m->value());
777 break;
778 case ID_MENUKEY:
779 if (!viewOnly) {
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000780 handleKeyEvent(menuKeyCode, menuKeyCode, "", true);
781 handleKeyEvent(menuKeyCode, menuKeyCode, "", false);
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000782 }
783 break;
784 case ID_CTRLALTDEL:
785 if (!viewOnly) {
786 cc->writer()->keyEvent(XK_Control_L, true);
787 cc->writer()->keyEvent(XK_Alt_L, true);
788 cc->writer()->keyEvent(XK_Delete, true);
789 cc->writer()->keyEvent(XK_Delete, false);
790 cc->writer()->keyEvent(XK_Alt_L, false);
791 cc->writer()->keyEvent(XK_Control_L, false);
792 }
793 break;
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000794 case ID_REFRESH:
795 cc->refreshFramebuffer();
796 break;
Pierre Ossmand463b572011-05-16 12:04:43 +0000797 case ID_OPTIONS:
798 OptionsDialog::showDialog();
799 break;
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000800 case ID_INFO:
Pierre Ossmanc628ba42011-05-23 12:21:21 +0000801 if (fltk_escape(cc->connectionInfo(), buffer, sizeof(buffer)) < sizeof(buffer)) {
802 fl_message_title(_("VNC connection info"));
803 fl_message(buffer);
804 }
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000805 break;
Pierre Ossmanb8858222011-04-29 11:51:38 +0000806 case ID_ABOUT:
807 about_vncviewer();
808 break;
Pierre Ossmanc7bfaac2011-04-29 11:08:11 +0000809 case ID_DISMISS:
810 // Don't need to do anything
811 break;
812 }
813}
Pierre Ossman4e7271e2011-05-24 12:47:12 +0000814
815
816void Viewport::setMenuKey()
817{
818 const char *menuKeyStr;
819
820 menuKeyCode = 0;
821
822 menuKeyStr = menuKey;
823 if (menuKeyStr[0] == 'F') {
824 int num = atoi(menuKeyStr + 1);
825 if ((num >= 1) && (num <= 12))
826 menuKeyCode = FL_F + num;
827 }
828
829 // Need to repopulate the context menu as it contains references to
830 // the menu key
831 initContextMenu();
832}
833
834
835void Viewport::handleOptions(void *data)
836{
837 Viewport *self = (Viewport*)data;
838
839 self->setMenuKey();
840}