diff --git a/include/freerdp/channels/channels.h b/include/freerdp/channels/channels.h index df9681939..aebccb2dc 100644 --- a/include/freerdp/channels/channels.h +++ b/include/freerdp/channels/channels.h @@ -33,6 +33,9 @@ extern "C" { #endif + /** @since version 3.9.0 */ + typedef BOOL (*freerdp_channel_handle_fkt_t)(rdpContext* context, void* userdata); + FREERDP_API int freerdp_channels_client_load(rdpChannels* channels, rdpSettings* settings, PVIRTUALCHANNELENTRY entry, void* data); FREERDP_API int freerdp_channels_client_load_ex(rdpChannels* channels, rdpSettings* settings, @@ -50,6 +53,40 @@ extern "C" FREERDP_API void* freerdp_channels_get_static_channel_interface(rdpChannels* channels, const char* name); + /** @brief A channel may register an event handle and a callback function to be called by \b + * freerdp_check_event_handles + * + * If a channel can not or does not want to use a thread to process data asynchronously it can + * register a \b non-blocking function that does this processing. Notification is done with the + * \b event-handle which will trigger the RDP loop. So this function is triggered until the \b + * event-handle is reset. + * @note This function may be called even without the \b event-handle to be set, so it must be + * capable of handling these calls properly. + * + * @param channels A pointer to the channels instance to register with. Must not be \b NULL + * @param handle A \b event-handle to be used to notify the RDP main thread that the callback + * function should be called again. Must not be \b INVALID_HANDLE_PARAM + * @param fkt The callback function responsible to handle the channel specifics. Must not be \b + * NULL + * @param userdata A pointer to a channel specific context. Most likely the channel context. + * May be \b NULL if not required. + * + * @return \b TRUE if successful, \b FALSE if any error occurs. + * @since version 3.9.0 */ + FREERDP_API BOOL freerdp_client_channel_register(rdpChannels* channels, HANDLE handle, + freerdp_channel_handle_fkt_t fkt, + void* userdata); + + /** @brief Remove an existing registration for \b event-handle from the channels instance + * + * @param channels A pointer to the channels instance to register with. Must not be \b NULL + * @param handle A \b event-handle to be used to notify the RDP main thread that the callback + * function should be called again. Must not be \b INVALID_HANDLE_PARAM + * + * @return \b TRUE if successful, \b FALSE if any error occurs. + * @since version 3.9.0 */ + FREERDP_API BOOL freerdp_client_channel_unregister(rdpChannels* channels, HANDLE handle); + FREERDP_API HANDLE freerdp_channels_get_event_handle(freerdp* instance); FREERDP_API int freerdp_channels_process_pending_messages(freerdp* instance); diff --git a/libfreerdp/core/client.c b/libfreerdp/core/client.c index 47f91b7a7..12285403a 100644 --- a/libfreerdp/core/client.c +++ b/libfreerdp/core/client.c @@ -33,6 +33,12 @@ #define TAG FREERDP_TAG("core.client") +typedef struct +{ + freerdp_channel_handle_fkt_t fkt; + void* userdata; +} ChannelEventEntry; + /* Use this instance to get access to channels in VirtualChannelInit. It is set during * freerdp_connect so channels that use VirtualChannelInit must be initialized from the same thread * as freerdp_connect was called */ @@ -131,6 +137,19 @@ static BOOL CALLBACK init_channel_handles_table(PINIT_ONCE once, PVOID param, PV return TRUE; } +static void* channel_event_entry_clone(const void* data) +{ + const ChannelEventEntry* entry = data; + if (!entry) + return NULL; + + ChannelEventEntry* copy = calloc(1, sizeof(ChannelEventEntry)); + if (!copy) + return NULL; + *copy = *entry; + return copy; +} + rdpChannels* freerdp_channels_new(freerdp* instance) { wObject* obj = NULL; @@ -156,6 +175,14 @@ rdpChannels* freerdp_channels_new(freerdp* instance) obj = MessageQueue_Object(channels->queue); obj->fnObjectFree = channel_queue_free; + channels->channelEvents = HashTable_New(FALSE); + if (!channels->channelEvents) + goto error; + + obj = HashTable_ValueObject(channels->channelEvents); + WINPR_ASSERT(obj); + obj->fnObjectFree = free; + obj->fnObjectNew = channel_event_entry_clone; return channels; error: WINPR_PRAGMA_DIAG_PUSH @@ -170,13 +197,10 @@ void freerdp_channels_free(rdpChannels* channels) if (!channels) return; - DeleteCriticalSection(&channels->channelsLock); + HashTable_Free(channels->channelEvents); + MessageQueue_Free(channels->queue); - if (channels->queue) - { - MessageQueue_Free(channels->queue); - channels->queue = NULL; - } + DeleteCriticalSection(&channels->channelsLock); free(channels); } @@ -706,24 +730,45 @@ void* freerdp_channels_get_static_channel_interface(rdpChannels* channels, const HANDLE freerdp_channels_get_event_handle(freerdp* instance) { - HANDLE event = NULL; - rdpChannels* channels = NULL; - channels = instance->context->channels; - event = MessageQueue_Event(channels->queue); - return event; + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + rdpChannels* channels = instance->context->channels; + WINPR_ASSERT(channels); + + return MessageQueue_Event(channels->queue); +} + +static BOOL channels_process(const void* key, void* value, void* arg) +{ + ChannelEventEntry* entry = value; + rdpContext* context = arg; + + WINPR_UNUSED(key); + + if (!entry->fkt) + return FALSE; + return entry->fkt(context, entry->userdata); } int freerdp_channels_process_pending_messages(freerdp* instance) { - rdpChannels* channels = NULL; - channels = instance->context->channels; + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + rdpChannels* channels = instance->context->channels; + WINPR_ASSERT(channels); if (WaitForSingleObject(MessageQueue_Event(channels->queue), 0) == WAIT_OBJECT_0) { - return freerdp_channels_process_sync(channels, instance); + if (!freerdp_channels_process_sync(channels, instance)) + return -1; } - return TRUE; + if (!HashTable_Foreach(channels->channelEvents, channels_process, instance->context)) + return -1; + + return 1; } /** @@ -743,6 +788,60 @@ BOOL freerdp_channels_check_fds(rdpChannels* channels, freerdp* instance) return status; } +BOOL freerdp_client_channel_register(rdpChannels* channels, HANDLE handle, + freerdp_channel_handle_fkt_t fkt, void* userdata) +{ + if (!channels || (handle == INVALID_HANDLE_VALUE) || !fkt) + { + WLog_ERR(TAG, "Invalid function arguments (channels=%p, handle=%p, fkt=%p, userdata=%p", + channels, handle, fkt, userdata); + return FALSE; + } + + ChannelEventEntry entry = { .fkt = fkt, .userdata = userdata }; + return HashTable_Insert(channels->channelEvents, handle, &entry); +} + +BOOL freerdp_client_channel_unregister(rdpChannels* channels, HANDLE handle) +{ + if (!channels || (handle == INVALID_HANDLE_VALUE)) + { + WLog_ERR(TAG, "Invalid function arguments (channels=%p, handle=%p", channels, handle); + return FALSE; + } + + return HashTable_Remove(channels->channelEvents, handle); +} + +SSIZE_T freerdp_client_channel_get_registered_event_handles(rdpChannels* channels, HANDLE* events, + DWORD count) +{ + SSIZE_T rc = -1; + + WINPR_ASSERT(channels); + WINPR_ASSERT(events || (count == 0)); + + HashTable_Lock(channels->channelEvents); + size_t len = HashTable_Count(channels->channelEvents); + if (len <= count) + { + ULONG_PTR* keys = NULL; + const size_t nrKeys = HashTable_GetKeys(channels->channelEvents, &keys); + if ((nrKeys <= SSIZE_MAX) && (nrKeys == len)) + { + for (size_t x = 0; x < nrKeys; x++) + { + HANDLE cur = (HANDLE)keys[x]; + events[x] = cur; + } + rc = (SSIZE_T)nrKeys; + } + free(keys); + } + HashTable_Unlock(channels->channelEvents); + return rc; +} + UINT freerdp_channels_disconnect(rdpChannels* channels, freerdp* instance) { UINT error = CHANNEL_RC_OK; @@ -759,7 +858,7 @@ UINT freerdp_channels_disconnect(rdpChannels* channels, freerdp* instance) /* tell all libraries we are shutting down */ for (int index = 0; index < channels->clientDataCount; index++) { - ChannelDisconnectedEventArgs e; + ChannelDisconnectedEventArgs e = { 0 }; pChannelClientData = &channels->clientDataList[index]; if (pChannelClientData->pChannelInitEventProc) diff --git a/libfreerdp/core/client.h b/libfreerdp/core/client.h index f10e9592e..17e619201 100644 --- a/libfreerdp/core/client.h +++ b/libfreerdp/core/client.h @@ -107,6 +107,8 @@ struct rdp_channels DrdynvcClientContext* drdynvc; CRITICAL_SECTION channelsLock; + + wHashTable* channelEvents; }; FREERDP_LOCAL void freerdp_channels_free(rdpChannels* channels); @@ -121,4 +123,9 @@ FREERDP_LOCAL void freerdp_channels_register_instance(rdpChannels* channels, fre FREERDP_LOCAL UINT freerdp_channels_pre_connect(rdpChannels* channels, freerdp* instance); FREERDP_LOCAL UINT freerdp_channels_post_connect(rdpChannels* channels, freerdp* instance); +/** @since version 3.9.0 */ +FREERDP_LOCAL SSIZE_T freerdp_client_channel_get_registered_event_handles(rdpChannels* channels, + HANDLE* events, + DWORD count); + #endif /* FREERDP_LIB_CORE_CLIENT_H */ diff --git a/libfreerdp/core/freerdp.c b/libfreerdp/core/freerdp.c index cd951ce37..a42f66689 100644 --- a/libfreerdp/core/freerdp.c +++ b/libfreerdp/core/freerdp.c @@ -391,7 +391,11 @@ DWORD freerdp_get_event_handles(rdpContext* context, HANDLE* events, DWORD count else return 0; - return nCount; + const SSIZE_T rc = freerdp_client_channel_get_registered_event_handles( + context->channels, &events[nCount], count - nCount); + if (rc < 0) + return 0; + return nCount + (DWORD)rc; } /* Resend mouse cursor position to prevent session lock in prevent-session-lock mode */