Skip to content

Fix virtual output - #1380

Draft
Groveer wants to merge 4 commits into
linuxdeepin:masterfrom
Groveer:fix_virtual_output
Draft

Fix virtual output#1380
Groveer wants to merge 4 commits into
linuxdeepin:masterfrom
Groveer:fix_virtual_output

Conversation

@Groveer

@Groveer Groveer commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Improve virtual output management and output reconfiguration to provide a complete graphical control tool while keeping display topology, primary selection, layouts, and copy-mode state consistent across output changes.

New Features:

  • Expand the virtual output example into a graphical display-management tool for mirroring, primary-output selection, color controls, and output configuration.
  • Add wlroots output-management support for inspecting and applying output position, mode, refresh rate, rotation, scale, and enablement settings.

Bug Fixes:

  • Fix virtual output lifecycle and synchronization when groups are created, modified, removed, or rebound.
  • Preserve primary-output configuration, restore disabled-output surfaces, and avoid stale or overlapping output layouts during output changes.
  • Improve copy-mode transitions when outputs are disabled or enabled and reject incomplete restoration of stale copy configurations.
  • Make cursor relocation deterministic and prevent disabled outputs from being selected for cursor-based placement.

Enhancements:

  • Add output-layout pinning to prevent automatic repositioning after output geometry changes.

Build:

  • Generate the virtual output example against the Treeland output-management and wlroots output-management protocols.

1. Clamp child resource version to the interface version on create
2. Reject duplicate group names with INVALID_GROUP_NAME protocol error
3. Return an empty proxy for vanished groups instead of a fatal error
4. Notify v3 clients via virtual_output_modified on config changes
5. Restore manager destroy() override; default leaves resource alive
6. Rebind config QPointer to the newest proxy in get_virtual_output

Log: Virtual output groups now notify clients on changes; getting a
vanished group returns an empty proxy instead of killing the client

Influence:
1. Run ctest protocol suites treeland-virtual-output-desktop-v1 and
   treeland-virtual-output-manager-v1
2. Create two copy groups with the same name; the second create must
   fail with INVALID_GROUP_NAME and not corrupt the first group
3. Get a group that no longer exists; the client must survive and see
   an empty outputs event
4. Update copy outputs while test-virtual-output runs; the client
   receives virtual_output_modified without reconnecting

fix(virtual-output): 加固虚拟输出协议层

1. 创建子资源时将版本钳制到接口版本
2. 重名组返回 INVALID_GROUP_NAME 协议错误
3. 已消失的组返回空代理而非致命错误
4. 配置变化时通过 virtual_output_modified 通知 v3 客户端
5. 恢复 manager 的 destroy() 重写;默认实现不会销毁资源
6. get_virtual_output 将配置 QPointer 重新绑定到最新代理

Log: 虚拟输出组变化时通知客户端;获取已消失的组返回空代理而非断开客户端

Influence:
1. 运行 ctest 协议测试 treeland-virtual-output-desktop-v1 与 manager-v1
2. 用同名创建两个复制组;第二次必须报 INVALID_GROUP_NAME 且不影响第一组
3. 获取不存在的组;客户端存活并收到空 outputs 事件
4. test-virtual-output 运行时更新复制输出;客户端无需重连即可收到通知

PMS: TASK-390751
Signed-off-by: groveer <guoyao@uniontech.com>
1. Add WOutputLayout::pin() to freeze settled auto-arranged outputs
2. Restore saved extension positions only when they do not overlap
   an enabled output; revalidate before expanding from one screen
3. Migrate surfaces stranded on a disabled output back when it is
   re-enabled
4. Warp the cursor to the closest layout point instead of the primary
   center when it leaves the output layout
5. Persist the auto-restored primary and copy group ids only after
   every commit of the configuration succeeds
6. Refuse copy mode restore when a configured id no longer resolves;
   guard onRestoreCopyOutput against an empty output list

Log: Re-enabling a screen no longer strands windows or drags the
cursor erratically; stale copy configs no longer restore a partial
group

Influence:
1. In extension mode, disable then re-enable the primary screen;
   windows return to it and the cursor does not jump
2. Switch the primary monitor resolution, re-enable; saved stale
   coordinates are abandoned for auto layout
3. Configure copy mode, unplug one member, reboot; copy mode must not
   restore and a warning is logged
4. Toggle outputs rapidly in a 3-screen layout; the cursor stays on
   the nearest enabled output

fix(output): 热插拔恢复与复制模式鲁棒性处理

1. 新增 WOutputLayout::pin() 冻结已排定的自动布局输出
2. 扩展模式仅在不与已启用输出重叠时恢复保存位置;单屏扩展前重新校验
3. 禁用的输出重新启用时迁移其上搁浅的窗口
4. 光标离开布局时移动到布局最近点而非主屏中心
5. 仅在该配置全部提交成功后持久化自动恢复的主屏与复制组 ID
6. 配置 ID 失效时拒绝恢复复制模式;onRestoreCopyOutput 增加空列表守卫

Log: 重新启用屏幕不再搁浅窗口或导致光标乱跳;过期的复制配置不再恢复出缺员组

Influence:
1. 扩展模式下禁用再启用主屏;窗口返回原屏且光标不跳动
2. 更换主屏分辨率后再启用;过期的保存坐标被放弃改用自动布局
3. 配置复制模式后拔掉一个成员重启;复制模式不得恢复并输出警告日志
4. 三屏布局下快速开关输出;光标停留在最近的已启用输出上

PMS: TASK-390751
Signed-off-by: groveer <guoyao@uniontech.com>
1. Add wlr-output-management client wrapper for real output discovery
2. Rework VirtualClient to drive copy mode end to end via treeland
   virtual output protocol and wlr output management events
3. Drop dead connects and redundant comments

Log: Example client discovers real outputs via the wlr protocol and
drives virtual copy groups end to end

Influence:
1. Build the examples target; test-virtual-output compiles and links
2. Create and destroy a copy group from the client; the compositor
   applies and restores the output topology
3. Toggle real monitor outputs; the client list refreshes through
   wlr-output-management events

feat(test-virtual-output): 扩展示例客户端

1. 新增 wlr-output-management 客户端封装以发现真实输出
2. 重构 VirtualClient,通过 treeland 虚拟输出协议与 wlr 输出管理事件端到端驱动复制模式
3. 清理无效连接与冗余注释

Log: 示例客户端可通过 wlr 协议发现真实输出,并端到端驱动虚拟复制组

Influence:
1. 构建 examples 目标;test-virtual-output 可编译链接
2. 从客户端创建/销毁复制组;合成器正确应用并恢复输出拓扑
3. 切换真实显示器输出;客户端列表通过 wlr-output-management 事件刷新

PMS: TASK-390751
Signed-off-by: groveer <guoyao@uniontech.com>
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Groveer

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai

sourcery-ai Bot commented Sep 7, 2026

Copy link
Copy Markdown

Reviewer's Guide

The pull request turns the virtual-output example into a full interactive output-management client and expands the virtual-output protocol, while hardening compositor output topology transitions, layout positioning, cursor placement, primary-output persistence, and surface restoration across disable/enable and copy-mode changes.

Sequence diagram for copy-source disable and restoration

sequenceDiagram
    participant Client as OutputManagementClient
    participant Helper
    participant RootSurfaceContainer
    participant OutputManager
    participant Wlroots

    Client->>Helper: onOutputTestOrApply(config)
    Helper->>Helper: handleCopyModeSourceDisabled(disabledSource, requestedDisabled)
    alt surviving mirrors remain
        Helper->>Helper: promoteCopyOutputToSource(promotedMirror, surfaces, surfacesFrom, shouldRetarget)
        Helper->>RootSurfaceContainer: setPrimaryOutput(normalSource)
        Helper->>Helper: m_pendingOutputConfig.copyOutputIds = copyOutputIds
    else no mirror remains
        Helper->>Helper: convertCopyModeToExtension(disabledSource)
    end
    Helper->>Wlroots: commit output states
    Wlroots-->>Helper: commit callback
    Helper->>OutputManager: onOutputCommitFinished(config, success)
    alt successful re-enable in extension mode
        Helper->>Helper: surface->setPosition(savedPosition)
        Helper->>RootSurfaceContainer: updateSurfaceOutputs(surface)
    end
Loading

Sequence diagram for virtual-output updates and protocol notifications

sequenceDiagram
    participant Client as WaylandClient
    participant Manager as VirtualOutputManagerInterfaceV1
    participant Binding as VirtualOutputInterfaceV1
    participant Config as VirtualOutputConfig

    Client->>Manager: create_virtual_output(resource, id, name, outputs)
    Manager->>Config: storeVirtualOutput(name, outputs, virtualOutput)
    Manager->>Manager: notifyVirtualOutputModified(name)
    Manager-->>Client: send_virtual_output_modified(resource, name)

    Client->>Manager: get_virtual_output(resource, name, id)
    Manager->>Binding: createVirtualOutput(resource, id, name)
    alt virtual output exists
        Manager->>Config: backfill virtualOutput binding
        Manager-->>Binding: sendOutputs(name, outputs)
    else virtual output was removed
        Manager-->>Binding: sendOutputs(name, empty outputs)
    end

    Manager->>Manager: updateVirtualOutput(name, outputs)
    Manager->>Binding: sendOutputs(name, outputs)
    Manager->>Manager: notifyVirtualOutputModified(name)
    Client->>Manager: destroy(resource)
    Manager->>Manager: wl_resource_destroy(resource)
Loading

Flow diagram for safe output layout restoration

flowchart TD
    A[Output added or layout restored] --> B[Compute effective saved size]
    B --> C{Saved position overlaps enabled output?}
    C -- Yes --> D[Keep auto-added position]
    C -- No --> E["layout.move(output, savedPos)"]
    D --> F["layout.pin(output)"]
    E --> F
    F --> G[Output remains stable during sibling geometry changes]

    H[Output-management apply] --> I{Restored states overlap?}
    I -- Yes --> J[Reject restored topology]
    I -- No --> K[Apply restored states]
Loading

File-Level Changes

Change Details Files
Rebuild the virtual-output example into an interactive display-management client.
  • Add Treeland color-control and primary-output bindings.
  • Add wlroots output-management bindings for head discovery and configuration.
  • Replace command-line cloning with a Qt UI for screen selection, mirroring, enablement, geometry, mode, refresh, rotation, scale, brightness, color temperature, and primary-output control.
  • Track virtual-output groups and refresh state when the compositor reports modifications.
examples/test_virtual_output/CMakeLists.txt
examples/test_virtual_output/main.cpp
examples/test_virtual_output/virtualclient.cpp
examples/test_virtual_output/virtualclient.h
examples/test_virtual_output/wlroutputmanager.cpp
examples/test_virtual_output/wlroutputmanager.h
Extend the virtual-output protocol implementation to support lifecycle and modification notifications.
  • Upgrade the manager interface to version 3 and handle empty output lists safely.
  • Emit modification events when virtual-output groups are created, updated, or removed.
  • Implement manager-resource destruction and safely handle groups that disappear between listing and lookup.
  • Rebind newly requested virtual-output resources to current server-side configurations.
examples/test_virtual_output/virtualoutputmanager.cpp
examples/test_virtual_output/virtualoutputmanager.h
src/modules/virtual-output/virtualoutputmanagerinterfacev1.cpp
src/modules/virtual-output/virtualoutputmanagerinterfacev1.h
Make output layout and cursor behavior resilient to stale or changing output topology.
  • Pin automatically positioned outputs after placement to prevent wlroots reflow.
  • Avoid restoring saved positions that overlap enabled outputs and de-overlap extension-mode states.
  • Move the cursor to the closest remaining layout point when it leaves all outputs.
  • Ignore disabled outputs during cursor hit testing.
src/core/rootsurfacecontainer.cpp
src/core/rootsurfacecontainer.h
src/seat/helper.cpp
waylib/src/server/kernel/woutputlayout.cpp
waylib/src/server/kernel/woutputlayout.h
Improve copy-mode transitions, output disable/enable handling, and persistence.
  • Promote surviving mirrors when a copy source is disabled, or collapse copy mode when insufficient mirrors remain.
  • Convert mirror outputs to normal outputs for extension-mode transitions and mixed topologies.
  • Persist rebuilt copy groups only after all output commits succeed.
  • Restore disabled-output surfaces and clear snapshots on re-enable.
  • Persist primary-output restoration and reject incomplete stale copy configurations.
src/output/outputmanager.cpp
src/seat/helper.cpp
src/seat/helper.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/modules/virtual-output/virtualoutputmanagerinterfacev1.cpp" line_range="256-259" />
<code_context>
-    const QByteArray arrSend = config.outputs.join('\0').toLatin1();
-    virtualOutput->sendOutputs(name, arrSend);
+    if (it != m_configs.end()) {
+        // Backfill so updateVirtualOutput() pushes to this newest binding
+        // instead of a stale (possibly disconnected) proxy.
+        it.value().virtualOutput = virtualOutput;
+        virtualOutput->sendOutputs(name, it.value().outputs.join('\0').toLatin1());
+    } else {
+        virtualOutput->sendOutputs(name, QByteArray());
</code_context>
<issue_to_address>
**issue (bug_risk):** Rebinding `m_configs[name].virtualOutput` to the newest proxy does not make the destroy cleanup proxy-specific: when the older proxy is later destroyed, its `beforeDestroy` handler unconditionally removes `m_configs[name]`, including the configuration now bound to the newer proxy.

**Triggers:** When a client calls `get_virtual_output` for an existing group more than once and then destroys the older proxy.

**Suggested fix:** Capture the proxy in the cleanup callback and remove the config only when `m_configs[name].virtualOutput` still points to that proxy.
</issue_to_address>

### Comment 2
<location path="src/modules/virtual-output/virtualoutputmanagerinterfacev1.cpp" line_range="261" />
<code_context>
+        it.value().virtualOutput = virtualOutput;
+        virtualOutput->sendOutputs(name, it.value().outputs.join('\0').toLatin1());
+    } else {
+        virtualOutput->sendOutputs(name, QByteArray());
+    }
 }
</code_context>
<issue_to_address>
**issue (bug_risk):** The vanished-group path sends an empty outputs array, but the example client's `VirtualOutput::treeland_virtual_output_v1_outputs` handler returns without emitting `outputsReceived` when the array is empty, so the client never observes the required empty outputs event.

**Triggers:** When a listed virtual-output group disappears before the client calls `get_virtual_output`.

**Suggested fix:** Emit `outputsReceived(name, {})` for an empty array instead of returning before the signal emission.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/modules/virtual-output/virtualoutputmanagerinterfacev1.cpp
Comment thread src/modules/virtual-output/virtualoutputmanagerinterfacev1.cpp
…mpty outputs

- Only drop the virtual-output config when it is still bound to the
  destroyed proxy, so a newer get_virtual_output() binding survives
  the destruction of an older one.
- Always emit outputsReceived in the example client, even for an empty
  outputs array, so a vanished group is observable by the client.
@Groveer
Groveer marked this pull request as draft September 8, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants