From b9698e03c1b3856179e7d8dd2d2acb05de730543 Mon Sep 17 00:00:00 2001 From: deepin-wm Date: Thu, 18 Jun 2026 20:14:35 +0800 Subject: [PATCH] fix: prevent compositor crash on screen hotplug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. In registerSurfaceToForeignToplevel, connect SurfaceWrapper::aboutToBeInvalidated to removeSurface guarded by skipDockPreView, and disconnect the skipDockPreViewChanged connection in the handler; this fixes a dangling pointer in ForeignToplevelManagerInterfaceV1 surfaces and avoids a duplicate removeSurface plus spurious qCCritical log during surface invalidation 2. Skip input-manager resources whose client has no wl_seat binding in bind_resource and the capability broadcast paths; capability events reference wl_seat, so an unbound client cannot receive them and is skipped by protocol semantics, fixing a SEGV in the disconnect race Log: Fixed crash when hot-plugging screens while the control center is opening Influence: 1. Test hot-plugging external screens while the control center opens 2. Verify no crash and no spurious qCCritical on client disconnect 3. Test stability across rapid screen plug/unplug cycles fix: 修复屏幕热插拔时合成器崩溃 1. 在 registerSurfaceToForeignToplevel 中连接 SurfaceWrapper::aboutToBeInvalidated,以 skipDockPreView 守卫调用 removeSurface,并在 handler 中断开 skipDockPreViewChanged 连接;修复 ForeignToplevelManagerInterfaceV1 中 surface 失效时的悬空指针,并避免 surface 失效过程中的重复 removeSurface 与虚假 qCCritical 日志 2. 在 bind_resource 及能力广播路径中跳过未绑定 wl_seat 的 input-manager resource;能力事件引用 wl_seat,未绑定客户端无法接收, 按协议语义跳过,修复客户端断连竞态中的 SEGV Log: 修复控制中心开启过程中热插拔屏幕导致的崩溃 Influence: 1. 测试控制中心开启时热插拔外接屏幕 2. 验证客户端断连时不崩溃且无虚假 qCCritical 日志 3. 测试快速插拔屏幕循环的系统稳定性 Fixes: #183 --- src/core/shellhandler.cpp | 14 +++++++++++++- .../input-manager/inputmanagerinterfacev1.cpp | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/core/shellhandler.cpp b/src/core/shellhandler.cpp index 72521e125..48ea3afe0 100644 --- a/src/core/shellhandler.cpp +++ b/src/core/shellhandler.cpp @@ -772,13 +772,25 @@ void ShellHandler::registerSurfaceToForeignToplevel(SurfaceWrapper *wrapper) if (!wrapper->skipDockPreView()) { m_treelandForeignToplevel->addSurface(wrapper); } - connect(wrapper, &SurfaceWrapper::skipDockPreViewChanged, this, [this, wrapper] { + + QMetaObject::Connection skipConn; + skipConn = connect(wrapper, &SurfaceWrapper::skipDockPreViewChanged, this, [this, wrapper] { if (wrapper->skipDockPreView()) { m_treelandForeignToplevel->removeSurface(wrapper); } else { m_treelandForeignToplevel->addSurface(wrapper); } }); + + connect(wrapper, &SurfaceWrapper::aboutToBeInvalidated, this, [this, wrapper, skipConn] { + // Only remove if the surface was actually added (skipDockPreView is false). + if (!wrapper->skipDockPreView()) { + m_treelandForeignToplevel->removeSurface(wrapper); + } + // Disconnect skipDockPreViewChanged to prevent double-remove when + // invalidate() calls setSkipDockPreView(true) after this signal. + QObject::disconnect(skipConn); + }); } void ShellHandler::setupDockPreview() diff --git a/src/modules/input-manager/inputmanagerinterfacev1.cpp b/src/modules/input-manager/inputmanagerinterfacev1.cpp index 3a227dfae..ecc690b4b 100644 --- a/src/modules/input-manager/inputmanagerinterfacev1.cpp +++ b/src/modules/input-manager/inputmanagerinterfacev1.cpp @@ -52,8 +52,14 @@ void TreelandInputManagerInterfaceV1Private::destroy(Resource *resource) void TreelandInputManagerInterfaceV1Private::bind_resource(Resource *resource) { + // Report capabilities available on the seat. wl_seat is created before this + // global, so the client has normally bound it; the null case only occurs in + // the disconnect race. A client without a wl_seat binding cannot receive + // seat-referencing events, so it is skipped by protocol semantics. TreelandInputManagerInterfaceV1::DeviceTypes types = q->inputDeviceListTypes(); struct wlr_seat_client *seatClient = Helper::instance()->seat()->handle()->client_for_wl_client(resource->client()); + if (!seatClient) + return; struct wl_resource *clientResource; wl_resource_for_each(clientResource, &seatClient->resources) { send_capability_available(resource->handle, types.toInt(), clientResource); @@ -173,6 +179,8 @@ void TreelandInputManagerInterfaceV1::sendCapabilityAvailable(TreelandInputManag for (const auto &resource : d->resourceMap()) { struct wlr_seat_client *seatClient = Helper::instance()->seat()->handle()->client_for_wl_client(resource->client()); + if (!seatClient) + continue; // No wl_seat binding: cannot deliver seat-referencing capability events. struct wl_resource *clientResource; wl_resource_for_each(clientResource, &seatClient->resources) { d->send_capability_available(resource->handle, types.toInt(), clientResource); @@ -185,6 +193,8 @@ void TreelandInputManagerInterfaceV1::sendCapabilityUnavailable(TreelandInputMan for (const auto &resource : d->resourceMap()) { struct wlr_seat_client *seatClient = Helper::instance()->seat()->handle()->client_for_wl_client(resource->client()); + if (!seatClient) + continue; // No wl_seat binding: cannot deliver seat-referencing capability events. struct wl_resource *clientResource; wl_resource_for_each(clientResource, &seatClient->resources) { d->send_capability_unavailable(resource->handle, types.toInt(), clientResource); @@ -267,6 +277,8 @@ void TreelandInputManagerInterfaceV1::onInputAdded(WInputDevice *input) for (const auto &resource : d->resourceMap()) { struct wlr_seat_client *seatClient = Helper::instance()->seat()->handle()->client_for_wl_client(resource->client()); + if (!seatClient) + continue; // No wl_seat binding: cannot deliver seat-referencing capability events. struct wl_resource *clientResource; wl_resource_for_each(clientResource, &seatClient->resources) { d->send_capability_available(resource->handle, type.toInt(), clientResource); @@ -292,6 +304,8 @@ void TreelandInputManagerInterfaceV1::onInputRemoved(WInputDevice *input) for (const auto &resource : d->resourceMap()) { struct wlr_seat_client *seatClient = Helper::instance()->seat()->handle()->client_for_wl_client(resource->client()); + if (!seatClient) + continue; // No wl_seat binding: cannot deliver seat-referencing capability events. struct wl_resource *clientResource; wl_resource_for_each(clientResource, &seatClient->resources) { d->send_capability_unavailable(resource->handle, type.toInt(), clientResource);