Skip to content

Commit 3827bad

Browse files
felixonmarsBLumia
authored andcommitted
feat: enable XdgIconLoader support for Qt6 builds
Previously, dtkgui.cmake unconditionally disabled libxdg for all non-Qt5 builds with: else() set(DTK_DISABLE_LIBXDG ON) endif() This caused XdgIconProxyEngine to be excluded from the Qt6 build, falling back to QPlatformTheme::createIconEngine() which uses Qt's built-in SVG renderer. That renderer incorrectly renders <clipPath>, <mask>, and <filter> elements outside <defs> as visible shapes, causing opaque background corners on many app icons. Fix by using find_package(qt6xdgiconloader QUIET) for Qt6 builds, mirroring the existing Qt5XdgIconLoader handling. The XdgIconProxyEngine is also updated to use DSvgRenderer (librsvg) for all scalable SVG entries to ensure correct rendering. Two additional fixes for the Qt6 XdgIconProxy path: 1. XdgIconProxyEngine::isNull() is added as a Qt6-only override. Qt6 changed isNull() from a virtual_hook-based mechanism to a direct virtual function (default: false), so without this override the proxy always appeared non-null even when the underlying engine found no icon. The override also treats entries with dir.size == 0 (from XdgIconLoader::unthemedFallback, e.g. /usr/share/pixmaps) as null, since XdgIconProxy cannot render them correctly. 2. DIconProxyEngine::ensureEngine() uses QPlatformTheme::createIconEngine() as an unconditional fallback after the XdgProxy attempt. This lets Qt's native QIconLoaderEngine handle icons only found in non-themed locations like /usr/share/pixmaps (e.g. xscreensaver). Fixes SVG icon rendering issues on Arch.
1 parent d87cde3 commit 3827bad

4 files changed

Lines changed: 87 additions & 13 deletions

File tree

dtkgui.cmake

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,18 @@ find_package(DtkBuildHelper REQUIRED)
116116
pkg_check_modules(librsvg REQUIRED IMPORTED_TARGET librsvg-2.0)
117117

118118
if(NOT DTK_DISABLE_LIBXDG)
119-
# Only use libxdg under Qt5
120119
if (${QT_VERSION_MAJOR} STREQUAL "5")
121120
find_package(Qt5XdgIconLoader REQUIRED)
122121
if (NOT Qt5XdgIconLoader_FOUND)
123122
message(WARNING " XdgIconLoader Not Found, DISABLE LIBXDG !")
124123
set (DTK_DISABLE_LIBXDG ON)
125124
endif()
126125
else()
127-
set (DTK_DISABLE_LIBXDG ON)
126+
find_package(qt6xdgiconloader QUIET)
127+
if (NOT qt6xdgiconloader_FOUND)
128+
message(WARNING " Qt6XdgIconLoader Not Found, DISABLE LIBXDG !")
129+
set (DTK_DISABLE_LIBXDG ON)
130+
endif()
128131
endif()
129132
endif()
130133

src/util/private/diconproxyengine.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -260,20 +260,20 @@ void DIconProxyEngine::ensureEngine()
260260
if (!m_iconEngine && Q_UNLIKELY(!m_option.testFlag(DIconTheme::IgnoreBuiltinIcons)) ) {
261261
m_iconEngine = createDBuiltinIconEngine(m_iconName);
262262
}
263-
#ifdef DTK_DISABLE_LIBXDG
263+
#ifndef DTK_DISABLE_LIBXDG
264+
if (!m_iconEngine) {
265+
m_iconEngine = createXdgProxyIconEngine(m_iconName);
266+
}
267+
#endif
268+
// Fallback to Qt's native icon engine for icons only found in /usr/share/pixmaps
269+
// or other non-themed locations that XdgIconLoader cannot render correctly
270+
// (e.g. xscreensaver). Also serves as fallback when libxdg is disabled.
264271
if (!m_iconEngine) {
265272
QPlatformTheme * const platformTheme = QGuiApplicationPrivate::platformTheme();
266273
if (platformTheme) {
267274
m_iconEngine = platformTheme->QPlatformTheme::createIconEngine(m_iconName);
268-
} else {
269-
qWarning() << "PlatformTheme not found!";
270275
}
271276
}
272-
#else
273-
if (!m_iconEngine ) {
274-
m_iconEngine = createXdgProxyIconEngine(m_iconName);
275-
}
276-
#endif
277277
if (!m_iconEngine && !nonCache[theme].contains(m_iconName)) {
278278
qWarning() << "create icon [" << m_iconName << "] engine failed."
279279
<< "theme:" << theme << "and nonCache's size:" << nonCache.size();

src/util/private/xdgiconproxyengine.cpp

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <QPainter>
1313
#include <QPalette>
1414
#include <QGuiApplication>
15+
#include <DSvgRenderer>
1516

1617
#include <cxxabi.h>
1718
#include <qmath.h>
@@ -61,6 +62,41 @@ static inline QPixmap entryPixmap(ScalableEntry *color_entry, const QSize &size,
6162
return color_entry->svgIcon.pixmap(size, mode, state);
6263
}
6364

65+
// Render a scalable SVG entry using DSvgRenderer (backed by librsvg) when available.
66+
//
67+
// This is a workaround for a long-standing Qt SVG rendering bug: QSvgRenderer
68+
// incorrectly renders <clipPath>, <mask>, and <filter> elements that appear as
69+
// direct children of the root <svg> element (outside any <defs> block) as visible
70+
// painted shapes. This causes icons that use such constructs (e.g., many GNOME app
71+
// icons like Epiphany) to render with opaque black backgrounds instead of transparent
72+
// corners.
73+
//
74+
// DSvgRenderer uses librsvg via cairo when available, which handles these SVG
75+
// features correctly. If librsvg is not installed, DSvgRenderer::isValid() returns
76+
// false and we fall back gracefully to the default Qt rendering path (entryPixmap),
77+
// preserving existing behavior on systems without librsvg.
78+
static QPixmap renderSvgWithLibrsvg(ScalableEntry *entry, const QSize &size,
79+
QIcon::Mode mode, QIcon::State state)
80+
{
81+
DGUI_USE_NAMESPACE
82+
DSvgRenderer renderer(entry->filename);
83+
if (renderer.isValid()) {
84+
QSize actualSize = renderer.defaultSize();
85+
if (!size.isEmpty() && !actualSize.isEmpty()) {
86+
if (actualSize.width() < size.width())
87+
actualSize.scale(size, Qt::KeepAspectRatio);
88+
}
89+
if (!actualSize.isEmpty()) {
90+
const QImage img = renderer.toImage(actualSize);
91+
if (!img.isNull())
92+
return QPixmap::fromImage(
93+
img.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
94+
}
95+
}
96+
// librsvg unavailable or rendering failed — fall back to QIcon-based path
97+
return entryPixmap(entry, size, mode, state);
98+
}
99+
64100
static const QString STYLE = QStringLiteral(".ColorScheme-Text, .ColorScheme-NeutralText{color:%1;}\
65101
\n.ColorScheme-Highlight{color:%2;}");
66102

@@ -186,9 +222,20 @@ QPixmap XdgIconProxyEngine::followColorPixmap(ScalableEntry *color_entry, const
186222

187223
QPixmap XdgIconProxyEngine::pixmapByEntry(QIconLoaderEngineEntry *entry, const QSize &size, QIcon::Mode mode, QIcon::State state)
188224
{
225+
char *type_name = abi::__cxa_demangle(typeid(*entry).name(), 0, 0, 0);
226+
// ScalableFollowsColorEntry is a subclass of ScalableEntry but its mangled name
227+
// does NOT contain the substring "ScalableEntry", so the contains() check below
228+
// correctly distinguishes between the two types.
229+
const bool isScalableFollowsColor = (type_name == QByteArrayLiteral("ScalableFollowsColorEntry"));
230+
const bool isScalable = isScalableFollowsColor || QByteArray(type_name).contains("ScalableEntry");
231+
free(type_name);
232+
189233
if (!XdgIconFollowColorScheme()) {
190234
DEEPIN_XDG_THEME::colorScheme.setLocalData(DEEPIN_XDG_THEME::PALETTE_MAP());
191235

236+
if (isScalable)
237+
return renderSvgWithLibrsvg(static_cast<ScalableEntry *>(entry), size, mode, state);
238+
192239
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 1)
193240
return entry->pixmap(size, mode, state, 1.0);
194241
#else
@@ -197,9 +244,8 @@ QPixmap XdgIconProxyEngine::pixmapByEntry(QIconLoaderEngineEntry *entry, const Q
197244
}
198245

199246
QPixmap pixmap;
200-
char *type_name = abi::__cxa_demangle(typeid(*entry).name(), 0, 0, 0);
201247

202-
if (type_name == QByteArrayLiteral("ScalableFollowsColorEntry")) {
248+
if (isScalableFollowsColor) {
203249
if (DEEPIN_XDG_THEME::colorScheme.localData().isEmpty()) {
204250
const QPalette &pal = palette(&pixmap);
205251
DEEPIN_XDG_THEME::colorScheme.setLocalData(DEEPIN_XDG_THEME::PALETTE_MAP({
@@ -209,6 +255,10 @@ QPixmap XdgIconProxyEngine::pixmapByEntry(QIconLoaderEngineEntry *entry, const Q
209255
}
210256

211257
pixmap = followColorPixmap(static_cast<ScalableEntry *>(entry), size, mode, state);
258+
} else if (isScalable) {
259+
// Regular scalable SVG entry (no color scheme) — use DSvgRenderer (librsvg)
260+
// to avoid Qt's QSvgRenderer bug with <clipPath> outside <defs>.
261+
pixmap = renderSvgWithLibrsvg(static_cast<ScalableEntry *>(entry), size, mode, state);
212262
} else {
213263
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 1)
214264
pixmap = entry->pixmap(size, mode, state, 1.0);
@@ -217,7 +267,6 @@ QPixmap XdgIconProxyEngine::pixmapByEntry(QIconLoaderEngineEntry *entry, const Q
217267
#endif
218268
}
219269

220-
free(type_name);
221270
DEEPIN_XDG_THEME::colorScheme.setLocalData(DEEPIN_XDG_THEME::PALETTE_MAP());
222271

223272
return pixmap;
@@ -301,6 +350,25 @@ bool XdgIconProxyEngine::write(QDataStream &out) const
301350
return engine->write(out);
302351
}
303352

353+
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
354+
bool XdgIconProxyEngine::isNull()
355+
{
356+
if (engine->isNull())
357+
return true;
358+
// Entries from XdgIconLoader::unthemedFallback() (e.g. /usr/share/pixmaps)
359+
// have a default-constructed QIconDirInfo with dir.size == 0. These entries
360+
// cannot be rendered correctly by XdgIconProxyEngine, so treat the engine as
361+
// null when all entries originate from the unthemed fallback. The caller
362+
// (DIconProxyEngine) will then use Qt's native QIconLoaderEngine which handles
363+
// pixmap-only icons correctly.
364+
for (const auto &entry : engine->m_info.entries) {
365+
if (entry->dir.size > 0)
366+
return false;
367+
}
368+
return true;
369+
}
370+
#endif
371+
304372
void XdgIconProxyEngine::virtual_hook(int id, void *data)
305373
{
306374
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)

src/util/private/xdgiconproxyengine_p.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class Q_DECL_HIDDEN XdgIconProxyEngine : public QIconEngine
4848
QIconEngine *clone() const override;
4949
bool read(QDataStream &in) override;
5050
bool write(QDataStream &out) const override;
51+
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
52+
bool isNull() override;
53+
#endif
5154
void virtual_hook(int id, void *data) override;
5255

5356
private:

0 commit comments

Comments
 (0)