diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx index bc15e74a37..c2658a70a3 100644 --- a/common/rfb/EncodeManager.cxx +++ b/common/rfb/EncodeManager.cxx @@ -297,7 +297,7 @@ void EncodeManager::writeLosslessRefresh(const Region& req, const PixelBuffer* p Region(), Point(), pb, renderedCursor); } -bool EncodeManager::handleTimeout(Timer* t) +void EncodeManager::handleTimeout(Timer* t) { if (t == &recentChangeTimer) { // Any lossy region that wasn't recently updated can @@ -307,10 +307,8 @@ bool EncodeManager::handleTimeout(Timer* t) // Will there be more to do? (i.e. do we need another round) if (!lossyRegion.subtract(pendingRefreshRegion).is_empty()) - return true; + t->repeat(); } - - return false; } void EncodeManager::doUpdate(bool allowLossy, const Region& changed_, diff --git a/common/rfb/EncodeManager.h b/common/rfb/EncodeManager.h index f2fd4ca48f..33484db8ac 100644 --- a/common/rfb/EncodeManager.h +++ b/common/rfb/EncodeManager.h @@ -61,7 +61,7 @@ namespace rfb { size_t maxUpdateSize); protected: - virtual bool handleTimeout(Timer* t); + virtual void handleTimeout(Timer* t); void doUpdate(bool allowLossy, const Region& changed, const Region& copied, const Point& copy_delta, diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx index 7a930af560..12ba0f1ab8 100644 --- a/common/rfb/SConnection.cxx +++ b/common/rfb/SConnection.cxx @@ -275,11 +275,11 @@ bool SConnection::processInitMsg() return reader_->readClientInit(); } -bool SConnection::handleAuthFailureTimeout(Timer* /*t*/) +void SConnection::handleAuthFailureTimeout(Timer* /*t*/) { if (state_ != RFBSTATE_SECURITY_FAILURE) { close("SConnection::handleAuthFailureTimeout: invalid state"); - return false; + return; } try { @@ -292,12 +292,10 @@ bool SConnection::handleAuthFailureTimeout(Timer* /*t*/) os->flush(); } catch (rdr::Exception& e) { close(e.str()); - return false; + return; } close(authFailureMsg.c_str()); - - return false; } void SConnection::throwConnFailedException(const char* format, ...) diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h index cc88cd1e64..5bc616776f 100644 --- a/common/rfb/SConnection.h +++ b/common/rfb/SConnection.h @@ -238,7 +238,7 @@ namespace rfb { bool processSecurityFailure(); bool processInitMsg(); - bool handleAuthFailureTimeout(Timer* t); + void handleAuthFailureTimeout(Timer* t); int defaultMajorVersion, defaultMinorVersion; diff --git a/common/rfb/SDesktop.h b/common/rfb/SDesktop.h index 9db0811617..94e4b0286c 100644 --- a/common/rfb/SDesktop.h +++ b/common/rfb/SDesktop.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2019 Pierre Ossman for Cendio AB + * Copyright 2009-2024 Pierre Ossman 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 @@ -49,20 +49,21 @@ namespace rfb { class SDesktop : public InputHandler { public: + // init() is called immediately when the VNCServer gets a reference + // to the SDesktop, so that a reverse reference can be set up. + virtual void init(rfb::VNCServer* vs) = 0; + // start() is called by the server when the first client authenticates // successfully, and can be used to begin any expensive tasks which are not // needed when there are no clients. A valid PixelBuffer must have been // set via the VNCServer's setPixelBuffer() method by the time this call // returns. - - virtual void start(VNCServer* vs) = 0; + virtual void start() {} // stop() is called by the server when there are no longer any // authenticated clients, and therefore the desktop can cease any - // expensive tasks. No further calls to the VNCServer passed to start() - // can be made once stop has returned. - - virtual void stop() = 0; + // expensive tasks. + virtual void stop() {} // queryConnection() is called when a connection has been // successfully authenticated. The sock and userName arguments @@ -86,6 +87,10 @@ namespace rfb { return resultProhibited; } + // frameTick() is called whenever a frame update has been processed, + // signalling that a good time to render new data + virtual void frameTick(uint64_t msc) { (void)msc; } + // InputHandler interface // pointerEvent(), keyEvent() and clientCutText() are called in response to // the relevant RFB protocol messages from clients. @@ -136,14 +141,10 @@ namespace rfb { if (buffer) delete buffer; } - virtual void start(VNCServer* vs) { + virtual void init(VNCServer* vs) { server = vs; server->setPixelBuffer(buffer); } - virtual void stop() { - server->setPixelBuffer(0); - server = 0; - } virtual void queryConnection(network::Socket* sock, const char* /*userName*/) { server->approveConnection(sock, true, NULL); diff --git a/common/rfb/Timer.cxx b/common/rfb/Timer.cxx index 4ff15bc595..e9ae52276d 100644 --- a/common/rfb/Timer.cxx +++ b/common/rfb/Timer.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2016-2018 Pierre Ossman for Cendio AB + * Copyright 2016-2024 Pierre Ossman 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 @@ -61,36 +61,20 @@ int Timer::checkTimeouts() { timeval start; if (pending.empty()) - return 0; + return -1; gettimeofday(&start, 0); while (pending.front()->isBefore(start)) { Timer* timer; - timeval before; timer = pending.front(); pending.pop_front(); - gettimeofday(&before, 0); - if (timer->cb->handleTimeout(timer)) { - timeval now; + timer->lastDueTime = timer->dueTime; + timer->cb->handleTimeout(timer); - gettimeofday(&now, 0); - - timer->dueTime = addMillis(timer->dueTime, timer->timeoutMs); - if (timer->isBefore(now)) { - // Time has jumped forwards, or we're not getting enough - // CPU time for the timers - - timer->dueTime = addMillis(before, timer->timeoutMs); - if (timer->isBefore(now)) - timer->dueTime = now; - } - - insertTimer(timer); - } else if (pending.empty()) { - return 0; - } + if (pending.empty()) + return -1; } return getNextTimeout(); } @@ -98,7 +82,12 @@ int Timer::checkTimeouts() { int Timer::getNextTimeout() { timeval now; gettimeofday(&now, 0); - int toWait = __rfbmax(1, pending.front()->getRemainingMs()); + + if (pending.empty()) + return -1; + + int toWait = pending.front()->getRemainingMs(); + if (toWait > pending.front()->timeoutMs) { if (toWait - pending.front()->timeoutMs < 1000) { vlog.info("gettimeofday is broken..."); @@ -107,8 +96,9 @@ int Timer::getNextTimeout() { // Time has jumped backwards! vlog.info("time has moved backwards!"); pending.front()->dueTime = now; - toWait = 1; + toWait = 0; } + return toWait; } @@ -128,13 +118,36 @@ void Timer::start(int timeoutMs_) { gettimeofday(&now, 0); stop(); timeoutMs = timeoutMs_; - // The rest of the code assumes non-zero timeout - if (timeoutMs <= 0) - timeoutMs = 1; dueTime = addMillis(now, timeoutMs); insertTimer(this); } +void Timer::repeat(int timeoutMs_) { + timeval now; + + gettimeofday(&now, 0); + + if (isStarted()) { + vlog.error("Incorrectly repeating already running timer"); + stop(); + } + + if (msBetween(&lastDueTime, &dueTime) != 0) + vlog.error("Timer incorrectly modified whilst repeating"); + + if (timeoutMs_ != -1) + timeoutMs = timeoutMs_; + + dueTime = addMillis(lastDueTime, timeoutMs); + if (isBefore(now)) { + // Time has jumped forwards, or we're not getting enough + // CPU time for the timers + dueTime = now; + } + + insertTimer(this); +} + void Timer::stop() { pending.remove(this); } diff --git a/common/rfb/Timer.h b/common/rfb/Timer.h index ddfce1b23a..36ec46c51c 100644 --- a/common/rfb/Timer.h +++ b/common/rfb/Timer.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2018 Pierre Ossman for Cendio AB + * Copyright 2018-2024 Pierre Ossman 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 @@ -27,93 +27,108 @@ namespace rfb { /* Timer - Cross-platform timeout handling. The caller creates instances of Timer and passes a - Callback implementation to each. The Callback will then be called with a pointer to - the Timer instance that timed-out when the timeout occurs. + Cross-platform timeout handling. The caller creates instances of + Timer and passes a Callback implementation to each. The Callback + will then be called with a pointer to the Timer instance that + timed-out when the timeout occurs. - The static methods of Timer are used by the main loop of the application both to - dispatch elapsed Timer callbacks and to determine how long to wait in select() for - the next timeout to occur. + The static methods of Timer are used by the main loop of the + application both to dispatch elapsed Timer callbacks and to + determine how long to wait in select() for the next timeout to + occur. - For classes that can be derived it's best to use MethodTimer which can call a specific - method on the class, thus avoiding conflicts when subclassing. + For classes that can be derived it's best to use MethodTimer which + can call a specific method on the class, thus avoiding conflicts + when subclassing. */ struct Timer { struct Callback { // handleTimeout - // Passed a pointer to the Timer that has timed out. If the handler returns true - // then the Timer is reset and left running, causing another timeout after the - // appropriate interval. + // Passed a pointer to the Timer that has timed out. If the + // handler returns true then the Timer is reset and left + // running, causing another timeout after the appropriate + // interval. // If the handler returns false then the Timer is cancelled. - virtual bool handleTimeout(Timer* t) = 0; + virtual void handleTimeout(Timer* t) = 0; virtual ~Callback() {} }; // checkTimeouts() - // Dispatches any elapsed Timers, and returns the number of milliseconds until the - // next Timer will timeout. + // Dispatches any elapsed Timers, and returns the number of + // milliseconds until the next Timer will timeout. static int checkTimeouts(); // getNextTimeout() - // Returns the number of milliseconds until the next timeout, without dispatching - // any elapsed Timers. + // Returns the number of milliseconds until the next timeout, + // without dispatching any elapsed Timers. static int getNextTimeout(); // Create a Timer with the specified callback handler Timer(Callback* cb_) {cb = cb_;} ~Timer() {stop();} - // startTimer - // Starts the timer, causing a timeout after the specified number of milliseconds. - // If the timer is already active then it will be implicitly cancelled and re-started. + // start() + // Starts the timer, causing a timeout after the specified number + // of milliseconds. If the timer is already active then it will + // be implicitly cancelled and re-started. void start(int timeoutMs_); - // stopTimer + // repeat() + // Restarts the timer in a way that repeats that last timeout. + // This allows you to have a periodic timer without the risk of + // accumulating drift caused by processing delays. + // A new interval can be specified, otherwise the previous + // interval is reused. + void repeat(int timeoutMs_=-1); + + // stop() // Cancels the timer. void stop(); - // isStarted + // isStarted() // Determines whether the timer is started. bool isStarted(); - // getTimeoutMs + // getTimeoutMs() // Determines the previously used timeout value, if any. // Usually used with isStarted() to get the _current_ timeout. int getTimeoutMs(); - // getRemainingMs + // getRemainingMs() // Determines how many milliseconds are left before the Timer // will timeout. Only valid for an active timer. int getRemainingMs(); - // isBefore - // Determine whether the Timer will timeout before the specified time. + // isBefore() + // Determine whether the Timer will timeout before the specified + // time. bool isBefore(timeval other); protected: - timeval dueTime; + timeval dueTime, lastDueTime; int timeoutMs; Callback* cb; static void insertTimer(Timer* t); - // The list of currently active Timers, ordered by time left until timeout. + // The list of currently active Timers, ordered by time left until + // timeout. static std::list pending; }; template class MethodTimer : public Timer, public Timer::Callback { public: - MethodTimer(T* obj_, bool (T::*cb_)(Timer*)) + MethodTimer(T* obj_, void (T::*cb_)(Timer*)) : Timer(this), obj(obj_), cb(cb_) {} - virtual bool handleTimeout(Timer* t) { return (obj->*cb)(t); } + virtual void handleTimeout(Timer* t) { (obj->*cb)(t); } private: T* obj; - bool (T::*cb)(Timer*); + void (T::*cb)(Timer*); }; }; diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index f1194eb69a..306bba1dc4 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -800,7 +800,7 @@ void VNCSConnectionST::supportsLEDState() writer()->writeLEDState(); } -bool VNCSConnectionST::handleTimeout(Timer* t) +void VNCSConnectionST::handleTimeout(Timer* t) { try { if ((t == &congestionTimer) || @@ -812,8 +812,6 @@ bool VNCSConnectionST::handleTimeout(Timer* t) if (t == &idleTimer) close("Idle timeout"); - - return false; } bool VNCSConnectionST::isShiftPressed() diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 2f117a7579..3a9ec2422b 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -141,7 +141,7 @@ namespace rfb { virtual void supportsLEDState(); // Timer callbacks - virtual bool handleTimeout(Timer* t); + virtual void handleTimeout(Timer* t); // Internal methods diff --git a/common/rfb/VNCServer.h b/common/rfb/VNCServer.h index cf14bd8621..3ac9fb9466 100644 --- a/common/rfb/VNCServer.h +++ b/common/rfb/VNCServer.h @@ -73,6 +73,9 @@ namespace rfb { virtual void blockUpdates() = 0; virtual void unblockUpdates() = 0; + virtual uint64_t getMsc() = 0; + virtual void queueMsc(uint64_t target) = 0; + // setPixelBuffer() tells the server to use the given pixel buffer (and // optionally a modified screen layout). If this differs in size from // the previous pixel buffer, this may result in protocol messages being diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index fb4210680c..b9579f12aa 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2019 Pierre Ossman for Cendio AB + * Copyright 2009-2024 Pierre Ossman 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 @@ -90,10 +90,12 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) renderedCursorInvalid(false), keyRemapper(&KeyRemapper::defInstance), idleTimer(this), disconnectTimer(this), connectTimer(this), - frameTimer(this) + msc(0), queuedMsc(0), frameTimer(this) { slog.debug("creating single-threaded server %s", name.c_str()); + desktop_->init(this); + // FIXME: Do we really want to kick off these right away? if (rfb::Server::maxIdleTime) idleTimer.start(secsToMillis(rfb::Server::maxIdleTime)); @@ -248,11 +250,22 @@ void VNCServerST::unblockUpdates() blockCounter--; - // Restart the frame clock if we have updates - if (blockCounter == 0) { - if (!comparer->is_empty()) - startFrameClock(); - } + // Restart the frame clock in case we have updates + if (blockCounter == 0) + startFrameClock(); +} + +uint64_t VNCServerST::getMsc() +{ + return msc; +} + +void VNCServerST::queueMsc(uint64_t target) +{ + if (target > queuedMsc) + queuedMsc = target; + + startFrameClock(); } void VNCServerST::setPixelBuffer(PixelBuffer* pb_, const ScreenSet& layout) @@ -623,22 +636,33 @@ SConnection* VNCServerST::getConnection(network::Socket* sock) { return 0; } -bool VNCServerST::handleTimeout(Timer* t) +void VNCServerST::handleTimeout(Timer* t) { if (t == &frameTimer) { - // We keep running until we go a full interval without any updates - if (comparer->is_empty()) - return false; + int timeout; - writeUpdate(); + // We keep running until we go a full interval without any updates, + // or there are no active clients anymore + if (comparer->is_empty() || !desktopStarted) { + // Unless something waits for us to advance the frame count + if (queuedMsc < msc) + return; + } // If this is the first iteration then we need to adjust the timeout - if (frameTimer.getTimeoutMs() != 1000/rfb::Server::frameRate) { - frameTimer.start(1000/rfb::Server::frameRate); - return false; - } + timeout = 1000/rfb::Server::frameRate; - return true; + // If there are no clients, then slow down the clock + if (!desktopStarted) + timeout = 1000; + + frameTimer.repeat(timeout); + + if (!comparer->is_empty() && desktopStarted) + writeUpdate(); + + msc++; + desktop->frameTick(msc); } else if (t == &idleTimer) { slog.info("MaxIdleTime reached, exiting"); desktop->terminate(); @@ -649,8 +673,6 @@ bool VNCServerST::handleTimeout(Timer* t) slog.info("MaxConnectionTime reached, exiting"); desktop->terminate(); } - - return false; } void VNCServerST::queryConnection(VNCSConnectionST* client, @@ -714,7 +736,7 @@ void VNCServerST::startDesktop() { if (!desktopStarted) { slog.debug("starting desktop"); - desktop->start(this); + desktop->start(); if (!pb) throw Exception("SDesktop::start() did not set a valid PixelBuffer"); desktopStarted = true; @@ -722,6 +744,12 @@ void VNCServerST::startDesktop() // stopped, so flush those out if (!comparer->is_empty()) writeUpdate(); + // If the frame clock is running, then it will be running slowly, + // so give it a kick to run at normal speed right away + if (frameTimer.isStarted()) { + stopFrameClock(); + startFrameClock(); + } } } @@ -731,7 +759,6 @@ void VNCServerST::stopDesktop() slog.debug("stopping desktop"); desktopStarted = false; desktop->stop(); - stopFrameClock(); } } @@ -759,9 +786,18 @@ void VNCServerST::startFrameClock() return; if (blockCounter > 0) return; - if (!desktopStarted) + + // Anyone actually interested in frames? + if (comparer->is_empty() && (queuedMsc <= msc)) return; + // Run the frame clock very slowly if there are no clients to actually + // send updates to + if (!desktopStarted) { + frameTimer.start(1000); + return; + } + // The first iteration will be just half a frame as we get a very // unstable update rate if we happen to be perfectly in sync with // the application's update rate diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index d303831e8d..90c8d75395 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -79,6 +79,8 @@ namespace rfb { virtual void blockUpdates(); virtual void unblockUpdates(); + virtual uint64_t getMsc(); + virtual void queueMsc(uint64_t target); virtual void setPixelBuffer(PixelBuffer* pb, const ScreenSet& layout); virtual void setPixelBuffer(PixelBuffer* pb); virtual void setScreenLayout(const ScreenSet& layout); @@ -153,7 +155,7 @@ namespace rfb { protected: // Timer callbacks - virtual bool handleTimeout(Timer* t); + virtual void handleTimeout(Timer* t); // - Internal methods @@ -204,6 +206,7 @@ namespace rfb { Timer disconnectTimer; Timer connectTimer; + uint64_t msc, queuedMsc; Timer frameTimer; }; diff --git a/common/rfb/util.h b/common/rfb/util.h index cafea20994..b47ac4c9de 100644 --- a/common/rfb/util.h +++ b/common/rfb/util.h @@ -73,13 +73,6 @@ namespace rfb { // HELPER functions for timeout handling - // soonestTimeout() is a function to help work out the soonest of several - // timeouts. - inline void soonestTimeout(int* timeout, int newTimeout) { - if (newTimeout && (!*timeout || newTimeout < *timeout)) - *timeout = newTimeout; - } - // secsToMillis() turns seconds into milliseconds, capping the value so it // can't wrap round and become -ve inline int secsToMillis(int secs) { diff --git a/unix/vncconfig/QueryConnectDialog.cxx b/unix/vncconfig/QueryConnectDialog.cxx index e13af34ba1..e725de7d12 100644 --- a/unix/vncconfig/QueryConnectDialog.cxx +++ b/unix/vncconfig/QueryConnectDialog.cxx @@ -74,14 +74,13 @@ void QueryConnectDialog::buttonActivate(TXButton* b) { callback->queryRejected(); } -bool QueryConnectDialog::handleTimeout(rfb::Timer* /*t*/) { +void QueryConnectDialog::handleTimeout(rfb::Timer* t) { if (timeUntilReject-- == 0) { unmap(); callback->queryTimedOut(); - return false; } else { refreshTimeout(); - return true; + t->repeat(); } } diff --git a/unix/vncconfig/QueryConnectDialog.h b/unix/vncconfig/QueryConnectDialog.h index f685dc346e..dcf64e4027 100644 --- a/unix/vncconfig/QueryConnectDialog.h +++ b/unix/vncconfig/QueryConnectDialog.h @@ -43,7 +43,7 @@ class QueryConnectDialog : public TXDialog, public TXEventHandler, void handleEvent(TXWindow*, XEvent* ) { } void deleteWindow(TXWindow*); void buttonActivate(TXButton* b); - bool handleTimeout(rfb::Timer* t); + void handleTimeout(rfb::Timer* t); private: void refreshTimeout(); TXLabel addressLbl, address, userLbl, user, timeoutLbl, timeout; diff --git a/unix/vncconfig/vncconfig.cxx b/unix/vncconfig/vncconfig.cxx index e0c9928a92..30d04ca6c8 100644 --- a/unix/vncconfig/vncconfig.cxx +++ b/unix/vncconfig/vncconfig.cxx @@ -313,7 +313,7 @@ int main(int argc, char** argv) // Process expired timers and get the time until the next one int timeoutMs = Timer::checkTimeouts(); - if (timeoutMs) { + if (timeoutMs >= 0) { tv.tv_sec = timeoutMs / 1000; tv.tv_usec = (timeoutMs % 1000) * 1000; tvp = &tv; diff --git a/unix/x0vncserver/XDesktop.cxx b/unix/x0vncserver/XDesktop.cxx index da298d3562..55ea9667e3 100644 --- a/unix/x0vncserver/XDesktop.cxx +++ b/unix/x0vncserver/XDesktop.cxx @@ -232,9 +232,13 @@ void XDesktop::poll() { } } +void XDesktop::init(VNCServer* vs) +{ + server = vs; +} -void XDesktop::start(VNCServer* vs) { - +void XDesktop::start() +{ // Determine actual number of buttons of the X pointer device. unsigned char btnMap[8]; int numButtons = XGetPointerMapping(dpy, btnMap, 8); @@ -249,7 +253,6 @@ void XDesktop::start(VNCServer* vs) { pb = new XPixelBuffer(dpy, factory, geometry->getRect()); vlog.info("Allocated %s", pb->getImage()->classDesc()); - server = vs; server->setPixelBuffer(pb, computeScreenLayout()); #ifdef HAVE_XDAMAGE @@ -292,7 +295,6 @@ void XDesktop::stop() { queryConnectDialog = 0; server->setPixelBuffer(0); - server = 0; delete pb; pb = 0; diff --git a/unix/x0vncserver/XDesktop.h b/unix/x0vncserver/XDesktop.h index 1cb73f4366..fc230e5b8b 100644 --- a/unix/x0vncserver/XDesktop.h +++ b/unix/x0vncserver/XDesktop.h @@ -47,7 +47,8 @@ class XDesktop : public rfb::SDesktop, virtual ~XDesktop(); void poll(); // -=- SDesktop interface - virtual void start(rfb::VNCServer* vs); + virtual void init(rfb::VNCServer* vs); + virtual void start(); virtual void stop(); virtual void terminate(); bool isRunning(); diff --git a/unix/x0vncserver/x0vncserver.cxx b/unix/x0vncserver/x0vncserver.cxx index 8e27e62b8d..ffaf57889b 100644 --- a/unix/x0vncserver/x0vncserver.cxx +++ b/unix/x0vncserver/x0vncserver.cxx @@ -382,7 +382,7 @@ int main(int argc, char** argv) PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage); while (!caughtSignal) { - int wait_ms; + int wait_ms, nextTimeout; struct timeval tv; fd_set rfds, wfds; std::list sockets; @@ -426,7 +426,10 @@ int main(int argc, char** argv) } } - soonestTimeout(&wait_ms, Timer::checkTimeouts()); + // Trigger timers and check when the next will expire + nextTimeout = Timer::checkTimeouts(); + if (nextTimeout >= 0 && nextTimeout < wait_ms) + wait_ms = nextTimeout; tv.tv_sec = wait_ms / 1000; tv.tv_usec = (wait_ms % 1000) * 1000; diff --git a/unix/xserver/hw/vnc/Makefile.am b/unix/xserver/hw/vnc/Makefile.am index 1e98596615..40eba4f205 100644 --- a/unix/xserver/hw/vnc/Makefile.am +++ b/unix/xserver/hw/vnc/Makefile.am @@ -11,12 +11,12 @@ COMMON_LIBS=$(NETWORK_LIB) $(RFB_LIB) $(RDR_LIB) $(OS_LIB) $(UNIXCOMMON_LIB) noinst_LTLIBRARIES = libvnccommon.la HDRS = vncExtInit.h vncHooks.h \ - vncBlockHandler.h vncSelection.h \ + vncBlockHandler.h vncPresent.h vncSelection.h \ XorgGlue.h XserverDesktop.h xorg-version.h \ vncInput.h RFBGlue.h libvnccommon_la_SOURCES = $(HDRS) \ - vncExt.c vncExtInit.cc vncHooks.c vncSelection.c \ + vncExt.c vncExtInit.cc vncHooks.c vncPresent.c vncSelection.c \ vncBlockHandler.c XorgGlue.c RandrGlue.c RFBGlue.cc XserverDesktop.cc \ vncInput.c vncInputXKB.c qnum_to_xorgevdev.c qnum_to_xorgkbd.c diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc index 4cf37937ba..d4ee16b83e 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.cc +++ b/unix/xserver/hw/vnc/XserverDesktop.cc @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2019 Pierre Ossman for Cendio AB + * Copyright 2009-2024 Pierre Ossman for Cendio AB * Copyright 2014 Brian P. Hinz * * This is free software; you can redistribute it and/or modify @@ -54,6 +54,7 @@ extern "C" { void vncSetGlueContext(int screenIndex); +void vncPresentMscEvent(uint64_t id, uint64_t msc); } using namespace rfb; @@ -145,15 +146,26 @@ void XserverDesktop::refreshScreenLayout() server->setScreenLayout(::computeScreenLayout(&outputIdMap)); } -void XserverDesktop::start(rfb::VNCServer* vs) +uint64_t XserverDesktop::getMsc() { - // We already own the server object, and we always keep it in a - // ready state - assert(vs == server); + return server->getMsc(); +} + +void XserverDesktop::queueMsc(uint64_t id, uint64_t msc) +{ + pendingMsc[id] = msc; + server->queueMsc(msc); } -void XserverDesktop::stop() +void XserverDesktop::abortMsc(uint64_t id) { + pendingMsc.erase(id); +} + +void XserverDesktop::init(rfb::VNCServer* vs) +{ + // We already own the server object, and we always keep it in a + // ready state } void XserverDesktop::queryConnection(network::Socket* sock, @@ -395,7 +407,7 @@ void XserverDesktop::blockHandler(int* timeout) // Trigger timers and check when the next will expire int nextTimeout = Timer::checkTimeouts(); - if (nextTimeout > 0 && (*timeout == -1 || nextTimeout < *timeout)) + if (nextTimeout >= 0 && (*timeout == -1 || nextTimeout < *timeout)) *timeout = nextTimeout; } catch (rdr::Exception& e) { vlog.error("XserverDesktop::blockHandler: %s",e.str()); @@ -476,6 +488,22 @@ unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height, return result; } +void XserverDesktop::frameTick(uint64_t msc) +{ + std::map::iterator iter, next; + + for (iter = pendingMsc.begin(); iter != pendingMsc.end();) { + next = iter; next++; + + if (iter->second <= msc) { + pendingMsc.erase(iter->first); + vncPresentMscEvent(iter->first, msc); + } + + iter = next; + } +} + void XserverDesktop::handleClipboardRequest() { vncHandleClipboardRequest(); @@ -518,13 +546,11 @@ void XserverDesktop::keyEvent(uint32_t keysym, uint32_t keycode, bool down) vncKeyboardEvent(keysym, keycode, down); } -bool XserverDesktop::handleTimeout(Timer* t) +void XserverDesktop::handleTimeout(Timer* t) { if (t == &queryConnectTimer) { server->approveConnection(queryConnectSocket, false, "The attempt to prompt the user to " "accept the connection failed"); } - - return false; } diff --git a/unix/xserver/hw/vnc/XserverDesktop.h b/unix/xserver/hw/vnc/XserverDesktop.h index 9cc5bf792e..e604295b0a 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.h +++ b/unix/xserver/hw/vnc/XserverDesktop.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2019 Pierre Ossman for Cendio AB + * Copyright 2009-2024 Pierre Ossman 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 @@ -59,6 +59,9 @@ class XserverDesktop : public rfb::SDesktop, public rfb::FullFramePixelBuffer, void unblockUpdates(); void setFramebuffer(int w, int h, void* fbptr, int stride); void refreshScreenLayout(); + uint64_t getMsc(); + void queueMsc(uint64_t id, uint64_t msc); + void abortMsc(uint64_t id); void requestClipboard(); void announceClipboard(bool available); void sendClipboardData(const char* data); @@ -88,8 +91,7 @@ class XserverDesktop : public rfb::SDesktop, public rfb::FullFramePixelBuffer, const char* rejectMsg=0); // rfb::SDesktop callbacks - virtual void start(rfb::VNCServer* vs); - virtual void stop(); + virtual void init(rfb::VNCServer* vs); virtual void terminate(); virtual void queryConnection(network::Socket* sock, const char* userName); @@ -97,6 +99,7 @@ class XserverDesktop : public rfb::SDesktop, public rfb::FullFramePixelBuffer, virtual void keyEvent(uint32_t keysym, uint32_t keycode, bool down); virtual unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout); + virtual void frameTick(uint64_t msc); virtual void handleClipboardRequest(); virtual void handleClipboardAnnounce(bool available); virtual void handleClipboardData(const char* data); @@ -112,7 +115,7 @@ class XserverDesktop : public rfb::SDesktop, public rfb::FullFramePixelBuffer, rfb::VNCServer* sockserv, bool read, bool write); - virtual bool handleTimeout(rfb::Timer* t); + virtual void handleTimeout(rfb::Timer* t); private: @@ -129,6 +132,8 @@ class XserverDesktop : public rfb::SDesktop, public rfb::FullFramePixelBuffer, OutputIdMap outputIdMap; + std::map pendingMsc; + rfb::Point oldCursorPos; }; #endif diff --git a/unix/xserver/hw/vnc/vncExtInit.cc b/unix/xserver/hw/vnc/vncExtInit.cc index b260e62664..4003e768b3 100644 --- a/unix/xserver/hw/vnc/vncExtInit.cc +++ b/unix/xserver/hw/vnc/vncExtInit.cc @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2019 Pierre Ossman for Cendio AB + * Copyright 2011-2024 Pierre Ossman 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 @@ -484,6 +484,33 @@ void vncRefreshScreenLayout(int scrIdx) } } +uint64_t vncGetMsc(int scrIdx) +{ + try { + return desktop[scrIdx]->getMsc(); + } catch (rdr::Exception& e) { + vncFatalError("vncGetMsc: %s\n", e.str()); + } +} + +void vncQueueMsc(int scrIdx, uint64_t id, uint64_t msc) +{ + try { + desktop[scrIdx]->queueMsc(id, msc); + } catch (rdr::Exception& e) { + vncFatalError("vncQueueMsc: %s\n", e.str()); + } +} + +void vncAbortMsc(int scrIdx, uint64_t id) +{ + try { + desktop[scrIdx]->abortMsc(id); + } catch (rdr::Exception& e) { + vncFatalError("vncAbortMsc: %s\n", e.str()); + } +} + int vncOverrideParam(const char *nameAndValue) { const char* equalSign = strchr(nameAndValue, '='); diff --git a/unix/xserver/hw/vnc/vncExtInit.h b/unix/xserver/hw/vnc/vncExtInit.h index 333e32a90d..6b37fe6274 100644 --- a/unix/xserver/hw/vnc/vncExtInit.h +++ b/unix/xserver/hw/vnc/vncExtInit.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2019 Pierre Ossman for Cendio AB + * Copyright 2011-2024 Pierre Ossman 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 @@ -87,6 +87,10 @@ void vncPreScreenResize(int scrIdx); void vncPostScreenResize(int scrIdx, int success, int width, int height); void vncRefreshScreenLayout(int scrIdx); +uint64_t vncGetMsc(int scrIdx); +void vncQueueMsc(int scrIdx, uint64_t id, uint64_t msc); +void vncAbortMsc(int scrIdx, uint64_t id); + int vncOverrideParam(const char *nameAndValue); #ifdef __cplusplus diff --git a/unix/xserver/hw/vnc/vncPresent.c b/unix/xserver/hw/vnc/vncPresent.c new file mode 100644 index 0000000000..89dcc1d053 --- /dev/null +++ b/unix/xserver/hw/vnc/vncPresent.c @@ -0,0 +1,93 @@ +/* Copyright 2024 Pierre Ossman 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_DIX_CONFIG_H +#include +#endif + +#include "vncExtInit.h" +#include "vncPresent.h" + +#include + +static RRCrtcPtr vncPresentGetCrtc(WindowPtr window) +{ + ScreenPtr pScreen = window->drawable.pScreen; + rrScrPrivPtr rp = rrGetScrPriv(pScreen); + + /* All output is synchronized, so just pick the first active crtc */ + for (int c = 0; c < rp->numCrtcs; c++) { + RRCrtcPtr crtc; + + crtc = rp->crtcs[c]; + if (crtc->mode == NULL) + continue; + + return crtc; + } + + return NULL; +} + +static int vncPresentGetUstMsc(RRCrtcPtr crtc, CARD64 *ust, CARD64 *msc) +{ + *ust = GetTimeInMicros(); + *msc = vncGetMsc(crtc->pScreen->myNum); + + return Success; +} + +static int vncPresentQueueVBlank(RRCrtcPtr crtc, uint64_t event_id, + uint64_t msc) +{ + vncQueueMsc(crtc->pScreen->myNum, event_id, msc); + return Success; +} + +void vncPresentMscEvent(uint64_t id, uint64_t msc) +{ + present_event_notify(id, GetTimeInMicros(), msc); +} + +static void vncPresentAbortVBlank(RRCrtcPtr crtc, uint64_t event_id, + uint64_t msc) +{ + vncAbortMsc(crtc->pScreen->myNum, event_id); +} + +static void vncPresentFlush(WindowPtr window) +{ +} + +static present_screen_info_rec vncPresentScreenInfo = { + .version = PRESENT_SCREEN_INFO_VERSION, + + .get_crtc = vncPresentGetCrtc, + .get_ust_msc = vncPresentGetUstMsc, + .queue_vblank = vncPresentQueueVBlank, + .abort_vblank = vncPresentAbortVBlank, + .flush = vncPresentFlush, + + .capabilities = PresentCapabilityNone, +}; + +Bool +vncPresentInit(ScreenPtr screen) +{ + return present_screen_init(screen, &vncPresentScreenInfo); +} \ No newline at end of file diff --git a/unix/xserver/hw/vnc/vncPresent.h b/unix/xserver/hw/vnc/vncPresent.h new file mode 100644 index 0000000000..1740740208 --- /dev/null +++ b/unix/xserver/hw/vnc/vncPresent.h @@ -0,0 +1,27 @@ +/* Copyright 2024 Pierre Ossman 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __PRESENT_H__ +#define __PRESENT_H__ + +#include + +Bool vncPresentInit(ScreenPtr screen); +void vncPresentMscEvent(uint64_t id, uint64_t msc); + +#endif diff --git a/unix/xserver/hw/vnc/xvnc.c b/unix/xserver/hw/vnc/xvnc.c index 16a2883169..706c9d5a0c 100644 --- a/unix/xserver/hw/vnc/xvnc.c +++ b/unix/xserver/hw/vnc/xvnc.c @@ -36,6 +36,7 @@ from the X Consortium. #include "RFBGlue.h" #include "XorgGlue.h" #include "RandrGlue.h" +#include "vncPresent.h" #include "xorg-version.h" #include @@ -670,14 +671,14 @@ vncRandRScreenSetSize(ScreenPtr pScreen, ret = vncRandRCrtcSet(pScreen, crtc, NULL, crtc->x, crtc->y, crtc->rotation, 0, NULL); if (!ret) - ErrorF("Warning: Unable to disable CRTC that is outside of new screen dimensions"); + ErrorF("Warning: Unable to disable CRTC that is outside of new screen dimensions\n"); continue; } /* Just needs to be resized to a temporary mode */ mode = vncRandRModeGet(width - crtc->x, height - crtc->y); if (mode == NULL) { - ErrorF("Warning: Unable to create custom mode for %dx%d", + ErrorF("Warning: Unable to create custom mode for %dx%d\n", width - crtc->x, height - crtc->y); continue; } @@ -687,7 +688,7 @@ vncRandRScreenSetSize(ScreenPtr pScreen, crtc->numOutputs, crtc->outputs); RRModeDestroy(mode); if (!ret) - ErrorF("Warning: Unable to crop CRTC to new screen dimensions"); + ErrorF("Warning: Unable to crop CRTC to new screen dimensions\n"); } return TRUE; @@ -1085,6 +1086,10 @@ vncScreenInit(ScreenPtr pScreen, int argc, char **argv) if (!ret) return FALSE; + ret = vncPresentInit(pScreen); + if (!ret) + ErrorF("Failed to initialize Present extension\n"); + return TRUE; } /* end vncScreenInit */ diff --git a/vncviewer/EmulateMB.cxx b/vncviewer/EmulateMB.cxx index 72335eb84e..cc680df43e 100644 --- a/vncviewer/EmulateMB.cxx +++ b/vncviewer/EmulateMB.cxx @@ -277,13 +277,13 @@ void EmulateMB::filterPointerEvent(const rfb::Point& pos, int buttonMask) } } -bool EmulateMB::handleTimeout(rfb::Timer *t) +void EmulateMB::handleTimeout(rfb::Timer *t) { int action1, action2; int buttonMask; if (&timer != t) - return false; + return; if ((state > 10) || (state < 0)) throw rfb::Exception(_("Invalid state for 3 button emulation")); @@ -310,8 +310,6 @@ bool EmulateMB::handleTimeout(rfb::Timer *t) } state = stateTab[state][4][2]; - - return false; } void EmulateMB::sendAction(const rfb::Point& pos, int buttonMask, int action) diff --git a/vncviewer/EmulateMB.h b/vncviewer/EmulateMB.h index 132f44fe29..77fdec669c 100644 --- a/vncviewer/EmulateMB.h +++ b/vncviewer/EmulateMB.h @@ -31,7 +31,7 @@ class EmulateMB : public rfb::Timer::Callback { protected: virtual void sendPointerEvent(const rfb::Point& pos, int buttonMask)=0; - virtual bool handleTimeout(rfb::Timer *t); + virtual void handleTimeout(rfb::Timer *t); private: void sendAction(const rfb::Point& pos, int buttonMask, int action); diff --git a/vncviewer/GestureHandler.cxx b/vncviewer/GestureHandler.cxx index c3cc15314e..ed99555e79 100644 --- a/vncviewer/GestureHandler.cxx +++ b/vncviewer/GestureHandler.cxx @@ -323,14 +323,12 @@ bool GestureHandler::hasDetectedGesture() return true; } -bool GestureHandler::handleTimeout(rfb::Timer* t) +void GestureHandler::handleTimeout(rfb::Timer* t) { if (t == &longpressTimer) longpressTimeout(); else if (t == &twoTouchTimer) twoTouchTimeout(); - - return false; } void GestureHandler::longpressTimeout() diff --git a/vncviewer/GestureHandler.h b/vncviewer/GestureHandler.h index 372b78655f..b07454df8f 100644 --- a/vncviewer/GestureHandler.h +++ b/vncviewer/GestureHandler.h @@ -42,7 +42,7 @@ class GestureHandler : public rfb::Timer::Callback { private: bool hasDetectedGesture(); - virtual bool handleTimeout(rfb::Timer* t); + virtual void handleTimeout(rfb::Timer* t); void longpressTimeout(); void twoTouchTimeout(); diff --git a/vncviewer/vncviewer.cxx b/vncviewer/vncviewer.cxx index 91e2be3b2d..366327faf8 100644 --- a/vncviewer/vncviewer.cxx +++ b/vncviewer/vncviewer.cxx @@ -188,7 +188,7 @@ static void mainloop(const char* vncserver, network::Socket* sock) int next_timer; next_timer = Timer::checkTimeouts(); - if (next_timer == 0) + if (next_timer < 0) next_timer = INT_MAX; if (Fl::wait((double)next_timer / 1000.0) < 0.0) { diff --git a/win/rfb_win32/EventManager.cxx b/win/rfb_win32/EventManager.cxx index 0f33f5ac7c..f034d36d49 100644 --- a/win/rfb_win32/EventManager.cxx +++ b/win/rfb_win32/EventManager.cxx @@ -64,14 +64,14 @@ void EventManager::removeEvent(HANDLE event) { int EventManager::checkTimeouts() { - return 0; + return -1; } BOOL EventManager::getMessage(MSG* msg, HWND hwnd, UINT minMsg, UINT maxMsg) { while (true) { // - Process any pending timeouts - DWORD timeout = checkTimeouts(); - if (timeout == 0) + int timeout = checkTimeouts(); + if (timeout < 0) timeout = INFINITE; // - Events take precedence over messages diff --git a/win/rfb_win32/SDisplay.cxx b/win/rfb_win32/SDisplay.cxx index 612f883bfa..dd1ac7da21 100644 --- a/win/rfb_win32/SDisplay.cxx +++ b/win/rfb_win32/SDisplay.cxx @@ -96,7 +96,12 @@ SDisplay::~SDisplay() // -=- SDesktop interface -void SDisplay::start(VNCServer* vs) +void SDisplay::init(VNCServer* vs) +{ + server = vs; +} + +void SDisplay::start() { vlog.debug("starting"); @@ -105,7 +110,6 @@ void SDisplay::start(VNCServer* vs) setConsoleSession(); // Start the SDisplay core - server = vs; startCore(); vlog.debug("started"); @@ -135,10 +139,8 @@ void SDisplay::stop() } // Stop the SDisplayCore - if (server) - server->setPixelBuffer(0); + server->setPixelBuffer(0); stopCore(); - server = 0; vlog.debug("stopped"); diff --git a/win/rfb_win32/SDisplay.h b/win/rfb_win32/SDisplay.h index febc720ecb..5b55cd66ab 100644 --- a/win/rfb_win32/SDisplay.h +++ b/win/rfb_win32/SDisplay.h @@ -71,7 +71,8 @@ namespace rfb { // -=- SDesktop interface - virtual void start(VNCServer* vs); + virtual void init(VNCServer* vs); + virtual void start(); virtual void stop(); virtual void terminate(); virtual void queryConnection(network::Socket* sock, diff --git a/win/rfb_win32/SocketManager.cxx b/win/rfb_win32/SocketManager.cxx index 8e88b79b83..015ba549f6 100644 --- a/win/rfb_win32/SocketManager.cxx +++ b/win/rfb_win32/SocketManager.cxx @@ -169,9 +169,9 @@ void SocketManager::setDisable(VNCServer* srvr, bool disable) int SocketManager::checkTimeouts() { int timeout = EventManager::checkTimeouts(); - std::map::iterator i; - for (i=listeners.begin(); i!=listeners.end(); i++) - soonestTimeout(&timeout, Timer::checkTimeouts()); + int nextTimeout = Timer::checkTimeouts(); + if (nextTimeout >= 0 && nextTimeout < timeout) + timeout = nextTimeout; std::list shutdownSocks; std::map::iterator j, j_next;