FreeRDP
Loading...
Searching...
No Matches
cache/bitmap.c
1
20#include <freerdp/config.h>
21
22#include <stdio.h>
23
24#include <winpr/crt.h>
25#include <winpr/assert.h>
26#include <winpr/cast.h>
27
28#include <freerdp/freerdp.h>
29#include <freerdp/constants.h>
30#include <winpr/stream.h>
31
32#include <freerdp/log.h>
33#include <freerdp/gdi/bitmap.h>
34
35#include "../gdi/gdi.h"
36#include "../core/graphics.h"
37
38#include "bitmap.h"
39#include "cache.h"
40
41#define TAG FREERDP_TAG("cache.bitmap")
42
43static rdpBitmap* bitmap_cache_get(rdpBitmapCache* bitmapCache, UINT32 id, UINT32 index);
44static BOOL bitmap_cache_put(rdpBitmapCache* bitmapCache, UINT32 id, UINT32 index,
45 rdpBitmap* bitmap);
46
47static BOOL update_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt)
48{
49 rdpBitmap* bitmap = nullptr;
50 rdpCache* cache = nullptr;
51
52 cache = context->cache;
53
54 if (memblt->cacheId == 0xFF)
55 bitmap = offscreen_cache_get(cache->offscreen, memblt->cacheIndex);
56 else
57 bitmap = bitmap_cache_get(cache->bitmap, (BYTE)memblt->cacheId, memblt->cacheIndex);
58
59 /* XP-SP2 servers sometimes ask for cached bitmaps they've never defined. */
60 if (bitmap == nullptr)
61 return TRUE;
62
63 memblt->bitmap = bitmap;
64 return IFCALLRESULT(TRUE, cache->bitmap->MemBlt, context, memblt);
65}
66
67static BOOL update_gdi_mem3blt(rdpContext* context, MEM3BLT_ORDER* mem3blt)
68{
69 rdpBitmap* bitmap = nullptr;
70 rdpCache* cache = context->cache;
71 rdpBrush* brush = &mem3blt->brush;
72 BOOL ret = TRUE;
73
74 if (mem3blt->cacheId == 0xFF)
75 bitmap = offscreen_cache_get(cache->offscreen, mem3blt->cacheIndex);
76 else
77 bitmap = bitmap_cache_get(cache->bitmap, (BYTE)mem3blt->cacheId, mem3blt->cacheIndex);
78
79 /* XP-SP2 servers sometimes ask for cached bitmaps they've never defined. */
80 if (!bitmap)
81 return TRUE;
82
83 const BYTE style = WINPR_ASSERTING_INT_CAST(UINT8, brush->style);
84
85 if (brush->style & CACHED_BRUSH)
86 {
87 brush->data = brush_cache_get(cache->brush, brush->index, &brush->bpp);
88
89 if (!brush->data)
90 return FALSE;
91
92 brush->style = 0x03;
93 }
94
95 mem3blt->bitmap = bitmap;
96 IFCALLRET(cache->bitmap->Mem3Blt, ret, context, mem3blt);
97 brush->style = style;
98 return ret;
99}
100
101static BOOL update_gdi_cache_bitmap(rdpContext* context, const CACHE_BITMAP_ORDER* cacheBitmap)
102{
103 rdpBitmap* bitmap = nullptr;
104 rdpBitmap* prevBitmap = nullptr;
105 rdpCache* cache = context->cache;
106 bitmap = Bitmap_Alloc(context);
107
108 if (!bitmap)
109 return FALSE;
110
111 if (!Bitmap_SetDimensions(bitmap, WINPR_ASSERTING_INT_CAST(UINT16, cacheBitmap->bitmapWidth),
112 WINPR_ASSERTING_INT_CAST(UINT16, cacheBitmap->bitmapHeight)))
113 goto fail;
114
115 if (!bitmap->Decompress(context, bitmap, cacheBitmap->bitmapDataStream,
116 cacheBitmap->bitmapWidth, cacheBitmap->bitmapHeight,
117 cacheBitmap->bitmapBpp, cacheBitmap->bitmapLength,
118 cacheBitmap->compressed, RDP_CODEC_ID_NONE))
119 goto fail;
120
121 if (!bitmap->New(context, bitmap))
122 goto fail;
123
124 prevBitmap = bitmap_cache_get(cache->bitmap, cacheBitmap->cacheId, cacheBitmap->cacheIndex);
125 Bitmap_Free(context, prevBitmap);
126 if (!bitmap_cache_put(cache->bitmap, cacheBitmap->cacheId, cacheBitmap->cacheIndex, bitmap))
127 goto fail;
128 return TRUE;
129
130fail:
131 Bitmap_Free(context, bitmap);
132 return FALSE;
133}
134
135static BOOL update_gdi_cache_bitmap_v2(rdpContext* context, CACHE_BITMAP_V2_ORDER* cacheBitmapV2)
136
137{
138 rdpBitmap* prevBitmap = nullptr;
139 rdpCache* cache = context->cache;
140 rdpSettings* settings = context->settings;
141 rdpBitmap* bitmap = Bitmap_Alloc(context);
142
143 if (!bitmap)
144 return FALSE;
145
146 const UINT32 ColorDepth = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
147 bitmap->key64 = ((UINT64)cacheBitmapV2->key1 | (((UINT64)cacheBitmapV2->key2) << 32));
148
149 if (!cacheBitmapV2->bitmapBpp)
150 cacheBitmapV2->bitmapBpp = ColorDepth;
151
152 if ((ColorDepth == 15) && (cacheBitmapV2->bitmapBpp == 16))
153 cacheBitmapV2->bitmapBpp = ColorDepth;
154
155 if (!Bitmap_SetDimensions(bitmap, WINPR_ASSERTING_INT_CAST(UINT16, cacheBitmapV2->bitmapWidth),
156 WINPR_ASSERTING_INT_CAST(UINT16, cacheBitmapV2->bitmapHeight)))
157 goto fail;
158
159 if (!bitmap->Decompress(context, bitmap, cacheBitmapV2->bitmapDataStream,
160 cacheBitmapV2->bitmapWidth, cacheBitmapV2->bitmapHeight,
161 cacheBitmapV2->bitmapBpp, cacheBitmapV2->bitmapLength,
162 cacheBitmapV2->compressed, RDP_CODEC_ID_NONE))
163 goto fail;
164
165 prevBitmap = bitmap_cache_get(cache->bitmap, cacheBitmapV2->cacheId, cacheBitmapV2->cacheIndex);
166
167 if (!bitmap->New(context, bitmap))
168 goto fail;
169
170 Bitmap_Free(context, prevBitmap);
171 if (!bitmap_cache_put(cache->bitmap, cacheBitmapV2->cacheId, cacheBitmapV2->cacheIndex, bitmap))
172 goto fail;
173 return TRUE;
174
175fail:
176 Bitmap_Free(context, bitmap);
177 return FALSE;
178}
179
180static BOOL update_gdi_cache_bitmap_v3(rdpContext* context, CACHE_BITMAP_V3_ORDER* cacheBitmapV3)
181{
182 rdpBitmap* bitmap = nullptr;
183 rdpBitmap* prevBitmap = nullptr;
184 BOOL compressed = TRUE;
185 rdpCache* cache = context->cache;
186 rdpSettings* settings = context->settings;
187 BITMAP_DATA_EX* bitmapData = &cacheBitmapV3->bitmapData;
188 bitmap = Bitmap_Alloc(context);
189
190 if (!bitmap)
191 return FALSE;
192
193 const UINT32 ColorDepth = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
194 bitmap->key64 = ((UINT64)cacheBitmapV3->key1 | (((UINT64)cacheBitmapV3->key2) << 32));
195
196 if (!cacheBitmapV3->bpp)
197 cacheBitmapV3->bpp = ColorDepth;
198
199 compressed = (bitmapData->codecID != RDP_CODEC_ID_NONE);
200
201 if (!Bitmap_SetDimensions(bitmap, WINPR_ASSERTING_INT_CAST(UINT16, bitmapData->width),
202 WINPR_ASSERTING_INT_CAST(UINT16, bitmapData->height)))
203 goto fail;
204
205 if (!bitmap->Decompress(context, bitmap, bitmapData->data, bitmapData->width,
206 bitmapData->height, bitmapData->bpp, bitmapData->length, compressed,
207 bitmapData->codecID))
208 goto fail;
209
210 if (!bitmap->New(context, bitmap))
211 goto fail;
212
213 prevBitmap = bitmap_cache_get(cache->bitmap, cacheBitmapV3->cacheId, cacheBitmapV3->cacheIndex);
214 Bitmap_Free(context, prevBitmap);
215 if (!bitmap_cache_put(cache->bitmap, cacheBitmapV3->cacheId, cacheBitmapV3->cacheIndex, bitmap))
216 goto fail;
217 return TRUE;
218
219fail:
220 Bitmap_Free(context, bitmap);
221 return FALSE;
222}
223
224rdpBitmap* bitmap_cache_get(rdpBitmapCache* bitmapCache, UINT32 id, UINT32 index)
225{
226 rdpBitmap* bitmap = nullptr;
227
228 if (id >= bitmapCache->maxCells)
229 {
230 WLog_ERR(TAG, "get invalid bitmap cell id: %" PRIu32 "", id);
231 return nullptr;
232 }
233
234 if (index == BITMAP_CACHE_WAITING_LIST_INDEX)
235 {
236 index = bitmapCache->cells[id].number;
237 }
238 else if (index > bitmapCache->cells[id].number)
239 {
240 WLog_ERR(TAG, "get invalid bitmap index %" PRIu32 " in cell id: %" PRIu32 "", index, id);
241 return nullptr;
242 }
243
244 bitmap = bitmapCache->cells[id].entries[index];
245 return bitmap;
246}
247
248BOOL bitmap_cache_put(rdpBitmapCache* bitmapCache, UINT32 id, UINT32 index, rdpBitmap* bitmap)
249{
250 WINPR_ASSERT(bitmapCache);
251 if (id > bitmapCache->maxCells)
252 {
253 WLog_ERR(TAG, "put invalid bitmap cell id: %" PRIu32 "", id);
254 return FALSE;
255 }
256
257 if (index == BITMAP_CACHE_WAITING_LIST_INDEX)
258 {
259 index = bitmapCache->cells[id].number;
260 }
261 else if (index > bitmapCache->cells[id].number)
262 {
263 WLog_ERR(TAG, "put invalid bitmap index %" PRIu32 " in cell id: %" PRIu32 "", index, id);
264 return FALSE;
265 }
266
267 bitmapCache->cells[id].entries[index] = bitmap;
268 return TRUE;
269}
270
271void bitmap_cache_register_callbacks(rdpUpdate* update)
272{
273 rdpCache* cache = nullptr;
274
275 WINPR_ASSERT(update);
276 WINPR_ASSERT(update->context);
277 WINPR_ASSERT(update->context->cache);
278
279 cache = update->context->cache;
280 WINPR_ASSERT(cache);
281
282 if (!freerdp_settings_get_bool(update->context->settings, FreeRDP_DeactivateClientDecoding))
283 {
284 cache->bitmap->MemBlt = update->primary->MemBlt;
285 cache->bitmap->Mem3Blt = update->primary->Mem3Blt;
286 update->primary->MemBlt = update_gdi_memblt;
287 update->primary->Mem3Blt = update_gdi_mem3blt;
288 update->secondary->CacheBitmap = update_gdi_cache_bitmap;
289 update->secondary->CacheBitmapV2 = update_gdi_cache_bitmap_v2;
290 update->secondary->CacheBitmapV3 = update_gdi_cache_bitmap_v3;
291 update->BitmapUpdate = gdi_bitmap_update;
292 }
293}
294
295static int bitmap_cache_save_persistent(rdpBitmapCache* bitmapCache)
296{
297 WINPR_ASSERT(bitmapCache);
298
299 rdpContext* context = bitmapCache->context;
300 rdpSettings* settings = context->settings;
301
302 const UINT32 version = freerdp_settings_get_uint32(settings, FreeRDP_BitmapCacheVersion);
303
304 if (version != 2)
305 return 0; /* persistent bitmap cache already saved in egfx channel */
306
307 if (!freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled))
308 return 0;
309
310 const char* BitmapCachePersistFile =
311 freerdp_settings_get_string(settings, FreeRDP_BitmapCachePersistFile);
312 if (!BitmapCachePersistFile)
313 return 0;
314
315 rdpPersistentCache* persistent = persistent_cache_new();
316
317 if (!persistent)
318 return -1;
319
320 int status = persistent_cache_open(persistent, BitmapCachePersistFile, TRUE, version);
321
322 if (status < 1)
323 goto end;
324
325 if (bitmapCache->cells)
326 {
327 for (UINT32 i = 0; i < bitmapCache->maxCells; i++)
328 {
329 BITMAP_V2_CELL* cell = &bitmapCache->cells[i];
330 for (UINT32 j = 0; j < cell->number + 1 && cell->entries; j++)
331 {
332 PERSISTENT_CACHE_ENTRY cacheEntry = WINPR_C_ARRAY_INIT;
333 rdpBitmap* bitmap = cell->entries[j];
334
335 if (!bitmap || !bitmap->key64)
336 continue;
337
338 cacheEntry.key64 = bitmap->key64;
339
340 cacheEntry.width = WINPR_ASSERTING_INT_CAST(UINT16, bitmap->width);
341 cacheEntry.height = WINPR_ASSERTING_INT_CAST(UINT16, bitmap->height);
342 const UINT64 size = 4ULL * bitmap->width * bitmap->height;
343 if (size > UINT32_MAX)
344 continue;
345 cacheEntry.size = (UINT32)size;
346 cacheEntry.flags = 0;
347 cacheEntry.data = bitmap->data;
348
349 if (persistent_cache_write_entry(persistent, &cacheEntry) < 1)
350 {
351 status = -1;
352 goto end;
353 }
354 }
355 }
356 }
357
358 status = 1;
359
360end:
361 persistent_cache_free(persistent);
362 return status;
363}
364
365static void bitmap_cache_cell_free(rdpBitmapCache* bitmapCache)
366{
367 WINPR_ASSERT(bitmapCache);
368 if (!bitmapCache->cells)
369 return;
370
371 /* iterate through maxCells + 1 to also free the overallocated extra slot */
372 for (UINT32 i = 0; i <= bitmapCache->maxCells; i++)
373 {
374 UINT32 j = 0;
375 BITMAP_V2_CELL* cell = &bitmapCache->cells[i];
376
377 if (!cell->entries)
378 continue;
379
380 for (j = 0; j < cell->number + 1; j++)
381 {
382 rdpBitmap* bitmap = cell->entries[j];
383 Bitmap_Free(bitmapCache->context, bitmap);
384 }
385
386 free((void*)cell->entries);
387 }
388
389 free(bitmapCache->cells);
390
391 bitmapCache->cells = nullptr;
392}
393
394BOOL bitmap_cache_resize(rdpBitmapCache* bitmapCache)
395{
396 if (!bitmapCache)
397 return FALSE;
398
399 rdpContext* context = bitmapCache->context;
400 WINPR_ASSERT(context);
401
402 rdpSettings* settings = context->settings;
403 WINPR_ASSERT(settings);
404
405 const UINT32 BitmapCacheV2NumCells =
406 freerdp_settings_get_uint32(settings, FreeRDP_BitmapCacheV2NumCells);
407
408 if (BitmapCacheV2NumCells == bitmapCache->maxCells)
409 return TRUE;
410
411 bitmap_cache_cell_free(bitmapCache);
412
413 /* overallocate by 1. older RDP servers do send a off by 1 cache index. */
414 bitmapCache->cells =
415 (BITMAP_V2_CELL*)calloc(BitmapCacheV2NumCells + 1ull, sizeof(BITMAP_V2_CELL));
416
417 if (!bitmapCache->cells)
418 return FALSE;
419 bitmapCache->maxCells = BitmapCacheV2NumCells;
420
421 for (UINT32 i = 0; i < bitmapCache->maxCells; i++)
422 {
423 const BITMAP_CACHE_V2_CELL_INFO* info =
424 freerdp_settings_get_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, i);
425 BITMAP_V2_CELL* cell = &bitmapCache->cells[i];
426 UINT32 nr = info->numEntries;
427 /* allocate an extra entry for BITMAP_CACHE_WAITING_LIST_INDEX */
428 cell->entries = (rdpBitmap**)calloc((nr + 1), sizeof(rdpBitmap*));
429
430 if (!cell->entries)
431 return FALSE;
432 cell->number = nr;
433 }
434
435 /* initialize the overallocated extra slot for old RDP servers that send
436 * cacheId == maxCells; use a minimal allocation since no protocol-negotiated
437 * capacity exists for this slot */
438 {
439 BITMAP_V2_CELL* extra = &bitmapCache->cells[bitmapCache->maxCells];
440 /* allocate an extra entry for BITMAP_CACHE_WAITING_LIST_INDEX */
441 extra->entries = (rdpBitmap**)calloc(1, sizeof(rdpBitmap*));
442
443 if (!extra->entries)
444 return FALSE;
445 extra->number = 0;
446 }
447
448 return TRUE;
449}
450
451rdpBitmapCache* bitmap_cache_new(rdpContext* context)
452{
453 WINPR_ASSERT(context);
454
455 rdpBitmapCache* bitmapCache = (rdpBitmapCache*)calloc(1, sizeof(rdpBitmapCache));
456
457 if (!bitmapCache)
458 return nullptr;
459
460 bitmapCache->context = context;
461
462 if (!bitmap_cache_resize(bitmapCache))
463 goto fail;
464
465 return bitmapCache;
466fail:
467 WINPR_PRAGMA_DIAG_PUSH
468 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
469 bitmap_cache_free(bitmapCache);
470 WINPR_PRAGMA_DIAG_POP
471 return nullptr;
472}
473
474void bitmap_cache_free(rdpBitmapCache* bitmapCache)
475{
476 if (!bitmapCache)
477 return;
478
479 bitmap_cache_save_persistent(bitmapCache);
480
481 bitmap_cache_cell_free(bitmapCache);
482
483 persistent_cache_free(bitmapCache->persistent);
484
485 free(bitmapCache);
486}
487
488static void free_bitmap_data(BITMAP_DATA* data, size_t count)
489{
490 if (!data)
491 return;
492
493 for (size_t x = 0; x < count; x++)
494 free(data[x].bitmapDataStream);
495
496 free(data);
497}
498
499static BITMAP_DATA* copy_bitmap_data(const BITMAP_DATA* data, size_t count)
500{
501 BITMAP_DATA* dst = (BITMAP_DATA*)calloc(count, sizeof(BITMAP_DATA));
502
503 if (!dst)
504 goto fail;
505
506 for (size_t x = 0; x < count; x++)
507 {
508 dst[x] = data[x];
509
510 if (data[x].bitmapLength > 0)
511 {
512 dst[x].bitmapDataStream = malloc(data[x].bitmapLength);
513
514 if (!dst[x].bitmapDataStream)
515 goto fail;
516
517 memcpy(dst[x].bitmapDataStream, data[x].bitmapDataStream, data[x].bitmapLength);
518 }
519 }
520
521 return dst;
522fail:
523 free_bitmap_data(dst, count);
524 return nullptr;
525}
526
527void free_bitmap_update(WINPR_ATTR_UNUSED rdpContext* context,
528 WINPR_ATTR_UNUSED BITMAP_UPDATE* pointer)
529{
530 if (!pointer)
531 return;
532
533 free_bitmap_data(pointer->rectangles, pointer->number);
534 free(pointer);
535}
536
537BITMAP_UPDATE* copy_bitmap_update(rdpContext* context, const BITMAP_UPDATE* pointer)
538{
539 BITMAP_UPDATE* dst = calloc(1, sizeof(BITMAP_UPDATE));
540
541 if (!dst || !pointer)
542 goto fail;
543
544 *dst = *pointer;
545 dst->rectangles = copy_bitmap_data(pointer->rectangles, pointer->number);
546
547 if (!dst->rectangles)
548 goto fail;
549
550 return dst;
551fail:
552 WINPR_PRAGMA_DIAG_PUSH
553 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
554 free_bitmap_update(context, dst);
555 WINPR_PRAGMA_DIAG_POP
556 return nullptr;
557}
558
559CACHE_BITMAP_ORDER* copy_cache_bitmap_order(rdpContext* context, const CACHE_BITMAP_ORDER* order)
560{
561 CACHE_BITMAP_ORDER* dst = calloc(1, sizeof(CACHE_BITMAP_ORDER));
562
563 if (!dst || !order)
564 goto fail;
565
566 *dst = *order;
567
568 if (order->bitmapLength > 0)
569 {
570 dst->bitmapDataStream = malloc(order->bitmapLength);
571
572 if (!dst->bitmapDataStream)
573 goto fail;
574
575 memcpy(dst->bitmapDataStream, order->bitmapDataStream, order->bitmapLength);
576 }
577
578 return dst;
579fail:
580 WINPR_PRAGMA_DIAG_PUSH
581 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
582 free_cache_bitmap_order(context, dst);
583 WINPR_PRAGMA_DIAG_POP
584 return nullptr;
585}
586
587void free_cache_bitmap_order(WINPR_ATTR_UNUSED rdpContext* context, CACHE_BITMAP_ORDER* order)
588{
589 if (order)
590 free(order->bitmapDataStream);
591
592 free(order);
593}
594
595CACHE_BITMAP_V2_ORDER* copy_cache_bitmap_v2_order(rdpContext* context,
596 const CACHE_BITMAP_V2_ORDER* order)
597{
598 CACHE_BITMAP_V2_ORDER* dst = calloc(1, sizeof(CACHE_BITMAP_V2_ORDER));
599
600 if (!dst || !order)
601 goto fail;
602
603 *dst = *order;
604
605 if (order->bitmapLength > 0)
606 {
607 dst->bitmapDataStream = malloc(order->bitmapLength);
608
609 if (!dst->bitmapDataStream)
610 goto fail;
611
612 memcpy(dst->bitmapDataStream, order->bitmapDataStream, order->bitmapLength);
613 }
614
615 return dst;
616fail:
617 WINPR_PRAGMA_DIAG_PUSH
618 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
619 free_cache_bitmap_v2_order(context, dst);
620 WINPR_PRAGMA_DIAG_POP
621 return nullptr;
622}
623
624void free_cache_bitmap_v2_order(WINPR_ATTR_UNUSED rdpContext* context,
625 WINPR_ATTR_UNUSED CACHE_BITMAP_V2_ORDER* order)
626{
627 if (order)
628 free(order->bitmapDataStream);
629
630 free(order);
631}
632
633CACHE_BITMAP_V3_ORDER* copy_cache_bitmap_v3_order(rdpContext* context,
634 const CACHE_BITMAP_V3_ORDER* order)
635{
636 CACHE_BITMAP_V3_ORDER* dst = calloc(1, sizeof(CACHE_BITMAP_V3_ORDER));
637
638 if (!dst || !order)
639 goto fail;
640
641 *dst = *order;
642
643 if (order->bitmapData.length > 0)
644 {
645 dst->bitmapData.data = malloc(order->bitmapData.length);
646
647 if (!dst->bitmapData.data)
648 goto fail;
649
650 memcpy(dst->bitmapData.data, order->bitmapData.data, order->bitmapData.length);
651 }
652
653 return dst;
654fail:
655 WINPR_PRAGMA_DIAG_PUSH
656 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
657 free_cache_bitmap_v3_order(context, dst);
658 WINPR_PRAGMA_DIAG_POP
659 return nullptr;
660}
661
662void free_cache_bitmap_v3_order(WINPR_ATTR_UNUSED rdpContext* context, CACHE_BITMAP_V3_ORDER* order)
663{
664 if (order)
665 free(order->bitmapData.data);
666
667 free(order);
668}
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
Definition persistent.h:70