Skip to content

fix: improve output configuration reliability and error handling#579

Merged
zccrs merged 1 commit intolinuxdeepin:masterfrom
Dami-star:multi-output
Oct 30, 2025
Merged

fix: improve output configuration reliability and error handling#579
zccrs merged 1 commit intolinuxdeepin:masterfrom
Dami-star:multi-output

Conversation

@Dami-star
Copy link
Collaborator

@Dami-star Dami-star commented Oct 9, 2025

refactor: implement atomic multi-output configuration

- Use WOutputRenderWindow interface for output configuration
- Complete output state management with all properties
- Ensure settings only saved when configuration succeeds

Summary by Sourcery

Overhaul the multi-output configuration flow to use atomic test/apply phases, track individual commit outcomes, extend rendering APIs for output state changes, and ensure output settings are only persisted upon successful configuration

Bug Fixes:

  • Prevent partial application of multi-output configurations by cancelling pending configs on error
  • Avoid writing output settings on failure and ensure settings saved only after all commits succeed

Enhancements:

  • Refactor Helper::onOutputTestOrApply to separate test and apply phases with pending commit tracking
  • Add onOutputCommitFinished to aggregate commit results and atomically update settings
  • Extend WOutputHelper and WOutputRenderWindow with API for atomic state changes (mode, custom mode, scale, transform, adaptive sync, enable) and commit listeners
  • Enhance WOutputManagerV1 to fully populate output state, defer updating current state until config succeeds, and schedule reconfiguration through the event loop

@Dami-star Dami-star requested a review from zccrs October 9, 2025 11:41
@sourcery-ai
Copy link

sourcery-ai bot commented Oct 9, 2025

Reviewer's Guide

Refactors output configuration to an atomic multi-output approach by leveraging WOutputRenderWindow for state changes, extending helper interfaces for granular control, and updating the manager to fully map and queue configurations, ensuring settings are saved only upon successful commits.

Sequence diagram for atomic multi-output configuration and commit process

sequenceDiagram
    participant Helper
    participant OutputManager
    participant RenderWindow
    participant OutputViewport
    participant Settings
    Helper->>OutputManager: onOutputTestOrApply(config, onlyTest)
    OutputManager->>RenderWindow: For each output, set state via WOutputRenderWindow
    RenderWindow->>OutputViewport: Apply state changes (mode, scale, transform, etc.)
    RenderWindow->>Helper: Notify commit finished (onOutputCommitFinished)
    Helper->>Settings: Save settings if all commits succeed
    Helper->>OutputManager: sendResult(config, success)
Loading

ER diagram for output configuration state mapping

erDiagram
    WOutputState {
        bool enabled
        int x
        int y
        wlr_output_mode* mode
        QSize customModeSize
        int customModeRefresh
        float scale
        int transform
        bool adaptiveSyncEnabled
    }
    qw_output_configuration_v1 ||--o{ qw_output_configuration_head_v1 : contains
    qw_output_configuration_head_v1 {
        wlr_output* output
        OutputState state
    }
    OutputState {
        bool enabled
        int x
        int y
        wlr_output_mode* mode
        float scale
        int transform
        bool adaptive_sync_enabled
        CustomMode custom_mode
    }
    CustomMode {
        int width
        int height
        int refresh
    }
    OutputState o|--|{ CustomMode : has
Loading

Class diagram for updated output configuration and helper interfaces

classDiagram
    class Helper {
        +onOutputTestOrApply(config, onlyTest)
        +onOutputCommitFinished(config, success)
        -PendingOutputConfig m_pendingOutputConfig
    }
    class PendingOutputConfig {
        +qw_output_configuration_v1 *config
        +QList<WOutputState> states
        +int pendingCommits
        +bool allSuccess
    }
    class WOutputHelper {
        +setScale(float)
        +setTransform(Transform)
        +setMode(wlr_output_mode*)
        +setCustomMode(int32_t, int32_t, int32_t)
        +setAdaptiveSyncEnabled(bool)
        +setEnabled(bool)
        +addCommitListener(CommitListener)
        +hasPendingModeChange() const
    }
    class WOutputRenderWindow {
        +setOutputScale(WOutputViewport*, float)
        +rotateOutput(WOutputViewport*, Transform)
        +setOutputMode(WOutputViewport*, wlr_output_mode*)
        +setOutputCustomMode(WOutputViewport*, int32_t, int32_t, int32_t)
        +setOutputAdaptiveSync(WOutputViewport*, bool)
        +setOutputEnabled(WOutputViewport*, bool)
    }
    Helper --> "1" PendingOutputConfig
    WOutputRenderWindow --> WOutputHelper
Loading

Class diagram for updated WOutputManagerV1 configuration handling

classDiagram
    class WOutputManagerV1 {
        +updateConfig()
        +sendResult(config, ok)
        -QList<WOutputState> stateList
        -QList<WOutputState> stateListPending
    }
    class qw_output_configuration_v1 {
        +send_succeeded()
        +send_failed()
    }
    WOutputManagerV1 --> qw_output_configuration_v1
Loading

File-Level Changes

Change Details Files
Implement atomic multi-output configuration in Helper with improved error handling
  • Separate test-only and apply logic with early returns for tests
  • Introduce PendingOutputConfig to track config, states, commits, and overall success
  • Apply state changes via WOutputRenderWindow and handle missing viewport/renderWindow with warnings
  • Aggregate commit results in onOutputCommitFinished and save settings only when all commits succeed
src/seat/helper.cpp
src/seat/helper.h
Expose granular output state controls in WOutputHelper and WOutputRenderWindow
  • Add setMode, setCustomMode, setAdaptiveSyncEnabled, setEnabled methods in WOutputHelper
  • Introduce addCommitListener and hasPendingModeChange in WOutputHelper
  • Implement setOutputMode/CustomMode/AdaptiveSync/Enabled in WOutputRenderWindow with update() calls
  • Adjust render logic to account for pending mode changes
waylib/src/server/qtquick/woutputhelper.cpp
waylib/src/server/qtquick/woutputhelper.h
waylib/src/server/qtquick/woutputrenderwindow.cpp
waylib/src/server/qtquick/woutputrenderwindow.h
Enhance WOutputManagerV1 to fully map output state and queue config updates
  • Populate enabled, position, mode, scale, transform, adaptive_sync_enabled, and custom_mode for each config head
  • Update sendResult to swap state lists on success, clear pending list, and queued-update updateConfig
  • Maintain previous state on failure and delete config after sending result
waylib/src/server/protocols/woutputmanagerv1.cpp

Possibly linked issues

  • fix: crash when popup window show #24: The PR improves output configuration reliability and error handling, which directly addresses the instability that caused the crash in output surface management in issue 24.

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

@Dami-star Dami-star requested a review from wineee October 9, 2025 11:42
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • The EBUSY retry paths don’t increment retryCount, so a busy output could retry indefinitely—consider incrementing and bounding those retries against MAX_RETRIES as well.
  • Directly calling updateConfig() at the end of sendResult() creates recursive flows that could blow the stack—consider scheduling the next configuration update on the event loop instead of immediate recursion.
  • The current backoff (RETRY_DELAY_BASE_MS * retryCount) is linear and may not be sufficient under load—consider using exponential backoff with jitter for more robust retry behavior.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The EBUSY retry paths don’t increment `retryCount`, so a busy output could retry indefinitely—consider incrementing and bounding those retries against `MAX_RETRIES` as well.
- Directly calling `updateConfig()` at the end of `sendResult()` creates recursive flows that could blow the stack—consider scheduling the next configuration update on the event loop instead of immediate recursion.
- The current backoff (RETRY_DELAY_BASE_MS * retryCount) is linear and may not be sufficient under load—consider using exponential backoff with jitter for more robust retry behavior.

## Individual Comments

### Comment 1
<location> `waylib/src/server/protocols/woutputmanagerv1.cpp:125-127` </location>
<code_context>
-        config->send_succeeded();
-    else
+
+    if (!ok && d->retryCount < d->MAX_RETRIES) {
+        d->retryCount++;
+        QTimer::singleShot(d->RETRY_DELAY_BASE_MS * d->retryCount, this, [this, config]() {
+            sendResult(config, true);
+        });
</code_context>

<issue_to_address>
**issue (bug_risk):** Retry logic may cause unexpected behavior due to always sending success after delay.

Currently, the retry always sends a success result without reattempting the operation, which may hide real failures. Please update the logic to retry the actual operation and only send success if it truly succeeds.
</issue_to_address>

### Comment 2
<location> `waylib/src/server/protocols/woutputmanagerv1.cpp:152-157` </location>
<code_context>
+            wlr_output_state_set_scale(&output_state, state.scale);
+            wlr_output_state_set_transform(&output_state, static_cast<wl_output_transform>(state.transform));
+
+            if (!wlr_output_test_state(wlr_output, &output_state)) {
+                if (errno == EBUSY) {
+                    QTimer::singleShot(d->RETRY_DELAY_BASE_MS, this, [this, config]() {
+                        sendResult(config, true);
+                    });
+                    wlr_output_state_finish(&output_state);
+                    return;
+                }
</code_context>

<issue_to_address>
**suggestion:** Handling of EBUSY is duplicated for both test and commit, consider refactoring.

Extract the EBUSY handling into a shared helper to avoid code duplication and simplify future maintenance.
</issue_to_address>

### Comment 3
<location> `waylib/src/server/protocols/woutputmanagerv1.cpp:121` </location>
<code_context>
 void WOutputManagerV1::sendResult(qw_output_configuration_v1 *config, bool ok)
 {
     W_D(WOutputManagerV1);
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring sendResult and updateConfig by extracting helper functions and centralizing retry logic to reduce nesting and improve clarity.

```suggestion
// 1) Extract per-output test/commit into a helper:
enum class StateResult { Success, Retry, Fail };

static StateResult tryApplyState(wlr_output *out, const WOutputState &st) {
    struct wlr_output_state os; 
    wlr_output_state_init(&os);
    wlr_output_state_set_enabled(&os, st.enabled);
    if (st.mode)  wlr_output_state_set_mode(&os, st.mode);
    wlr_output_state_set_scale(&os, st.scale);
    wlr_output_state_set_transform(&os, (wl_output_transform)st.transform);

    auto cleanup = [&]{ wlr_output_state_finish(&os); };

    if (!wlr_output_test_state(out, &os)) {
        if (errno == EBUSY) { cleanup(); return StateResult::Retry; }
        cleanup(); return StateResult::Fail;
    }
    if (!wlr_output_commit_state(out, &os)) {
        if (errno == EBUSY) { cleanup(); return StateResult::Retry; }
        cleanup(); return StateResult::Fail;
    }
    cleanup();
    return StateResult::Success;
}

// 2) Centralize retry scheduling:
void WOutputManagerV1::scheduleRetry(qw_output_configuration_v1 *cfg) {
    ++d->retryCount;
    int delay = d->RETRY_DELAY_BASE_MS * d->retryCount;
    QTimer::singleShot(delay, this, [this, cfg] {
        sendResult(cfg, /*retry=*/true);
    });
}

// 3) Flatten sendResult:
void WOutputManagerV1::sendResult(qw_output_configuration_v1 *cfg, bool ok) {
    W_D(WOutputManagerV1);
    if (!ok && d->retryCount < d->MAX_RETRIES) {
        return scheduleRetry(cfg);
    }
    d->retryCount = 0;

    bool allOk = true;
    for (auto &st : d->stateListPending) {
        auto *out = st.output->nativeHandle();
        if (!out) { allOk = false; break; }

        auto r = tryApplyState(out, st);
        if (r == StateResult::Retry)   return scheduleRetry(cfg);
        if (r == StateResult::Fail)    { allOk = false; break; }
    }

    if (allOk) {
        cfg->send_succeeded();
        d->stateList = std::move(d->stateListPending);
    } else {
        cfg->send_failed();
    }
    delete cfg;
    d->stateListPending.clear();
    updateConfig();
}

// 4) Simplify updateConfig with a small lambda:
void WOutputManagerV1::updateConfig() {
    W_D(WOutputManagerV1);
    auto *cfg = qw_output_configuration_v1::create();
    auto apply = [&](const WOutputState &s){
        auto *h = qw_output_configuration_head_v1::create(*cfg, s.output->nativeHandle())->handle()->state;
        h->enabled = s.enabled; h->x = s.x; h->y = s.y;
        if (!s.enabled) return;
        h->mode   = s.mode;
        h->scale  = s.scale;
        h->transform = (wl_output_transform)s.transform;
        h->adaptive_sync_enabled = s.adaptiveSyncEnabled;
        if (s.customModeSize.isValid()) {
            h->custom_mode = {
                uint32_t(s.customModeSize.width()), 
                uint32_t(s.customModeSize.height()), 
                s.customModeRefresh
            };
        }
    };

    for (auto &st : d->stateList) apply(st);
    d->manager->set_configuration(*cfg);
}
```
These extracts remove deep nesting in `sendResult`, centralize retry logic, and keep `updateConfig` a single flat loop with one small lambda.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@Dami-star
Copy link
Collaborator Author

Dami-star commented Oct 9, 2025

@sourcery-ai review

Copy link

@SourceryAI SourceryAI left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • updateConfig never populates stateListPending before calling set_configuration, so sendResult ends up working on an empty list—assign stateListPending = stateList (or the new state) prior to invoking the configuration.
  • The shared d->retryCount can get out of sync across overlapping configuration attempts—consider tracking retries per config instance or using a local counter scoped to each sendResult invocation.
  • When scheduling a retry you’re calling sendResult(config, true), which jumps into the success path on retry—pass ok=false or refactor so retries properly re-attempt the failed operation.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- updateConfig never populates stateListPending before calling set_configuration, so sendResult ends up working on an empty list—assign stateListPending = stateList (or the new state) prior to invoking the configuration.
- The shared d->retryCount can get out of sync across overlapping configuration attempts—consider tracking retries per config instance or using a local counter scoped to each sendResult invocation.
- When scheduling a retry you’re calling sendResult(config, true), which jumps into the success path on retry—pass ok=false or refactor so retries properly re-attempt the failed operation.

## Individual Comments

### Comment 1
<location> `waylib/src/server/protocols/woutputmanagerv1.cpp:125-131` </location>
<code_context>
-    else
+
+    if (!ok && d->retryCount < d->MAX_RETRIES) {
+        d->retryCount++;
+        QTimer::singleShot(d->RETRY_DELAY_MS * (1 << d->retryCount), this, [this, config]() {
+            sendResult(config, true);
</code_context>

<issue_to_address>
**suggestion:** retryCount is incremented in multiple places, which could lead to confusion.

Since retryCount is incremented in both the main retry block and within the tryOperation lambda, this could cause off-by-one errors or make the logic harder to follow. Centralize the increment to improve clarity and prevent mistakes.

```suggestion
    if (!ok && d->retryCount < d->MAX_RETRIES) {
        QTimer::singleShot(d->RETRY_DELAY_MS * (1 << (d->retryCount + 1)), this, [this, config]() {
            W_D(WOutputManagerV1);
            d->retryCount++;
            sendResult(config, true);
        });
        return;
    }
```
</issue_to_address>

### Comment 2
<location> `waylib/src/server/protocols/woutputmanagerv1.cpp:121` </location>
<code_context>
 void WOutputManagerV1::sendResult(qw_output_configuration_v1 *config, bool ok)
 {
     W_D(WOutputManagerV1);
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring retry and commit logic into small helper methods and flattening control flow in sendResult() for clarity.

Here’s one way to peel apart the retry‐and‐commit logic into small helpers and flatten the nesting in `sendResult()`. You can apply the same pattern to the other retry site, and likewise factor out the per‐state heads in `updateConfig()`.

```cpp
// In WOutputManagerV1Private:
void scheduleConfigRetry(qw_output_configuration_v1 *config) {
    retryCount++;
    const int delay = RETRY_DELAY_MS * (1 << retryCount);
    QTimer::singleShot(delay, q, [q = QPointer<WOutputManagerV1>(q), config] {
        if (q) q->sendResult(config, true);
    });
}

void resetRetry() { retryCount = 0; }

// A helper to commit/test a single output state:
bool commitOutputState(const WOutputState &st) {
    struct wlr_output_state os; 
    wlr_output_state_init(&os);
    // apply st to os...
    if (!wlr_output_test_state(h, &os) || !wlr_output_commit_state(h, &os)) {
        wlr_output_state_finish(&os);
        return false;
    }
    wlr_output_state_finish(&os);
    return true;
}
```

Then simplify `sendResult()`:
```cpp
void WOutputManagerV1::sendResult(qw_output_configuration_v1 *config, bool ok) {
    W_D(WOutputManagerV1);

    // 1) Retry-on-failure blanket
    if (!ok && d->retryCount < d->MAX_RETRIES) {
        d->scheduleConfigRetry(config);
        return;
    }
    d->resetRetry();

    // 2) Hard fail if still bad
    if (!ok) {
        config->send_failed();
        delete config;
        d->stateListPending.clear();
        QTimer::singleShot(0, this, &WOutputManagerV1::updateConfig);
        return;
    }

    // 3) Try each pending state
    bool allSuccess = true;
    for (auto &st : d->stateListPending) {
        if (!commitOutputState(st)) {
            allSuccess = false;
            break;
        }
    }

    // 4) Succeed or fail
    if (allSuccess) {
        config->send_succeeded();
        d->stateList.swap(d->stateListPending);
    } else {
        config->send_failed();
    }

    // 5) Cleanup & reapply
    delete config;
    d->stateListPending.clear();
    QTimer::singleShot(0, this, &WOutputManagerV1::updateConfig);
}
```

And for `updateConfig()`, pull the inner‐loop body into a helper:

```cpp
void WOutputManagerV1::applyStateToConfig(qw_output_configuration_v1 *cfg, const WOutputState &st) {
    auto *h = qw_output_configuration_head_v1::create(*cfg, st.output->nativeHandle())->handle();
    h->state.enabled = st.enabled;
    if (!st.enabled) return;
    h->state.x = st.x; h->state.y = st.y;
    h->state.mode = st.mode; /*…and so on…*/
}

void WOutputManagerV1::updateConfig() {
    W_D(WOutputManagerV1);
    auto *cfg = qw_output_configuration_v1::create();
    for (auto &st : d->stateList)
        applyStateToConfig(cfg, st);
    d->manager->set_configuration(*cfg);
}
```

This:

- Extracts retry scheduling & reset into two tiny methods.
- Flattens `sendResult()` into 5 clear steps with early returns.
- Removes the inline lambda retry and duplicates.
- Moves per-state “head” setup into its own helper so `updateConfig()` reads as a straight loop.
</issue_to_address>

Hi @Dami-star! 👋

Thanks for trying out Sourcery by commenting with @sourcery-ai review! 🚀

Install the sourcery-ai bot to get automatic code reviews on every pull request ✨

Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@Dami-star Dami-star mentioned this pull request Oct 10, 2025
26 tasks
@zccrs zccrs requested a review from Copilot October 11, 2025 01:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR improves the reliability of output configuration in the Wayland output manager by implementing error-based retry mechanisms and more comprehensive state management.

  • Adds retry logic with progressive delays and EBUSY error handling for output configuration operations
  • Expands output state configuration to include enabled flag, mode, scale, transform, adaptive sync, and custom mode settings
  • Implements proper error handling and validation during output state operations

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@Dami-star Dami-star marked this pull request as draft October 13, 2025 07:49
@Dami-star Dami-star marked this pull request as ready for review October 13, 2025 10:24
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

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

## Individual Comments

### Comment 1
<location> `src/seat/helper.cpp:481` </location>
<code_context>
 void Helper::onOutputTestOrApply(qw_output_configuration_v1 *config, bool onlyTest)
 {
     QList<WOutputState> states = m_outputManager->stateListPending();
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring duplicated state-building, viewport update, and settings logic into helper functions to simplify the main loops.

```markdown
Consider pulling out the duplicated “build‐qw_output_state” logic (and similarly viewport updates / settings writes) into small helpers.  That will collapse the two big loops in onOutputTestOrApply and applyOutputConfiguration into a few calls.

Example:

```cpp
// header
private:
    qw_output_state buildState(const WOutputState &s);
    void updateViewport(const WOutputState &s);
    void saveSettings(const QList<WOutputState> &states, const QString &groupPrefix);
```

```cpp
// impl
qw_output_state Helper::buildState(const WOutputState &s) {
    qw_output_state st;
    st.set_enabled(s.enabled);
    if (!s.enabled)
        return st;

    if (s.mode)
        st.set_mode(s.mode);
    else
        st.set_custom_mode(s.customModeSize.width(),
                           s.customModeSize.height(),
                           s.customModeRefresh);

    st.set_adaptive_sync_enabled(s.adaptiveSyncEnabled);
    st.set_transform(static_cast<wl_output_transform>(s.transform));
    st.set_scale(s.scale);
    return st;
}

void Helper::updateViewport(const WOutputState &s) {
    if (!s.enabled) return;
    if (auto *vp = getOutput(s.output)->screenViewport()) {
        if (auto *item = qobject_cast<WOutputItem*>(vp->parentItem())) {
            item->setX(s.x);
            item->setY(s.y);
        }
    }
}

void Helper::saveSettings(const QList<WOutputState> &states,
                          const QString &groupPrefix)
{
    QString loc = QStandardPaths::writableLocation(
                      QStandardPaths::AppConfigLocation)
                  + "/output.ini";
    QSettings settings(loc, QSettings::IniFormat);

    for (auto &s : states) {
        settings.beginGroup(
            QString("%1.%2").arg(groupPrefix).arg(s.output->name()));
        const auto &m = s.mode;
        settings.setValue("width",
            m ? m->width : s.customModeSize.width());
        settings.setValue("height",
            m ? m->height : s.customModeSize.height());
        settings.setValue("refresh",
            m ? m->refresh : s.customModeRefresh);
        settings.setValue("transform", s.transform);
        settings.setValue("scale", s.scale);
        settings.setValue("adaptiveSyncEnabled",
                          s.adaptiveSyncEnabled);
        settings.endGroup();
    }
}
```

Then your loops reduce to:

```cpp
// test‐only path
bool ok = true;
for (auto &st : states) {
    auto built = buildState(st);
    ok &= st.output->handle()->test_state(built);
}
m_outputManager->sendResult(config, ok);

// apply path
QVector<qw_output_state> v;
v.reserve(states.size());
for (auto &st : states)
    v.append(buildState(st));

// … run tests + commit …

if (ok) {
    for (auto &st : states)
        updateViewport(st);
    saveSettings(states, "output");
}
```

This collapses duplication, and makes each method’s structure far clearer.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@Dami-star Dami-star marked this pull request as draft October 14, 2025 02:24
@Dami-star Dami-star force-pushed the multi-output branch 3 times, most recently from c672f29 to 5047633 Compare October 15, 2025 07:28
@Dami-star Dami-star marked this pull request as ready for review October 15, 2025 07:29
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Sorry @Dami-star, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@Dami-star Dami-star marked this pull request as draft October 20, 2025 10:25
@Dami-star Dami-star force-pushed the multi-output branch 2 times, most recently from 9ec4da6 to b728b6e Compare October 21, 2025 11:01
@Dami-star Dami-star marked this pull request as ready for review October 21, 2025 11:05
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • The Helper::onOutputTestOrApply method is quite large and mixes test/apply logic heavily—consider refactoring common state‐preparation into smaller helper functions to reduce duplication and improve clarity.
  • The m_outputConfigTimer member in Helper is never used—either remove it or implement the intended delay/throttle behavior for pending output commits.
  • In WOutputHelper::commit, the hardwareChanged flag uses ok || committedBits, but it probably should be ok && committedBits to only emit change signals on successful commits.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The Helper::onOutputTestOrApply method is quite large and mixes test/apply logic heavily—consider refactoring common state‐preparation into smaller helper functions to reduce duplication and improve clarity.
- The m_outputConfigTimer member in Helper is never used—either remove it or implement the intended delay/throttle behavior for pending output commits.
- In WOutputHelper::commit, the hardwareChanged flag uses `ok || committedBits`, but it probably should be `ok && committedBits` to only emit change signals on successful commits.

## Individual Comments

### Comment 1
<location> `src/seat/helper.cpp:582-583` </location>
<code_context>
+        }
+        m_pendingOutputConfig.pendingCommits++;
+    }
+    for (int i = 0; i < states.size(); ++i) {
+        onOutputCommitFinished(m_pendingOutputConfig.config, true);
+    }
+}
</code_context>

<issue_to_address>
**issue (bug_risk):** Output commit completion is called synchronously for all outputs, which may not reflect actual commit results.

The current approach assumes all output commits succeed, which may hide individual failures or asynchronous issues. Please update the logic to reflect actual commit results for each output, using appropriate completion signals.
</issue_to_address>

### Comment 2
<location> `waylib/src/server/qtquick/woutputhelper.cpp:245-246` </location>
<code_context>
     bool ok = d->qwoutput()->commit_state(&state);
     wlr_output_state_finish(&state);

+    bool hardwareChanged = ok || (state.committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_TRANSFORM));
+    if (hardwareChanged) {
+        if (state.committed & WLR_OUTPUT_STATE_MODE) {
+            Q_EMIT d->output->modeChanged();
</code_context>

<issue_to_address>
**question (bug_risk):** The hardwareChanged condition may emit signals even if commit failed.

Since 'hardwareChanged' can be true even if 'ok' is false, signals may be emitted after a failed commit. Please review if signals should only be emitted when the commit succeeds, or if error handling should be added.
</issue_to_address>

### Comment 3
<location> `waylib/src/server/qtquick/woutputrenderwindow.cpp:1063-1061` </location>
<code_context>
     if (output()->offscreen())
         return true;

+    if (hasPendingModeChange()) {
+        WOutputHelper::commit();
+        return true;
+    }
+
</code_context>

<issue_to_address>
**question (bug_risk):** Early return on pending mode change may skip buffer commit logic.

Please verify that committing and returning early when hasPendingModeChange is true will not cause missed buffer updates or inconsistent output, particularly if mode changes and buffer commits can occur together.
</issue_to_address>

### Comment 4
<location> `src/seat/helper.cpp:481` </location>
<code_context>
 void Helper::onOutputTestOrApply(qw_output_configuration_v1 *config, bool onlyTest)
 {
     QList<WOutputState> states = m_outputManager->stateListPending();
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring onOutputTestOrApply by extracting viewport/render-window lookup and settings-save logic into helper methods to reduce duplication and improve maintainability.

```markdown
The new `onOutputTestOrApply` is still very large and duplicates viewport/render-window lookup and settings-save logic across test vs. apply paths. Splitting it into smaller methods and extracting the common “find viewport & renderWindow” and “write settings” pieces will make it much easier to follow and maintain.

For example, at the top of your .cpp you could add:

```cpp
static bool findViewportAndWindow(WOutput *output,
                                  WOutputViewport*& outViewport,
                                  WOutputRenderWindow*& outWindow)
{
    outViewport = getOutput(output)->screenViewport();
    if (!outViewport) return false;
    outWindow   = outViewport->outputRenderWindow();
    return outWindow != nullptr;
}
```

And a helper to save your final list of states:

```cpp
static void writeStatesToSettings(const QList<WOutputState>& states) {
    QString file = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)
                   + "/output.ini";
    QSettings settings(file, QSettings::IniFormat);
    for (auto &s : states) {
        settings.beginGroup(QString("output.%1").arg(s.output->name()));
        settings.setValue("width",
            s.mode ? s.mode->width : s.customModeSize.width());
        settings.setValue("height",
            s.mode ? s.mode->height : s.customModeSize.height());
        settings.setValue("refresh",
            s.mode ? s.mode->refresh : s.customModeRefresh);
        settings.setValue("transform",      s.transform);
        settings.setValue("scale",          s.scale);
        settings.setValue("adaptiveSyncEnabled",
                          s.adaptiveSyncEnabled);
        settings.endGroup();
    }
}
```

Then your `onOutputTestOrApply` reduces to:

```cpp
void Helper::onOutputTestOrApply(qw_output_configuration_v1 *config,
                                 bool onlyTest)
{
    auto states = m_outputManager->stateListPending();
    if (onlyTest) {
        bool ok = true;
        for (auto &st : states) {
            WOutputViewport *vp;
            WOutputRenderWindow *rw;
            if (!findViewportAndWindow(st.output, vp, rw)) {
                ok = false;
            } else {
                qw_output_state ns;
                // ... set ns fields ...
                ok &= st.output->handle()->test_state(ns);
            }
        }
        m_outputManager->sendResult(config, ok);
        return;
    }

    // apply case
    if (m_pendingOutputConfig.config)
        m_outputManager->sendResult(m_pendingOutputConfig.config, false);
    m_pendingOutputConfig = { config, states, 0, true };

    for (auto &st : states) {
        WOutputViewport *vp;
        WOutputRenderWindow *rw;
        if (!findViewportAndWindow(st.output, vp, rw)) {
            qWarning() << "No viewport/renderWindow for" << st.output->name();
            m_outputManager->sendResult(config, false);
            m_pendingOutputConfig = {};
            return;
        }
        // ... do renderWindow->setOutputXXX(vp, st.xxx) ...
        m_pendingOutputConfig.pendingCommits++;
    }
    // immediately fire fake commit callbacks
    for (int i = 0; i < states.size(); ++i)
        onOutputCommitFinished(config, true);
}
```

And finally in `onOutputCommitFinished`:

```cpp
void Helper::onOutputCommitFinished(...) {
    // ...
    if (m_pendingOutputConfig.pendingCommits == 0) {
        if (m_pendingOutputConfig.allSuccess)
            writeStatesToSettings(m_pendingOutputConfig.states);
        m_outputManager->sendResult(config, ok);
        m_pendingOutputConfig = {};
    }
}
```

This extracts the two major concerns (lookup & settings-save) and splits the test vs. apply flows into concise blocks. It keeps all functionality intact, removes duplication, and drastically reduces the cyclomatic complexity of the original large method.
</issue_to_address>

### Comment 5
<location> `waylib/src/server/qtquick/woutputrenderwindow.cpp:1819` </location>
<code_context>
     }
 }

+void WOutputRenderWindow::setOutputMode(WOutputViewport *output, const wlr_output_mode *mode)
+{
+    Q_D(WOutputRenderWindow);
</code_context>

<issue_to_address>
**issue (complexity):** Consider introducing a helper method to encapsulate the repeated pattern of looking up the output helper, invoking a setter, and calling update().

Consider factoring out the “lookup‐helper + call + update()” pattern. For example, add in your class:

```cpp
private:
template<typename F>
void modifyOutput(WOutputViewport *output, F fn) {
    Q_D(WOutputRenderWindow);
    if (auto helper = d->getOutputHelper(output)) {
        fn(helper);
        update();
    }
}
```

Then each setter collapses to a one‐liner:

```cpp
void WOutputRenderWindow::setOutputMode(WOutputViewport *output, const wlr_output_mode *mode) {
    modifyOutput(output, [mode](auto *h){ h->setMode(mode); });
}

void WOutputRenderWindow::setOutputCustomMode(WOutputViewport *output,
                                              int32_t w, int32_t h, int32_t r) {
    modifyOutput(output, [w,h,r](auto *h){ h->setCustomMode(w,h,r); });
}

void WOutputRenderWindow::setOutputAdaptiveSync(WOutputViewport *output, bool e) {
    modifyOutput(output, [e](auto *h){ h->setAdaptiveSyncEnabled(e); });
}

void WOutputRenderWindow::setOutputEnabled(WOutputViewport *output, bool e) {
    modifyOutput(output, [e](auto *h){ h->setEnabled(e); });
}
```

If you prefer member‐pointer syntax:

```cpp
template<typename M, typename... A>
void applySetting(WOutputViewport *output, M m, A&&... args) {
    Q_D(WOutputRenderWindow);
    if (auto helper = d->getOutputHelper(output)) {
        (helper->*m)(std::forward<A>(args)...);
        update();
    }
}

// usage
applySetting(output, &OutputHelper::setScale, scale);
applySetting(output, &OutputHelper::setTransform, t);
```

This eliminates the boilerplate while preserving all functionality.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@Dami-star Dami-star force-pushed the multi-output branch 2 times, most recently from 839fb6c to a02cc11 Compare October 21, 2025 11:29
@Dami-star Dami-star requested a review from zccrs October 22, 2025 01:23
@Dami-star Dami-star force-pushed the multi-output branch 2 times, most recently from 9a41e2d to a663c97 Compare October 28, 2025 12:09
@Dami-star Dami-star force-pushed the multi-output branch 2 times, most recently from 4836a1c to 0a96efa Compare October 29, 2025 02:35
@Dami-star Dami-star requested a review from zccrs October 29, 2025 09:23
- Use WOutputRenderWindow interface for output configuration
- Complete output state management with all properties
- Ensure settings only saved when configuration succeeds
@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Dami-star, zccrs

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

@zccrs zccrs merged commit f50669e into linuxdeepin:master Oct 30, 2025
10 of 12 checks passed
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.

5 participants