Compare commits

...

6 commits

Author SHA1 Message Date
BruebachL
a571a9aa04
[UserList] Restore accent-tinted rows for regular users in light mode (#7171)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 23:00:08 +02:00
BruebachL
648b472bfb
[UserList] Prevent failed loads from poisoning the banner card art cache (#7170)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 22:46:07 +02:00
BruebachL
daa0dcb2ea
[UserList] Keep banner art when a params-less user copy arrives (#7173)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 22:45:49 +02:00
BruebachL
21633eb0ec
[UserList] Raise banner card art cache to 1024 entries (#7169)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 22:33:26 +02:00
tooomm
b80e6994ab
[CI] Use modern macos-26 across Mac builds (#7035)
* use macos26 across

* fix xcode versions

* fix cross-arch compilation test

* No cross-compile

* reduce cache churn and race conditions between matrix runs better

* Increase timeout to 300s

* Update CMakeDMGSetup.script

* switch intel target to faster `macos-15-intel` runner

* Newest Xcode on macOS 15 runners is 26.3

* Revert timeout change

* Qt bump, cache key updates, cleanup

* more cleanup

* more cleanup + separation

* cache key
2026-08-23 20:40:28 +02:00
BruebachL
03229db0bd
Fix cn and ss flags losing stars and clamp svg render sizes (#7168)
cn.svg and ss.svg defined their star polygons with coordinates around
plus/minus 5e5 compensated by tiny scale transforms. Qt drops shapes whose
device space bounds exceed its rasterizer coordinate limit, so both flags
silently lost their stars when rendered wider than roughly 76px. That
threshold was always exceeded because loadSvg with expandOnly renders at
the declared 640x480 native size before scaling down to the icon size.

Fold the scale transforms into the polygon coordinates so the geometry is
unchanged while bounds stay small at every render size.

Also cap expandOnly and usericon render canvases at four times the
requested size to bound memory use and keep pathological theme svgs away
from the rasterizer limit.

Took 8 minutes

Took 57 seconds


Took 3 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 20:13:14 +02:00
8 changed files with 86 additions and 53 deletions

View file

@ -161,14 +161,11 @@ jobs:
uses: actions/checkout@v7
- name: "Restore compiler cache (ccache)"
id: ccache_restore
id: restore_ccache
uses: actions/cache/restore@v6
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
key: ccache-${{ matrix.distro }}${{ matrix.version }}-${{ env.BRANCH_NAME }}
key: ccache-${{ matrix.distro }}${{ matrix.version }}
path: ${{ env.CACHE }}
restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}-
- name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
shell: bash
@ -203,10 +200,10 @@ jobs:
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: "Delete remote compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
if: github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
continue-on-error: true
env:
CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }}
CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
@ -217,7 +214,7 @@ jobs:
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
path: ${{ env.CACHE }}
- name: "Upload artifact"
@ -264,13 +261,14 @@ jobs:
- os: macOS
target: 13 # EOL 2025-09-15
runner: macos-15-intel # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
# macos-26-intel is very slow and fails in CPack during DMG config if not increasing Finder timeout
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 13
package_suffix: "-macOS13_Intel"
qt_version: 6.11.0
qt_version: 6.11.1
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Intel
type: Release
@ -279,47 +277,48 @@ jobs:
- os: macOS
target: 14 # EOL 2026-??
runner: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md
runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 14
package_suffix: "-macOS14"
qt_version: 6.11.0
qt_version: 6.11.1
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Release
use_ccache: 1
xcode: "26.3"
xcode: "26.6"
- os: macOS
target: 15
runner: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md
runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 15
package_suffix: "-macOS15"
qt_version: 6.11.0
qt_version: 6.11.1
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Release
use_ccache: 1
xcode: "26.3"
xcode: "26.6"
- os: macOS
target: 15
runner: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md
target: 26
runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
qt_version: 6.11.0
qt_version: 6.11.1
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Debug
use_ccache: 1
xcode: "26.3"
xcode: "26.6"
- os: Windows
target: 10
@ -329,7 +328,7 @@ jobs:
cmake_generator_platform: x64
make_package: 1
package_suffix: "-Win10"
qt_version: 6.11.0
qt_version: 6.11.1
qt_modules: qtimageformats qtmultimedia qtwebsockets
type: Release
@ -349,7 +348,6 @@ jobs:
- name: "[Windows] Add msbuild to PATH"
if: matrix.os == 'Windows'
id: add-msbuild
uses: microsoft/setup-msbuild@v3
with:
msbuild-architecture: x64
@ -360,14 +358,11 @@ jobs:
- name: "[macOS] Restore compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1
id: ccache_restore
id: restore_ccache
uses: actions/cache/restore@v6
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }}
key: ccache-${{ matrix.runner }}_${{ matrix.override_target }}-Xcode${{ matrix.xcode }}
path: ${{ env.CCACHE_DIR }}
restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-
- name: "Install aqtinstall"
run: pipx install aqtinstall
@ -385,7 +380,7 @@ jobs:
id: restore_qt
uses: actions/cache/restore@v6
with:
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
key: Qt-${{ steps.resolve_qt_version.outputs.version }}-macOS-${{ matrix.soc }}-${{ matrix.qt_modules }}-thin
path: ${{ github.workspace }}/Qt
# Using jurplel/install-qt-action to install Qt without using brew
@ -407,7 +402,7 @@ jobs:
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: actions/cache/save@v6
with:
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
key: ${{ steps.restore_qt.outputs.cache-primary-key }}
path: ${{ github.workspace }}/Qt
- name: "[Windows] Install Qt ${{ matrix.qt_version }}"
@ -417,6 +412,7 @@ jobs:
# Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
aqtsource: git+https://github.com/miurahr/aqtinstall.git
cache: true
cache-key-prefix: Qt
modules: ${{ matrix.qt_modules }}
version: ${{ steps.resolve_qt_version.outputs.version }}
@ -457,21 +453,21 @@ jobs:
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: "[macOS] Delete remote compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
continue-on-error: true
env:
CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }}
CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
echo "Cache deleted successfully"
fi
- name: "[macOS] Save updated compiler cache (ccache)"
- name: "[macOS] Cache updated compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
path: ${{ env.CCACHE_DIR }}
- name: "[macOS] Sign app bundle"

View file

@ -52,8 +52,7 @@
id="defs8">
<polygon
id="s"
points="-301930,415571 0,-513674 301930,415571 -488533,-158734 488533,-158734 "
transform="scale(1.94676e-6,1.94676e-6)"
points="-0.587785,0.809017 0,-1 0.587785,0.809017 -0.951057,-0.309017 0.951057,-0.309017 "
style="fill:#ffde00" />
<clipPath
clipPathUnits="userSpaceOnUse"

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

View file

@ -16,6 +16,6 @@
<rect id="rect10" height="2.88" width="12.8" y="4.278e-9" x="0"/>
<rect id="rect12" height="2.88" width="12.8" y="3.36" x="0" fill="#da121a"/>
<polygon id="polygon14" points="0 0 8.6603 5 0 10" fill="#0f47af" transform="scale(.96)"/>
<polygon id="polygon16" points="4.1557e5 -3.0193e5 -5.1367e5 0 4.1557e5 3.0193e5 -1.5873e5 -4.8853e5 -1.5873e5 4.8853e5" fill="#fcdd09" transform="matrix(.0000029902 0 0 .0000029902 2.7713 4.8)"/>
<polygon id="polygon16" points="4.01394 3.897169 1.23532 4.8 4.01394 5.702831 2.296666 3.3392 2.296666 6.2608" fill="#fcdd09"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

@ -14,6 +14,32 @@
#define DEFAULT_COLOR_MODERATOR_RIGHT "#000000";
#define DEFAULT_COLOR_ADMIN "#ff2701";
/**
* Clamps an svg render size so that rendering does not exceed a multiple of the requested size.
*
* Rendering at the full native size of an svg just to scale it down afterwards wastes memory,
* and canvases with extreme coordinates can exceed Qt's rasterizer coordinate limit which makes
* Qt silently drop shapes from the rendered image.
*
* @param renderSize The size the svg would be rendered at.
* @param requestedSize The size that was actually requested.
*
* @return A size with the aspect ratio of renderSize whose longest side is at most four times
* the longest side of requestedSize.
*/
static QSize capRenderSize(const QSize &renderSize, const QSize &requestedSize)
{
const int longestRequestedSide = qMax(requestedSize.width(), requestedSize.height());
if (longestRequestedSide <= 0) {
return renderSize;
}
const int longestRenderSide = qMax(renderSize.width(), renderSize.height());
const qreal scale = qMin<qreal>(1.0, static_cast<qreal>(longestRequestedSide * 4) / longestRenderSide);
return QSize(qMax(1, static_cast<int>(renderSize.width() * scale)),
qMax(1, static_cast<int>(renderSize.height() * scale)));
}
/**
* Loads in an svg from file and scales it without affecting image quality.
*
@ -35,6 +61,9 @@ static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnl
// If expandOnly, make sure the pixmap is at least as large as the svg, so that we don't lose any detail.
// QIcon.pixmap(size) will automatically scale down the image, but it won't scale it up.
QSize pixmapSize = expandOnly ? svgRenderer.defaultSize().expandedTo(size) : size;
if (expandOnly) {
pixmapSize = capRenderSize(pixmapSize, size);
}
QPixmap pix(pixmapSize);
pix.fill(Qt::transparent);
@ -247,7 +276,9 @@ static QIcon loadAndColorSvg(const QString &iconPath,
QSvgRenderer svgRenderer(doc.toByteArray());
QPixmap pix(svgRenderer.defaultSize().expandedTo(QSize(minSize, minSize)));
const QSize pixmapSize =
capRenderSize(svgRenderer.defaultSize().expandedTo(QSize(minSize, minSize)), QSize(minSize, minSize));
QPixmap pix(pixmapSize);
pix.fill(Qt::transparent);
QPainter pixPainter(&pix);

View file

@ -39,7 +39,10 @@ void UserCardArtProvider::requestCardArt(const QString &userName, const QString
const QString key = makeKey(userName, cardName, providerId);
if (cardArtCache.contains(key) || pending.contains(key)) {
if (pending.contains(key)) {
return;
}
if (cardArtCache.contains(key) && !cardArtCache.value(key).isNull()) {
return;
}
@ -63,6 +66,10 @@ QPixmap UserCardArtProvider::cropCardArt(const QPixmap &fullRes)
void UserCardArtProvider::insertIntoCache(const QString &key, const QPixmap &pixmap)
{
if (pixmap.isNull()) {
return;
}
if (!cardArtCache.contains(key)) {
cacheInsertionOrder.append(key);
while (cacheInsertionOrder.size() > MaxCacheEntries) {
@ -129,8 +136,6 @@ void UserCardArtProvider::processQueue()
if (!fullRes.isNull()) {
self->insertIntoCache(key, self->cropCardArt(fullRes));
} else {
self->insertIntoCache(key, QPixmap());
}
self->pending.remove(key);

View file

@ -26,7 +26,7 @@ public slots:
private:
bool dbReady = false;
static constexpr int MaxCacheEntries = 300;
static constexpr int MaxCacheEntries = 1024;
QList<QString> cacheInsertionOrder; // FIFO eviction
QMap<QString, QPixmap> cardArtCache;
QSet<QString> pending;

View file

@ -101,23 +101,21 @@ void UserListPainter::drawBackground(QPainter *painter,
bg.setColorAt(0, blend(style.cardStart, accentColor, selected ? 0.75 : 0.65));
bg.setColorAt(1, blend(style.cardEnd, accentColor, selected ? 0.18 : 0.10));
} else {
// Regular users are the light theme's neutral paper cards. A flat
// warm card fill (the normal row surface, slightly deepened) keeps
// every row clearly visible without borrowing a role color. Selection
// shifts the fill toward a soft slate so the highlight still reads.
const QColor paper = style.cardEnd.darker(108);
bg.setColorAt(0, blend(paper, accentColor, selected ? 0.35 : 0.0));
bg.setColorAt(1, blend(paper, accentColor, selected ? 0.25 : 0.0));
// Regular users keep a scaled-down accent tint so the banner card art
// stays legible over a colored backdrop (the pre-branch painter was
// always dark-styled) while the role hierarchy still reads.
bg.setColorAt(0, blend(style.cardStart, accentColor, (selected ? 0.75 : 0.65) * 0.7));
bg.setColorAt(1, blend(style.cardEnd, accentColor, (selected ? 0.18 : 0.10) * 0.7));
}
painter->setPen(Qt::NoPen);
painter->setBrush(bg);
painter->drawRoundedRect(cardRect, 6, 6);
if (style.dark || hasRole || selected) {
painter->setBrush(accentColor);
painter->drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2);
}
// The 3px accent bar anchors every row so the banner card art reads as a
// consistent strip in either scheme (pre-branch parity).
painter->setBrush(accentColor);
painter->drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2);
}
static QString makeKey(const QString &user, const QString &card, const QString &providerId)

View file

@ -1372,9 +1372,13 @@ void UserListWidget::updateCardArtParams(const ServerInfo_User &user, const QStr
params.zoom = cap.zoom();
cardArtParamsMap.insert(userName, params);
cardArtProvider->requestCardArt(userName, params.cardName, params.cardProviderId);
} else {
cardArtParamsMap.remove(userName); // clear stale params on removal
}
// Intentionally no removal branch: buddy/ignore list copies never carry
// card_art_params (the server omits the column), so a params-less copy here
// means "this snapshot doesn't include it", not "the banner was removed".
// Removing on such copies would wipe banners that the live online list set.
// The map is rebuilt from scratch (clear() + repopulate) on every rebuild,
// which is what actually drops stale entries.
}
void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online)