Skip to content

Commit e757a44

Browse files
committed
fix: eliminate remaining memory leaks in application-tray
SNI icon handling: - Add m_cachedAttentionIcon / m_cachedOverlayIcon to cache attention and overlay icons, following the same pattern as m_cachedIcon, to avoid repeated D-Bus pixmap reads during attention state blinking - Skip icon pixmap updates on NewIcon / NewOverlayIcon / NewAttentionIcon signals when the client provides a theme icon name, since the getter already resolves icons from the icon theme in that case and the cached pixmap is not needed - Rework dbusImageList2QIcon: deep-copy the DBusImageList before modifying pixel data to fix const-cast undefined behavior with Qt6 QByteArray; use quint32* for correct type semantics and bounded iteration (size/4 instead of raw byte stride) XCB image lifecycle (XEmbed path): - Fix xcb_image_t leak in Util::convertFromNative when the pixel depth falls into the unsupported default case - Explicitly call xcb_image_destroy when QImage construction from xcb_image_t fails, since Qt's cleanup callback is not invoked in the null state - Replace QSharedPointer/QScopedPointer default delete deleters with explicit free() for xcb reply pointers allocated via malloc, fixing undefined behavior and potential leaks in both Util and XembedProtocolHandler - Refactor getX11WindowImageNonComposite to deep-copy the image data via QImage::copy and immediately destroy the xcb_image_t, bypassing Qt's unreliable cleanup callback that can be evaded by std::move and heuristic mask operations Build: - Fix debian/rules to export QT_SELECT=qt6 instead of qt5 since the tray plugin links against Qt6 Together with the corresponding DTK fix (QMetaType::destruct before construct in DDBusExtendedAbstractInterface::internalPropGet), this reduces application-tray RSS growth from ~23 KB/s to negligible levels. SNI 图标处理: - 新增 m_cachedAttentionIcon / m_cachedOverlayIcon 缓存 attention/overlay 图标,沿用 m_cachedIcon 的缓存模式,避免闪烁状态期间每次 paint 重复走 DBus 属性读取 - NewIcon / NewOverlayIcon / NewAttentionIcon 信号处理中,当客户端提供 主题图标名时跳过 pixmap 更新,因为 getter 已经优先从图标主题加载 - 重写 dbusImageList2QIcon: 修改像素数据前显式深拷贝 DBusImageList, 修正 Qt6 QByteArray 的 const_cast 未定义行为;使用 quint32* 替代 qint32* 获得正确的类型语义,循环边界改为 size/4 XCB 图像生命周期: - convertFromNative 函数 default 分支中释放 xcb_image_t 防止泄漏 - QImage 从 xcb_image_t 构造失败时显式调用 xcb_image_destroy,弥补 Qt 在 null state 下不触发 cleanup callback 的问题 - QSharedPointer/QScopedPointer 使用 free 替代默认的 delete 释放 malloc 分配的 xcb reply 指针,修正未定义行为 - getX11WindowImageNonComposite 通过 QImage::copy 深拷贝后直接 xcb_image_destroy,绕过 Qt cleanup callback 被 std::move 和 heuristic mask 绕过的风险 构建: - debian/rules 修正 QT_SELECT=qt6,匹配实际 Qt6 链接 配合 DTK 修复(DDBusExtendedAbstractInterface::internalPropGet 中 construct 前添加 destruct),application-tray RSS 增长从约 23 KB/s 降至可忽略水平。 Log: fix: eliminate remaining memory leaks in application-tray Pms: BUG-359161
1 parent 975cac4 commit e757a44

5 files changed

Lines changed: 63 additions & 38 deletions

File tree

debian/rules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/make -f
22
DPKG_EXPORT_BUILDFLAGS = 1
33
include /usr/share/dpkg/default.mk
4-
export QT_SELECT = qt5
4+
export QT_SELECT = qt6
55
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
66
export DEB_CFLAGS_MAINT_APPEND = -Wall
77
export DEB_CXXFLAGS_MAINT_APPEND = -Wall

plugins/application-tray/sniprotocolhandler.cpp

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,23 @@ SniTrayProtocolHandler::SniTrayProtocolHandler(const QString &sniServicePath, QO
154154
init();
155155

156156
connect(m_sniInter, &StatusNotifierItem::NewIcon, this, [this] {
157-
m_cachedIcon = dbusImageList2QIcon(m_sniInter->iconPixmap());
157+
if (m_sniInter->iconName().isEmpty())
158+
m_cachedIcon = dbusImageList2QIcon(m_sniInter->iconPixmap());
158159
Q_EMIT iconChanged();
159160
});
160-
connect(m_sniInter, &StatusNotifierItem::NewOverlayIcon, this, &SniTrayProtocolHandler::overlayIconChanged);
161+
connect(m_sniInter, &StatusNotifierItem::NewOverlayIcon, this, [this] {
162+
if (m_sniInter->overlayIconName().isEmpty())
163+
m_cachedOverlayIcon = dbusImageList2QIcon(m_sniInter->overlayIconPixmap());
164+
Q_EMIT overlayIconChanged();
165+
});
161166
connect(m_sniInter, &StatusNotifierItem::NewAttentionIcon, this, [this] {
162167
if (m_ignoreFirstAttention) {
163168
m_ignoreFirstAttention = false;
164169
return;
165170
}
166171

172+
if (m_sniInter->attentionIconName().isEmpty())
173+
m_cachedAttentionIcon = dbusImageList2QIcon(m_sniInter->attentionIconPixmap());
167174
Q_EMIT attentionIconChanged();
168175
});
169176

@@ -193,6 +200,8 @@ void SniTrayProtocolHandler::init()
193200
generateId();
194201
m_menuPath = m_sniInter->menu().path();
195202
m_cachedIcon = dbusImageList2QIcon(m_sniInter->iconPixmap());
203+
m_cachedAttentionIcon = dbusImageList2QIcon(m_sniInter->attentionIconPixmap());
204+
m_cachedOverlayIcon = dbusImageList2QIcon(m_sniInter->overlayIconPixmap());
196205
}
197206

198207
void SniTrayProtocolHandler::generateId()
@@ -261,8 +270,7 @@ QIcon SniTrayProtocolHandler::overlayIcon() const
261270
return QIcon::fromTheme(iconName);
262271
}
263272

264-
auto icon = dbusImageList2QIcon(m_sniInter->overlayIconPixmap());
265-
return icon;
273+
return m_cachedOverlayIcon;
266274
}
267275

268276
QIcon SniTrayProtocolHandler::attentionIcon() const
@@ -272,8 +280,7 @@ QIcon SniTrayProtocolHandler::attentionIcon() const
272280
return QIcon::fromTheme(iconName);
273281
}
274282

275-
auto icon = dbusImageList2QIcon(m_sniInter->attentionIconPixmap());
276-
return icon;
283+
return m_cachedAttentionIcon;
277284
}
278285

279286
QIcon SniTrayProtocolHandler::icon() const
@@ -356,18 +363,19 @@ QPair<QString, QString> SniTrayProtocolHandler::serviceAndPath(const QString &se
356363
QIcon SniTrayProtocolHandler::dbusImageList2QIcon(const DBusImageList &dbusImageList)
357364
{
358365
QIcon res;
359-
if (!dbusImageList.isEmpty() && !dbusImageList.first().pixels.isEmpty()) {
360-
for (auto image = dbusImageList.begin(); image < dbusImageList.end(); image++) {
361-
const char *image_data = image->pixels.data();
362-
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
363-
for (int i = 0; i < image->pixels.size(); i += 4) {
364-
*(qint32 *)(image_data + i) = qFromBigEndian(*(qint32 *)(image_data + i));
365-
}
366-
}
367-
368-
QImage qimage((const uchar *)image->pixels.constData(), image->width, image->height, QImage::Format_ARGB32);
369-
res.addPixmap(QPixmap::fromImage(qimage));
366+
if (dbusImageList.isEmpty() || dbusImageList.first().pixels.isEmpty())
367+
return res;
368+
369+
DBusImageList copy = dbusImageList;
370+
for (auto &image : copy) {
371+
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
372+
quint32 *pixels = reinterpret_cast<quint32 *>(image.pixels.data());
373+
for (int i = 0; i < image.pixels.size() / 4; i++)
374+
pixels[i] = qFromBigEndian(pixels[i]);
370375
}
376+
377+
QImage qimage(reinterpret_cast<const uchar *>(image.pixels.constData()), image.width, image.height, QImage::Format_ARGB32);
378+
res.addPixmap(QPixmap::fromImage(qimage));
371379
}
372380

373381
return res;

plugins/application-tray/sniprotocolhandler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,7 @@ class SniTrayProtocolHandler : public AbstractTrayProtocolHandler
8888
QString m_menuPath;
8989
bool m_ignoreFirstAttention;
9090
QIcon m_cachedIcon;
91+
QIcon m_cachedAttentionIcon;
92+
QIcon m_cachedOverlayIcon;
9193
};
9294
}

plugins/application-tray/util.cpp

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ void Util::setX11WindowSize(const xcb_window_t& window, const QSize& size)
204204
QRect Util::getX11WindowGeometry(const xcb_window_t& window) const
205205
{
206206
auto cookie = xcb_get_geometry(m_x11connection, window);
207-
QSharedPointer<xcb_get_geometry_reply_t> clientGeom(xcb_get_geometry_reply(m_x11connection, cookie, nullptr));
207+
QSharedPointer<xcb_get_geometry_reply_t> clientGeom(xcb_get_geometry_reply(m_x11connection, cookie, nullptr), [](xcb_get_geometry_reply_t* ptr){ free(ptr); });
208208

209209
return clientGeom ? QRect(clientGeom->x, clientGeom->y, clientGeom->width, clientGeom->height) : QRect();
210210
}
@@ -239,23 +239,33 @@ void Util::setX11WindowInputShape(const xcb_window_t& window, const QSize& size)
239239
QImage Util::getX11WindowImageNonComposite(const xcb_window_t& window)
240240
{
241241
QSize size = getX11WindowGeometry(window).size();
242+
if (size.isEmpty()) {
243+
return QImage();
244+
}
245+
242246
xcb_image_t *image = xcb_image_get(m_x11connection, window, 0, 0, size.width(), size.height(), 0xFFFFFFFF, XCB_IMAGE_FORMAT_Z_PIXMAP);
243247

244-
QImage naiveConversion;
245-
if (image) {
246-
naiveConversion = QImage(image->data, image->width, image->height, QImage::Format_ARGB32);
247-
} else {
248+
if (!image) {
249+
return QImage();
250+
}
251+
252+
QImage naiveConversion(image->data, image->width, image->height, QImage::Format_ARGB32);
253+
if (naiveConversion.isNull()) {
254+
xcb_image_destroy(image);
248255
return QImage();
249256
}
250257

251258
if (isTransparentImage(naiveConversion)) {
252259
QImage elaborateConversion = QImage(convertFromNative(image));
253260
if (isTransparentImage(elaborateConversion)) {
254261
return QImage();
255-
} else
262+
} else {
256263
return elaborateConversion;
264+
}
257265
} else {
258-
return QImage(image->data, image->width, image->height, image->stride, QImage::Format_ARGB32, clean_xcb_image, image);
266+
QImage res = naiveConversion.copy();
267+
xcb_image_destroy(image);
268+
return res;
259269
}
260270
}
261271

@@ -384,36 +394,41 @@ QImage Util::convertFromNative(xcb_image_t *xcbImage)
384394
format = QImage::Format_ARGB32_Premultiplied;
385395
break;
386396
default:
397+
xcb_image_destroy(xcbImage);
387398
return QImage();
388399
}
389400

390-
QImage image(xcbImage->data, xcbImage->width, xcbImage->height, xcbImage->stride, format, clean_xcb_image, xcbImage);
401+
QImage image(xcbImage->data, xcbImage->width, xcbImage->height, xcbImage->stride, format);
391402

392403
if (image.isNull()) {
404+
xcb_image_destroy(xcbImage);
393405
return QImage();
394406
}
407+
408+
QImage deepCopy = image.copy();
409+
xcb_image_destroy(xcbImage);
395410

396-
if (format == QImage::Format_RGB32 && xcbImage->bpp == 32) {
397-
QImage m = image.createHeuristicMask();
398-
QPixmap p = QPixmap::fromImage(std::move(image));
411+
if (format == QImage::Format_RGB32 && deepCopy.depth() == 32) {
412+
QImage m = deepCopy.createHeuristicMask();
413+
QPixmap p = QPixmap::fromImage(std::move(deepCopy));
399414
p.setMask(QBitmap::fromImage(std::move(m)));
400-
image = p.toImage();
415+
deepCopy = p.toImage();
401416
}
402417

403-
if (image.format() == QImage::Format_MonoLSB) {
404-
image.setColorCount(2);
405-
image.setColor(0, QColor(Qt::white).rgb());
406-
image.setColor(1, QColor(Qt::black).rgb());
418+
if (deepCopy.format() == QImage::Format_MonoLSB) {
419+
deepCopy.setColorCount(2);
420+
deepCopy.setColor(0, QColor(Qt::white).rgb());
421+
deepCopy.setColor(1, QColor(Qt::black).rgb());
407422
}
408423

409-
return image;
424+
return deepCopy;
410425
}
411426

412427
QPoint Util::getMousePos() const
413428
{
414429
QPoint pos;
415430
xcb_query_pointer_cookie_t cookie = xcb_query_pointer(m_x11connection, m_rootWindow);
416-
QScopedPointer<xcb_query_pointer_reply_t> reply(xcb_query_pointer_reply(m_x11connection, cookie, NULL));
431+
QSharedPointer<xcb_query_pointer_reply_t> reply(xcb_query_pointer_reply(m_x11connection, cookie, NULL), [](xcb_query_pointer_reply_t* ptr){ free(ptr); });
417432
if (reply) {
418433
pos = QPoint(reply->root_x, reply->root_y);
419434
}

plugins/application-tray/xembedprotocolhandler.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ void XembedProtocolHandler::initX11resources()
284284
xcb_flush(c);
285285

286286
auto waCookie = xcb_get_window_attributes(c, m_windowId);
287-
QSharedPointer<xcb_get_window_attributes_reply_t> windowAttributes(xcb_get_window_attributes_reply(c, waCookie, nullptr));
287+
QSharedPointer<xcb_get_window_attributes_reply_t> windowAttributes(xcb_get_window_attributes_reply(c, waCookie, nullptr), [](xcb_get_window_attributes_reply_t* ptr){ free(ptr); });
288288
if (windowAttributes && !(windowAttributes->all_event_masks & XCB_EVENT_MASK_BUTTON_PRESS)) {
289289
m_injectMode = XTest;
290290
}
@@ -399,7 +399,7 @@ void XembedProtocolHandler::sendClick(uint8_t qMouseButton)
399399
auto dis = UTIL->getDisplay();
400400

401401
auto cookieSize = xcb_get_geometry(c, m_windowId);
402-
QSharedPointer<xcb_get_geometry_reply_t> clientGeom(xcb_get_geometry_reply(c, cookieSize, nullptr));
402+
QSharedPointer<xcb_get_geometry_reply_t> clientGeom(xcb_get_geometry_reply(c, cookieSize, nullptr), [](xcb_get_geometry_reply_t* ptr){ free(ptr); });
403403

404404
if (!clientGeom) {
405405
return;

0 commit comments

Comments
 (0)