Repository navigation
rdp-backend: port to FreeRDP 3.x server API (Phase 3 / 3) - #163
Ben Hillis (benhillis) wants to merge 8 commits into
Conversation
WSLg has been pinned to a FreeRDP 2.4.0-era fork
(microsoft/FreeRDP-mirror) since 2023-08. Port the rdp-backend so it
also compiles against upstream FreeRDP 3.x (3.26.0 verified).
All edits are wrapped in #if FREERDP_VERSION_MAJOR >= 3 guards so
this branch still compiles unchanged against FreeRDP 2.
Changes (rdp.c, rdp.h, rdpclip.c, rdprail.c):
* RFX_CONTEXT became opaque in 3.x. Replace direct field writes
(ctx->mode/width/height) with rfx_context_set_mode() and
rfx_context_reset().
* Keyboard input event signature changed from
(rdpInput*, UINT16 flags, UINT16 code) to (..., UINT8 code), and
KBD_FLAGS_DOWN was removed (the absence of KBD_FLAGS_RELEASE now
implicitly means key-down).
* GetKeycodeFromVirtualKeyCode now takes WINPR_KEYCODE_TYPE_XKB
instead of KEYCODE_TYPE_EVDEV.
* TLS server cert/key: settings->{Certificate,PrivateKey}{File,Content}
and settings->RdpKeyFile were removed in 3.x. Construct rdpPrivateKey*
and rdpCertificate* with freerdp_{key,certificate}_new_from_pem() and
apply via freerdp_settings_set_pointer_len(FreeRDP_RdpServer{RsaKey,
Certificate}, ..., 1). The 3.x line has no rdp4-style key support, so
the file-cert path is gated and currently errors out with a clear
message (wslg in production uses the session-generated TLS path via
Hyper-V vsock, which is fully implemented).
* settings->FastPathInput must be explicitly set TRUE; it was the
default in 2.x but is no longer.
* rdp_client_activity: WTSVirtualChannelManagerCheckFileDescriptor()
asserts in 3.x if drdynvc has not yet joined. Add a
WTSVirtualChannelManagerIsChannelJoined(vcm,"drdynvc") guard.
* rdp_drdynvc_init: 2.x checked state == DRDYNVC_STATE_NONE before
waiting; 3.x has additional intermediate states (e.g. INITIALIZED),
so check state != DRDYNVC_STATE_READY instead, plus the same
IsChannelJoined guard.
* rdpclip.c: CLIPRDR PDU headers now nest under .common -- introduce
CLIP_MSG_HEADER(s,m) macro to access .common.msgType / .msgFlags /
.dataLen in 3.x while keeping the 2.x layout. MultiByteToWideChar
/WideCharToMultiByte are replaced by ConvertUtf8NToWChar
/ConvertWCharNToUtf8 (return SSIZE_T, negative on error).
* AudioPlayback / AudioCapture are explicitly set FALSE on 3.x with a
one-time weston_log warning. The audio_server_context APIs were
fully rewritten in 3.x (SendVersion/SendFormats/SendOpen/IncomingData
PDU-callback model) and the port lands in a follow-up commit.
The libfreerdp-server.so dlopen path is already version-conditional
(libfreerdp-server3.so on 3.x).
Verified end-to-end: wslg system-distro image builds cleanly against
upstream FreeRDP 3.26.0 with -DCHANNEL_GFXREDIR=ON; gfxredir and
rdpapplist server context constructors are exported.
Audio module port (rdpaudio.c + rdpaudioin.c) tracked separately.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wrap rdpaudio.c and rdpaudioin.c implementations with
#if FREERDP_VERSION_MAJOR < 3 so the wslg compositor can link against
FreeRDP 3.x without a full audio port. On FreeRDP 3, rdp_audio_{out,in}
_init() return NULL (treated by the rdp-backend as "continue without
audio") and rdp_audio_{out,in}_destroy() are no-ops.
The audin_server_context and rdpsnd_server_context APIs were rewritten
in FreeRDP 3.x: the field-based model (Activated, ConfirmBlock,
{num_,}server_formats, src_format, dst_format, frames_per_packet,
Opening, OpenResult, ReceiveSamples, data, Initialize) was replaced by
a PDU-callback model (SendVersion, SendFormats, SendOpen,
SendFormatChange, Open, Close, IsOpen, plus ChannelIdAssigned and
IncomingData callbacks). Porting the full state machine is a
substantial rewrite tracked as a follow-up.
The companion change in libweston/backend-rdp/rdp.c explicitly sets
settings->AudioPlayback = FALSE and settings->AudioCapture = FALSE on
FreeRDP 3, so the protocol does not advertise an audio capability the
server cannot service.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously the FreeRDP 3.x file-cert path was a TODO no-op that
erred out. Implement it using freerdp_{certificate,key}_new_from_file()
which exist alongside the *_new_from_pem() variants in 3.x.
Loading order:
1. Try whichever source (PEM content from session-TLS, or file path
from --rdp-tls-cert/--rdp-tls-key) is configured.
2. Wrap into the FreeRDP-3 settings via FreeRDP_RdpServer{RsaKey,Certificate}.
3. Free key/cert on any failure path to avoid leaks before
error_initialize falls through.
Verified via Linux loopback smoke test that file-based cert/key loads
cleanly and TLS support activates against FreeRDP 3.26.0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Smoke test update — file-based TLS cert/key implemented (commit e8e58d1)The FreeRDP-3 file-based TLS cert/key path ( Linux loopback smoke test (xfreerdp3 → weston-rdp)Setup:
The licensing failure manifests as: i.e. the license PDU that the server intends to send via Disabling RAIL ( StatusEnd-to-end against
This commit is a strict improvement (file-cert path was previously a hard error); pushing it now so the PR reflects the current verified state. |
FreeRDP 3.x added two unconditional connect-time steps to the server
state machine that did not exist in 2.x and that WSLg's rdp-backend
never instrumented:
1. NetworkAutoDetect — SECURE_SETTINGS_EXCHANGE goes through
CONNECT_TIME_AUTO_DETECT_REQUEST/RESPONSE before LICENSING, with
the server sending AUTODETECT_REQ and waiting for a response.
The 3.x default for FreeRDP_NetworkAutoDetect is TRUE so this
step is always taken. WSLg does not register autodetect callbacks,
so the server gets stuck waiting.
2. SupportMultitransport (with the UDPFECR flag) — after LICENSING
the server emits a SEC_TRANSPORT_REQ on the message channel before
transitioning to CAPABILITIES_EXCHANGE. The 3.x defaults for
FreeRDP_SupportMultitransport (TRUE) and FreeRDP_MultitransportFlags
(TRANSPORT_TYPE_UDP_FECR) both engage this step. WSLg runs over
TCP-only Hyper-V vsock with no UDP path, so the multitransport
bootstrap is unusable here.
Force both off in rdp_peer_init so the 3.x state machine collapses
back to the 2.x topology of SECURE_SETTINGS_EXCHANGE -> LICENSING ->
CAPABILITIES_EXCHANGE. No-op on 2.x (settings exist but are ignored
at connect-time on that branch).
Verified via TRACE-level WLog: with these settings the server log
shows CONNECT_TIME_AUTO_DETECT_REQUEST -> LICENSING transitioning
in a single tick, and LICENSING -> CAPABILITIES_EXCHANGE_DEMAND_ACTIVE
following without a transport-request being emitted.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Smoke-test follow-up: server state-machine fixes for FreeRDP 3.xPushed
Verified via TRACE-level
Remaining smoke-test gap (xfreerdp3 loopback only)xfreerdp3 over TCP loopback still fails at the licensing-stage with: The server-side trace confirms it correctly writes one PDU ( This appears to be either an xfreerdp3-client-specific framing quirk over TCP loopback or a buffered-data race; the bytes the server writes (TPKT+TPDU+MCS-send-data-indication with PER-encoded channelId=1003 and SEC_LICENSE_PKT in the security header) are byte-correct by spec. Plan
PR remains in draft until mstsc validation completes. |
Replace 128 direct `settings->FieldName` reads/writes across rdp.c,
rdpdisp.c and rdprail.c with the canonical `freerdp_settings_get_*` and
`freerdp_settings_set_*` accessor functions, and drop the
FREERDP_SETTINGS_INTERNAL_USE workaround that was suppressing the
SETTINGS_DEPRECATED warnings while the migration was in flight.
- Reads: `expr->settings->X` → `freerdp_settings_get_TYPE(expr->settings, FreeRDP_X)`
- Writes: `expr->settings->X = V;` → `(void)freerdp_settings_set_TYPE(expr->settings, FreeRDP_X, V);`
- MonitorDefArray pointer: `settings->MonitorDefArray` →
`freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray)`
The 5 remaining `settings->X` accesses in rdp.c (RdpKeyFile,
{Certificate,PrivateKey}{File,Content}) are inside the
`#if FREERDP_VERSION_MAJOR < 3` else-branch and reference fields that
exist only in 2.x's rdpSettings struct, so they are intentionally left
alone — 2.x has no SETTINGS_DEPRECATED attribute and the 3.x build never
sees them.
The accessor APIs exist in both FreeRDP 2.x and 3.x with compatible
signatures (the 2.x version takes `size_t id` whereas 3.x takes a typed
enum, but both accept the `FreeRDP_<FieldName>` enum constants), so this
change compiles cleanly under either FreeRDP major version.
Verified end-to-end Docker build succeeds against FreeRDP 3.26.0 with
zero SETTINGS_DEPRECATED warnings, and the rdp-backend.so module loads
and initializes correctly (TLS support active, head added, output
enabled, plugin APIs registered).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Settings accessor migration landed (e758643)Replaced 128 direct
Verification
The MonitorDefArray pointer field uses Both 2.x and 3.x of FreeRDP expose these accessors with compatible signatures and the same Status
|
In FreeRDP 2.x the `RAIL_EXEC_ORDER` struct held `char *` for the
RemoteApplication{Program,WorkingDir,Arguments} fields; in 3.x those
were changed to `const char *`. The original 2.x rdp-backend code
freed and strcpy'd into those fields directly, which produces three
classes of `-Wdiscarded-qualifiers` warnings under the 3.x headers:
* `char *remoteProgramAndArgs = exec->RemoteApplicationProgram`
in rail_client_Exec_callback (rdprail.c:186)
* `free(exec->RemoteApplicationProgram)` × 3 (rdprail.c:247-249)
* `strcpy(exec_order.RemoteApplicationProgram, ...)` × 3 in
rail_client_Exec (rdprail.c:262/267/272)
In all of these the buffer is one we own — rail_client_Exec receives
the const arg from FreeRDP and creates fresh xmalloc'd copies before
dispatching the work to the display loop — so the const-cast is safe.
* Write side (rail_client_Exec): xmalloc into a local `char *buf`,
strcpy into it, then assign to the const field. The cast is implicit
at the assignment and the compiler is happy.
* Read side (rail_client_Exec_callback): explicit `(char *)` cast on
the free() calls and on the initial `remoteProgramAndArgs`
initializer, with comments explaining why.
End-to-end build now emits zero `-Wdiscarded-qualifiers` warnings for
backend-rdp. Remaining backend-rdp warnings are all pre-existing and
unrelated to the FreeRDP 3.x port:
* OpenSSL 3.0 RSA_* deprecation (rdp.c:2077-2082, in the 2.x-only
`#else` TLS path).
* `-Wformat-truncation` in rdp_log_timestamp's snprintf.
* `-Warray-bounds` in clipboard_data_source_send's debug log.
* `-Wformat-overflow` from xkbRuleNames->options being NULL in
convert_rdp_keyboard_to_xkb_rule_names's debug log.
Smoke-tested: backend loads, reports FreeRDP 3.26.0, TLS activates,
head added, output enabled, plugin APIs registered.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3.8 → 3.26 API drift sweep (5fd0638)Reviewed 4,407 commits and 44 public-header changes in upstream FreeRDP between 3.8.0 and 3.26.0 for anything that could affect the wslg rdp-backend. Issues found and fixed
Issues investigated and confirmed harmless
Pre-existing warnings (not 3.x related, out of this PR's scope)
End-to-end build is now warning-clean for FreeRDP-related issues. Smoke test passes (rdp-backend.so loads, FreeRDP 3.26.0 reported, TLS active, head added). |
The 2.x RdpsndServerContext and audin_server_context APIs were stubbed
to no-op on FreeRDP 3.x in the prior port to avoid advertising audio
capabilities we could not service. This commit completes the audio port:
rdpsnd (audio output) — vendor/weston/compositor/rdpaudio.c
========================================================
The 3.x RdpsndServerContext public struct is field-for-field compatible
with 2.x (Activated/ConfirmBlock callbacks, Initialize/SelectFormat/
SendSamples/SetVolume/Close APIs, server_formats/num_server_formats/
src_format/client_formats/num_client_formats/block_no/data fields).
The only API-surface changes are WINPR_ATTR_NODISCARD on the function
pointers and minor int->size_t / int->UINT32 / int->UINT16 widening on
SendSamples/latency/SetVolume.
* Remove the outer #if FREERDP_VERSION_MAJOR < 3 guards on the format
table and helper functions so they compile under both versions
* Remove the version-branch in rdp_audio_out_init / rdp_audio_out_destroy
— the 2.x body works as-is for 3.x
* Check SelectFormat()'s return value (per rubber-duck feedback — a
silent failure would create a 'connected but no audio' state). Cast
SetVolume and Close/Stop returns to (void) since those failures
are non-fatal.
audin (audio input) — vendor/weston/compositor/rdpaudioin.c
==========================================================
The 3.x audin_server_context was reworked to a PDU-callback model
(SendVersion/SendFormats/SendOpen + ReceiveVersion/ReceiveFormats/
Data callbacks), but FreeRDP provides convenience helpers
(audin_server_set_formats) that hide most of the new mechanics. The
shared helpers (format table, AUDIO_FORMAT_to_String, source thread,
listener setup) are ungated.
* Keep 2.x-specific callbacks (rdp_audioin_client_opening/
open_result/receive_samples) under #if FREERDP_VERSION_MAJOR < 3
* Add 3.x-specific callbacks under #if FREERDP_VERSION_MAJOR >= 3:
- rdp_audioin_client_receive_formats — overrides the default
ReceiveFormats handler. The default in FreeRDP 3.x
(audin_server_receive_formats_default in
channels/audin/server/audin.c) negotiates a compatible format
but then sends a hardcoded stereo 44.1kHz 16-bit captureFormat
in the SNDIN_OPEN PDU. WSLg's pulseaudio source pipe expects
mono, so we override to send the negotiated (mono) format.
- rdp_audioin_client_data — replaces ReceiveSamples; reads
Stream_Length / Stream_Buffer from the SNDIN_DATA wStream and
forwards to the pulseaudio source fd.
* In rdp_audio_in_init, split the context-setup block by version.
The shared source-thread that calls Open()/Close() works for both.
backend-rdp/rdp.c
=================
* Remove the temporary stub block (#if FREERDP_VERSION_MAJOR >= 3 in
xf_peer_activate) that forced AudioPlayback = FALSE and
AudioCapture = FALSE on 3.x. The full audio_out_setup /
audio_out_teardown gate is now used for both versions.
Validation
==========
* Full docker build clean — both rdpaudio.c and rdpaudioin.c compile
with zero new warnings under FreeRDP 3.26.0
* Smoke test passes: backend loads, no more 'audio playback/capture
disabled: FreeRDP 3.x port pending' warning. End-to-end audio
handshake validation requires mstsc.exe over the production vsock
path (the xfreerdp3 loopback path still has the pre-existing
licensing framing issue tracked separately).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Audio module port complete — rdpsnd + audin now wired up under FreeRDP 3.x. Pushed as What changed
API-delta highlightsrdpsnd: the audin: the 3.x context struct was reworked into a PDU model with helpers ( Validation
Rubber-duck reviewBefore implementing, I consulted the rubber-duck agent. It caught two blocking issues that I would have shipped otherwise:
Both addressed. |
FreeRDP 3.x marks audin_server_context->Close as WINPR_ATTR_NODISCARD (see include/freerdp/server/audin.h). The two call sites in rdp_audioin_source_thread were discarding the return value without a (void) cast, producing -Wunused-result noise. rdpaudio.c already follows the (void)-cast pattern for the same nodiscard methods on RdpsndServerContext; apply the same pattern here for consistency. No behavioral change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed code-review finding (8 commits now): two call sites in The other two code-review findings (theoretical TOCTOU on pulseAudioSourceFd, partial-send handling) were both pre-existing in the 2.x code path and out of scope for this port — leaving them for a follow-up if observed in practice. |
Windows-side validation report — FreeRDP 3.x portRan the TL;DR✅ P0 PASS — the FreeRDP 3.26.0 server-side licensing exchange completes cleanly with
Path / build configuration
Environment
Checklist (Step 5 of Path A)Special P0 row added because that was the question in front of the change set.
Additional positive signal beyond P0The server also accepted a second mstsc connection that negotiated TLS: TLS handshake + MCS create/erect/attach/skip-channel-join + RDP_SECURITY_COMMENCEMENT + SECURE_SETTINGS_EXCHANGE all complete cleanly under FreeRDP 3.26.0. The connection ends after the client's P0 evidence excerpt (
|
🟢 mstsc.exe validates licensing P0 — server is soundExternal Windows-agent validation against mstsc.exe just came back PASS on the licensing exchange: Zero What this means:
Full validation report: #163 (comment) Marking this PR ready for code review (still draft pending functional sweep). Maintainer eyes welcome on the 8-commit series; recommended review order is in the PR description. |
Round 2 — functional sweep: BLOCKED, needs human runnerFollowing up on #163 (comment) — round 1 P0 PASS. TL;DRThe full functional checklist (#1–#10 from runbook Path A Step 5) cannot be performed from a non-interactive automation harness. A human with an active Windows desktop session needs to run this round. Posting partial signal + the root cause so the next person doesn't spend the time I did rediscovering it. What I did and where I got stuckApproach: get a sustained Result: every connection attempt is blocked at a Root cause (this surprised me — sharing in case it bites anyone else): The agent process and the spawned Things I tried (in case you want to skip them):
I'm 90% sure the right fix here is operator-side: log in to the test host once, run mstsc against the smoke harness, tick "Remember my choices for remote connections from this publisher" in the first dialog and "Don't ask me again for connections to this computer" in the second, then subsequent automated runs work. Side observation worth flaggingWhen That delegation also surfaces a second dialog box that I don't see documented anywhere — a "RemoteApp security warning" for a RemoteApp program with Partial functional signal from this round's traceContainer So we do get a (very limited) extra positive datapoint beyond round 1:
Everything else (#1–#9) remains SKIP — needs human at active desktop session, same verdict as round 1. What a human runner needs to do
If you want to subsequently re-enable automation for this same harness, tick the "Remember" boxes once and the dialogs won't reappear for the trusted publisher / remote computer pair. Captured artifacts
Net verdict for the PR
|
Additional evidence: static channels negotiate cleanly under msrdcMining the existing round-1+2 server trace, the Windows validation agent confirmed that all four static RDP channels are accepted and joined cleanly on every msrdc connection attempt:
Zero What this validates: the server-side static-channel-creation paths in our FreeRDP 3.x port (the What this does NOT validate: dynamic-channel runtime traffic. msrdc disconnected immediately after TLDR: Round 1 = licensing OK. Round 2 = static channels OK + connection-cycle stability across multiple connects without server crash/wedge. Dynamic channel data flow + visual desktop = still needs human-driven Path B validation. Not gating PR review on that. |
| { | ||
| #if FREERDP_VERSION_MAJOR >= 3 | ||
| /* FreeRDP 3 asserts on calling CheckFileDescriptor before drdynvc joined */ | ||
| if (!WTSVirtualChannelManagerIsChannelJoined(peerCtx->vcm, "drdynvc")) |
There was a problem hiding this comment.
can try:
if (client->activated != TRUE || !WTSVirtualChannelManagerIsChannelJoined(peerCtx->vcm, "drdynvc"))
Port the WSLg rdp-backend so it compiles cleanly against upstream FreeRDP 3.x (tag
3.26.0verified) in addition to the FreeRDP 2.4.0-eramicrosoft/FreeRDP-mirrorfork currently in use.Status
DRAFT -- do not merge.
wslg/build-and-export.sh) succeeds againstFreeRDP/FreeRDP@3.26.0with-DCHANNEL_GFXREDIR=ON.libfreerdp-server3.so.3.26.0ships, exportsgfxredir_server_context_{new,free}, andlibrdpapplist-server.so(separate plugin) exports its server context too.libweston-9/rdp-backend.solinks againstlibfreerdp3.so.3/libwinpr3.so.3/libfreerdp-server3.so.3.xfreerdpand Windows mstsc, this PR will be marked Ready for review.rdp_audio_{out,in}_initreturnsNULLon FreeRDP 3.x; the rdp-backend explicitly disablesAudioPlayback/AudioCapture. Theaudin_server_context/rdpsnd_server_contextAPIs were rewritten in 3.x (PDU-callback model) and the port lands as a follow-up before this PR is marked ready.#if FREERDP_VERSION_MAJOR >= 3guards, so this branch continues to compile unchanged against FreeRDP 2 (preserves bisectability for theworkingconsumers).Commits
rdp-backend: port to FreeRDP 3.x server API-- core port (RFX accessors, keyboard sig+KBD_FLAGS_DOWN logic, WINPR_KEYCODE_TYPE_XKB, PEM cert/key viafreerdp_settings_set_pointer_len,FastPathInput=TRUE, drdynvcIsChannelJoinedguards, CLIPRDR.commonheader nesting,ConvertUtf8NToWChar/ConvertWCharNToUtf8, explicit AudioPlayback/Capture disable on 3.x).compositor: stub rdp audio modules for FreeRDP 3.x build--rdp_audio_{out,in}_{init,destroy}body wrapped in#if FREERDP_VERSION_MAJOR < 3; 3.x init returns NULL, destroy is a no-op.API churn handled
RFX_CONTEXTopaque ->rfx_context_set_mode/rfx_context_resetaccessors.(rdpInput*, UINT16 flags, UINT16 code)->(..., UINT8 code);KBD_FLAGS_DOWNremoved (absence ofKBD_FLAGS_RELEASEmeans down).KEYCODE_TYPE_EVDEV->WINPR_KEYCODE_TYPE_XKB.settings->{Certificate,PrivateKey}{File,Content}andRdpKeyFileremoved -> constructrdpPrivateKey*andrdpCertificate*withfreerdp_{key,certificate}_new_from_pem()and apply viafreerdp_settings_set_pointer_len(FreeRDP_RdpServer{RsaKey,Certificate}, ..., 1).FastPathInputno longer defaults to TRUE.WTSVirtualChannelManagerCheckFileDescriptorasserts if drdynvc has not joined ->WTSVirtualChannelManagerIsChannelJoined(vcm,"drdynvc")guard.state != DRDYNVC_STATE_READYinstead ofstate == DRDYNVC_STATE_NONE(3.x has intermediate states)..common(msgType/msgFlags/dataLen).MultiByteToWideChar(CP_UTF8,...)->ConvertUtf8NToWChar(returnSSIZE_T, negative on error). Same forWideCharToMultiByte.audin_server_context/rdpsnd_server_contextrewritten to a PDU-callback model.Known limitations
FREERDP_SETTINGS_INTERNAL_USEdefine silencesSETTINGS_DEPRECATEDwarnings for directsettings->Xwrites. The newly touched security/audio settings are migrated tofreerdp_settings_set_bool; full conversion to accessors for the rest is tracked as cleanup.b->server_cert/b->server_key) is currentlyerror_initializeon FreeRDP 3 with a clear message. WSLg in production uses the session-generated TLS path via Hyper-V vsock, which is fully implemented.Coordinated PRs
microsoft/wslg-- bumpDockerfile FREERDP_VERSION=2 -> 3, add-DCHANNEL_GFXREDIR=ONand-DWITH_{FFMPEG,DSP_FFMPEG,VIDEO_FFMPEG,SWSCALE,KRB5}=OFFcmake flags. Will be opened after this PR.microsoft/wslg-build-- switchresources.repositories.FreeRDPfrommicrosoft/FreeRDP-mirrortoFreeRDP/FreeRDP@3.26.0, pinwestonRefto this branch until merged toworking. Will be opened after this PR.VCM (
peerCtx->vcm = WTSOpenServerA(...)) is deliberately kept enabled despite Hideyuki Endo's abandonedbuild_with_freerdp_v3.8.0branch having disabled it; disabling VCM would break cliprdr, rdpgfx, gfxredir, rdpsnd, audin, and rdpapplist.