aboutsummaryrefslogtreecommitdiffstats
path: root/vncviewer/DesktopWindow.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vncviewer/DesktopWindow.cxx')
-rw-r--r--vncviewer/DesktopWindow.cxx592
1 files changed, 346 insertions, 246 deletions
diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx
index 6dbd7d5f..831bb107 100644
--- a/vncviewer/DesktopWindow.cxx
+++ b/vncviewer/DesktopWindow.cxx
@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ * Copyright 2011-2025 Pierre Ossman <ossman@cendio.se> for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,11 +26,16 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
+#include <time.h>
+#include <unistd.h>
#include <sys/time.h>
-#include <rfb/LogWriter.h>
+#include <core/LogWriter.h>
+#include <core/string.h>
+#include <core/time.h>
+
#include <rfb/CMsgWriter.h>
-#include <rfb/util.h>
+#include <rfb/ScreenSet.h>
#include "DesktopWindow.h"
#include "OptionsDialog.h"
@@ -70,21 +75,21 @@ static int edge_scroll_size_y = 96;
// default: roughly 60 fps for smooth motion
#define EDGE_SCROLL_SECONDS_PER_FRAME 0.016666
-using namespace rfb;
+// Time before we show an overlay tip again
+const time_t OVERLAY_REPEAT_TIMEOUT = 600;
-static rfb::LogWriter vlog("DesktopWindow");
+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), delayedDesktopSize(false),
- keyboardGrabbed(false), mouseGrabbed(false),
+ delayedFullscreen(false), sentDesktopSize(false),
+ pendingRemoteResize(false), lastResize({0, 0}),
+ keyboardGrabbed(false), mouseGrabbed(false), regrabOnFocus(false),
statsLastUpdates(0), statsLastPixels(0), statsLastPosition(0),
statsGraph(nullptr)
{
@@ -95,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);
@@ -108,7 +113,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name,
callback(handleClose, this);
- setName(name);
+ updateCaption();
OptionsDialog::addCallback(handleOptions, this);
@@ -174,7 +179,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name,
#ifdef __APPLE__
// On OS X we can do the maximize thing properly before the
// window is showned. Other platforms handled further down...
- if (maximize) {
+ if (::maximize) {
int dummy;
Fl::screen_work_area(dummy, dummy, w, h, geom_x, geom_y);
}
@@ -199,6 +204,11 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name,
show();
+#ifdef __APPLE__
+ // FLTK does its own full screen, so disable the system one
+ cocoa_prevent_native_fullscreen(this);
+#endif
+
// Full screen events are not sent out for a hidden window,
// so send a fake one here to set up things properly.
if (fullscreen_active())
@@ -208,7 +218,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name,
// maximized property on Windows and X11 before showing the window.
// See STR #2083 and STR #2178
#ifndef __APPLE__
- if (maximize) {
+ if (::maximize) {
maximizeWindow();
}
#endif
@@ -216,22 +226,22 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name,
// Adjust layout now that we're visible and know our final size
repositionWidgets();
- if (delayedFullscreen) {
- // Hack: Fullscreen requests may be ignored, so we need a timeout for
- // when we should stop waiting. We also really need to wait for the
- // resize, which can come after the fullscreen event.
- Fl::add_timeout(0.5, handleFullscreenTimeout, this);
- fullscreen_on();
- }
-
// Throughput graph for debugging
- if (vlog.getLevel() >= LogWriter::LEVEL_DEBUG) {
+ if (vlog.getLevel() >= core::LogWriter::LEVEL_DEBUG) {
memset(&stats, 0, sizeof(stats));
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
@@ -252,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;
@@ -285,13 +296,50 @@ const rfb::PixelFormat &DesktopWindow::getPreferredPF()
}
-void DesktopWindow::setName(const char *name)
+void DesktopWindow::updateCaption()
{
- char windowNameStr[256];
+ const size_t maxLen = 100;
+ std::string windowName;
+ const char *labelFormat;
+ size_t maxNameSize;
+ std::string name;
+
+ // 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 = maxLen - strlen(labelFormat) + 2;
+
+ name = cc->server.name();
+
+ 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"
+
+ windowName = core::format(labelFormat, name.c_str());
- snprintf(windowNameStr, 256, "%.240s - TigerVNC", name);
+#pragma GCC diagnostic pop
- copy_label(windowNameStr);
+ copy_label(windowName.c_str());
}
@@ -301,15 +349,8 @@ void DesktopWindow::setName(const char *name)
void DesktopWindow::updateWindow()
{
if (firstUpdate) {
- if (cc->server.supportsSetDesktopSize) {
- // Hack: Wait until we're in the proper mode and position until
- // resizing things, otherwise we might send the wrong thing.
- if (delayedFullscreen)
- delayedDesktopSize = true;
- else
- handleDesktopSize();
- }
firstUpdate = false;
+ remoteResize();
}
viewport->updateWindow();
@@ -372,15 +413,26 @@ void DesktopWindow::resizeFramebuffer(int new_w, int new_h)
}
-void DesktopWindow::setCursor(int width, int height,
- const rfb::Point& hotspot,
- const uint8_t* data)
+void DesktopWindow::setDesktopSizeDone(unsigned result)
+{
+ pendingRemoteResize = false;
+
+ if (result != 0)
+ return;
+
+ // We might have resized again whilst waiting for the previous
+ // request, so check if we are in sync
+ remoteResize();
+}
+
+
+void DesktopWindow::setCursor()
{
- viewport->setCursor(width, height, hotspot, data);
+ viewport->setCursor();
}
-void DesktopWindow::setCursorPos(const rfb::Point& pos)
+void DesktopWindow::setCursorPos(const core::Point& pos)
{
if (!mouseGrabbed) {
// Do nothing if we do not have the mouse captured.
@@ -498,16 +550,19 @@ 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
if (fullscreen_active()) {
assert(Fl::screen_count() >= 1);
- rfb::Rect windowRect, screenRect;
+ core::Rect windowRect, screenRect;
windowRect.setXYWH(x(), y(), w(), h());
bool foundEnclosedScreen = false;
@@ -538,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);
}
}
@@ -659,51 +716,59 @@ void DesktopWindow::resize(int x, int y, int w, int h)
Fl_Window::resize(x, y, w, h);
if (resizing) {
- // Try to get the remote size to match our window size, provided
- // the following conditions are true:
- //
- // a) The user has this feature turned on
- // b) The server supports it
- // c) We're not still waiting for a chance to handle DesktopSize
- // d) We're not still waiting for startup fullscreen to kick in
- //
- if (not firstUpdate and not delayedFullscreen and
- ::remoteResize and cc->server.supportsSetDesktopSize) {
- // We delay updating the remote desktop as we tend to get a flood
- // of resize events as the user is dragging the window.
- Fl::remove_timeout(handleResizeTimeout, this);
- Fl::add_timeout(0.5, handleResizeTimeout, this);
- }
+ remoteResize();
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);
- if (strcmp((const char*)menuKey, "") != 0) {
- self->setOverlay(_("Press %s to open the context menu"),
- (const char*)menuKey);
+ // 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;
@@ -717,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
@@ -733,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;
@@ -746,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;
@@ -782,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 = 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);
}
@@ -828,10 +901,44 @@ 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
+ if (delayedFullscreen && fullscreen_active()) {
+ Fl::remove_timeout(handleFullscreenTimeout, this);
+ delayedFullscreen = false;
+ remoteResize();
+ }
break;
@@ -892,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.
@@ -904,15 +1022,33 @@ 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:
+ // In this particular place, FL_SHOW means an actual MapNotify,
+ // which means we can continue enabling initial fullscreen.
+ if (dw->delayedFullscreen) {
+ // Hack: Fullscreen requests may be ignored, so we need a
+ // timeout for when we should stop waiting. We also really need
+ // to wait for the resize, which can come after the fullscreen
+ // event.
+ Fl::add_timeout(0.5, handleFullscreenTimeout, dw);
+ dw->fullscreen_on();
}
break;
@@ -941,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);
}
@@ -958,8 +1086,8 @@ int DesktopWindow::fltkHandle(int event)
void DesktopWindow::fullscreen_on()
{
- bool allMonitors = !strcasecmp(fullScreenMode, "all");
- bool selectedMonitors = !strcasecmp(fullScreenMode, "selected");
+ bool allMonitors = fullScreenMode == "all";
+ bool selectedMonitors = fullScreenMode == "selected";
int top, bottom, left, right;
if (not selectedMonitors and not allMonitors) {
@@ -972,7 +1100,7 @@ void DesktopWindow::fullscreen_on()
std::set<int> monitors;
if (selectedMonitors and not allMonitors) {
- std::set<int> selected = fullScreenSelectedMonitors.getParam();
+ std::set<int> selected = fullScreenSelectedMonitors.getMonitors();
monitors.insert(selected.begin(), selected.end());
} else {
for (int idx = 0; idx < Fl::screen_count(); idx++)
@@ -1024,40 +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;
- 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 (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;
@@ -1072,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
@@ -1139,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
}
@@ -1202,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()
{
@@ -1252,32 +1341,13 @@ void DesktopWindow::maximizeWindow()
}
-void DesktopWindow::handleDesktopSize()
-{
- if (strcmp(desktopSize, "") != 0) {
- int width, height;
-
- // An explicit size has been requested
-
- if (sscanf(desktopSize, "%dx%d", &width, &height) != 2)
- return;
-
- remoteResize(width, height);
- } else if (::remoteResize) {
- // No explicit size, but remote resizing is on so make sure it
- // matches whatever size the window ended up being
- remoteResize(w(), h());
- }
-}
-
-
void DesktopWindow::handleResizeTimeout(void *data)
{
DesktopWindow *self = (DesktopWindow *)data;
assert(self);
- self->remoteResize(self->w(), self->h());
+ self->remoteResize();
}
@@ -1292,10 +1362,47 @@ void DesktopWindow::reconfigureFullscreen(void* /*data*/)
}
-void DesktopWindow::remoteResize(int width, int height)
+void DesktopWindow::remoteResize()
{
- ScreenSet layout;
- ScreenSet::const_iterator iter;
+ int width, height;
+ rfb::ScreenSet layout;
+ rfb::ScreenSet::const_iterator iter;
+
+ if (viewOnly)
+ return;
+
+ if (!::remoteResize)
+ return;
+ if (!cc->server.supportsSetDesktopSize)
+ return;
+
+ // Don't pester the server with a resize until we have our final size
+ if (delayedFullscreen)
+ return;
+
+ // Rate limit to one pending resize at a time
+ if (pendingRemoteResize)
+ return;
+
+ // And no more than once every 100ms
+ if (core::msSince(&lastResize) < 100) {
+ Fl::remove_timeout(handleResizeTimeout, this);
+ Fl::add_timeout((100.0 - core::msSince(&lastResize)) / 1000.0,
+ handleResizeTimeout, this);
+ return;
+ }
+
+ width = w();
+ height = h();
+
+ if (!sentDesktopSize && (strcmp(desktopSize, "") != 0)) {
+ // An explicit size has been requested
+
+ if (sscanf(desktopSize, "%dx%d", &width, &height) != 2)
+ return;
+
+ sentDesktopSize = true;
+ }
if (!fullscreen_active() || (width > w()) || (height > h())) {
// In windowed mode (or the framebuffer is so large that we need
@@ -1331,7 +1438,7 @@ void DesktopWindow::remoteResize(int width, int height)
} else {
uint32_t id;
int sx, sy, sw, sh;
- rfb::Rect viewport_rect, screen_rect;
+ core::Rect viewport_rect, screen_rect;
// In full screen we report all screens that are fully covered.
@@ -1415,6 +1522,8 @@ void DesktopWindow::remoteResize(int width, int height)
vlog.debug("%s", buffer);
}
+ pendingRemoteResize = true;
+ gettimeofday(&lastResize, nullptr);
cc->writer()->writeSetDesktopSize(width, height, layout);
}
@@ -1515,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)
@@ -1535,11 +1639,7 @@ void DesktopWindow::handleFullscreenTimeout(void *data)
assert(self);
self->delayedFullscreen = false;
-
- if (self->delayedDesktopSize) {
- self->handleDesktopSize();
- self->delayedDesktopSize = false;
- }
+ self->remoteResize();
}
void DesktopWindow::scrollTo(int x, int y)
@@ -1644,7 +1744,7 @@ void DesktopWindow::handleStatsTimeout(void *data)
updates = self->cc->getUpdateCount();
pixels = self->cc->getPixelCount();
pos = self->cc->getPosition();
- elapsed = msSince(&self->statsLastTime);
+ elapsed = core::msSince(&self->statsLastTime);
if (elapsed < 1)
elapsed = 1;
@@ -1719,11 +1819,11 @@ void DesktopWindow::handleStatsTimeout(void *data)
fl_draw(buffer, 5, statsHeight - 5);
fl_color(FL_YELLOW);
- fl_draw(siPrefix(self->stats[statsCount-1].pps, "pix/s").c_str(),
+ fl_draw(core::siPrefix(self->stats[statsCount-1].pps, "pix/s").c_str(),
5 + (statsWidth-10)/3, statsHeight - 5);
fl_color(FL_RED);
- fl_draw(siPrefix(self->stats[statsCount-1].bps * 8, "bps").c_str(),
+ fl_draw(core::siPrefix(self->stats[statsCount-1].bps * 8, "bps").c_str(),
5 + (statsWidth-10)*2/3, statsHeight - 5);
image = surface->image();