Skip to content

feat(activex): negotiate monitor topology - #1675

Open
Marc-André Moreau (mamoreau-devolutions) wants to merge 7 commits into
Devolutions:masterfrom
mamoreau-devolutions:copilot/activex-multimon-topology
Open

feat(activex): negotiate monitor topology#1675
Marc-André Moreau (mamoreau-devolutions) wants to merge 7 commits into
Devolutions:masterfrom
mamoreau-devolutions:copilot/activex-multimon-topology

Conversation

@mamoreau-devolutions

@mamoreau-devolutions Marc-André Moreau (mamoreau-devolutions) commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Negotiate the client monitor layout from UseMultimon and expose the confirmed remote topology through the ActiveX compatibility interface.

Forward Monitor Layout PDUs from activation, active sessions, and reactivation so advertised support does not terminate sessions.

Keep fallback reporting truthful when servers do not honor the request, while preserving single-monitor resize behavior and blocking multi-monitor resizing.

Do not send Client Monitor Extended Data; per-monitor DPI and orientation remain unavailable.

Copilot AI balanced review requested due to automatic review settings August 14, 2026 20:48
@github-actions github-actions Bot added breaking-change Includes a breaking change, and requires special scrutiny at the boundaries kind/protocol Affects RDP or related protocol behavior maintainer-required Maintainer review or intervention is required risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny scope/core Touches the core architectural tier scope/cross-cutting Spans multiple architectural boundaries scope/ffi Affects native or .NET bindings scope/web Affects the web/WASM ecosystem size/L Size: up to 899 counted lines and 20 files; exceeds M in either measure labels Aug 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds ActiveX multi-monitor negotiation and exposes the resulting topology through compatibility APIs.

Changes:

  • Plumbs monitor layouts into GCC Client Monitor Data.
  • Adds ActiveX topology discovery, reporting, and resize restrictions.
  • Adds tests, documentation, and single-monitor defaults.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ffi/src/connector/config.rs Defaults monitor layout to none.
crates/ironrdp/examples/screenshot.rs Updates example configuration.
crates/ironrdp-web/src/session.rs Preserves single-monitor web behavior.
crates/ironrdp-testsuite-extra/tests/e2e.rs Updates E2E configuration.
crates/ironrdp-testsuite-core/tests/session/connection_activation.rs Updates activation test configuration.
crates/ironrdp-testsuite-core/tests/connector/autodetect.rs Updates autodetect test configuration.
crates/ironrdp-connector/src/lib.rs Exposes optional monitor layout.
crates/ironrdp-connector/src/connection.rs Adds layout to GCC blocks.
crates/ironrdp-client/src/config.rs Adds monitor-layout builder support.
crates/ironrdp-activex/src/control.rs Implements ActiveX monitor negotiation and reporting.
crates/ironrdp-activex/README.md Documents multi-monitor behavior.
Suppressed comments (1)

crates/ironrdp-connector/src/connection.rs:1466

  • The newly exposed public configuration accepts layouts above the protocol limit of 16 monitors. ClientMonitorData::encode declares the full count/size but serializes only .take(16), so such a value produces a malformed GCC stream rather than the ConnectorResult error callers expect. Reject oversized layouts before placing them in the GCC blocks.
        monitor: config.monitor_layout.clone(),

Comment thread crates/ironrdp-connector/src/connection.rs Outdated
Comment thread crates/ironrdp-activex/src/control.rs Outdated
@github-actions github-actions Bot added risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny size/XL Size: up to 1299 counted lines and 49 files; exceeds L in either measure and removed size/L Size: up to 899 counted lines and 20 files; exceeds M in either measure risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny labels Aug 14, 2026
@github-actions github-actions Bot added risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny and removed risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny labels Aug 15, 2026
Map the ActiveX multi-monitor contract to GCC Client Monitor Data and the virtual desktop configuration sent to IronRDP.

Validate local monitor geometry, retain negotiated topology for COM state reporting, and reject dynamic topology changes. The GDI renderer continues to present the virtual desktop as one composite framebuffer.
Keep requested topology separate from framebuffer confirmation so COM monitor APIs do not report an unhonored layout.\n\nRetain the multi-monitor resize restriction after a fallback while allowing a successfully queued single-monitor resize to clear stale topology state.\n\nAdd defaults for downstream connector configurations affected by the new monitor layout field.
Gate GCC extended client data on the server negotiation flag and\nactivate ActiveX topology only from the server Monitor Layout PDU.\n\nReject monitor counts that cannot be encoded and generate compliant\ninclusive server monitor bounds.
Store X.224 response flags in the connector instead of public state\nvariants so downstream protocol front ends retain source compatibility.
Log server monitor layouts in the viewer and clone finalization state\nfor the FFI wrapper now that it carries monitor layout data.
Document the intentionally private negotiation flags field so strict Clippy preserves the established ClientConnector construction API.
@github-actions github-actions Bot added ai-reviewed/1 One automated review completed and removed maintainer-required Maintainer review or intervention is required labels Aug 16, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR wires GCC monitor topology negotiation end-to-end: gates Client Monitor Data / Message Channel Data / Multitransport Channel Data on EXTENDED_CLIENT_DATA_SUPPORTED (fixing prior spec-noncompliant unconditional sends), fixes inclusive/exclusive off-by-one monitor rectangle coordinates in the acceptor and a new ActiveX topology builder, replaces silent truncation with a hard encode error above 16 monitors, and adds Monitor Layout PDU handling to connection finalization plus a well-tested ActiveX multi-monitor path gated behind UseMultimon. Copy->Clone changes to activation/finalization state types are a necessary consequence of threading Option<MonitorLayoutPdu> through, not gratuitous. I independently verified the core protocol claims and found them accurate. Main residual concern: Monitor Layout PDUs arriving after finalization still hit ironrdp-session's catch-all disconnect path, newly reachable now that the client advertises SUPPORT_MONITOR_LAYOUT_PDU.

Protocol analysis: accepted — Re-checked the handoff's code claims against pr-head: encode() now errors above MONITOR_COUNT_MAX instead of the prior len()-vs-truncated-array mismatch; the acceptor's synthesized monitor uses width-1/height-1 for right/bottom; create_gcc_blocks gates monitor/message_channel/multi_transport_channel on response_flags.contains(EXTENDED_CLIENT_DATA_SUPPORTED) from the X.224 Connection Confirm; and the discrepancy note about ironrdp-session's catch-all disconnect on unhandled ShareDataPdu variants is accurate -- MonitorLayout is only handled inside WaitForResponse. All claims matched; no contradicting evidence found.

ShareDataPdu::MonitorLayout(monitor_layout) => {
self.monitor_layout = Some(monitor_layout);
ConnectionFinalizationState::WaitForResponse
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non_blocking / medium: ShareDataPdu::MonitorLayout is only handled while ConnectionFinalizationSequence remains in WaitForResponse; once finalization completes (Font Map received), ironrdp-session's active-stage dispatch (crates/ironrdp-session/src/x224/mod.rs, unmodified) has no arm for MonitorLayout and its catch-all branch returns an error, disconnecting the session. Before this PR the client never advertised SUPPORT_MONITOR_LAYOUT_PDU, so no server would send this PDU at all; this PR makes that flag reachable (crates/ironrdp-connector/src/connection.rs) for any config with a monitor_layout set, so a server that later resends a Monitor Layout PDU outside the finalization window (e.g. on a host display change) would now hard-disconnect an otherwise-healthy ActiveX multimon session. MS-RDPBCGR's cited text says the PDU is sent 'after the Demand Active PDU' but does not explicitly forbid later delivery, so this is a plausible but unconfirmed interop gap rather than a proven regression; worth a follow-up to at least ignore rather than error on a late MonitorLayout PDU.

}
if extended_client_data_supported && config.monitor_layout.is_some() {
early_capability_flags |= ClientEarlyCapabilityFlags::SUPPORT_MONITOR_LAYOUT_PDU;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question / low: SUPPORT_MONITOR_LAYOUT_PDU is advertised only when both extended_client_data_supported and config.monitor_layout.is_some() hold, which is narrower than the flag's spec meaning (an unconditional 'client supports the Monitor Layout PDU' capability declaration, not tied to requesting a custom layout). Since connection_finalization.rs now handles MonitorLayout regardless of who set the flag, is the extra monitor_layout.is_some() gate intentional product scoping (only ActiveX multimon opts in) or should any client capable of finalization-time handling advertise the capability so servers that behave differently based on this flag alone (independent of Client Monitor Data) are not affected?

device_scale_factor: u32,
}

const MAX_RDP_MONITORS: usize = 16;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non_blocking / low: MAX_RDP_MONITORS: usize = 16 duplicates the same protocol-defined maximum already encoded as the private MONITOR_COUNT_MAX constant in crates/ironrdp-pdu/src/gcc/monitor_data.rs (also touched by this PR). Exporting MONITOR_COUNT_MAX (or a public accessor) and reusing it here would remove the duplicated magic number and prevent the two limits from silently drifting apart in a future change.

Forward Monitor Layout PDUs received after activation through client output.
This prevents an advertised capability from turning a permitted update into an
unhandled PDU. Reactivation layouts now notify consumers as well.
@github-actions github-actions Bot added maintainer-required Maintainer review or intervention is required risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny size/XXL Size: 1300 or more counted lines or 50 or more files and removed size/XL Size: up to 1299 counted lines and 49 files; exceeds L in either measure breaking-change Includes a breaking change, and requires special scrutiny at the boundaries risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny labels Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request is size/XXL, so automated review is disabled for it: a change this large is hard to review well in one piece, whether by a human or a model.

Please split it into focused pull requests that can each be reviewed on their own. When the parts build on each other, stacked pull requests let you open each one on top of the last without waiting for the one below to merge. Stacks require every branch to live in this repository, so from a fork, please open separate pull requests instead.

Automated review resumes once the change is below the size/XXL threshold.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed/1 One automated review completed kind/protocol Affects RDP or related protocol behavior maintainer-required Maintainer review or intervention is required risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny scope/core Touches the core architectural tier scope/cross-cutting Spans multiple architectural boundaries scope/ffi Affects native or .NET bindings scope/web Affects the web/WASM ecosystem size/XXL Size: 1300 or more counted lines or 50 or more files

Development

Successfully merging this pull request may close these issues.

2 participants