aboutsummaryrefslogtreecommitdiffstats
path: root/vncviewer/DesktopWindow.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vncviewer/DesktopWindow.cxx')
-rw-r--r--vncviewer/DesktopWindow.cxx423
1 files changed, 229 insertions, 194 deletions
diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx
index 4a15117a..831bb107 100644
--- a/vncviewer/DesktopWindow.cxx
+++ b/vncviewer/DesktopWindow.cxx
@@ -26,6 +26,8 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
+#include <time.h>
+#include <unistd.h>
#include <sys/time.h>
#include <core/LogWriter.h>
@@ -73,20 +75,21 @@ static int edge_scroll_size_y = 96;
// default: roughly 60 fps for smooth motion
#define EDGE_SCROLL_SECONDS_PER_FRAME 0.016666
+// Time before we show an overlay tip again
+const time_t OVERLAY_REPEAT_TIMEOUT = 600;
+
static core::LogWriter vlog("DesktopWindow");
// Global due to http://www.fltk.org/str.php?L2177 and the similar
// issue for Fl::event_dispatch.
static std::set<DesktopWindow *> instances;
-DesktopWindow::DesktopWindow(int w, int h, const char *name,
- const rfb::PixelFormat& serverPF,
- CConn* cc_)
- : Fl_Window(w, h), cc(cc_), offscreen(nullptr), overlay(nullptr),
+DesktopWindow::DesktopWindow(int w, int h, CConn* cc_)
+ : Fl_Window(w, h), cc(cc_), offscreen(nullptr),
firstUpdate(true),
delayedFullscreen(false), sentDesktopSize(false),
pendingRemoteResize(false), lastResize({0, 0}),
- keyboardGrabbed(false), mouseGrabbed(false),
+ keyboardGrabbed(false), mouseGrabbed(false), regrabOnFocus(false),
statsLastUpdates(0), statsLastPixels(0), statsLastPosition(0),
statsGraph(nullptr)
{
@@ -97,7 +100,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name,
group->resizable(nullptr);
resizable(group);
- viewport = new Viewport(w, h, serverPF, cc);
+ viewport = new Viewport(w, h, cc);
// Position will be adjusted later
hscroll = new Fl_Scrollbar(0, 0, 0, 0);
@@ -110,7 +113,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name,
callback(handleClose, this);
- setName(name);
+ updateCaption();
OptionsDialog::addCallback(handleOptions, this);
@@ -229,8 +232,16 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name,
Fl::add_timeout(0, handleStatsTimeout, this);
}
- // Show hint about menu key
- Fl::add_timeout(0.5, menuOverlay, this);
+ // Show hint about menu shortcut
+ unsigned modifierMask;
+
+ modifierMask = 0;
+ for (core::EnumListEntry key : shortcutModifiers)
+ modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str());
+
+ if (modifierMask)
+ addOverlayTip(_("Press %sM to open the context menu"),
+ ShortcutHandler::modifierPrefix(modifierMask));
// By default we get a slight delay when we warp the pointer, something
// we don't want or we'll get jerky movement
@@ -251,17 +262,18 @@ DesktopWindow::~DesktopWindow()
// Unregister all timeouts in case they get a change tro trigger
// again later when this object is already gone.
- Fl::remove_timeout(handleGrab, this);
Fl::remove_timeout(handleResizeTimeout, this);
Fl::remove_timeout(handleFullscreenTimeout, this);
Fl::remove_timeout(handleEdgeScroll, this);
Fl::remove_timeout(handleStatsTimeout, this);
- Fl::remove_timeout(menuOverlay, this);
Fl::remove_timeout(updateOverlay, this);
OptionsDialog::removeCallback(handleOptions);
- delete overlay;
+ while (!overlays.empty()) {
+ delete overlays.front().surface;
+ overlays.pop_front();
+ }
delete offscreen;
delete statsGraph;
@@ -284,46 +296,50 @@ const rfb::PixelFormat &DesktopWindow::getPreferredPF()
}
-void DesktopWindow::setName(const char *name)
+void DesktopWindow::updateCaption()
{
- char windowNameStr[100];
+ const size_t maxLen = 100;
+ std::string windowName;
const char *labelFormat;
size_t maxNameSize;
- char truncatedName[sizeof(windowNameStr)];
+ std::string name;
- labelFormat = "%s - TigerVNC";
+ // FIXME: All of this consideres bytes, not characters
+
+ if (keyboardGrabbed)
+ labelFormat = _("%s - TigerVNC (keyboard grabbed)");
+ else
+ labelFormat = _("%s - TigerVNC");
// Ignore the length of '%s' since it is
// a format marker which won't take up space
- maxNameSize = sizeof(windowNameStr) - 1 - strlen(labelFormat) + 2;
-
- if (maxNameSize > strlen(name)) {
- // Guaranteed to fit, no need to truncate
- strcpy(truncatedName, name);
- } else if (maxNameSize <= strlen("...")) {
- // Even an ellipsis won't fit
- truncatedName[0] = '\0';
- } else {
- int offset;
+ maxNameSize = maxLen - strlen(labelFormat) + 2;
+
+ name = cc->server.name();
- // We need to truncate, add an ellipsis
- offset = maxNameSize - strlen("...");
- strncpy(truncatedName, name, sizeof(truncatedName));
- strcpy(truncatedName + offset, "...");
+ if (name.size() > maxNameSize) {
+ if (maxNameSize <= strlen("...")) {
+ // Even an ellipsis won't fit
+ name.clear();
+ }
+ else {
+ int offset;
+
+ // We need to truncate, add an ellipsis
+ offset = maxNameSize - strlen("...");
+ name.resize(offset);
+ name += "...";
+ }
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
- if (snprintf(windowNameStr, sizeof(windowNameStr), labelFormat,
- truncatedName) >= (int)sizeof(windowNameStr)) {
- // This is just to shut up the compiler, as we've already made sure
- // we won't truncate anything
- }
+ windowName = core::format(labelFormat, name.c_str());
#pragma GCC diagnostic pop
- copy_label(windowNameStr);
+ copy_label(windowName.c_str());
}
@@ -410,11 +426,9 @@ void DesktopWindow::setDesktopSizeDone(unsigned result)
}
-void DesktopWindow::setCursor(int width, int height,
- const core::Point& hotspot,
- const uint8_t* data)
+void DesktopWindow::setCursor()
{
- viewport->setCursor(width, height, hotspot, data);
+ viewport->setCursor();
}
@@ -536,9 +550,12 @@ void DesktopWindow::draw()
}
// Overlay (if active)
- if (overlay) {
+ if (!overlays.empty()) {
int ox, oy, ow, oh;
int sx, sy, sw, sh;
+ struct Overlay overlay;
+
+ overlay = overlays.front();
// Make sure it's properly seen by adjusting it relative to the
// primary screen rather than the entire window
@@ -576,18 +593,20 @@ void DesktopWindow::draw()
sw = w();
}
- ox = X = sx + (sw - overlay->width()) / 2;
+ ox = X = sx + (sw - overlay.surface->width()) / 2;
oy = Y = sy + 50;
- ow = overlay->width();
- oh = overlay->height();
+ ow = overlay.surface->width();
+ oh = overlay.surface->height();
fl_clip_box(ox, oy, ow, oh, ox, oy, ow, oh);
if ((ow != 0) && (oh != 0)) {
if (offscreen)
- overlay->blend(offscreen, ox - X, oy - Y, ox, oy, ow, oh, overlayAlpha);
+ overlay.surface->blend(offscreen, ox - X, oy - Y,
+ ox, oy, ow, oh, overlay.alpha);
else
- overlay->blend(ox - X, oy - Y, ox, oy, ow, oh, overlayAlpha);
+ overlay.surface->blend(ox - X, oy - Y,
+ ox, oy, ow, oh, overlay.alpha);
}
}
@@ -701,34 +720,55 @@ void DesktopWindow::resize(int x, int y, int w, int h)
repositionWidgets();
}
-
- // Some systems require a grab after the window size has been changed.
- // Otherwise they might hold on to displays, resulting in them being unusable.
- maybeGrabKeyboard();
}
-
-void DesktopWindow::menuOverlay(void* data)
+void DesktopWindow::addOverlayTip(const char* text, ...)
{
- DesktopWindow *self;
+ va_list ap;
+ char textbuf[1024];
- self = (DesktopWindow*)data;
+ std::map<std::string, time_t>::iterator iter;
+
+ va_start(ap, text);
+ vsnprintf(textbuf, sizeof(textbuf), text, ap);
+ textbuf[sizeof(textbuf)-1] = '\0';
+ va_end(ap);
- // Empty string means None, for backward compatibility
- if ((menuKey != "") && (menuKey != "None")) {
- self->setOverlay(_("Press %s to open the context menu"),
- menuKey.getValueStr().c_str());
+ // Purge all old entries
+ for (iter = overlayTimes.begin(); iter != overlayTimes.end(); ) {
+ if ((time(nullptr) - iter->second) >= OVERLAY_REPEAT_TIMEOUT)
+ overlayTimes.erase(iter++);
+ else
+ iter++;
}
+
+ // Recently shown?
+ if (overlayTimes.count(textbuf) > 0)
+ return;
+
+ overlayTimes[textbuf] = time(nullptr);
+
+ addOverlay(textbuf);
}
-void DesktopWindow::setOverlay(const char* text, ...)
+void DesktopWindow::addOverlayError(const char* text, ...)
{
- const Fl_Fontsize fontsize = 16;
- const int margin = 10;
-
va_list ap;
char textbuf[1024];
+ va_start(ap, text);
+ vsnprintf(textbuf, sizeof(textbuf), text, ap);
+ textbuf[sizeof(textbuf)-1] = '\0';
+ va_end(ap);
+
+ addOverlay(textbuf);
+}
+
+void DesktopWindow::addOverlay(const char *text)
+{
+ const Fl_Fontsize fontsize = 16;
+ const int margin = 10;
+
Fl_Image_Surface *surface;
Fl_RGB_Image* imageText;
@@ -742,13 +782,7 @@ void DesktopWindow::setOverlay(const char* text, ...)
unsigned char* a;
const unsigned char* b;
- delete overlay;
- Fl::remove_timeout(updateOverlay, this);
-
- va_start(ap, text);
- vsnprintf(textbuf, sizeof(textbuf), text, ap);
- textbuf[sizeof(textbuf)-1] = '\0';
- va_end(ap);
+ struct Overlay overlay;
#if !defined(WIN32) && !defined(__APPLE__)
// FLTK < 1.3.5 crashes if fl_gc is unset
@@ -758,7 +792,7 @@ void DesktopWindow::setOverlay(const char* text, ...)
fl_font(FL_HELVETICA, fontsize);
w = 0;
- fl_measure(textbuf, w, h);
+ fl_measure(text, w, h);
// Margins
w += margin * 2 * 2;
@@ -771,7 +805,7 @@ void DesktopWindow::setOverlay(const char* text, ...)
fl_font(FL_HELVETICA, fontsize);
fl_color(FL_WHITE);
- fl_draw(textbuf, 0, 0, w, h, FL_ALIGN_CENTER);
+ fl_draw(text, 0, 0, w, h, FL_ALIGN_CENTER);
imageText = surface->image();
delete surface;
@@ -807,39 +841,53 @@ void DesktopWindow::setOverlay(const char* text, ...)
delete imageText;
- overlay = new Surface(image);
- overlayAlpha = 0;
- gettimeofday(&overlayStart, nullptr);
+ overlay.surface = new Surface(image);
+ overlay.alpha = 0;
+ memset(&overlay.start, 0, sizeof(overlay.start));
+ overlays.push_back(overlay);
delete image;
delete [] buffer;
- Fl::add_timeout(1.0/60, updateOverlay, this);
+ if (overlays.size() == 1)
+ Fl::add_timeout(0.5, updateOverlay, this);
}
void DesktopWindow::updateOverlay(void *data)
{
DesktopWindow *self;
+ struct Overlay* overlay;
unsigned elapsed;
self = (DesktopWindow*)data;
- elapsed = core::msSince(&self->overlayStart);
+ if (self->overlays.empty())
+ return;
+
+ overlay = &self->overlays.front();
+
+ if (overlay->start.tv_sec == 0)
+ gettimeofday(&overlay->start, nullptr);
+
+ elapsed = core::msSince(&overlay->start);
if (elapsed < 500) {
- self->overlayAlpha = (unsigned)255 * elapsed / 500;
+ overlay->alpha = (unsigned)255 * elapsed / 500;
Fl::add_timeout(1.0/60, updateOverlay, self);
} else if (elapsed < 3500) {
- self->overlayAlpha = 255;
+ overlay->alpha = 255;
Fl::add_timeout(3.0, updateOverlay, self);
} else if (elapsed < 4000) {
- self->overlayAlpha = (unsigned)255 * (4000 - elapsed) / 500;
+ overlay->alpha = (unsigned)255 * (4000 - elapsed) / 500;
Fl::add_timeout(1.0/60, updateOverlay, self);
} else {
- delete self->overlay;
- self->overlay = nullptr;
+ delete overlay->surface;
+ self->overlays.pop_front();
+ if (!self->overlays.empty())
+ Fl::add_timeout(0.5, updateOverlay, self);
}
+ // FIXME: Only damage relevant area
self->damage(FL_DAMAGE_USER1);
}
@@ -853,10 +901,36 @@ int DesktopWindow::handle(int event)
// Update scroll bars
repositionWidgets();
- if (fullscreen_active())
- maybeGrabKeyboard();
- else
- ungrabKeyboard();
+ // Show how to get out of full screen
+ if (fullscreen_active()) {
+ unsigned modifierMask;
+
+ modifierMask = 0;
+ for (core::EnumListEntry key : shortcutModifiers)
+ modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str());
+
+ if (modifierMask)
+ addOverlayTip(_("Press %sEnter to leave full-screen mode"),
+ ShortcutHandler::modifierPrefix(modifierMask));
+ }
+
+#ifdef __APPLE__
+ // Complain to the user if we won't have permission to grab keyboard
+ if (fullscreenSystemKeys && fullscreen_active()) {
+ // FIXME: There is some race during initial full screen where we
+ // fail to give focus to the popup, but we can work around
+ // it using a timer
+ Fl::add_timeout(0, [](void*) { cocoa_is_trusted(true); }, nullptr);
+ }
+#endif
+
+ // Automatically toggle keyboard grab?
+ if (fullscreenSystemKeys) {
+ if (fullscreen_active())
+ grabKeyboard();
+ else
+ ungrabKeyboard();
+ }
// The window manager respected our full screen request, so stop
// waiting and delaying the session resize
@@ -925,6 +999,17 @@ int DesktopWindow::fltkDispatch(int event, Fl_Window *win)
if ((event == FL_MOVE) && (win == nullptr))
return 0;
+#if !defined(WIN32) && !defined(__APPLE__)
+ // FLTK passes through the fake grab focus events that can cause us
+ // to end up in an infinite loop
+ // https://github.com/fltk/fltk/issues/295
+ if ((event == FL_FOCUS) || (event == FL_UNFOCUS)) {
+ const XFocusChangeEvent* xfocus = &fl_xevent->xfocus;
+ if ((xfocus->mode == NotifyGrab) || (xfocus->mode == NotifyUngrab))
+ return 0;
+ }
+#endif
+
ret = Fl::handle_(event, win);
// This is hackish and the result of the dodgy focus handling in FLTK.
@@ -937,16 +1022,21 @@ int DesktopWindow::fltkDispatch(int event, Fl_Window *win)
if (dw) {
switch (event) {
// Focus might not stay with us just because we have grabbed the
- // keyboard. E.g. we might have sub windows, or we're not using
- // all monitors and the user clicked on another application.
- // Make sure we update our grabs with the focus changes.
+ // keyboard. E.g. we might have sub windows, or the user clicked on
+ // another application. Make sure we update our grabs with the focus
+ // changes.
case FL_FOCUS:
- dw->maybeGrabKeyboard();
+ if (dw->regrabOnFocus ||
+ (fullscreenSystemKeys && dw->fullscreen_active()))
+ dw->grabKeyboard();
+ dw->regrabOnFocus = false;
break;
case FL_UNFOCUS:
- if (fullscreenSystemKeys) {
- dw->ungrabKeyboard();
- }
+ // If the grab is active when we lose focus, the user likely wants
+ // the grab to remain once we regain focus
+ if (dw->keyboardGrabbed)
+ dw->regrabOnFocus = true;
+ dw->ungrabKeyboard();
break;
case FL_SHOW:
@@ -987,14 +1077,6 @@ int DesktopWindow::fltkHandle(int event)
// not be resized to cover the new screen. A timer makes sense
// also on other systems, to make sure that whatever desktop
// environment has a chance to deal with things before we do.
- // Please note that when using FullscreenSystemKeys on macOS, the
- // display configuration cannot be changed: macOS will not detect
- // added or removed screens and there will be no
- // FL_SCREEN_CONFIGURATION_CHANGED event. This is by design:
- // "When you capture a display, you have exclusive use of the
- // display. Other applications and system services are not allowed
- // to use the display or change its configuration. In addition,
- // they are not notified of display changes"
Fl::remove_timeout(reconfigureFullscreen);
Fl::add_timeout(0.5, reconfigureFullscreen);
}
@@ -1070,43 +1152,13 @@ void DesktopWindow::fullscreen_on()
}
}
-#ifdef __APPLE__
- // This is a workaround for a bug in FLTK, see: https://github.com/fltk/fltk/pull/277
- int savedLevel = -1;
- if (shown())
- savedLevel = cocoa_get_level(this);
-#endif
+
fullscreen_screens(top, bottom, left, right);
-#ifdef __APPLE__
- // This is a workaround for a bug in FLTK, see: https://github.com/fltk/fltk/pull/277
- if (savedLevel != -1) {
- if (cocoa_get_level(this) != savedLevel)
- cocoa_set_level(this, savedLevel);
- }
-#endif
if (!fullscreen_active())
fullscreen();
}
-#if !defined(WIN32) && !defined(__APPLE__)
-Bool eventIsFocusWithSerial(Display* /*display*/, XEvent *event,
- XPointer arg)
-{
- unsigned long serial;
-
- serial = *(unsigned long*)arg;
-
- if (event->xany.serial != serial)
- return False;
-
- if ((event->type != FocusIn) && (event->type != FocusOut))
- return False;
-
- return True;
-}
-#endif
-
bool DesktopWindow::hasFocus()
{
Fl_Widget* focus;
@@ -1121,66 +1173,66 @@ bool DesktopWindow::hasFocus()
return focus->window() == this;
}
-void DesktopWindow::maybeGrabKeyboard()
-{
- if (fullscreenSystemKeys && fullscreen_active() && hasFocus())
- grabKeyboard();
-}
-
void DesktopWindow::grabKeyboard()
{
+ unsigned modifierMask;
+
// Grabbing the keyboard is fairly safe as FLTK reroutes events to the
// correct widget regardless of which low level window got the system
// event.
// FIXME: Push this stuff into FLTK.
+ if (keyboardGrabbed)
+ return;
+
+ if (!hasFocus())
+ return;
+
#if defined(WIN32)
int ret;
ret = win32_enable_lowlevel_keyboard(fl_xid(this));
if (ret != 0) {
- vlog.error(_("Failure grabbing keyboard"));
+ vlog.error(_("Failure grabbing control of the keyboard"));
+ addOverlayError(_("Failure grabbing control of the keyboard"));
return;
}
#elif defined(__APPLE__)
- int ret;
-
- ret = cocoa_capture_displays(this);
- if (ret != 0) {
- vlog.error(_("Failure grabbing keyboard"));
+ bool ret;
+
+ ret = cocoa_tap_keyboard();
+ if (!ret) {
+ vlog.error(_("Failure grabbing control of the keyboard"));
+ addOverlayError(_("Failure grabbing control of the keyboard"));
return;
}
#else
int ret;
- XEvent xev;
- unsigned long serial;
-
- serial = XNextRequest(fl_display);
-
ret = XGrabKeyboard(fl_display, fl_xid(this), True,
GrabModeAsync, GrabModeAsync, CurrentTime);
if (ret) {
if (ret == AlreadyGrabbed) {
- // It seems like we can race with the WM in some cases.
- // Try again in a bit.
- if (!Fl::has_timeout(handleGrab, this))
- Fl::add_timeout(0.500, handleGrab, this);
- } else {
- vlog.error(_("Failure grabbing keyboard"));
+ // It seems like we can race with the WM in some cases, e.g. when
+ // the WM holds the keyboard as part of handling Alt+Tab.
+ // Repeat the request a few times and see if we get it...
+ for (int attempt = 0; attempt < 5; attempt++) {
+ usleep(100000);
+ // Also throttle based on how busy the X server is
+ XSync(fl_display, False);
+ ret = XGrabKeyboard(fl_display, fl_xid(this), True,
+ GrabModeAsync, GrabModeAsync, CurrentTime);
+ if (ret != AlreadyGrabbed)
+ break;
+ }
}
- return;
- }
- // Xorg 1.20+ generates FocusIn/FocusOut even when there is no actual
- // change of focus. This causes us to get stuck in an endless loop
- // grabbing and ungrabbing the keyboard. Avoid this by filtering out
- // any focus events generated by XGrabKeyboard().
- XSync(fl_display, False);
- while (XCheckIfEvent(fl_display, &xev, &eventIsFocusWithSerial,
- (XPointer)&serial) == True) {
- vlog.debug("Ignored synthetic focus event cause by grab change");
+ if (ret) {
+ vlog.error(_("Failure grabbing control of the keyboard"));
+ addOverlayError(_("Failure grabbing control of the keyboard"));
+ return;
+ }
}
#endif
@@ -1188,39 +1240,37 @@ void DesktopWindow::grabKeyboard()
if (contains(Fl::belowmouse()))
grabPointer();
+
+ updateCaption();
+
+ modifierMask = 0;
+ for (core::EnumListEntry key : shortcutModifiers)
+ modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str());
+
+ if (modifierMask)
+ addOverlayTip(_("Press %s to release keyboard control from the session"),
+ ShortcutHandler::modifierPrefix(modifierMask, true));
}
void DesktopWindow::ungrabKeyboard()
{
- Fl::remove_timeout(handleGrab, this);
-
keyboardGrabbed = false;
ungrabPointer();
+ updateCaption();
+
#if defined(WIN32)
win32_disable_lowlevel_keyboard(fl_xid(this));
#elif defined(__APPLE__)
- cocoa_release_displays(this);
+ cocoa_untap_keyboard();
#else
// FLTK has a grab so lets not mess with it
if (Fl::grab())
return;
- XEvent xev;
- unsigned long serial;
-
- serial = XNextRequest(fl_display);
-
XUngrabKeyboard(fl_display, CurrentTime);
-
- // See grabKeyboard()
- XSync(fl_display, False);
- while (XCheckIfEvent(fl_display, &xev, &eventIsFocusWithSerial,
- (XPointer)&serial) == True) {
- vlog.debug("Ignored synthetic focus event cause by grab change");
- }
#endif
}
@@ -1251,16 +1301,6 @@ void DesktopWindow::ungrabPointer()
}
-void DesktopWindow::handleGrab(void *data)
-{
- DesktopWindow *self = (DesktopWindow*)data;
-
- assert(self);
-
- self->maybeGrabKeyboard();
-}
-
-
#define _NET_WM_STATE_ADD 1 /* add/set property */
void DesktopWindow::maximizeWindow()
{
@@ -1584,11 +1624,6 @@ void DesktopWindow::handleOptions(void *data)
{
DesktopWindow *self = (DesktopWindow*)data;
- if (fullscreenSystemKeys)
- self->maybeGrabKeyboard();
- else
- self->ungrabKeyboard();
-
// Call fullscreen_on even if active since it handles
// fullScreenMode
if (fullScreen)