FreeRDP
Loading...
Searching...
No Matches
plugin_services.c
Go to the documentation of this file.
1
20#include <freerdp/config.h>
21
22#include <stddef.h>
23
24#include <winpr/assert.h>
25#include <winpr/error.h>
26#include <winpr/synch.h>
27#include <winpr/wlog.h>
28
29#include <freerdp/log.h>
31
32#include "plugin_services.h"
33
34#define TAG FREERDP_TAG("core.services")
35
51typedef struct s_plugin_services CPluginServiceProvider;
52typedef struct s_window_info_proxy CWindowInfoServiceProxy;
53typedef struct s_window_changed_relay CWindowChangedRelay;
54
55struct s_plugin_services
56{
57 wComBase com; /* IWTSPluginServiceProvider */
59 CRITICAL_SECTION subscribeLock;
60 rdpContext* context; /* nullptr once detached */
61
62 /* the subscription to the client's window info service, while a proxy is subscribed */
63 IWTSWindowInfoService* subscribed; /* a reference */
64 CWindowChangedRelay* relay; /* a reference */
65 CWindowInfoServiceProxy* subscribers;
66};
67
68struct s_window_info_proxy
69{
70 wComBase com; /* IWTSWindowInfoService */
71 CPluginServiceProvider* provider; /* a reference */
72 IWTSWindowChangedCallback* callback; /* a reference, while subscribed */
73 CWindowInfoServiceProxy* next; /* in provider->subscribers */
74};
75
76/* what the provider subscribes to the client's service with */
77struct s_window_changed_relay
78{
79 wComBase com; /* IWTSWindowChangedCallback */
80 CPluginServiceProvider* provider; /* a reference: the relay only lives while subscribed */
81};
82
83/* ============================ helpers ============================ */
84
87WINPR_ATTR_NODISCARD
88static IWTSWindowInfoService* client_window_info(CPluginServiceProvider* sp)
89{
90 IWTSWindowInfoService* svc = nullptr;
91 EnterCriticalSection(&sp->lock);
92 if (sp->context && sp->context->windowInfoService)
93 {
94 svc = sp->context->windowInfoService;
95 WINPR_COM_ADDREF(svc);
96 }
97 LeaveCriticalSection(&sp->lock);
98 return svc;
99}
100
101/* ============================ CWindowChangedRelay ============================ */
102
103WINPR_COM_IUNKNOWN_THUNKS(CWindowChangedRelay, nullptr)
104
105static HRESULT WINPR_COM_CALL CWindowChangedRelay_OnWindowChanged(IWTSWindowChangedCallback* This,
106 const WTS_WINDOW_INFO* windowInfo)
107{
108 CWindowChangedRelay* relay = (CWindowChangedRelay*)This;
109 CPluginServiceProvider* sp = relay->provider;
110
111 /* the callbacks are called without the lock: they may call the service back */
112 size_t count = 0;
113 EnterCriticalSection(&sp->lock);
114 for (CWindowInfoServiceProxy* p = sp->subscribers; p; p = p->next)
115 count++;
116 IWTSWindowChangedCallback** callbacks =
117 count ? (IWTSWindowChangedCallback**)calloc(count, sizeof(IWTSWindowChangedCallback*))
118 : nullptr;
119 if (callbacks)
120 {
121 size_t i = 0;
122 for (CWindowInfoServiceProxy* p = sp->subscribers; p; p = p->next)
123 {
124 WINPR_COM_ADDREF(p->callback);
125 callbacks[i++] = p->callback;
126 }
127 }
128 LeaveCriticalSection(&sp->lock);
129 if (count && !callbacks)
130 return E_OUTOFMEMORY;
131
132 for (size_t i = 0; i < count; i++)
133 {
134 (void)callbacks[i]->lpVtbl->OnWindowChanged(callbacks[i], windowInfo);
135 WINPR_COM_RELEASE(callbacks[i]);
136 }
137 free((void*)callbacks);
138 return S_OK;
139}
140
141static const IWTSWindowChangedCallbackVtbl CWindowChangedRelay_vtbl = {
142 .iunknown = WINPR_COM_IUNKNOWN_ENTRIES(CWindowChangedRelay),
143 .OnWindowChanged = CWindowChangedRelay_OnWindowChanged,
144};
145
146static void CWindowChangedRelay_free(wComBase* object)
147{
148 CWindowChangedRelay* relay = (CWindowChangedRelay*)object;
149 winpr_com_base_release(&relay->provider->com);
150 free(relay);
151}
152
153WINPR_ATTR_NODISCARD
154static CWindowChangedRelay* CWindowChangedRelay_new(CPluginServiceProvider* sp)
155{
156 CWindowChangedRelay* relay = calloc(1, sizeof(*relay));
157 if (!relay)
158 return nullptr;
159 winpr_com_base_init(&relay->com, "CWindowChangedRelay", &CWindowChangedRelay_vtbl.iunknown,
160 CWindowChangedRelay_free);
161 winpr_com_base_add_ref(&sp->com);
162 relay->provider = sp;
163 return relay;
164}
165
166/* ============================ shared subscription ============================ */
167
169WINPR_ATTR_NODISCARD
170static HRESULT shared_subscribe(CPluginServiceProvider* sp, UINT64 remoteHwnd)
171{
172 if (sp->subscribed)
173 return S_OK;
174
176 if (!svc)
177 return E_UNEXPECTED;
178
179 CWindowChangedRelay* relay = CWindowChangedRelay_new(sp);
180 if (!relay)
181 {
182 WINPR_COM_RELEASE(svc);
183 return E_OUTOFMEMORY;
184 }
185
186 /* Microsoft's client ignores remoteHwnd: the first subscriber's is as good as any */
187 const HRESULT hr =
188 svc->lpVtbl->SubscribeWindowChanged(svc, remoteHwnd, (IWTSWindowChangedCallback*)relay);
189 if (FAILED(hr))
190 {
191 WLog_WARN(TAG, "the client's SubscribeWindowChanged failed: 0x%08" PRIX32, (UINT32)hr);
192 winpr_com_base_release(&relay->com);
193 WINPR_COM_RELEASE(svc);
194 return hr;
195 }
196 sp->subscribed = svc;
197 sp->relay = relay;
198 return S_OK;
199}
200
202static void shared_unsubscribe(CPluginServiceProvider* sp)
203{
204 IWTSWindowInfoService* svc = sp->subscribed;
205 CWindowChangedRelay* relay = sp->relay;
206 sp->subscribed = nullptr;
207 sp->relay = nullptr;
208 if (svc)
209 {
210 const HRESULT hr = svc->lpVtbl->UnsubscribeWindowChanged(svc);
211 if (FAILED(hr))
212 WLog_WARN(TAG, "the client's UnsubscribeWindowChanged failed: 0x%08" PRIX32,
213 (UINT32)hr);
214 WINPR_COM_RELEASE(svc);
215 }
216 if (relay)
217 winpr_com_base_release(&relay->com);
218}
219
222WINPR_ATTR_NODISCARD
223static IWTSWindowChangedCallback* proxy_unlink(CWindowInfoServiceProxy* p, BOOL* last)
224{
225 CPluginServiceProvider* sp = p->provider;
226 EnterCriticalSection(&sp->lock);
227 IWTSWindowChangedCallback* callback = p->callback;
228 p->callback = nullptr;
229 for (CWindowInfoServiceProxy** it = &sp->subscribers; *it; it = &(*it)->next)
230 {
231 if (*it == p)
232 {
233 *it = p->next;
234 break;
235 }
236 }
237 p->next = nullptr;
238 *last = (sp->subscribers == nullptr);
239 LeaveCriticalSection(&sp->lock);
240 return callback;
241}
242
243/* ============================ CWindowInfoServiceProxy ============================ */
244
245WINPR_COM_IUNKNOWN_THUNKS(CWindowInfoServiceProxy, &IID_IWTSWindowInfoService)
246
247static HRESULT WINPR_COM_CALL CWindowInfoServiceProxy_GetWindowInfo(IWTSWindowInfoService* This,
248 UINT64 remoteHwnd,
249 WTS_WINDOW_INFO* windowInfo)
250{
251 CWindowInfoServiceProxy* p = (CWindowInfoServiceProxy*)This;
252 IWTSWindowInfoService* svc = client_window_info(p->provider);
253 if (!svc)
254 return E_UNEXPECTED;
255 const HRESULT hr = svc->lpVtbl->GetWindowInfo(svc, remoteHwnd, windowInfo);
256 WINPR_COM_RELEASE(svc);
257 return hr;
258}
259
260static HRESULT WINPR_COM_CALL CWindowInfoServiceProxy_GetProcessId(IWTSWindowInfoService* This,
261 DWORD* processId)
262{
263 CWindowInfoServiceProxy* p = (CWindowInfoServiceProxy*)This;
264 IWTSWindowInfoService* svc = client_window_info(p->provider);
265 if (!svc)
266 return E_UNEXPECTED;
267 const HRESULT hr = svc->lpVtbl->GetProcessId(svc, processId);
268 WINPR_COM_RELEASE(svc);
269 return hr;
270}
271
272static HRESULT WINPR_COM_CALL CWindowInfoServiceProxy_GetRdpSessionType(IWTSWindowInfoService* This,
273 UINT32* sessionType)
274{
275 CWindowInfoServiceProxy* p = (CWindowInfoServiceProxy*)This;
276 IWTSWindowInfoService* svc = client_window_info(p->provider);
277 if (!svc)
278 return E_UNEXPECTED;
279 const HRESULT hr = svc->lpVtbl->GetRdpSessionType(svc, sessionType);
280 WINPR_COM_RELEASE(svc);
281 return hr;
282}
283
284static HRESULT WINPR_COM_CALL CWindowInfoServiceProxy_SubscribeWindowChanged(
285 IWTSWindowInfoService* This, UINT64 remoteHwnd, IWTSWindowChangedCallback* callback)
286{
287 CWindowInfoServiceProxy* p = (CWindowInfoServiceProxy*)This;
288 CPluginServiceProvider* sp = p->provider;
289 if (!callback)
290 return E_POINTER;
291
292 EnterCriticalSection(&sp->subscribeLock);
293 EnterCriticalSection(&sp->lock);
294 const BOOL already = (p->callback != nullptr);
295 const BOOL detached = (sp->context == nullptr);
296 LeaveCriticalSection(&sp->lock);
297
298 HRESULT hr = S_OK;
299 if (already)
300 hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS);
301 else if (detached)
302 hr = E_UNEXPECTED;
303 else
304 hr = shared_subscribe(sp, remoteHwnd);
305
306 if (SUCCEEDED(hr))
307 {
308 WINPR_COM_ADDREF(callback);
309 EnterCriticalSection(&sp->lock);
310 p->callback = callback;
311 p->next = sp->subscribers;
312 sp->subscribers = p;
313 LeaveCriticalSection(&sp->lock);
314 }
315 LeaveCriticalSection(&sp->subscribeLock);
316 return hr;
317}
318
319static HRESULT unsubscribe(CWindowInfoServiceProxy* p)
320{
321 CPluginServiceProvider* sp = p->provider;
322
323 EnterCriticalSection(&sp->subscribeLock);
324 EnterCriticalSection(&sp->lock);
325 const BOOL subscribed = (p->callback != nullptr);
326 LeaveCriticalSection(&sp->lock);
327
328 HRESULT hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
329 if (subscribed)
330 {
331 BOOL last = FALSE;
332 IWTSWindowChangedCallback* callback = proxy_unlink(p, &last);
333 if (last)
335 WINPR_COM_RELEASE(callback);
336 hr = S_OK;
337 }
338 LeaveCriticalSection(&sp->subscribeLock);
339 return hr;
340}
341
342static HRESULT WINPR_COM_CALL
343CWindowInfoServiceProxy_UnsubscribeWindowChanged(IWTSWindowInfoService* This)
344{
345 return unsubscribe((CWindowInfoServiceProxy*)This);
346}
347
348static const IWTSWindowInfoServiceVtbl CWindowInfoServiceProxy_vtbl = {
349 .iunknown = WINPR_COM_IUNKNOWN_ENTRIES(CWindowInfoServiceProxy),
350 .GetWindowInfo = CWindowInfoServiceProxy_GetWindowInfo,
351 .GetProcessId = CWindowInfoServiceProxy_GetProcessId,
352 .GetRdpSessionType = CWindowInfoServiceProxy_GetRdpSessionType,
353 .SubscribeWindowChanged = CWindowInfoServiceProxy_SubscribeWindowChanged,
354 .UnsubscribeWindowChanged = CWindowInfoServiceProxy_UnsubscribeWindowChanged,
355};
356
357static void CWindowInfoServiceProxy_free(wComBase* object)
358{
359 CWindowInfoServiceProxy* p = (CWindowInfoServiceProxy*)object;
360 /* released while subscribed: the subscription goes with it */
361 (void)unsubscribe(p);
362 winpr_com_base_release(&p->provider->com);
363 free(p);
364}
365
366WINPR_ATTR_NODISCARD
367static CWindowInfoServiceProxy* CWindowInfoServiceProxy_new(CPluginServiceProvider* sp)
368{
369 CWindowInfoServiceProxy* p = calloc(1, sizeof(*p));
370 if (!p)
371 return nullptr;
372 winpr_com_base_init(&p->com, "CWindowInfoServiceProxy", &CWindowInfoServiceProxy_vtbl.iunknown,
373 CWindowInfoServiceProxy_free);
374 winpr_com_base_add_ref(&sp->com);
375 p->provider = sp;
376 return p;
377}
378
379/* ============================ CPluginServiceProvider ============================ */
380
381WINPR_COM_IUNKNOWN_THUNKS(CPluginServiceProvider, &IID_IWTSPluginServiceProvider)
382
383static HRESULT WINPR_COM_CALL CPluginServiceProvider_GetService(IWTSPluginServiceProvider* This,
384 GUID ServiceId,
385 wIUnknown** ppunkObject)
386{
387 CPluginServiceProvider* sp = (CPluginServiceProvider*)This;
388 if (!ppunkObject)
389 return E_POINTER;
390 *ppunkObject = nullptr;
391
392 HRESULT hr = E_NOINTERFACE;
393 if (winpr_com_guid_equal(&ServiceId, &RDCLIENT_WINDOW_INFO_SERVICE))
394 {
396 if (svc)
397 {
398 WINPR_COM_RELEASE(svc);
399 /* a new object for each caller, as Microsoft's client does */
400 CWindowInfoServiceProxy* p = CWindowInfoServiceProxy_new(sp);
401 if (p)
402 {
403 *ppunkObject = (wIUnknown*)p;
404 hr = S_OK;
405 }
406 else
407 hr = E_OUTOFMEMORY;
408 }
409 }
410 else if (winpr_com_guid_equal(&ServiceId, &RDCLIENT_BITMAP_RENDER_SERVICE))
411 {
412 /* stateless: every renderer comes from GetMappedRenderer, the client's object is
413 * shared */
414 EnterCriticalSection(&sp->lock);
415 if (sp->context && sp->context->bitmapRenderService)
416 {
417 IWTSBitmapRenderService* svc = sp->context->bitmapRenderService;
418 WINPR_COM_ADDREF(svc);
419 *ppunkObject = (wIUnknown*)svc;
420 hr = S_OK;
421 }
422 LeaveCriticalSection(&sp->lock);
423 }
424
425 WLog_DBG(TAG, "GetService(" WINPR_COM_GUID_FMT ") -> 0x%08" PRIX32,
426 WINPR_COM_GUID_ARGS(&ServiceId), (UINT32)hr);
427 return hr;
428}
429
430static const IWTSPluginServiceProviderVtbl CPluginServiceProvider_vtbl = {
431 .iunknown = WINPR_COM_IUNKNOWN_ENTRIES(CPluginServiceProvider),
432 .GetService = CPluginServiceProvider_GetService,
433};
434
435static void CPluginServiceProvider_free(wComBase* object)
436{
437 CPluginServiceProvider* sp = (CPluginServiceProvider*)object;
438 /* subscribed proxies and the relay hold references: nothing is left here */
439 WINPR_ASSERT(!sp->subscribed);
440 WINPR_ASSERT(!sp->subscribers);
441 DeleteCriticalSection(&sp->subscribeLock);
442 DeleteCriticalSection(&sp->lock);
443 free(sp);
444}
445
447{
448 WINPR_ASSERT(context);
449
450 /* the rdp_context slots are ABI: 64 bit each, whatever the pointer size */
451 WINPR_STATIC_ASSERT(offsetof(rdpContext, windowInfoService) == 46ull * 8ull);
452 WINPR_STATIC_ASSERT(offsetof(rdpContext, bitmapRenderService) == 47ull * 8ull);
453 WINPR_STATIC_ASSERT(offsetof(rdpContext, pluginServiceProvider) == 48ull * 8ull);
454 WINPR_STATIC_ASSERT(offsetof(rdpContext, dump) == 64ull * 8ull);
455
456 /* the vtables have the flat layout of MIDL's: IUnknown's three entries, then the others */
457 WINPR_STATIC_ASSERT(offsetof(IWTSPluginServiceProviderVtbl, GetService) == 3 * sizeof(void*));
458 WINPR_STATIC_ASSERT(offsetof(IWTSBitmapRendererVtbl, Render) == 3 * sizeof(void*));
459 WINPR_STATIC_ASSERT(offsetof(IWTSBitmapRendererCallbackVtbl, OnTargetSizeChanged) ==
460 3 * sizeof(void*));
461 WINPR_STATIC_ASSERT(offsetof(IWTSBitmapRenderServiceVtbl, GetMappedRenderer) ==
462 3 * sizeof(void*));
463 WINPR_STATIC_ASSERT(offsetof(IWTSWindowChangedCallbackVtbl, OnWindowChanged) ==
464 3 * sizeof(void*));
465 WINPR_STATIC_ASSERT(offsetof(IWTSWindowInfoServiceVtbl, UnsubscribeWindowChanged) ==
466 7 * sizeof(void*));
467
468#if defined(_M_X64) || defined(__x86_64__) || defined(_M_ARM64) || defined(__aarch64__)
469 WINPR_STATIC_ASSERT(sizeof(WTS_WINDOW_INFO) == 40);
470 WINPR_STATIC_ASSERT(offsetof(WTS_WINDOW_INFO, viewWidth) == 0x10);
471 WINPR_STATIC_ASSERT(offsetof(WTS_WINDOW_INFO, scale) == 0x20);
472#endif
473
474 CPluginServiceProvider* sp = calloc(1, sizeof(*sp));
475 if (!sp)
476 return nullptr;
477 if (!InitializeCriticalSectionAndSpinCount(&sp->lock, 4000))
478 {
479 free(sp);
480 return nullptr;
481 }
482 if (!InitializeCriticalSectionAndSpinCount(&sp->subscribeLock, 4000))
483 {
484 DeleteCriticalSection(&sp->lock);
485 free(sp);
486 return nullptr;
487 }
488 winpr_com_base_init(&sp->com, "CPluginServiceProvider", &CPluginServiceProvider_vtbl.iunknown,
489 CPluginServiceProvider_free);
490 sp->context = context;
491 return (IWTSPluginServiceProvider*)sp;
492}
493
495{
496 CPluginServiceProvider* sp = (CPluginServiceProvider*)provider;
497 if (!sp)
498 return;
499
500 EnterCriticalSection(&sp->subscribeLock);
501 EnterCriticalSection(&sp->lock);
502 sp->context = nullptr;
503 LeaveCriticalSection(&sp->lock);
504 /* proxies stay subscribed (their Unsubscribe still answers S_OK), without notifications */
506 LeaveCriticalSection(&sp->subscribeLock);
507}
#define WINPR_COM_GUID_FMT
printf a GUID as {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}: WLog_DBG(TAG, "id=" WINPR_COM_GUID_FMT,...
Definition com.h:137
static BOOL winpr_com_guid_equal(const GUID *a, const GUID *b)
IsEqualGUID.
Definition com.h:130
WINPR_API void winpr_com_base_init(wComBase *object, const char *className, const wIUnknownVtbl *vtbl, pfnwComBaseFree Free)
sets the vtable and the reference count to 1, the caller owns that first reference.
Definition com.c:30
WINPR_API ULONG winpr_com_base_add_ref(wComBase *object)
AddRef of an object starting with a wComBase.
Definition com.c:43
#define WINPR_COM_IUNKNOWN_THUNKS(prefix, iid)
WINPR_COM_REFCOUNT_THUNKS plus prefix_QueryInterface answering IUnknown and iid (a const IID*,...
Definition com.h:231
#define WINPR_COM_IUNKNOWN_ENTRIES(prefix)
the initializer of a wIUnknownVtbl from prefix_QueryInterface (the thunk above or the object's own),...
Definition com.h:241
#define WINPR_COM_CALL
the calling convention of COM methods (STDMETHODCALLTYPE)
Definition com.h:73
WINPR_API ULONG winpr_com_base_release(wComBase *object)
Release of an object starting with a wComBase: calls its Free at 0.
Definition com.c:51
IUnknown, C form.
Definition com.h:111
static WINPR_ATTR_NODISCARD HRESULT shared_subscribe(CPluginServiceProvider *sp, UINT64 remoteHwnd)
subscribes to the client's service; subscribeLock held
static void shared_unsubscribe(CPluginServiceProvider *sp)
ends the subscription to the client's service, if any; subscribeLock held
IWTSPluginServiceProvider * plugin_services_new(rdpContext *context)
the context's IWTSPluginServiceProvider (rdpContext::pluginServiceProvider), one reference for the ca...
static WINPR_ATTR_NODISCARD IWTSWindowInfoService * client_window_info(CPluginServiceProvider *sp)
the client's window info service, a reference for the caller; nullptr if it has none or the provider ...
void plugin_services_detach(IWTSPluginServiceProvider *provider)
cuts the provider from the context's client services, before the client frees them (ContextFree): the...
static WINPR_ATTR_NODISCARD IWTSWindowChangedCallback * proxy_unlink(CWindowInfoServiceProxy *p, BOOL *last)
removes p from the subscribers; its callback is returned (the reference with it). subscribeLock held
#define TAG
40 bytes on x64