Skip to content

refactor: migrate foreign-toplevel protocol to qtwayland scanner#911

Open
zorowk wants to merge 4 commits into
linuxdeepin:masterfrom
zorowk:forgin_qt
Open

refactor: migrate foreign-toplevel protocol to qtwayland scanner#911
zorowk wants to merge 4 commits into
linuxdeepin:masterfrom
zorowk:forgin_qt

Conversation

@zorowk
Copy link
Copy Markdown
Contributor

@zorowk zorowk commented May 29, 2026

Replace the legacy ws_generate_local scanner with the qtwayland QWaylandServer protocol generator. Merge the separate impl layer into ForeignToplevelManagerInterfaceV1 (renamed from ForeignToplevelV1), eliminating redundant indirection between the server protocol wrapper and the underlying implementation.

log: migrate foreign-toplevel protocol to qtwayland scanner

Summary by Sourcery

Migrate the treeland foreign-toplevel manager protocol implementation to use the QtWayland server protocol generator and consolidate the protocol wrapper and implementation into a single manager interface class.

Enhancements:

  • Introduce ForeignToplevelManagerInterfaceV1, ForeignToplevelHandleV1, and DockPreviewContextV1 to replace the previous foreign-toplevel manager implementation layer.
  • Refactor dock preview handling to be driven directly by QtWayland-generated resources and events, including idle done signaling and output tracking for toplevels.
  • Rename and update all core and QML integrations to use ForeignToplevelManagerInterfaceV1 and its PreviewDirection enum instead of the old ForeignToplevelV1 API.

Build:

  • Replace ws_generate_local with QtWayland's local_qtwayland_server_protocol_treeland for generating the treeland-foreign-toplevel-manager-v1 server protocol C sources and update include paths accordingly.

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: zorowk

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
Copy link
Copy Markdown

sourcery-ai Bot commented May 29, 2026

Reviewer's Guide

Refactors the foreign-toplevel Wayland protocol implementation to use the QtWayland QWaylandServer code generator, inlining the previous impl layer into a new ForeignToplevelManagerInterfaceV1 and reworking dock-preview and toplevel-handle handling while updating shell and QML integration accordingly.

Sequence diagram for dock preview interaction via ForeignToplevelManagerInterfaceV1

sequenceDiagram
    actor User
    participant DockPreviewQml as DockPreview
    participant ShellHandler
    participant Manager as ForeignToplevelManagerInterfaceV1
    participant Context as DockPreviewContextV1
    participant ContextPriv as DockPreviewContextV1Private

    User->>DockPreviewQml: hover over dock item
    DockPreviewQml->>Manager: enterDockPreview(relativeSurface)
    Manager->>Context: enter()

    note over ContextPriv,ContextPriv: client later requests preview
    ContextPriv->>Context: show(resource, surfaces, x, y, direction)
    Context->>Manager: requestShow(event)
    Manager->>Manager: handleDockPreviewShow(event)
    Manager->>ShellHandler: requestDockPreview(surfaces, target, pos, direction)
    ShellHandler->>DockPreviewQml: onDockPreview(...)

    User-->>DockPreviewQml: leaves dock item
    DockPreviewQml->>Manager: leaveDockPreview(relativeSurface)
    Manager->>Context: leave()
Loading

File-Level Changes

Change Details Files
Replace legacy ws_generate_local-generated protocol with QtWaylandServer-based implementation and introduce ForeignToplevelManagerInterfaceV1 / ForeignToplevelHandleV1 / DockPreviewContextV1.
  • Create ForeignToplevelManagerInterfaceV1Private inheriting from QtWaylandServer::treeland_foreign_toplevel_manager_v1 and manage wl_global, event loop, and surface-to-handle map.
  • Implement manager requests (stop, get_dock_preview_context) directly via wl_resource APIs and track DockPreviewContextV1 instances globally.
  • Inline the old impl layer into ForeignToplevelManagerInterfaceV1, including addSurface/removeSurface, dock preview enter/leave, and toplevel handle initialization logic.
src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp
src/modules/foreign-toplevel/foreigntoplevelmanagerv1.h
Implement server-side dock preview context handling using a QtWaylandServer-backed DockPreviewContextV1 abstraction.
  • Introduce DockPreviewContextV1Private subclassing QtWaylandServer::treeland_dock_preview_context_v1 and implement destroy, show, show_tooltip, and close handlers that translate Wayland requests to Qt signals.
  • Maintain a global list of DockPreviewContextV1 objects, provide lookup helpers by wl_resource/WSurface, and emit requestDockPreview / requestDockPreviewTooltip / requestDockClose / beforeDestroy signals.
  • Adapt ForeignToplevelManagerInterfaceV1 to create DockPreviewContextV1 objects on get_dock_preview_context, wiring their signals to manager slots for dock preview handling.
src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp
src/modules/foreign-toplevel/foreigntoplevelmanagerv1.h
Replace treeland_foreign_toplevel_handle_v1 C-style wrapper with a C++ OO ForeignToplevelHandleV1 around QtWaylandServer::treeland_foreign_toplevel_handle_v1.
  • Add ForeignToplevelHandleV1Private subclassing QtWaylandServer::treeland_foreign_toplevel_handle_v1, implementing requests such as set_maximized, set_minimized, activate, close, set_rectangle, and set_fullscreen/unset_fullscreen by emitting new typed signals.
  • Introduce ForeignToplevelHandleV1 with state tracking, title/app-id/pid/identifier properties, parent/outputs management, and helpers for sending state/output events plus idle-based send_done coalescing via wl_event_loop_add_idle.
  • Track all active ForeignToplevelHandleV1 instances in a global list, provide lookup by wl_resource, and ensure handles unregister themselves from the manager via ForeignToplevelManagerInterfaceV1::releaseHandle on destruction.
src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp
src/modules/foreign-toplevel/foreigntoplevelmanagerv1.h
Update ShellHandler, Helper, and QML bindings to use ForeignToplevelManagerInterfaceV1 instead of ForeignToplevelV1.
  • Change server attachment and QML registration in ShellHandler to ForeignToplevelManagerInterfaceV1, update signal/slot signatures (PreviewDirection type) and member variable type.
  • Update DockPreview.qml to reference ForeignToplevelManagerInterfaceV1 for leaveDockPreview/enterDockPreview and direction enum comparisons.
  • Adjust forward declarations in shellhandler.h and helper.h from ForeignToplevelV1 to ForeignToplevelManagerInterfaceV1.
src/core/shellhandler.cpp
src/core/shellhandler.h
src/core/qml/DockPreview.qml
src/seat/helper.h
Switch CMake integration from ws_generate_local to local_qtwayland_server_protocol_treeland and remove the old impl layer sources.
  • Use find_package(TreelandProtocols REQUIRED) without version pin and call local_qtwayland_server_protocol_treeland to generate wayland-treeland-foreign-toplevel-manager-v1-server-protocol.c.
  • Update impl_treeland sources and include paths to use the QtWayland-generated C file and current binary dir instead of the old treeland-foreign-toplevel-manager-protocol.c and WAYLAND_PROTOCOLS_OUTPUTDIR.
  • Remove the obsolete impl/foreign_toplevel_manager_impl.cpp/.h files from the build and delete their contents.
src/modules/foreign-toplevel/CMakeLists.txt
src/modules/foreign-toplevel/impl/foreign_toplevel_manager_impl.cpp
src/modules/foreign-toplevel/impl/foreign_toplevel_manager_impl.h

Possibly linked issues

  • #[Bug]: offset of on non-standard-layout type 'typeof (*context)' (aka 'treeland_dock_preview_context_v1') [-Winvalid-offsetof]: The PR migrates foreign-toplevel to qtwayland and replaces treeland_dock_preview_context_v1 usage that caused offsetof warning.

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

Copy link
Copy Markdown

@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 - I've found 2 issues, and left some high level feedback:

  • In ForeignToplevelManagerInterfaceV1::addSurface, a single ForeignToplevelHandleV1 per SurfaceWrapper is stored in d->m_surfaces even though you create one wl_resource per manager_resource; this means multiple client-specific handles for the same wrapper overwrite each other, so consider tracking handles per (SurfaceWrapper, client) or otherwise ensuring the map accurately represents all active toplevel handles.
  • PreviewDirection now uses plain 0/1/2/3 constants instead of the protocol enums (TREELAND_DOCK_PREVIEW_CONTEXT_V1_DIRECTION_*); to avoid subtle mismatches between the QML-facing enum and the wire protocol, it would be safer to keep these values derived directly from the protocol enums or provide an explicit mapping function.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In ForeignToplevelManagerInterfaceV1::addSurface, a single ForeignToplevelHandleV1 per SurfaceWrapper is stored in d->m_surfaces even though you create one wl_resource per manager_resource; this means multiple client-specific handles for the same wrapper overwrite each other, so consider tracking handles per (SurfaceWrapper, client) or otherwise ensuring the map accurately represents all active toplevel handles.
- PreviewDirection now uses plain 0/1/2/3 constants instead of the protocol enums (TREELAND_DOCK_PREVIEW_CONTEXT_V1_DIRECTION_*); to avoid subtle mismatches between the QML-facing enum and the wire protocol, it would be safer to keep these values derived directly from the protocol enums or provide an explicit mapping function.

## Individual Comments

### Comment 1
<location path="src/modules/foreign-toplevel/foreigntoplevelmanagerv1.h" line_range="34" />
<code_context>
+};
+
+class DockPreviewContextV1Private;
+class DockPreviewContextV1 : public QObject
+{
+    Q_OBJECT
</code_context>
<issue_to_address>
**issue (complexity):** Consider restructuring the new foreign-toplevel API so that wl/protocol details are hidden behind private implementations and smaller headers, leaving a slimmer, higher-level public surface.

You can reduce the added complexity without changing behavior by tightening the public surface and pushing protocol/wl details into private/PIMPL classes and cpp files. Concretely:

---

### 1. Split the monolithic header and hide protocol structs

`DockPreviewContextV1`, `ForeignToplevelHandleV1`, `ForeignToplevelManagerInterfaceV1`, and all `treeland_*` structs don’t need to live in one public header.

**Actionable suggestion**

Create smaller headers and move the protocol structs into the corresponding cpp, exposing only high-level types in the header:

```cpp
// dockpreviewcontextv1.h (public)
class DockPreviewContextV1Private;

class DockPreviewContextV1 : public QObject {
    Q_OBJECT
public:
    ~DockPreviewContextV1() override;

    WSurface *relativeSurface() const;

    void enter();
    void leave();

Q_SIGNALS:
    void requestShow(const QPoint &pos, ForeignToplevelManagerInterfaceV1::PreviewDirection dir,
                     const QList<ForeignToplevelHandleV1 *> &toplevels);
    void requestShowTooltip(const QString &tooltip, const QPoint &pos,
                            ForeignToplevelManagerInterfaceV1::PreviewDirection dir);
    void requestClose();
    void beforeDestroy();

private:
    explicit DockPreviewContextV1(wl_resource *resource, wlr_surface *_relativeSurface);
    std::unique_ptr<DockPreviewContextV1Private> d;

    friend class ForeignToplevelManagerInterfaceV1Private;
};
```

```cpp
// dockpreviewcontextv1.cpp (private protocol structs)
struct DockPreviewPreviewEvent {
    std::vector<uint32_t> toplevels;
    int32_t x, y;
    int32_t direction;
};

struct DockPreviewTooltipEvent {
    QString tooltip;
    int32_t x, y;
    int32_t direction;
};

// map generated treeland_* events into the higher-level signal signatures above
```

Same pattern for `ForeignToplevelHandleV1` – keep only Qt/Waylib-friendly types in the header and map to/from `treeland_*` structs in the cpp.

---

### 2. Hide wl/wlr pointers from the public API

The new public structs (e.g. `treeland_foreign_toplevel_handle_v1_*_event`) expose `wlr_surface *`, `wlr_output *`, and `wlr_seat *` directly. You can keep this wiring internal and expose wrappers in the signals instead.

**Actionable suggestion**

In the header, expose higher-level objects, and keep wl/wlr types in the private implementation:

```cpp
// foreigntoplevelhandlev1.h
class ForeignToplevelHandleV1 : public QObject {
    Q_OBJECT
public:
    // ...

Q_SIGNALS:
    void requestMaximize(bool maximized);
    void requestMinimize(bool minimized);
    void requestActivate(QSeat *seat);    // or your existing seat wrapper
    void requestFullscreen(bool fullscreen, WOutput *output);
    void requestClose();
    void rectangleChanged(WSurface *surface, const QRect &rect);

private:
    std::unique_ptr<ForeignToplevelHandleV1Private> d;
};
```

```cpp
// foreigntoplevelhandlev1.cpp
struct MaximizedEvent { bool maximized; };
struct MinimizedEvent { bool minimized; };
struct ActivatedEvent { wlr_seat *seat; };
// ...

void ForeignToplevelHandleV1Private::on_maximize(const MaximizedEvent &ev)
{
    // convert & emit public signal
    Q_EMIT q->requestMaximize(ev.maximized);
}

void ForeignToplevelHandleV1Private::on_fullscreen(const FullscreenEvent &ev)
{
    auto outputWrapper = WOutput::from(ev.output);
    Q_EMIT q->requestFullscreen(ev.fullscreen, outputWrapper);
}
```

This keeps consumers decoupled from wlroots, while retaining your protocol plumbing internally.

---

### 3. Encapsulate state → wire-encoding mapping

`ForeignToplevelHandleV1::State` + `States` and `send_state()` currently encode protocol values manually. You can hide this serialization detail in a small helper in the cpp.

**Actionable suggestion**

Keep the public enum/flags, but move the wire encoding into a private helper:

```cpp
// foreigntoplevelhandlev1.h
class ForeignToplevelHandleV1 : public QObject {
    Q_OBJECT
public:
    enum class State {
        Maximized = 1,
        Minimized = 2,
        Activated = 4,
        Fullscreen = 8,
        Attention = 16,
    };
    Q_ENUM(State)
    Q_DECLARE_FLAGS(States, State)

    void set_maximized(bool maximized);
    // ...

private:
    void send_state(); // keep public behaviour via thin wrapper

    std::unique_ptr<ForeignToplevelHandleV1Private> d;
};
```

```cpp
// foreigntoplevelhandlev1.cpp
namespace {
    QByteArray encodeStates(ForeignToplevelHandleV1::States states)
    {
        QByteArray data;
        // map States bits to protocol enum ints here, once
        if (states.testFlag(ForeignToplevelHandleV1::State::Maximized))
            data.append(static_cast<char>(TREELAND_FOREIGN_TOPLEVEL_STATE_MAXIMIZED));
        // ...
        return data;
    }
}

void ForeignToplevelHandleV1::send_state()
{
    auto data = encodeStates(d->states);
    // use data for treeland_foreign_toplevel_handle_v1_state
}
```

Now the mapping logic lives in a single implementation helper, not spread through the public class.

---

### 4. Keep lifetime/idle management methods non-public

`ForeignToplevelManagerInterfaceV1::releaseHandle`, `ForeignToplevelHandleV1::reset_idle_source`, and `send_done` represent internal lifetime / idle handling and don’t need to be part of the public surface.

**Actionable suggestion**

Move them to the private class and expose only semantic operations:

```cpp
// foreigntoplevelhandlev1.h
class ForeignToplevelHandleV1 : public QObject {
    Q_OBJECT
public:
    // semantic methods only
    void set_maximized(bool maximized);
    void set_minimized(bool minimized);
    // ...
    void close(); // wraps send_closed()

private:
    void reset_idle_source();   // moved to private section
    void send_done();           // moved to private section
    // send_output can also be private if only manager uses it

    std::unique_ptr<ForeignToplevelHandleV1Private> d;

    friend class ForeignToplevelManagerInterfaceV1Private;
};
```

```cpp
// foreigntoplevelmanagerinterfacev1.h
class ForeignToplevelManagerInterfaceV1 : public QObject, public WServerInterface {
    Q_OBJECT
public:
    void addSurface(SurfaceWrapper *wrapper);
    void removeSurface(SurfaceWrapper *wrapper);

private:
    void releaseHandle(ForeignToplevelHandleV1 *handle);

    std::unique_ptr<ForeignToplevelManagerInterfaceV1Private> d;

    friend class ForeignToplevelManagerInterfaceV1Private;
};
```

Callers continue to use `addSurface/removeSurface`; the manager’s private side orchestrates handle release and idle timers.

---

### 5. Avoid exposing global lookup helpers

`DockPreviewContextV1::get(wl_resource *)` and `getDockPreviewContext(WSurface *)` encourage global searches and leak registry details. If you must maintain the registry, keep lookup methods internal.

**Actionable suggestion**

Remove them from the public interface and relocate to the private/impl:

```cpp
// dockpreviewcontextv1.h
class DockPreviewContextV1 : public QObject {
    Q_OBJECT
public:
    // ...
    WSurface *relativeSurface() const;

private:
    std::unique_ptr<DockPreviewContextV1Private> d;

    friend class ForeignToplevelManagerInterfaceV1Private;
};
```

```cpp
// dockpreviewcontextv1_p.h (internal header)
class DockPreviewContextV1Private {
public:
    static DockPreviewContextV1 *fromResource(wl_resource *resource);
    static DockPreviewContextV1 *fromSurface(WSurface *relativeSurface);
    // internal registry implementation
};
```

Internal call sites (manager, impl cpp files) can still use these helpers; external consumers only see the managed objects.

---

Overall, these focused changes allow you to keep all added functionality but:

- shrink the public header surface,
- decouple consumers from wlroots/protocol details,
- centralize state/wire mapping, and
- keep lifetime and lookup mechanics internal.
</issue_to_address>

### Comment 2
<location path="src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp" line_range="24" />
<code_context>
 #include <qwxdgshell.h>

-static ForeignToplevelV1 *FOREIGN_TOPLEVEL_MANAGER = nullptr;
+static QList<DockPreviewContextV1 *> s_dockPreviewContexts;
+static QList<ForeignToplevelHandleV1 *> s_foreignToplevelHandles;

</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring the new manager/handle code to avoid global registries, factor out handle creation, and hide idle scheduling details behind private helpers to keep ownership and responsibilities localized.

You can reduce some of the new complexity without changing behavior by tightening ownership and encapsulating repeated patterns.

### 1. Replace global registries with manager‑owned containers

`static QList<DockPreviewContextV1 *> s_dockPreviewContexts;` and  
`static QList<ForeignToplevelHandleV1 *> s_foreignToplevelHandles;` introduce global mutable state and require manual cleanup from multiple places (`destroy_resource`, `QObject::destroyed` lambdas, etc).

You can keep the existing behavior while making ownership explicit by moving these lists into `ForeignToplevelManagerInterfaceV1Private` and using `wl_resource_set_user_data` for lookups.

Example:

```cpp
// foreigntoplevelmanagerv1_p.h (or current TU)
class ForeignToplevelManagerInterfaceV1Private
    : public QtWaylandServer::treeland_foreign_toplevel_manager_v1
{
public:
    // ...

    QList<DockPreviewContextV1 *> dockPreviewContexts;
    QList<ForeignToplevelHandleV1 *> handles;
};
```

Creation:

```cpp
void ForeignToplevelManagerInterfaceV1Private::get_dock_preview_context(
    Resource *resource,
    struct ::wl_resource *relative_surface,
    uint32_t id)
{
    // ... validation ...

    wl_resource *dockPreviewContextResource = wl_resource_create(...);
    if (!dockPreviewContextResource) {
        wl_client_post_no_memory(resource->client());
        return;
    }

    auto *context = new DockPreviewContextV1(dockPreviewContextResource, relativeSurface);
    wl_resource_set_user_data(dockPreviewContextResource, context);

    dockPreviewContexts.append(context);
    QObject::connect(context, &QObject::destroyed, q, [this, context]() {
        dockPreviewContexts.removeOne(context);
    });

    // connects unchanged...
}
```

Then `DockPreviewContextV1::get` can avoid the global:

```cpp
DockPreviewContextV1 *DockPreviewContextV1::get(wl_resource *resource)
{
    return static_cast<DockPreviewContextV1 *>(wl_resource_get_user_data(resource));
}
```

Same pattern for `ForeignToplevelHandleV1`:

```cpp
ForeignToplevelHandleV1::ForeignToplevelHandleV1(ForeignToplevelManagerInterfaceV1 *manager,
                                                 wl_resource *resource)
    : QObject(nullptr)
    , d(new ForeignToplevelHandleV1Private(this, manager, resource))
{
    wl_resource_set_user_data(resource, this);
    d->manager->d->handles.append(this);
}
```

And `destroy_resource` becomes simpler:

```cpp
void ForeignToplevelHandleV1Private::destroy_resource(Resource *resource)
{
    if (manager) {
        manager->d->handles.removeOne(q);
        manager->releaseHandle(q);  // keeps current manager logic
        return;
    }

    delete q;
}
```

This preserves existing semantics, but removes global state and dual registries, and lets you centralize all cleanup under the manager.

### 2. Encapsulate handle creation per client in a helper

`ForeignToplevelManagerInterfaceV1::addSurface` currently mixes:

- iterating over `resourceMap()`,
- creating Wayland resources,
- instantiating `ForeignToplevelHandleV1`,
- inserting into `m_surfaces`,
- wiring up signals.

Extract the “create handle resource for manager resource” into a private helper; `addSurface` will read more clearly and be easier to maintain.

```cpp
// in ForeignToplevelManagerInterfaceV1Private
ForeignToplevelHandleV1 *ForeignToplevelManagerInterfaceV1Private::createHandle(
    SurfaceWrapper *wrapper,
    Resource *manager_resource)
{
    wl_client *client = wl_resource_get_client(manager_resource->handle);
    wl_resource *resource = wl_resource_create(client,
                                               &treeland_foreign_toplevel_handle_v1_interface,
                                               wl_resource_get_version(manager_resource->handle),
                                               0);
    if (!resource) {
        qCCritical(treelandProtocol) << "wl_resource_create failed!";
        wl_client_post_no_memory(client);
        return nullptr;
    }

    auto *handle = new ForeignToplevelHandleV1(q, resource);
    handles.append(handle);  // from suggestion #1

    QObject::connect(handle, &QObject::destroyed, q, [this, handle]() {
        handles.removeOne(handle);
    });

    send_toplevel(manager_resource->handle, resource);
    m_surfaces.insert({ wrapper, std::unique_ptr<ForeignToplevelHandleV1>(handle) });

    return handle;
}
```

Usage:

```cpp
void ForeignToplevelManagerInterfaceV1::addSurface(SurfaceWrapper *wrapper)
{
    if (d->m_surfaces.contains(wrapper)) {
        qCCritical(treelandProtocol)
            << wrapper << " has been add to foreign toplevel twice";
        return;
    }

    for (const auto &manager_resource : d->resourceMap()) {
        auto *handle = d->createHandle(wrapper, manager_resource);
        if (!handle)
            break;

        handle->set_identifier(d->m_nextIdentifier++);

        connect(handle, &ForeignToplevelHandleV1::requestClose, wrapper, [wrapper]() {
            wrapper->close();
        });

        if (wrapper->type() == SurfaceWrapper::Type::SplashScreen) {
            // existing splash handling...
        } else {
            initializeToplevelHandle(wrapper, handle);
        }
    }
}
```

This keeps behavior identical, but localizes low-level Wayland boilerplate inside the private implementation.

### 3. Hide idle scheduling inside `ForeignToplevelHandleV1Private`

`ForeignToplevelHandleV1` currently exposes `reset_idle_source()` and `update_idle_source()` publicly and relies on a free function (`toplevel_idle_send_done`) to drive `send_done()`. This makes the public API aware of event-loop details that only the private side needs.

You can keep the current semantics and reduce surface area by:

- Moving `update_idle_source()` / `reset_idle_source()` into the private class.
- Making the idle callback a `static` member or friend that operates purely on the private.

Example:

```cpp
// in ForeignToplevelHandleV1Private
static void idleSendDone(void *data)
{
    auto *self = static_cast<ForeignToplevelHandleV1Private *>(data);
    self->send_done();
    self->idle_source = nullptr;
}

void ForeignToplevelHandleV1Private::scheduleDone()
{
    if (idle_source || !manager) {
        return;
    }
    idle_source = wl_event_loop_add_idle(manager->eventLoop(), idleSendDone, this);
}
```

Public methods then call `scheduleDone()` instead of `update_idle_source()`:

```cpp
void ForeignToplevelHandleV1::set_title(const QString &title)
{
    if (d->title == title)
        return;

    d->title = title;
    d->send_title(title);
    d->scheduleDone();
}
```

And similarly in `set_app_id`, `set_pid`, `set_identifier`, `set_parent`, `send_state`, `send_output`.

You can then drop the public `reset_idle_source()` / `update_idle_source()` entirely, shrinking the visible API while keeping the same “batch and send done on idle” behavior.
</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.

Comment thread src/modules/foreign-toplevel/foreigntoplevelmanagerv1.h Outdated
Comment thread src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp Outdated
Copy link
Copy Markdown
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 migrates the foreign-toplevel protocol implementation from the legacy local scanner to the QtWayland server protocol generator, while consolidating the old implementation layer into ForeignToplevelManagerInterfaceV1.

此 PR 将 foreign-toplevel 协议实现从旧的本地 scanner 迁移到 QtWayland server 协议生成器,并把旧的 impl 层合并到 ForeignToplevelManagerInterfaceV1 中。

Changes:

  • Replaces ws_generate_local with local_qtwayland_server_protocol_treeland.
  • Renames and expands the API around ForeignToplevelManagerInterfaceV1, ForeignToplevelHandleV1, and DockPreviewContextV1.
  • Updates ShellHandler and QML dock preview integration to use the renamed singleton/API.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/seat/helper.h Updates forward declaration to the renamed manager type.
src/modules/foreign-toplevel/impl/foreign_toplevel_manager_impl.h Removes the old protocol implementation header.
src/modules/foreign-toplevel/impl/foreign_toplevel_manager_impl.cpp Removes the old protocol implementation source.
src/modules/foreign-toplevel/foreigntoplevelmanagerv1.h Introduces the consolidated manager, handle, and dock preview context interfaces.
src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp Implements the QtWayland-generated protocol integration and refactored runtime behavior.
src/modules/foreign-toplevel/CMakeLists.txt Switches protocol generation to the QtWayland scanner path.
src/core/shellhandler.h Updates type references for the renamed manager.
src/core/shellhandler.cpp Registers and connects the renamed QML/C++ manager singleton.
src/core/qml/DockPreview.qml Updates QML references to the renamed singleton and enum.

Comment thread src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp Outdated
Comment thread src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp Outdated
Comment thread src/modules/foreign-toplevel/CMakeLists.txt
Comment thread src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp
Comment thread src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp Outdated
Replace the legacy ws_generate_local scanner with the qtwayland
QWaylandServer protocol generator. Merge the separate impl layer
into ForeignToplevelManagerInterfaceV1 (renamed from ForeignToplevelV1),
eliminating redundant indirection between the server protocol wrapper
and the underlying implementation.

log: migrate foreign-toplevel protocol to qtwayland scanner
Comment thread src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp Outdated
@zccrs zccrs requested a review from lbwtw May 30, 2026 08:35
zorowk added 2 commits June 1, 2026 13:44
Replace one-to-one surface-to-handle mapping with one-to-many model.
SurfaceEntry now owns a handle list per surface, one per bound client.
Fixes parent window matching and dock preview for multi-client scenarios.

Log: multi-client foreign-toplevel handles
Influence: foreign-toplevel handle lifecycle, parent matching, dock preview
…heduling

- Move dock preview contexts and foreign toplevel handles into manager-private ownership.
- Factor out per-client handle creation and simplify dock preview lookup/cleanup.
- Hide idle done scheduling behind ForeignToplevelHandleV1Private and remove public idle helpers.

Log: refactored foreign-toplevel manager ownership and idle batching
Influence: reduces global state, centralizes lifecycle cleanup, and lowers public API surface.
zccrs
zccrs previously approved these changes Jun 1, 2026
- Move DockPreviewContextV1 and ForeignToplevelHandleV1 lists into manager private class.
- Encapsulate handle creation in manager private helper.
- Move idle scheduling and protocol details to private classes.
- Clean up public API: only expose semantic methods and Qt-friendly signals.
- Keeps existing behavior but improves ownership, readability, and maintainability.

Log: remove globals and encapulate handles
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