Merge pull request #11578 from akallabeth/timer

Timer implementation
This commit is contained in:
akallabeth
2025-05-16 13:00:21 +02:00
committed by GitHub
13 changed files with 684 additions and 214 deletions

View File

@@ -42,6 +42,7 @@
#include <freerdp/channels/log.h>
#include <freerdp/codec/h264.h>
#include <freerdp/codec/yuv.h>
#include <freerdp/timer.h>
#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
{

View File

@@ -21,11 +21,13 @@
#include <winpr/sysinfo.h>
#include <winpr/cast.h>
#include <freerdp/timer.h>
#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,

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -20,6 +20,9 @@
#include <math.h>
#include <winpr/assert.h>
#include <winpr/sysinfo.h>
#include <freerdp/timer.h>
#include <X11/Xutil.h>
#ifdef WITH_XRANDR
@@ -37,7 +40,7 @@
#include <freerdp/log.h>
#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);
}

101
include/freerdp/timer.h Normal file
View File

@@ -0,0 +1,101 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Timer implementation
*
* Copyright 2025 Armin Novak <anovak@thincast.com>
* 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 <stdint.h>
#include <stdbool.h>
#include <freerdp/api.h>
#include <freerdp/types.h>
#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

View File

@@ -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})

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -45,6 +45,7 @@
#include "redirection.h"
#include "capabilities.h"
#include "channels.h"
#include "timer.h"
#include <freerdp/freerdp.h>
#include <freerdp/settings.h>
@@ -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 */

321
libfreerdp/core/timer.c Normal file
View File

@@ -0,0 +1,321 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Timer implementation
*
* Copyright 2025 Armin Novak <anovak@thincast.com>
* 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 <winpr/thread.h>
#include <winpr/collections.h>
#include <freerdp/timer.h>
#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;
}

34
libfreerdp/core/timer.h Normal file
View File

@@ -0,0 +1,34 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Timer implementation
*
* Copyright 2025 Armin Novak <anovak@thincast.com>
* 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 <freerdp/api.h>
#include <freerdp/types.h>
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);

View File

@@ -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)