From 72a09b1675e455272aab828ead2a2a035458b3ef Mon Sep 17 00:00:00 2001 From: Armin Novak Date: Wed, 14 May 2025 11:05:54 +0200 Subject: [PATCH 1/5] [core,timer] Add a timer implementation Adds a timer implementation (bound to a RDP context) that is capable of handling multiple timers simultaneously. --- include/freerdp/timer.h | 101 +++++++++++ libfreerdp/core/CMakeLists.txt | 2 + libfreerdp/core/freerdp.c | 8 +- libfreerdp/core/rdp.c | 135 +++++++------- libfreerdp/core/rdp.h | 4 + libfreerdp/core/timer.c | 321 +++++++++++++++++++++++++++++++++ libfreerdp/core/timer.h | 34 ++++ 7 files changed, 536 insertions(+), 69 deletions(-) create mode 100644 include/freerdp/timer.h create mode 100644 libfreerdp/core/timer.c create mode 100644 libfreerdp/core/timer.h diff --git a/include/freerdp/timer.h b/include/freerdp/timer.h new file mode 100644 index 000000000..63d0c058c --- /dev/null +++ b/include/freerdp/timer.h @@ -0,0 +1,101 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Timer implementation + * + * Copyright 2025 Armin Novak + * Copyright 2025 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** Type definition for timer IDs + * @since version 3.16.0 + */ + typedef uint64_t FreeRDP_TimerID; + + /** @brief Callback function pointer type definition. + * An expired timer will be called, depending on \ref mainloop argument of \ref + * freerdp_timer_add, background thread or mainloop. This also greatly influence jitter and + * precision of the call. If called by \b mainloop, which might be blocked, delays for up to + * 100ms are to be expected. If called from a background thread no locking is performed, so be + * sure to lock your resources where necessary. + * + * + * @param context The RDP context this timer belongs to + * @param userdata Custom userdata provided by \ref freerdp_timer_add + * @param timerID The timer ID that expired + * @param timestamp The current timestamp for the call. The base is not specified, but the + * resolution is in nanoseconds. + * @param interval The last interval value + * + * @return A new interval (might differ from the last one set) or \b 0 to disable the timer + * + * @since version 3.16.0 + */ + typedef uint64_t (*FreeRDP_TimerCallback)(rdpContext* context, void* userdata, + FreeRDP_TimerID timerID, uint64_t timestamp, + uint64_t interval); + + /** @brief Add a new timer to the list of running timers + * + * @note While the API allows nano second precision the execution time might vary depending on + * various circumstances. + * \b mainloop executed callbacks will have a huge jitter and execution times are expected to be + * delayed up to multiple 10s of milliseconds. Current implementation also does not guarantee + * more than 10ms granularity even for background thread callbacks, but that might improve with + * newer versions. + * + * @note Current implementation limits all timers to be executed by a single background thread. + * So ensure your callbacks are not blocking for a long time as both, \b mainloop and background + * thread executed callbacks will delay execution of other tasks. + * + * @param context The RDP context the timer belongs to + * @param intervalNS The (first) timer expiration interval in nanoseconds + * @param callback The function to be called when the timer expires. Must not be \b NULL + * @param userdata Custom userdata passed to the callback. The pointer is only passed, it is up + * to the user to ensure the data exists when the timer expires. + * @param mainloop \b true run the callback in mainloop context or \b false from background + * thread + * @return A new timer ID or \b 0 in case of failure + * @since version 3.16.0 + */ + FREERDP_API FreeRDP_TimerID freerdp_timer_add(rdpContext* context, uint64_t intervalNS, + FreeRDP_TimerCallback callback, void* userdata, + bool mainloop); + + /** @brief Remove a timer from the list of running timers + * + * @param context The RDP context the timer belongs to + * @param id The timer ID to remove + * + * @return \b true if the timer was removed, \b false otherwise + * @since version 3.16.0 + */ + FREERDP_API bool freerdp_timer_remove(rdpContext* context, FreeRDP_TimerID id); + +#ifdef __cplusplus +} +#endif diff --git a/libfreerdp/core/CMakeLists.txt b/libfreerdp/core/CMakeLists.txt index 0c1fc8f08..c31e7e5f5 100644 --- a/libfreerdp/core/CMakeLists.txt +++ b/libfreerdp/core/CMakeLists.txt @@ -149,6 +149,8 @@ set(${MODULE_PREFIX}_SRCS rdstls.h aad.c aad.h + timer.c + timer.h ) set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${${MODULE_PREFIX}_GATEWAY_SRCS}) diff --git a/libfreerdp/core/freerdp.c b/libfreerdp/core/freerdp.c index bee702dc3..1f1436ccd 100644 --- a/libfreerdp/core/freerdp.c +++ b/libfreerdp/core/freerdp.c @@ -390,16 +390,16 @@ DWORD freerdp_get_event_handles(rdpContext* context, HANDLE* events, DWORD count WINPR_ASSERT(context->rdp); WINPR_ASSERT(events || (count == 0)); - nCount += transport_get_event_handles(context->rdp->transport, events, count); - - if (nCount == 0) + const size_t rrc = rdp_get_event_handles(context->rdp, &events[nCount], count - nCount); + if (rrc == 0) return 0; + nCount += WINPR_ASSERTING_INT_CAST(uint32_t, rrc); + if (events && (nCount < count + 2)) { events[nCount++] = freerdp_channels_get_event_handle(context->instance); events[nCount++] = getChannelErrorEventHandle(context); - events[nCount++] = utils_get_abort_event(context->rdp); } else return 0; diff --git a/libfreerdp/core/rdp.c b/libfreerdp/core/rdp.c index ee2732949..6d3b1ead6 100644 --- a/libfreerdp/core/rdp.c +++ b/libfreerdp/core/rdp.c @@ -2279,6 +2279,8 @@ int rdp_check_fds(rdpRdp* rdp) if (status < 0) WLog_Print(rdp->log, WLOG_DEBUG, "transport_check_fds() - %i", status); + else + status = freerdp_timer_poll(rdp->timer); return status; } @@ -2301,6 +2303,46 @@ BOOL freerdp_get_stats(rdpRdp* rdp, UINT64* inBytes, UINT64* outBytes, UINT64* i return TRUE; } +static bool rdp_new_common(rdpRdp* rdp) +{ + WINPR_ASSERT(rdp); + + bool rc = false; + rdp->transport = transport_new(rdp->context); + if (!rdp->transport) + goto fail; + + if (rdp->io) + { + if (!transport_set_io_callbacks(rdp->transport, rdp->io)) + goto fail; + } + + rdp->aad = aad_new(rdp->context, rdp->transport); + if (!rdp->aad) + goto fail; + + rdp->nego = nego_new(rdp->transport); + if (!rdp->nego) + goto fail; + + rdp->mcs = mcs_new(rdp->transport); + if (!rdp->mcs) + goto fail; + + rdp->license = license_new(rdp); + if (!rdp->license) + goto fail; + + rdp->fastpath = fastpath_new(rdp); + if (!rdp->fastpath) + goto fail; + + rc = true; +fail: + return rc; +} + /** * Instantiate new RDP module. * @return new RDP module @@ -2308,9 +2350,8 @@ BOOL freerdp_get_stats(rdpRdp* rdp, UINT64* inBytes, UINT64* outBytes, UINT64* i rdpRdp* rdp_new(rdpContext* context) { - rdpRdp* rdp = NULL; DWORD flags = 0; - rdp = (rdpRdp*)calloc(1, sizeof(rdpRdp)); + rdpRdp* rdp = (rdpRdp*)calloc(1, sizeof(rdpRdp)); if (!rdp) return NULL; @@ -2356,9 +2397,7 @@ rdpRdp* rdp_new(rdpContext* context) #endif } - rdp->transport = transport_new(context); - - if (!rdp->transport) + if (!rdp_new_common(rdp)) goto fail; { @@ -2371,15 +2410,6 @@ rdpRdp* rdp_new(rdpContext* context) *rdp->io = *io; } - rdp->aad = aad_new(context, rdp->transport); - if (!rdp->aad) - goto fail; - - rdp->license = license_new(rdp); - - if (!rdp->license) - goto fail; - rdp->input = input_new(rdp); if (!rdp->input) @@ -2390,21 +2420,6 @@ rdpRdp* rdp_new(rdpContext* context) if (!rdp->update) goto fail; - rdp->fastpath = fastpath_new(rdp); - - if (!rdp->fastpath) - goto fail; - - rdp->nego = nego_new(rdp->transport); - - if (!rdp->nego) - goto fail; - - rdp->mcs = mcs_new(rdp->transport); - - if (!rdp->mcs) - goto fail; - rdp->redirection = redirection_new(); if (!rdp->redirection) @@ -2438,6 +2453,11 @@ rdpRdp* rdp_new(rdpContext* context) rdp->abortEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!rdp->abortEvent) goto fail; + + rdp->timer = freerdp_timer_new(rdp); + if (!rdp->timer) + goto fail; + return rdp; fail: @@ -2462,12 +2482,14 @@ static void rdp_reset_free(rdpRdp* rdp) rdp->fips_decrypt = NULL; (void)security_unlock(rdp); + aad_free(rdp->aad); mcs_free(rdp->mcs); nego_free(rdp->nego); license_free(rdp->license); transport_free(rdp->transport); fastpath_free(rdp->fastpath); + rdp->aad = NULL; rdp->mcs = NULL; rdp->nego = NULL; rdp->license = NULL; @@ -2478,15 +2500,10 @@ static void rdp_reset_free(rdpRdp* rdp) BOOL rdp_reset(rdpRdp* rdp) { BOOL rc = TRUE; - rdpContext* context = NULL; - rdpSettings* settings = NULL; WINPR_ASSERT(rdp); - context = rdp->context; - WINPR_ASSERT(context); - - settings = rdp->settings; + rdpSettings* settings = rdp->settings; WINPR_ASSERT(settings); bulk_reset(rdp->bulk); @@ -2505,41 +2522,13 @@ BOOL rdp_reset(rdpRdp* rdp) if (!rc) goto fail; - rc = FALSE; - rdp->transport = transport_new(context); - if (!rdp->transport) - goto fail; - - if (rdp->io) - { - if (!transport_set_io_callbacks(rdp->transport, rdp->io)) - goto fail; - } - - aad_free(rdp->aad); - rdp->aad = aad_new(context, rdp->transport); - if (!rdp->aad) - goto fail; - - rdp->nego = nego_new(rdp->transport); - if (!rdp->nego) - goto fail; - - rdp->mcs = mcs_new(rdp->transport); - if (!rdp->mcs) + rc = rdp_new_common(rdp); + if (!rc) goto fail; if (!transport_set_layer(rdp->transport, TRANSPORT_LAYER_TCP)) goto fail; - rdp->license = license_new(rdp); - if (!rdp->license) - goto fail; - - rdp->fastpath = fastpath_new(rdp); - if (!rdp->fastpath) - goto fail; - rdp->errorInfo = 0; rc = rdp_finalize_reset_flags(rdp, TRUE); @@ -2556,6 +2545,7 @@ void rdp_free(rdpRdp* rdp) { if (rdp) { + freerdp_timer_free(rdp->timer); rdp_reset_free(rdp); freerdp_settings_free(rdp->settings); @@ -3147,3 +3137,18 @@ void rdp_log_build_warnings(rdpRdp* rdp) option_is_runtime_checks); log_build_warn_ssl(rdp); } + +size_t rdp_get_event_handles(rdpRdp* rdp, HANDLE* handles, uint32_t count) +{ + size_t nCount = transport_get_event_handles(rdp->transport, handles, count); + + if (nCount == 0) + return 0; + + if (count < nCount + 2UL) + return 0; + + handles[nCount++] = utils_get_abort_event(rdp); + handles[nCount++] = freerdp_timer_get_event(rdp->timer); + return nCount; +} diff --git a/libfreerdp/core/rdp.h b/libfreerdp/core/rdp.h index c0082251b..cfa8995d8 100644 --- a/libfreerdp/core/rdp.h +++ b/libfreerdp/core/rdp.h @@ -45,6 +45,7 @@ #include "redirection.h" #include "capabilities.h" #include "channels.h" +#include "timer.h" #include #include @@ -207,6 +208,7 @@ struct rdp_rdp wLog* log; char log_context[64]; WINPR_JSON* wellknown; + FreeRDPTimer* timer; }; FREERDP_LOCAL BOOL rdp_read_security_header(rdpRdp* rdp, wStream* s, UINT16* flags, UINT16* length); @@ -304,4 +306,6 @@ BOOL rdp_reset_runtime_settings(rdpRdp* rdp); void rdp_log_build_warnings(rdpRdp* rdp); +FREERDP_LOCAL size_t rdp_get_event_handles(rdpRdp* rdp, HANDLE* handles, uint32_t count); + #endif /* FREERDP_LIB_CORE_RDP_H */ diff --git a/libfreerdp/core/timer.c b/libfreerdp/core/timer.c new file mode 100644 index 000000000..74d63b475 --- /dev/null +++ b/libfreerdp/core/timer.c @@ -0,0 +1,321 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Timer implementation + * + * Copyright 2025 Armin Novak + * Copyright 2025 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include "rdp.h" +#include "utils.h" +#include "timer.h" + +typedef ALIGN64 struct +{ + FreeRDP_TimerID id; + uint64_t intervallNS; + uint64_t nextRunTimeNS; + FreeRDP_TimerCallback cb; + void* userdata; + rdpContext* context; + bool mainloop; +} timer_entry_t; + +struct ALIGN64 freerdp_timer_s +{ + rdpRdp* rdp; + wArrayList* entries; + HANDLE thread; + HANDLE event; + HANDLE mainevent; + size_t maxIdx; + bool running; +}; + +FreeRDP_TimerID freerdp_timer_add(rdpContext* context, uint64_t intervalNS, + FreeRDP_TimerCallback callback, void* userdata, bool mainloop) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->rdp); + + FreeRDPTimer* timer = context->rdp->timer; + WINPR_ASSERT(timer); + + if ((intervalNS == 0) || !callback) + return false; + + const uint64_t cur = winpr_GetTickCount64NS(); + const timer_entry_t entry = { .id = timer->maxIdx++, + .intervallNS = intervalNS, + .nextRunTimeNS = cur + intervalNS, + .cb = callback, + .userdata = userdata, + .context = context, + .mainloop = mainloop }; + + if (!ArrayList_Append(timer->entries, &entry)) + return 0; + (void)SetEvent(timer->event); + return entry.id; +} + +static BOOL foreach_entry(void* data, WINPR_ATTR_UNUSED size_t index, va_list ap) +{ + timer_entry_t* entry = data; + WINPR_ASSERT(entry); + + FreeRDP_TimerID id = va_arg(ap, FreeRDP_TimerID); + + if (entry->id == id) + { + /* Mark the timer to be disabled. + * It will be removed on next rescheduling event + */ + entry->intervallNS = 0; + return FALSE; + } + return TRUE; +} + +bool freerdp_timer_remove(rdpContext* context, FreeRDP_TimerID id) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->rdp); + + FreeRDPTimer* timer = context->rdp->timer; + WINPR_ASSERT(timer); + + return !ArrayList_ForEach(timer->entries, foreach_entry, id); +} + +static BOOL runTimerEvent(timer_entry_t* entry, uint64_t* now) +{ + WINPR_ASSERT(entry); + + entry->intervallNS = + entry->cb(entry->context, entry->userdata, entry->id, *now, entry->intervallNS); + *now = winpr_GetTickCount64NS(); + entry->nextRunTimeNS = *now + entry->intervallNS; + return TRUE; +} + +static BOOL runExpiredTimer(void* data, WINPR_ATTR_UNUSED size_t index, + WINPR_ATTR_UNUSED va_list ap) +{ + timer_entry_t* entry = data; + WINPR_ASSERT(entry); + WINPR_ASSERT(entry->cb); + + /* Skip all timers that have been deactivated. */ + if (entry->intervallNS == 0) + return TRUE; + + uint64_t* now = va_arg(ap, uint64_t*); + WINPR_ASSERT(now); + + bool* mainloop = va_arg(ap, bool*); + WINPR_ASSERT(mainloop); + + if (entry->nextRunTimeNS > *now) + return TRUE; + + if (entry->mainloop) + *mainloop = true; + else + runTimerEvent(entry, now); + + return TRUE; +} + +static uint64_t expire_and_reschedule(FreeRDPTimer* timer) +{ + WINPR_ASSERT(timer); + + bool mainloop = false; + uint64_t next = UINT64_MAX; + uint64_t now = winpr_GetTickCount64NS(); + + ArrayList_Lock(timer->entries); + ArrayList_ForEach(timer->entries, runExpiredTimer, &now, &mainloop); + if (mainloop) + (void)SetEvent(timer->mainevent); + + size_t pos = 0; + while (pos < ArrayList_Count(timer->entries)) + { + timer_entry_t* entry = ArrayList_GetItem(timer->entries, pos); + WINPR_ASSERT(entry); + if (entry->intervallNS == 0) + { + ArrayList_RemoveAt(timer->entries, pos); + continue; + } + if (next > entry->nextRunTimeNS) + next = entry->nextRunTimeNS; + pos++; + } + ArrayList_Unlock(timer->entries); + + if (next == UINT64_MAX) + return 0; + return next; +} + +static DWORD WINAPI timer_thread(LPVOID arg) +{ + FreeRDPTimer* timer = arg; + WINPR_ASSERT(timer); + + // TODO: Currently we only support ms granularity, look for ways to improve + DWORD timeout = INFINITE; + HANDLE handles[2] = { utils_get_abort_event(timer->rdp), timer->event }; + + while (WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, timeout) != WAIT_OBJECT_0) + { + (void)ResetEvent(timer->event); + const uint64_t next = expire_and_reschedule(timer); + const uint64_t now = winpr_GetTickCount64NS(); + if (now >= next) + { + timeout = INFINITE; + continue; + } + + const uint64_t diff = next - now; + const uint64_t diffMS = diff / 1000; + timeout = MIN(INFINITE, (uint32_t)diffMS); + } + return 0; +} + +void freerdp_timer_free(FreeRDPTimer* timer) +{ + if (!timer) + return; + + if (timer->event) + (void)SetEvent(timer->event); + timer->running = false; + if (timer->thread) + { + (void)WaitForSingleObject(timer->thread, INFINITE); + CloseHandle(timer->thread); + } + if (timer->mainevent) + CloseHandle(timer->mainevent); + if (timer->event) + CloseHandle(timer->event); + ArrayList_Free(timer->entries); + free(timer); +} + +static void* entry_new(const void* val) +{ + const timer_entry_t* entry = val; + if (!entry) + return NULL; + + timer_entry_t* copy = calloc(1, sizeof(timer_entry_t)); + if (!copy) + return NULL; + *copy = *entry; + return copy; +} + +FreeRDPTimer* freerdp_timer_new(rdpRdp* rdp) +{ + WINPR_ASSERT(rdp); + FreeRDPTimer* timer = calloc(1, sizeof(FreeRDPTimer)); + if (!timer) + return NULL; + timer->rdp = rdp; + + timer->entries = ArrayList_New(TRUE); + if (!timer->entries) + goto fail; + wObject* obj = ArrayList_Object(timer->entries); + WINPR_ASSERT(obj); + obj->fnObjectNew = entry_new; + obj->fnObjectFree = free; + + timer->event = CreateEventA(NULL, TRUE, FALSE, NULL); + if (!timer->event) + goto fail; + + timer->mainevent = CreateEventA(NULL, TRUE, FALSE, NULL); + if (!timer->mainevent) + goto fail; + + timer->running = true; + timer->thread = CreateThread(NULL, 0, timer_thread, timer, 0, NULL); + if (!timer->thread) + goto fail; + return timer; + +fail: + freerdp_timer_free(timer); + return NULL; +} + +static BOOL runExpiredTimerOnMainloop(void* data, WINPR_ATTR_UNUSED size_t index, + WINPR_ATTR_UNUSED va_list ap) +{ + timer_entry_t* entry = data; + WINPR_ASSERT(entry); + WINPR_ASSERT(entry->cb); + + /* Skip events not on mainloop */ + if (!entry->mainloop) + return TRUE; + + /* Skip all timers that have been deactivated. */ + if (entry->intervallNS == 0) + return TRUE; + + uint64_t* now = va_arg(ap, uint64_t*); + WINPR_ASSERT(now); + + if (entry->nextRunTimeNS > *now) + return TRUE; + + runTimerEvent(entry, now); + return TRUE; +} + +bool freerdp_timer_poll(FreeRDPTimer* timer) +{ + WINPR_ASSERT(timer); + + if (WaitForSingleObject(timer->mainevent, 0) != WAIT_OBJECT_0) + return true; + + ArrayList_Lock(timer->entries); + (void)ResetEvent(timer->mainevent); + uint64_t now = winpr_GetTickCount64NS(); + ArrayList_ForEach(timer->entries, runExpiredTimerOnMainloop, &now); + (void)SetEvent(timer->event); // Trigger a wakeup of timer thread to reschedule + ArrayList_Unlock(timer->entries); + return true; +} + +HANDLE freerdp_timer_get_event(FreeRDPTimer* timer) +{ + WINPR_ASSERT(timer); + return timer->mainevent; +} diff --git a/libfreerdp/core/timer.h b/libfreerdp/core/timer.h new file mode 100644 index 000000000..0234cd96d --- /dev/null +++ b/libfreerdp/core/timer.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Timer implementation + * + * Copyright 2025 Armin Novak + * Copyright 2025 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +typedef struct freerdp_timer_s FreeRDPTimer; + +FREERDP_LOCAL void freerdp_timer_free(FreeRDPTimer* timer); + +WINPR_ATTR_MALLOC(freerdp_timer_free, 1) +FREERDP_LOCAL FreeRDPTimer* freerdp_timer_new(rdpRdp* rdp); + +FREERDP_LOCAL bool freerdp_timer_poll(FreeRDPTimer* timer); +FREERDP_LOCAL HANDLE freerdp_timer_get_event(FreeRDPTimer* timer); From 06e9ceff4c47a53422ef579afcdab9cd56983592 Mon Sep 17 00:00:00 2001 From: Armin Novak Date: Wed, 14 May 2025 14:06:08 +0200 Subject: [PATCH 2/5] [channels,video] use new timer implementation Use the new timer API to push frames in a defined interval --- channels/video/client/video_main.c | 33 ++++++++++++++++++++++++++---- libfreerdp/gdi/video.c | 21 +++---------------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/channels/video/client/video_main.c b/channels/video/client/video_main.c index eae5a93ff..c82961b4b 100644 --- a/channels/video/client/video_main.c +++ b/channels/video/client/video_main.c @@ -42,6 +42,7 @@ #include #include #include +#include #define TAG CHANNELS_TAG("video") @@ -58,6 +59,7 @@ typedef struct VideoClientContext* context; BOOL initialized; + rdpContext* rdpcontext; } VIDEO_PLUGIN; #define XF_VIDEO_UNLIMITED_RATE 31 @@ -105,6 +107,7 @@ struct s_VideoClientContextPriv UINT32 lastSentRate; UINT64 nextFeedbackTime; PresentationContext* currentPresentation; + FreeRDP_TimerID timerID; }; static void PresentationContext_unref(PresentationContext** presentation); @@ -1087,6 +1090,21 @@ static UINT video_data_on_new_channel_connection(IWTSListenerCallback* pListener return CHANNEL_RC_OK; } +static uint64_t timer_cb(WINPR_ATTR_UNUSED rdpContext* context, void* userdata, + WINPR_ATTR_UNUSED FreeRDP_TimerID timerID, uint64_t timestamp, + uint64_t interval) +{ + VideoClientContext* video = userdata; + if (!video) + return 0; + if (!video->timer) + return 0; + + video->timer(video, timestamp); + + return interval; +} + /** * Function description * @@ -1140,7 +1158,12 @@ static UINT video_plugin_initialize(IWTSPlugin* plugin, IWTSVirtualChannelManage if (status == CHANNEL_RC_OK) video->dataListener->pInterface = video->wtsPlugin.pInterface; - video->initialized = status == CHANNEL_RC_OK; + if (status == CHANNEL_RC_OK) + video->context->priv->timerID = + freerdp_timer_add(video->rdpcontext, 20000, timer_cb, video->context, true); + video->initialized = video->context->priv->timerID != 0; + if (!video->initialized) + status = ERROR_INTERNAL_ERROR; return status; } @@ -1153,6 +1176,7 @@ static UINT video_plugin_terminated(IWTSPlugin* pPlugin) { VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)pPlugin; + freerdp_timer_remove(video->rdpcontext, video->context->priv->timerID); if (video->control_callback) { IWTSVirtualChannelManager* mgr = video->control_callback->channel_mgr; @@ -1186,7 +1210,7 @@ static UINT video_plugin_terminated(IWTSPlugin* pPlugin) */ FREERDP_ENTRY_POINT(UINT VCAPITYPE video_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) { - UINT error = CHANNEL_RC_OK; + UINT error = ERROR_INTERNAL_ERROR; VIDEO_PLUGIN* videoPlugin = NULL; VideoClientContext* videoContext = NULL; VideoClientContextPriv* priv = NULL; @@ -1230,8 +1254,9 @@ FREERDP_ENTRY_POINT(UINT VCAPITYPE video_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* p videoPlugin->wtsPlugin.pInterface = (void*)videoContext; videoPlugin->context = videoContext; - - error = pEntryPoints->RegisterPlugin(pEntryPoints, "video", &videoPlugin->wtsPlugin); + videoPlugin->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints); + if (videoPlugin->rdpcontext) + error = pEntryPoints->RegisterPlugin(pEntryPoints, "video", &videoPlugin->wtsPlugin); } else { diff --git a/libfreerdp/gdi/video.c b/libfreerdp/gdi/video.c index b12705b04..dddbf1e35 100644 --- a/libfreerdp/gdi/video.c +++ b/libfreerdp/gdi/video.c @@ -145,32 +145,17 @@ void gdi_video_control_uninit(rdpGdi* gdi, WINPR_ATTR_UNUSED VideoClientContext* gdi->video = NULL; } -static void gdi_video_timer(void* context, const TimerEventArgs* timer) -{ - rdpContext* ctx = (rdpContext*)context; - rdpGdi* gdi = NULL; - - WINPR_ASSERT(ctx); - WINPR_ASSERT(timer); - - gdi = ctx->gdi; - - if (gdi && gdi->video) - gdi->video->timer(gdi->video, timer->now); -} - -void gdi_video_data_init(rdpGdi* gdi, WINPR_ATTR_UNUSED VideoClientContext* video) +void gdi_video_data_init(WINPR_ATTR_UNUSED rdpGdi* gdi, WINPR_ATTR_UNUSED VideoClientContext* video) { WINPR_ASSERT(gdi); WINPR_ASSERT(gdi->context); - PubSub_SubscribeTimer(gdi->context->pubSub, gdi_video_timer); } -void gdi_video_data_uninit(rdpGdi* gdi, WINPR_ATTR_UNUSED VideoClientContext* context) +void gdi_video_data_uninit(WINPR_ATTR_UNUSED rdpGdi* gdi, + WINPR_ATTR_UNUSED VideoClientContext* context) { WINPR_ASSERT(gdi); WINPR_ASSERT(gdi->context); - PubSub_UnsubscribeTimer(gdi->context->pubSub, gdi_video_timer); } VideoSurface* VideoClient_CreateCommonContext(size_t size, UINT32 x, UINT32 y, UINT32 w, UINT32 h) From 8e3e163fd4724c93c50c72747919d73bd2d428d6 Mon Sep 17 00:00:00 2001 From: Armin Novak Date: Wed, 14 May 2025 14:34:41 +0200 Subject: [PATCH 3/5] [client,xfreerdp] remove SetWaitableTimer use --- client/X11/xf_client.c | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/client/X11/xf_client.c b/client/X11/xf_client.c index 14948b8d0..caf5bc951 100644 --- a/client/X11/xf_client.c +++ b/client/X11/xf_client.c @@ -1553,11 +1553,7 @@ static DWORD WINAPI xf_client_thread(LPVOID param) DWORD exit_code = 0; DWORD waitStatus = 0; HANDLE inputEvent = NULL; - HANDLE timer = NULL; - LARGE_INTEGER due = { 0 }; - TimerEventArgs timerEvent = { 0 }; - EventArgsInit(&timerEvent, "xfreerdp"); freerdp* instance = (freerdp*)param; WINPR_ASSERT(instance); @@ -1601,27 +1597,12 @@ static DWORD WINAPI xf_client_thread(LPVOID param) goto disconnect; } - timer = CreateWaitableTimerA(NULL, FALSE, "mainloop-periodic-timer"); - - if (!timer) - { - WLog_ERR(TAG, "failed to create timer"); - goto disconnect; - } - - due.QuadPart = 0; - - if (!SetWaitableTimer(timer, &due, 20, NULL, NULL, FALSE)) - { - goto disconnect; - } inputEvent = xfc->x11event; while (!freerdp_shall_disconnect_context(instance->context)) { HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; DWORD nCount = 0; - handles[nCount++] = timer; handles[nCount++] = inputEvent; /* @@ -1682,12 +1663,6 @@ static DWORD WINAPI xf_client_thread(LPVOID param) if (!handle_window_events(instance)) break; - - if ((waitStatus != WAIT_TIMEOUT) && (waitStatus == WAIT_OBJECT_0)) - { - timerEvent.now = GetTickCount64(); - PubSub_OnTimer(context->pubSub, context, &timerEvent); - } } if (!exit_code) @@ -1706,9 +1681,6 @@ static DWORD WINAPI xf_client_thread(LPVOID param) disconnect: - if (timer) - (void)CloseHandle(timer); - freerdp_disconnect(instance); end: ExitThread(exit_code); From 9bb7e5dfaa0dec295330c929131571878e2d47f6 Mon Sep 17 00:00:00 2001 From: Armin Novak Date: Wed, 14 May 2025 14:33:26 +0200 Subject: [PATCH 4/5] [client,xfreerdp] use FreeRDP timer API for updates Use the freerdp_timer_add API to delay a display update --- client/X11/xf_disp.c | 99 ++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 36 deletions(-) diff --git a/client/X11/xf_disp.c b/client/X11/xf_disp.c index cbeed28e5..734a17f46 100644 --- a/client/X11/xf_disp.c +++ b/client/X11/xf_disp.c @@ -20,6 +20,9 @@ #include #include #include + +#include + #include #ifdef WITH_XRANDR @@ -37,7 +40,7 @@ #include #define TAG CLIENT_TAG("x11disp") -#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */ +#define RESIZE_MIN_DELAY_NS 200000UL /* minimum delay in ms between two resizes */ struct s_xfDispContext { @@ -59,8 +62,12 @@ struct s_xfDispContext UINT32 lastSentDesktopScaleFactor; UINT32 lastSentDeviceScaleFactor; BYTE reserved3[4]; + FreeRDP_TimerID timerID; }; +static BOOL xf_disp_check_context(void* context, xfContext** ppXfc, xfDispContext** ppXfDisp, + rdpSettings** ppSettings); +static BOOL xf_disp_sendResize(xfDispContext* xfDisp, BOOL fromTimer); static UINT xf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, UINT32 nmonitors); @@ -120,31 +127,71 @@ static BOOL xf_update_last_sent(xfDispContext* xfDisp) return TRUE; } -static BOOL xf_disp_sendResize(xfDispContext* xfDisp) +static uint64_t xf_disp_OnTimer(rdpContext* context, WINPR_ATTR_UNUSED void* userdata, + WINPR_ATTR_UNUSED FreeRDP_TimerID timerID, + WINPR_ATTR_UNUSED uint64_t timestamp, + WINPR_ATTR_UNUSED uint64_t interval) + +{ + xfContext* xfc = NULL; + xfDispContext* xfDisp = NULL; + rdpSettings* settings = NULL; + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return interval; + + if (!xfDisp->activated) + return interval; + + xf_disp_sendResize(xfDisp, TRUE); + xfDisp->timerID = 0; + return 0; +} + +static BOOL update_timer(xfDispContext* xfDisp, uint64_t intervalNS) +{ + WINPR_ASSERT(xfDisp); + + if (xfDisp->timerID == 0) + { + rdpContext* context = &xfDisp->xfc->common.context; + + xfDisp->timerID = freerdp_timer_add(context, intervalNS, xf_disp_OnTimer, NULL, true); + } + return TRUE; +} + +BOOL xf_disp_sendResize(xfDispContext* xfDisp, BOOL fromTimer) { DISPLAY_CONTROL_MONITOR_LAYOUT layout = { 0 }; - xfContext* xfc = NULL; - rdpSettings* settings = NULL; if (!xfDisp || !xfDisp->xfc) return FALSE; - xfc = xfDisp->xfc; - settings = xfc->common.context.settings; + /* If there is already a timer running skip the update and wait for the timer to expire. */ + if ((xfDisp->timerID != 0) && !fromTimer) + return TRUE; + + xfContext* xfc = xfDisp->xfc; + rdpSettings* settings = xfc->common.context.settings; if (!settings) return FALSE; if (!xfDisp->activated || !xfDisp->disp) - return TRUE; + return update_timer(xfDisp, RESIZE_MIN_DELAY_NS); - if (GetTickCount64() - xfDisp->lastSentDate < RESIZE_MIN_DELAY) - return TRUE; + const uint64_t diff = winpr_GetTickCount64NS() - xfDisp->lastSentDate; + if (diff < RESIZE_MIN_DELAY_NS) + { + const uint64_t interval = RESIZE_MIN_DELAY_NS - diff; + return update_timer(xfDisp, interval); + } if (!xf_disp_settings_changed(xfDisp)) return TRUE; - xfDisp->lastSentDate = GetTickCount64(); + xfDisp->lastSentDate = winpr_GetTickCount64NS(); const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); if (mcount > 1) @@ -184,8 +231,7 @@ static BOOL xf_disp_queueResize(xfDispContext* xfDisp, UINT32 width, UINT32 heig return TRUE; xfDisp->targetWidth = width; xfDisp->targetHeight = height; - xfDisp->lastSentDate = GetTickCount64(); - return xf_disp_sendResize(xfDisp); + return xf_disp_sendResize(xfDisp, FALSE); } static BOOL xf_disp_set_window_resizable(xfDispContext* xfDisp) @@ -207,8 +253,8 @@ static BOOL xf_disp_set_window_resizable(xfDispContext* xfDisp) return TRUE; } -static BOOL xf_disp_check_context(void* context, xfContext** ppXfc, xfDispContext** ppXfDisp, - rdpSettings** ppSettings) +BOOL xf_disp_check_context(void* context, xfContext** ppXfc, xfDispContext** ppXfDisp, + rdpSettings** ppSettings) { xfContext* xfc = NULL; @@ -245,7 +291,7 @@ static void xf_disp_OnActivated(void* context, const ActivatedEventArgs* e) if (e->firstActivation) return; - xf_disp_sendResize(xfDisp); + xf_disp_sendResize(xfDisp, FALSE); } } @@ -263,27 +309,10 @@ static void xf_disp_OnGraphicsReset(void* context, const GraphicsResetEventArgs* if (xfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) { xf_disp_set_window_resizable(xfDisp); - xf_disp_sendResize(xfDisp); + xf_disp_sendResize(xfDisp, FALSE); } } -static void xf_disp_OnTimer(void* context, const TimerEventArgs* e) -{ - xfContext* xfc = NULL; - xfDispContext* xfDisp = NULL; - rdpSettings* settings = NULL; - - WINPR_UNUSED(e); - - if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) - return; - - if (!xfDisp->activated) - return; - - xf_disp_sendResize(xfDisp); -} - static void xf_disp_OnWindowStateChange(void* context, const WindowStateChangeEventArgs* e) { xfContext* xfc = NULL; @@ -298,7 +327,7 @@ static void xf_disp_OnWindowStateChange(void* context, const WindowStateChangeEv if (!xfDisp->activated || !xfc->fullscreen) return; - xf_disp_sendResize(xfDisp); + xf_disp_sendResize(xfDisp, FALSE); } xfDispContext* xf_disp_new(xfContext* xfc) @@ -335,7 +364,6 @@ xfDispContext* xf_disp_new(xfContext* xfc) freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); PubSub_SubscribeActivated(pubSub, xf_disp_OnActivated); PubSub_SubscribeGraphicsReset(pubSub, xf_disp_OnGraphicsReset); - PubSub_SubscribeTimer(pubSub, xf_disp_OnTimer); PubSub_SubscribeWindowStateChange(pubSub, xf_disp_OnWindowStateChange); return ret; } @@ -350,7 +378,6 @@ void xf_disp_free(xfDispContext* disp) wPubSub* pubSub = disp->xfc->common.context.pubSub; PubSub_UnsubscribeActivated(pubSub, xf_disp_OnActivated); PubSub_UnsubscribeGraphicsReset(pubSub, xf_disp_OnGraphicsReset); - PubSub_UnsubscribeTimer(pubSub, xf_disp_OnTimer); PubSub_UnsubscribeWindowStateChange(pubSub, xf_disp_OnWindowStateChange); } From c27433cc28d3d05b1db56e70cad2e00dd339b752 Mon Sep 17 00:00:00 2001 From: Armin Novak Date: Wed, 14 May 2025 15:20:39 +0200 Subject: [PATCH 5/5] [client,wayland] use freerdp_timer_add API --- client/Wayland/wlf_disp.c | 82 ++++++++++++++++++++++++-------------- client/Wayland/wlfreerdp.c | 30 -------------- 2 files changed, 53 insertions(+), 59 deletions(-) diff --git a/client/Wayland/wlf_disp.c b/client/Wayland/wlf_disp.c index 2a3ad7538..df5e5c218 100644 --- a/client/Wayland/wlf_disp.c +++ b/client/Wayland/wlf_disp.c @@ -21,11 +21,13 @@ #include #include +#include + #include "wlf_disp.h" #define TAG CLIENT_TAG("wayland.disp") -#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */ +#define RESIZE_MIN_DELAY_NS 200000UL /* minimum delay in ns between two resizes */ struct s_wlfDispContext { @@ -42,8 +44,12 @@ struct s_wlfDispContext UINT16 lastSentDesktopOrientation; UINT32 lastSentDesktopScaleFactor; UINT32 lastSentDeviceScaleFactor; + FreeRDP_TimerID timerID; }; +static BOOL wlf_disp_sendResize(wlfDispContext* wlfDisp, BOOL fromTimer); +static BOOL wlf_disp_check_context(void* context, wlfContext** ppwlc, wlfDispContext** ppwlfDisp, + rdpSettings** ppSettings); static UINT wlf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, size_t nmonitors); @@ -91,6 +97,7 @@ static BOOL wlf_update_last_sent(wlfDispContext* wlfDisp) settings = wlfDisp->wlc->common.context.settings; WINPR_ASSERT(settings); + wlfDisp->lastSentDate = winpr_GetTickCount64NS(); wlfDisp->lastSentWidth = wlfDisp->targetWidth; wlfDisp->lastSentHeight = wlfDisp->targetHeight; wlfDisp->lastSentDesktopOrientation = @@ -103,7 +110,39 @@ static BOOL wlf_update_last_sent(wlfDispContext* wlfDisp) return TRUE; } -static BOOL wlf_disp_sendResize(wlfDispContext* wlfDisp) +static uint64_t wlf_disp_OnTimer(rdpContext* context, WINPR_ATTR_UNUSED void* userdata, + WINPR_ATTR_UNUSED FreeRDP_TimerID timerID, + WINPR_ATTR_UNUSED uint64_t timestamp, uint64_t interval) +{ + wlfContext* wlc = NULL; + wlfDispContext* wlfDisp = NULL; + rdpSettings* settings = NULL; + + if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) + return interval; + + if (!wlfDisp->activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + return interval; + + wlf_disp_sendResize(wlfDisp, TRUE); + wlfDisp->timerID = 0; + return 0; +} + +static BOOL update_timer(wlfDispContext* wlfDisp, uint64_t intervalNS) +{ + WINPR_ASSERT(wlfDisp); + + if (wlfDisp->timerID == 0) + { + rdpContext* context = &wlfDisp->wlc->common.context; + + wlfDisp->timerID = freerdp_timer_add(context, intervalNS, wlf_disp_OnTimer, NULL, true); + } + return TRUE; +} + +BOOL wlf_disp_sendResize(wlfDispContext* wlfDisp, BOOL fromTimer) { DISPLAY_CONTROL_MONITOR_LAYOUT layout; wlfContext* wlc = NULL; @@ -119,12 +158,15 @@ static BOOL wlf_disp_sendResize(wlfDispContext* wlfDisp) return FALSE; if (!wlfDisp->activated || !wlfDisp->disp) - return TRUE; + return update_timer(wlfDisp, RESIZE_MIN_DELAY_NS); - if (GetTickCount64() - wlfDisp->lastSentDate < RESIZE_MIN_DELAY) - return TRUE; + const uint64_t now = winpr_GetTickCount64NS(); + const uint64_t diff = now - wlfDisp->lastSentDate; + if (diff < RESIZE_MIN_DELAY_NS) + return update_timer(wlfDisp, RESIZE_MIN_DELAY_NS); - wlfDisp->lastSentDate = GetTickCount64(); + if (!fromTimer && (wlfDisp->timerID != 0)) + return TRUE; if (!wlf_disp_settings_changed(wlfDisp)) return TRUE; @@ -164,8 +206,8 @@ static BOOL wlf_disp_set_window_resizable(WINPR_ATTR_UNUSED wlfDispContext* wlfD return TRUE; } -static BOOL wlf_disp_check_context(void* context, wlfContext** ppwlc, wlfDispContext** ppwlfDisp, - rdpSettings** ppSettings) +BOOL wlf_disp_check_context(void* context, wlfContext** ppwlc, wlfDispContext** ppwlfDisp, + rdpSettings** ppSettings) { wlfContext* wlc = NULL; @@ -204,7 +246,7 @@ static void wlf_disp_OnActivated(void* context, const ActivatedEventArgs* e) if (e->firstActivation) return; - wlf_disp_sendResize(wlfDisp); + wlf_disp_sendResize(wlfDisp, FALSE); } } @@ -223,26 +265,10 @@ static void wlf_disp_OnGraphicsReset(void* context, const GraphicsResetEventArgs if (wlfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) { wlf_disp_set_window_resizable(wlfDisp); - wlf_disp_sendResize(wlfDisp); + wlf_disp_sendResize(wlfDisp, FALSE); } } -static void wlf_disp_OnTimer(void* context, const TimerEventArgs* e) -{ - wlfContext* wlc = NULL; - wlfDispContext* wlfDisp = NULL; - rdpSettings* settings = NULL; - - WINPR_UNUSED(e); - if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) - return; - - if (!wlfDisp->activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) - return; - - wlf_disp_sendResize(wlfDisp); -} - wlfDispContext* wlf_disp_new(wlfContext* wlc) { wlfDispContext* ret = NULL; @@ -266,7 +292,6 @@ wlfDispContext* wlf_disp_new(wlfContext* wlc) WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); PubSub_SubscribeActivated(pubSub, wlf_disp_OnActivated); PubSub_SubscribeGraphicsReset(pubSub, wlf_disp_OnGraphicsReset); - PubSub_SubscribeTimer(pubSub, wlf_disp_OnTimer); return ret; } @@ -280,7 +305,6 @@ void wlf_disp_free(wlfDispContext* disp) wPubSub* pubSub = disp->wlc->common.context.pubSub; PubSub_UnsubscribeActivated(pubSub, wlf_disp_OnActivated); PubSub_UnsubscribeGraphicsReset(pubSub, wlf_disp_OnGraphicsReset); - PubSub_UnsubscribeTimer(pubSub, wlf_disp_OnTimer); } free(disp); @@ -369,7 +393,7 @@ BOOL wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t heig disp->targetWidth = width; disp->targetHeight = height; - return wlf_disp_sendResize(disp); + return wlf_disp_sendResize(disp, FALSE); } static UINT wlf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors, diff --git a/client/Wayland/wlfreerdp.c b/client/Wayland/wlfreerdp.c index 15d71f258..ae84d8403 100644 --- a/client/Wayland/wlfreerdp.c +++ b/client/Wayland/wlfreerdp.c @@ -476,11 +476,6 @@ static int wlfreerdp_run(freerdp* instance) wlfContext* context = NULL; HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; DWORD status = WAIT_ABANDONED; - HANDLE timer = NULL; - LARGE_INTEGER due = { 0 }; - - TimerEventArgs timerEvent; - EventArgsInit(&timerEvent, "xfreerdp"); if (!instance) return -1; @@ -496,25 +491,9 @@ static int wlfreerdp_run(freerdp* instance) return -1; } - timer = CreateWaitableTimerA(NULL, FALSE, "mainloop-periodic-timer"); - - if (!timer) - { - WLog_ERR(TAG, "failed to create timer"); - goto disconnect; - } - - due.QuadPart = 0; - - if (!SetWaitableTimer(timer, &due, 20, NULL, NULL, FALSE)) - { - goto disconnect; - } - while (!freerdp_shall_disconnect_context(instance->context)) { DWORD count = 0; - handles[count++] = timer; handles[count++] = context->displayHandle; count += freerdp_get_event_handles(instance->context, &handles[count], ARRAYSIZE(handles) - count); @@ -564,17 +543,8 @@ static int wlfreerdp_run(freerdp* instance) break; } - - if ((status != WAIT_TIMEOUT) && (status == WAIT_OBJECT_0)) - { - timerEvent.now = GetTickCount64(); - PubSub_OnTimer(context->common.context.pubSub, context, &timerEvent); - } } -disconnect: - if (timer) - (void)CloseHandle(timer); freerdp_disconnect(instance); return WINPR_ASSERTING_INT_CAST(int, status); }