diff options
Diffstat (limited to 'vncviewer/DesktopWindow.cxx')
-rw-r--r-- | vncviewer/DesktopWindow.cxx | 592 |
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(); |