mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
4112 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3aba1ef929
|
[Settings] Back up the settings folder before migrating (#7386)
* [Settings] Back up the settings folder before migrating The per-file settings migration only preserved global.ini, as global.ini.old. Everything else the migrations write was overwritten in place with no way back: the legacy migration and the 17 per-domain migrateXxxSettings functions all write straight into the per-domain .ini files, so any of them that got a key mapping wrong silently replaced a working file. The re-migration path is the sharpest case, because migrateSettingsFromGlobalIni() runs again whenever an older build wrote non-sentinel keys back into global.ini, and those values then land on top of per-domain files the user had already edited. Snapshot the whole settings folder before either migration runs, into a timestamped sibling settings-backup-<yyyyMMdd-HHmmss>. A sibling rather than a child, so a backup is never copied into the next backup and the "Open Settings Folder" action keeps showing only live files. Keep every snapshot; settings files are kilobytes, so growth is not a concern. SettingsMigration::isMigrationPending() gates the copy so a normal start creates nothing. It reuses the same condition migrateSettingsFromGlobalIni() uses to decide whether it has work, factored into a shared helper so the two cannot drift, and it ignores the legacy migration on an empty profile where that migration only writes its sentinel. The backup itself skips a folder whose files are all empty, which covers a fresh profile whose global.ini was created but never written. A failed copy logs a warning and returns an empty path rather than aborting the migration, since a full disk or an unwritable data folder must not leave the client unable to start. global.ini.old still works as the single-file escape hatch. Document the folder layout, the automatic backups, and the fact that migrations only run forwards, so users know to copy the settings folder before downgrading to a version that cannot read the per-group files. * [Client] Match the desktop file name case for the portal app ID QGuiApplication::setDesktopFileName() was given "Cockatrice"/"Oracle" while the installed desktop entries are cockatrice.desktop and oracle.desktop. Linux paths are case-sensitive, so xdg-desktop-portal failed the app-info lookup for "Cockatrice" and Qt warned on every QDesktopServices call that it could not register with the host portal. The same string becomes the xdg-shell appId, so the window icon lookup failed on Wayland for the same reason. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
b8b81170bf
|
[Client] Show card names in the game log and [card] tags in the chosen card language (#7387)
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
The game log translates its sentences, zone names and counter names, but every card name was rendered as the raw canonical name straight from the server. The card board and the card info text already went through CardLocalization::displayName(), so the log was the odd one out. cardLink() used one string for both the card:// href and the visible label, which left no room for a localized label. Split the two: the href keeps the canonical name because ChatView::mousePressEvent feeds it straight to showCardInfoPopup for lookup, while the label goes through the display language. Same split for [card] chat tags in ChatView::appendCardTag. Sanitize the name while building the link. logUndoDraw already did this by hand and had drifted into its own non-italic copy of cardLink(); route it back through the helper. Add CardLocalization::displayNameFor() for callers that only hold a canonical name and need no CardInfoPtr of their own. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
014983b5bc
|
[Theme] Carry Default theme directory customizations over to System (#7385)
* [Theme] Carry Default theme directory customizations over to System The Default -> System rename rewrote the stored theme name but left the user profile's themes/Default directory behind, so anyone who had customized zone graphics, stylesheets or palettes there silently lost them. Migrate the directory alongside the name. Rename it when System does not exist yet, otherwise merge file by file without overwriting what System already has, then drop the legacy directory. Users who already ran the rename get their customizations back on the next start, and a hand-made Default folder of a user on another theme is left untouched. * [Tests] Give theme_migration_test an explicit main and gate it on WITH_CLIENT The build machines that cannot find GTest fall back to the downloaded googletest, which links the bare gtest library without gtest_main. Every other test defines its own main() for that reason; this one did not, so Debian 13, Fedora 44 and Ubuntu 26.04 failed to link it. The test compiles client sources, so -DTEST=1 -DWITH_CLIENT=0 has no way to build it. Gate the subdirectory on WITH_CLIENT like the other client-source test. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
add65caae5
|
[Client] Hand off single-instance payloads outside the readyRead signal (#7373)
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
* [Client] Hand off single-instance payloads outside the readyRead signal Opening a cockatrice:// link from outside a running client showed the "Join game ...?" confirmation and then crashed on both answers. The join reached the server first, so the other players saw the user join and immediately disconnect. SingleInstanceManager::handleNewConnection() emitted filesReceived from inside the forwarding socket's readyRead lambda and only disconnected afterwards. A joingame link makes the handler show a modal QMessageBox, which runs a nested event loop. The sending instance exits as soon as it has the acknowledgment, so the socket was disconnected and deleteLater'd inside that loop: it died in the middle of its own readyRead signal, and both this lambda and Qt's signal dispatch kept using the freed memory. Disconnect right after the acknowledgment and emit filesReceived through a queued call, so no handler and no nested event loop ever runs inside the socket's signal. Guarding the disconnect call alone is not enough, since Qt still touches the socket after the lambda returns. Add a regression test that reproduces the nested-event-loop hand-off. * [Tests] Apply cmake-format to the single-instance test wiring * [Client] Defer the single-instance socket teardown past the readyRead frame The forwarded payload is emitted synchronously from inside the forwarding socket's readyRead, which is what lets the modal "Join game?" confirmation appear. A handler can spin a nested event loop, and the sending instance exits as soon as it has the ACK, so the socket is disconnected and deleteLater'd while that loop runs. Control then returns to this frame with the socket already destroyed, and the inline disconnectFromServer() touched freed memory. Queue the teardown instead and guard it with a QPointer taken before the handlers run -- building the guard afterwards is itself a use-after-free, since QPointer dereferences the pointer it is constructed from. The payload still reaches the handlers synchronously, so the confirmation is unchanged. The regression test covers both halves: the handlers must run while the socket is still connected, and the socket must not be torn down until the readyRead frame has returned. * Lint. * [Client] Only delete the single-instance socket outside the readyRead frame The auto-delete on `disconnected` was the remaining use-after-free. A handler may spin a nested event loop (the modal "Join game?" box), the sending instance exits as soon as it has the ACK, and so the peer closes while that loop runs. deleteLater() issued from inside such a loop is not deferred to the outer loop: Qt frees the object as soon as the nested loop unwinds, which is still inside this readyRead emission, so Qt's own signal dispatch and the handler lambda both keep using a freed socket. When the notification happens to arrive late the bug hides, which is why this only reproduced on some platforms. Drop the auto-delete and let the queued teardown own the socket's lifetime: it disconnects and then deletes, once the frame has unwound. The QPointer guard was necessary but not sufficient -- it stopped us dereferencing a dead pointer, not the socket being freed underneath the frame. The test waits on the socket's destroyed signal instead of polling, so the teardown assertion does not depend on platform event timing. * [Client] Do not queue the single-instance socket teardown The previous commit moved the deleteLater() off the disconnected signal and into a queued invocation, believing that "queued" meant "after the readyRead frame returns". It does not. A queued call is delivered by whichever event loop is running, and the modal "Join game?" box runs a nested one -- so the teardown was still executed inside the readyRead emission, which is exactly the use-after-free it claimed to prevent. Queueing was never necessary. Because nothing connects deleted/close to the socket's disconnected signal any more, no nested loop can free it while the handlers run, and emit filesReceived() is synchronous: once it returns, the frame has unwound and the socket can be closed and deleted inline. The QPointer stays, covering the one case that remains -- the primary shutting down while the confirmation box is open, since QLocalServer destroys its pending connections. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
4ce2e8c3d3
|
[UserList] Fix join/spectate from the profile hover card doing nothing (#7383)
The games list in the profile hover card offered "Join game" and "Spectate", but UserInfoPopup::joinGameRequested was only forwarded to an identical UserListWidget::joinGameRequested signal that nothing connected to. Clicking either entry sent no command, opened no tab and showed no error. Route the request to TabSupervisor, which owns the join flow. The popup's games list spans every room the user is in, so the target room may not be open yet; IntentJoinServerGame already joins the room first and retries, which is why this goes through the intent rather than straight to GameSelector. Extract the intent construction that joinReportGame already had into a shared joinGameByIntent helper instead of duplicating it. Also stop disabling "Join game" for games that are full or already running. GameSelector::joinGame() offers to join as a spectator in that case, so a greyed-out entry read as a dead button; GameSelector's own context menu never disables it either. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
750d5b3c21
|
[Client] Add option to use game time in game log timestamps (#7265)
* [Client] Add option to use game time in game log timestamps (#2202) Game log timestamps showed the local clock time, which is meaningless in a replay or in a log read after the fact — nobody can tell when a play happened relative to the game. Add a "Use game time instead of local time in game logs" setting. When enabled, the message log stamps entries with the elapsed game time (HH:MM:SS since the game started); regular chat views keep the local clock. * [Game] Move formatElapsedTime helper out of public slots (#2202) The message log needs the HH:MM:SS formatter used by the game timer, but it lives in GameState's public slots, tying a pure formatting helper to the signal-slot machinery. Move formatElapsedTime into a public section of GameState so the log can call it directly, and add the missing trailing newline to game_state.cpp. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
ffcb9094ca
|
[Replay] Fix cards accumulating in zone view when rewinding (#7357)
* [Replay] Fix cards accumulating in zone view when rewinding * rename method |
||
|
|
ecd955bc60
|
[Installer] Wait on the launching process so auto-updates cannot write over a mixed Qt runtime (#7372)
* [Installer] Abort instead of installing over leftover runtime files
The silent upgrade could leave a mix of old and new Qt DLLs next to the new
executables, which is what makes the next start fail with "The procedure entry
point X could not be located in the dynamic link library ...Qt6QuickWidgets.dll"
or "The code execution cannot proceed because Qt6Multimedia.dll was not found".
The check that was supposed to catch this probed only Qt6Core.dll and
Qt6Network.dll, so it was blind to the two files the reports name. It also only
warned and then installed anyway, so a caught problem was still compounded.
- Scan every file the purge targets with FindFirst, which takes the same
wildcards the Delete/RMDir calls use, instead of two hardcoded names
- Widen IsExistingCockatriceInstall from Qt6Core.dll to Qt6*.dll so a directory
left holding only some Qt modules is still recognised as an install
- Gate the scan on that, so a first-time install into a directory that happens to
hold unrelated Qt5 DLLs is not turned into an abort
- Abort with a non-zero exit code instead of warning, so the new payload is
never written next to files of the previous version
- Skip the message box on an unattended /S install, which would block on it
forever, matching the two existing abort sites
* [Installer] Cancel a silent upgrade when the running-instance guard fails
The running instance guard is what stands between a locked install directory
and the mixed Qt DLL set that breaks the next start, but it failed open in two
places and let the install carry on anyway:
- powershell.exe could not be started: the check warned and returned
- the process lookup answered "could not determine": WaitForAppToClose returned
on $R0 != 1, so "unknown" was treated exactly like "not running"
On a silent upgrade there is nobody at the keyboard to close the applications in
response to that warning, which is precisely the state in which the launcher was
still holding its Qt DLLs mapped.
- Treat "could not determine" as its own state rather than folding it into "not
running", in all three places WaitForAppToClose inspects the result
- Cancel a silent upgrade in both cases; keep an interactive install on the
existing warn-and-continue path, since refusing there would leave an installer
that can never complete and the stale-runtime check still applies
- Route every abort through one AbortInstall so the dialog-vs-silent decision
lives in a single place, and stop the two pre-existing warning dialogs that a
/S install without /R would have blocked on forever
* [Installer] Wait on the process that launched the update installer
The in-app updater starts the installer and only then quits itself, so the
installer has to decide whether the launching process is still alive while it is
running. It did that by polling powershell.exe for an image name under the
install directory, which is a proxy for the real question - is the process that
started me still holding its Qt DLLs mapped - and a proxy that can answer "not
running" or "could not determine" while the process is very much alive. Either
answer let the install carry on into the mixed Qt DLL set.
Take /PID= from the command line and wait on that process handle directly:
- OpenProcess with PROCESS_SYNCHRONIZE only, which is all that is needed to wait
and the least likely to be refused for an unrelated reason
- WaitForSingleObject for the same 60 s the name based check allows, so a client
slow to flush its card database is not squeezed
- Treat an unopenable handle as "already exited", which is what it means for a
dead process
- Cancel the upgrade if the launcher has not exited, rather than replacing files
it is still using
Run it before EnsureAppsNotRunning, not after: it needs no powershell.exe, so it
is the one check that still works on a machine where the process lookup cannot
run, and it is what lets that case keep upgrading instead of being cancelled by
what was only ever a proxy for it. Keep an older client, which passes no /PID=,
on the existing path.
An older installer ignores /PID=, and /D= stays the last parameter either way.
* [Client] Pass our process id to the update installer
The updater starts the installer and only then quits itself, so the installer
begins replacing files while this process still has every Qt runtime DLL mapped,
and a mapped DLL cannot be deleted. It has to work out that it must wait, and
until now all it had was its own image name to go on.
Pass /PID= so it can wait on this process directly instead of guessing, and
cancel the update rather than replace files this process is still using. /D=
stays the last parameter, which NSIS requires.
* [CI] Fail the build when the installer is missing a Qt runtime DLL
The Windows payload is assembled by globbing every *.dll out of the build output
directories, so nothing but luck decides whether a module the client links
statically - Qt6Multimedia.dll, Qt6QuickWidgets.dll - reaches the installer at
all. A module that is left out is invisible until a user installs an update and
the client then refuses to start, with "The code execution cannot proceed because
Qt6Multimedia.dll was not found" or a missing procedure entry point in a
Qt6QuickWidgets.dll carried over from the previous version.
Derive the required list from SERVATRICE_QT_MODULES, COCKATRICE_QT_MODULES and
ORACLE_QT_MODULES, the same module lists the targets are actually built against,
so it cannot drift from what the binaries need, and write it next to the CMake
cache as required-qt-runtime.txt. Add the platform plugin, without which the
client cannot start at all.
Assert it in the existing 7-Zip pass that already rejects build-tree artifacts,
comparing exact relative paths so a same-named file in the wrong directory does
not satisfy a requirement, and normalizing the archive's own separators so it
does not matter which one NSIS chose. Missing manifest, missing entry or missing
7-Zip-independent failures all stop the build rather than warn, which is the
whole point: the previous check already failed hard for artifacts, and a warning
here is what let a broken package ship.
* [Client] Point the Windows qt.conf translations path at the real one
The Windows qt.conf carried Translations = Resources/translations, copied from
the macOS bundle block, but the Windows install has no Resources directory: the
.qm files are installed to COCKATRICE_WIN32_QM_INSTALL_DIR, which is
translations, and main.cpp looks for them at applicationDirPath() +
"/translations". Leave the macOS value alone, which is correct for it.
* [Installer] Close the /? help string so the installer compiles again
The last line of the continued MessageBox string ended in $\n\" instead of $\n".
NSIS reads \" as an escaped quote, so the string never terminated and makensis
rejected the script, which took the CPack packaging step down with it. The
Windows build failed on "Problem running NSIS command" before ever reaching the
new required-runtime check.
* [Installer] Give FindFirst the two arguments it takes
FindFirst takes the pattern and the output variable and leaves the search
handle in $0. There is no handle argument, and FindNext taking only a variable
is the giveaway: the handle has to live somewhere implicit, and it is $0.
The call passed three arguments, so makensis rejected the script and the CPack
packaging step failed with "Problem running NSIS command" before ever reaching
the required-runtime check.
This was not only a compile error. The handle variable was never populated, so
the stale-runtime check that commit
|
||
|
|
1365f3cb49
|
[Avatar] Cap user avatar upload resolution at 1024x1024 (#7353)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
* [Avatar] Cap user avatar upload resolution at 1024x1024 * Remove stylesheet. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
2632f8a040
|
[Client/Server] Keep non-deck files out of the deck storage (#7352)
The local deck and replay browsers list every file in the folder, so a stray file (e.g. a PNG screenshot of a deck) can be parsed as a garbage plaintext deck, opened in the editor, and uploaded. The server also accepts oversized deck payloads with only silent truncation. Hide files that are not in a supported deck/replay format in the two QFileSystemModel views (directories stay visible), skip them when opening, reject them when uploading, and make the server reject deck uploads larger than MAX_FILE_LENGTH instead of truncating them. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
fa8220df3c
|
[Client] Exit deterministically when launching the update installer (#7309)
* [Client] Exit deterministically when launching the update installer * [Client] Only quit when the main window close is accepted Wait for MainWindow::close() to be accepted before quitting after the update installer is launched. When the close is vetoed (a running card DB update, open games, or an unsaved deck), keep running and tell the user the installer is already waiting, instead of exiting over their answer. Also fix the comment so it does not claim settings are saved on the vetoed path. * [Client] Only quit on update when the shutdown actually ran MainWindow::closeEvent has a static re-entrancy guard that returns early on a second close event, leaving it in its default accepted state. DlgUpdate reached from a nested event loop while a shutdown prompt was up could then get close() == true with no shutdown work done (no settings flush, no tab shutdown) and tear down the process behind an unanswered prompt. Add MainWindow::closeForUpdate(), which reports false when a close is already in progress or was vetoed, and gate the update-exit on it. * [Client] Use QCoreApplication::exit() to leave on update QCoreApplication::quit() only asks the application to quit and can be interrupted if a top-level window refuses its close event. On the update path the process must actually leave so its Qt DLLs are unlocked before the installer starts replacing files - apply the exit(0) variant both on the accepted-close path and the fallback where no MainWindow was ever closed. * [Client] Warn that the update installer force-closes within a minute Telling the user Cockatrice just 'stays open for now' is misleading: the installer launched by #7308 waits only about 60 s for a graceful close before it force-terminates the process, which would lose unsaved work. State the deadline and tell the user to save and close before then. * [Client] Warn that a vetoed update is cancelled, not forced The warning on the veto path promised the opposite of what the installer does: WaitForAppToClose in #7308 never force-closes cockatrice.exe ($AllowForceClose is 0), it aborts the update after 60 s so no unsaved data is lost. A user who is told Cockatrice will be terminated in a minute hurries to close it, only to find the update already given up. State the real outcome instead: the installer waits about a minute for a close, and the update is cancelled if that does not happen. * [Client] Warn about a waiting installer without blocking it CloseMatchingApps in #7308 sends the WM_CLOSE once and the wait loop only polls IsAppRunning afterwards, but QMessageBox::warning is application modal and QGuiApplicationPrivate::processCloseEvent drops spontaneous close events for windows blocked by a modal widget. The veto path therefore swallowed the installer's one and only request, so even a user who resolved the blocker and closed the app still ended in the 60 s timeout. Move the warning into warnInstallerIsWaiting() and show it modeless, so the main window stays able to answer the installer while the message is up. * [Client] Close the parent on the no-MainWindow update fallback The fallback branch exits the process without closing anything, so it skips the settings flush and tab shutdown this change exists to get in front of the installer - the opposite of the fix if a call site ever stops passing a MainWindow. Close the parent widget first, like the code before this change did, and only then leave unconditionally: the parent is the one that knows how to shut down gracefully, and the deterministic exit is what the installer needs. * Add override. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
7c9dc303f8
|
[Game] Make 'Put top cards on stack until...' history case-sensitive (#7358)
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
|
||
|
|
e9cd9453ea
|
[Startup] Run the onboarding wizard before the first-run set dialog (#7349)
On a clean install, checkUnknownSets() fired before the onboarding wizard, so the legacy 'all the sets have been enabled' welcome and the Manage Sets dialog appeared first - with no card data there are no sets to manage yet. Defer the set check until after the wizard closes, suppress its dialogs while onboarding runs, and enable newly downloaded sets silently instead. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
bc26e19564
|
[DeckEditor] Highlight card name in VDE quick search after adding (#7350)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
a9b38ec0e1
|
[UserList] Sort developers below judges (#7347)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
712ca61a9e
|
[Themes] Encode home backgrounds as JPEG to slim the repo (#7346)
The home background PNGs shipped since #7280 total ~67 MB and bloat every clone and pull for other devs. Encode them as JPEG at quality 95 (matching resolution, visually lossless at 40-48 dB PSNR since the art is noise-heavy renders PNG cannot compress) for ~79% size reduction. The scheme-variant and theme asset resolution already probe .jpg, so only the qrc entries needed updating. Refs #7327 Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
eae9591a17
|
[Server/Client] Unify report categories (#7345)
* [Server/Client] Unify report categories into one shared source of truth * Lont. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
a1d8ce6165
|
[Accessibility] Bind card size sliders to Ctrl + Scroll (#7338)
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
Ctrl+scroll over a card display now resizes the cards everywhere a card size slider is shown, matching the standard way of resizing content. CardSizeWidget learns to forward Ctrl+wheel events onto its slider via an event filter that is installed on the display container and, when present, on the scroll area's content widget so the resize intercepts the wheel event before the view scrolls. The existing slider valueChanged wiring then rescales the displayed cards. Applied to the visual deck editor (per card group, covering flat and overlapped layouts), visual database display, printing selector, sample hand, visual and public deck storage, Archidekt previews and EDHRec card displays. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
71ef36374d
|
[DeckEditor] Show card counts in Visual Deck Editor banners (#7339)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
7a41dfe175
|
[PictureLoader] Fix custom-folder pictures in subdirectories not loading (#7340)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
CardPictureLoaderLocal compared the full file name (including extension) of the custom-folder candidate against the extension-stripped base name of each directory entry, so a CUSTOM subfolder image like pics/CUSTOM/poker/1 of Hearts.png never matched and the card fell through to the network. Compare the complete base names instead. Add matcher tests covering CUSTOM subfolder resolution. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
e97ef5a617
|
[Style] Do not offer broken windows11 (#7341)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
fe83966087
|
[Theme] Revert default light palette and only include AppColors (#7342)
* [Theme] Revert default light palette and only include AppColors * [Style] Also remove dark mode palette --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
e01912d3c8
|
[Tests] Add manual picture loader benchmark against the real card hosts (#7288)
* tests: add manual picture loader benchmark against the real card hosts (cherry picked from commit 588d71d3b9a8278ab98b847802908f322c1035b7) * tests: address review on picture loader benchmark - Sandbox via unique app/org names plus Linux-only XDG redirection, derive the warm-cache probe and data path from SettingsCache, and bail out when the temporary sandbox cannot be created. - Run each pass with a fresh worker and shut workers down before reading the 429 counters, so the redirect cache is persisted and no worker thread can outlive the stack-local counters or the installed message handler. - Make s_activeCounters atomic and always forward log output when the previous handler is the built-in (nullptr) one. - Validate --timeout-min, scale the cached-pass budget with --count, honour the first --url as the stress template, and add the missing trailing newlines. - Zero-initialise SettingsCache members so the benchmark mock cannot dereference an indeterminate pointer. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
9f66a098ac
|
[PictureLoader] Fix worker thread shutdown and cross-thread cache clearing (#7292)
* [PictureLoader] Fix worker thread shutdown and cross-thread cache clearing clearNetworkCache() ran directly on the UI thread while the worker thread owned the disk cache and redirect cache, racing cache reads/writes. Make it a worker-thread slot invoked via a blocking queued call when the thread is running, so the 'Cached card pictures have been reset.' message is truthful. The worker thread was also never quit()/wait()ed: both destructors only deleteLater'd their objects, so Qt warned 'QThread: Destroyed while thread is still running' and leaked a running loop at exit. Wire the worker's finished() signal to its own deleteLater() (canonical worker-object pattern), add shutdownThread() to stop the loop, and let CardPictureLoader destroy the QThread only after wait() has returned. * [PictureLoader] Guard cache teardown and stop blocking the UI thread --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
14acf3bf64
|
[PictureLoader] Add user-configurable per-host request caps (#7287)
* [PictureLoader] Add user-configurable per-host request caps Picture downloads were throttled to a uniform 10 requests/second per host with no way to tune a specific server. A rate-limited API host (Scryfall caps at 10 req/s) can trip 429s during bursts, and CDN hosts with no rate limit were throttled needlessly. Introduce developer-owned per-host caps that users can only ever lower, never raise, exposed in the download settings page: - DownloadSettings::DEVELOPER_HOST_CAPS sets the ceiling per host (api.scryfall.com 9, cards.scryfall.io unlimited, others 10). - A new hostRequestLimits setting stores user overrides in downloads.ini; clampHostRequestLimit() bounds them to [1, devCap] so a user can reduce api.scryfall.com to 5 but never raise it above 9. - The picture worker seeds, halves on 429, and recovers its sustained per-host allowance against the effective ceiling instead of the global maximum, and skips per-host accounting entirely for unlocked hosts (cards.scryfall.io) while global pacing and 429 backoff still apply. - The deck editor settings page gains one spinbox per known host, each clamped to its developer cap. * [PictureLoader] Let unlocked hosts skip dispatch pacing; adjust limits per URL Two refinements to the per-host request caps: - Unlocked hosts (UNLIMITED_HOST_QUOTA, e.g. cards.scryfall.io) no longer wait on the 100ms dispatch pacing or consume the global per-second quota. dispatchQueuedRequest fires their queued requests back-to-back, bounded only by their 429 backoff window and Qt's per-host connection pool, so an unthrottled CDN is not artificially slowed. - The deck editor download settings page replaces the static grid of one spinbox per known host with an "Adjust Rate Limit" toolbar action on the URL list. It picks the host out of the selected URL and clamps the entry against the developer cap table (including for user-added URLs). Also fixes a review finding: resetRequestQuota could write the UNLIMITED_HOST_QUOTA sentinel (-1) into the sustained per-host quota when a host became unlocked mid-run, permanently poisoning its allowance. Stale entries for unlocked hosts are now dropped, and the per-second seed is clamped against the effective ceiling so a lowered limit applies immediately. * [PictureLoader] Cap unlocked host bursts and adapt them to 429s * [PictureLoader] Store per-host limits readably and show them per URL * [PictureLoader] Make dispatch and rate-limit bookkeeping key on the real host Addresses ZeldaZach's round-4 review nits: - Dispatch now resolves the cached-redirect chain before the in-flight gate, so a redirect learned after a URL was queued can no longer bypass the MAX_IN_FLIGHT_PER_HOST cap and drain the whole queue onto the redirect target, which may carry its own developer cap. processSingleRequest does the same so the allowance math keys on the host that is actually hit. - The per-host in-flight slot is released when the reply is destroyed (with the worker as the connection context) rather than on a 'finished' connection bound to the work object, so an aborted reply or a work object deleted while a reply is pending can never permanently shrink the fast path's concurrency. - storeSettings only prunes limits for hosts with neither a URL nor a developer cap, so throttles on redirect targets (api.scryfall.com -> cards.scryfall.io) survive URL removal. - Unlocked hosts are offered 0..UNLOCKED_HOST_LIMIT_MAX (50) in the rate limit dialog, matching clampHostRequestLimit() and the documented hand-editable range, so values written into downloads.ini are no longer silently rewritten on the next edit. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
3d5eb84d81
|
[PictureLoader] Seed per-host allowances on demand and skip hosts in 429 backoff (#7286)
* [PictureLoader] Seed per-host allowances on demand and skip hosts in 429 backoff The quota reset re-filled every host's remaining allowance to a full MAX_REQUESTS_PER_SEC as soon as the queue had a request for it, so a server that was just rate limited could be hammered again at full speed immediately after (or even during) recovery. Only seed a host's allowance the first time it is dispatched in the current second, seeded from its reduced sustained quota, and skip hosts still inside their 429 backoff window entirely, handing the entry back to its worker so it can wait the backoff out or fall through to another source instead of parking in the queue with no reply pending. Deferrals wait on the host that is actually blocking the request (cached-redirect targets and the reply host of a 429) rather than the current card URL's host. Rebased onto network-requests/request-pacing, which absorbed the earlier pacing and dispatch-guard commits, and reuses its updateTimerState idle-429 recovery plumbing. * [PictureLoader] Hand backed-off entries back across the whole dispatch tick Review fixes on the 429-backoff skip: - Hand a backed-off queue entry back to its worker via scheduleDeferredRetry(host) instead of startNextPicDownload(), which was looping on the pre-redirect host every 100ms tick whenever the queued URL was a cached-redirect target whose redirect host was the one in backoff. - Keep scanning the queue after a hand-back (--i; continue) so a backed-off host at the head no longer consumes the entire dispatch tick, stalling every healthy host further down. - Emit imageRequestSucceeded(url) before makeRequest()'s cached-redirect backoff return so the status bar reclaims the deferred URL's widget instead of inflating the progress bar forever. - Only spend a per-second allowance when makeRequest() actually issues a request: it returns nullptr when the redirect target is backed off and the work is handed back, so the slot would otherwise be wasted on a no-op. - Gate the dispatch-time backoff skip on requestTouchesNetwork() so entries served straight from the disk cache (e.g. with downloads disabled) are not bounced into a 30-60s deferred retry for a host that 429'd earlier. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
334a743953
|
[PictureLoader] Pace requests and run the throttle timers on the worker thread (#7285)
* [PictureLoader] Pace requests and run the throttle timers on the worker thread Previously the whole backed-up queue was drained in a burst as soon as a request was enqueued, sending up to 10 requests back-to-back and then immediately re-filling the quota one second later. That hard-bursts a rate-limited API like Scryfall's (10 requests/second) into a 30 second lockout. Introduce a pacing timer that dispatches a single queue entry every 100 ms, so the per-second allowance is used smoothly instead of in spikes, and keep the quota timer at 1 second. Also fix both timers' thread affinity: they are QTimer value members and so are not QObject children, meaning moveToThread() on the worker left them on the main thread while the slot code started them from the picture thread, which was a no-op that also warned. They are moved to the worker thread explicitly and started lazily from there. * [PictureLoader] Guard dispatch timer restarts and drop dead request quota * [PictureLoader] Reconcile quota-timer lifecycle with idle 429 recovery --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
7a2492ac67
|
[Chat] Render room chat history usernames as live user tags (#7269)
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
* Render room chat history usernames as live user tags (#1595) Room chat history carries no user-level data, so history usernames were rendered as fixed, zero-level tags: the moderation context menu was missing the buddy/ignore and promote/demote entries and the stored name casing was never corrected. Resolve each history author against the online user list and, when found, build the user tag with the real user level and name so the entry behaves exactly like a live chat tag. Offline users keep the plain fallback. - chat_view: look up history authors via getOnlineUser for the real level/name * Fix offline history usernames getting a leading underscore The offline fallback used "_" as the level placeholder, producing an href of user://__NAME. The hover handler splits at the first underscore, so interactions targeted a nonexistent "_NAME" user. Use level 0 so offline history entries render as zero-level tags like before. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
ef68a7bdcc
|
[Card] Add a setting for the language used in card search (#7314)
* [Card] Add a setting for the language used in card search Localized card names and texts can now be searched too, controlled by a 'Language used in card search' toggle (English, selected card language, or both) on the general settings page. Untranslated cards always keep matching in English. Removed the redundant local copy of the URL templates list in the localized picture loader while here. * [Card] Bind search language per FilterString instance The peg parser rules are set up once per process, so the GenericQuery and OracleQuery rule actions could not capture per-instance state. Instead of storing the search language in a process-global that FilterString instance methods mutate, hand it to the rule actions through a thread-local parse context and copy it into the filter closures they produce. Card evaluation in FilterString::check no longer reads any process-global state, and each instance keeps the language it was built with; constructing one instance no longer changes what unrelated instances (deck filter, drop-to-hand, zone views) match against. The card database display model stores the raw query and rebuilds the FilterString when the search language changes, since the language is now bound at parse time. Add tests for the English/Selected/Both search modes, the English fallback for untranslated cards, and per-instance language independence. * [Card] Pass the card search language to deck and zone card searches Wire the two remaining FilterString consumers to the configured card search language so card-name matches respect it everywhere: - DeckFilterString now takes the search language and mode, exposes them to its [[card name]] rule action via a thread-local parse context (same pattern as FilterString), and the engine's card database uses them for content search. - ZoneViewZone reads the card language from CardsDisplaySettings when applying its search filter, and the reveal-zone widget re-applies the active search when the language setting changes. - The deck-storage search re-runs its filter against the current card language setting, including live re-application when the setting changes. Game-action targeting (DlgMoveTopCardsUntil) intentionally keeps evaluating against English card names. * [Card] Rename CardSearchLanguage to SearchLanguageMode * [Card] Restore displaced namespace doc in card_localization.h * [Filters] Pass CardSearchLanguage as a single struct * [CardSearchModel] Match English and localized names in Both mode Card names are stored in both English and localized forms, so search for matches in both during the 'Both' search mode instead of checking only the localized name. * [CreateTokenDialog] Fetch cardsDisplay settings inside the apply lambda Avoid capturing the raw settings pointer in the lambda: resolve the card language and card search language from the settings cache at call time so the values are always current when the search language is re-applied. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
6823d54c1e
|
[DeckShare] Browse and open public decks with loading, error and accessibility states (#7245)
* [DeckShare] Browse and open public decks with loading, error and accessibility states Add a public-decks tab that lists decks published by other users using the server's deck visibility feature, previewing each deck's banner card, color identity, tags and upload time without downloading the deck list until the user opens it. - Add a public-decks tab with a shared-settings widget and a remote model that fetches the target user's decks and refreshes both automatically and on user request, with a loading indicator and a server-error message instead of a blank tab when the fetch fails or the connection drops - Render each deck as a focusable preview tile whose banner, color identity, tags and upload time follow the existing Preview settings, with the deck name announced as the tile's accessible name and Space/Enter opening the deck, mirroring the shared-deck preview tile - Show a message box when opening a public deck fails or arrives corrupted - Publish and unpublish decks from the server storage toolbar and context menu, toggling the deck's own visibility bit (what the server persists) rather than the inherited effective state, and batch the visibility refresh until the last in-flight change is acknowledged - Add the Show Upload Time setting so the tile's upload stamp can be hidden like the other preview details - Update the retranslateUi wiring for the new public-decks tab and rename the share action tooltip from "Deck share" to "Share link" * [DeckShare] Adapt deck upload to the server-derived banner and tag protocol The server now derives the banner card and tags from the uploaded deck list itself, so Command_DeckUpload only carries the client-computed color identity. Drop the reserved banner/tag setters from the editor and storage uploads, send the color identity on remote saves, and read tags from the now-repeated ServerInfo_DeckStorage_TreeItem field. * [DeckStorage] Refresh the visibility column with a guarded timer instead of a latch counter A dropped visibility reply used to leave the pendingVisibilityChanges counter permanently positive, so the Public/Private column never refreshed again and nothing reset it on disconnect. A restartable single-shot timer with a boolean guard re-reads the tree whenever publishes quiet down and is stopped on disconnect, so a lost reply costs one stale refresh instead of killing the column for the session. * [DeckStorage] Summarize batch publish failures when the batch drains Each rejected node stacked its own modal dialog, so publishing a ten-deck selection against a rejecting server made the user dismiss ten dialogs one at a time. Failures are now collected while the batch is in flight and shown as a single summary when the visibility refresh timer fires; a reply that lands outside an active batch still reports right away. * [PublicDecks] Time out the loading state so a dropped reply cannot wedge the tab loading only cleared in decksReceived, but the ping sweep can drop a pending command without ever emitting finished, leaving the tab stuck on 'Loading public decks...' and the refresh button permanently inert. A single-shot timer started per refresh clears the latch and reports a timeout; the latch also clears when the client disconnects. * [PublicDecks] Escape remote-crafted text in tooltips and the tab title Deck names and usernames come from other users' records and Qt renders QLabel tooltips as AutoText, so a name like '<h1><table>...' parsed as markup. Escape and bound the deck-name tooltip and escape the username interpolated into the title label. * [DeckStorage] Distinguish an inherited public state in the visibility column The column reported the effective state while publishing toggles the node's own bit, so a private deck inside a public folder already read 'Public' and toggling appeared to do nothing (and toggling again silently unpublished it). The cell now shows 'Public (inherited)' for that case and the tooltip explains why. * [PublicDecks] Run retranslateUi at construction and name the refresh button retranslateUi was never called from the constructor, so the tooltips set there were absent until a language change. Call it before the first refresh, and give the icon-only refresh button an accessible name for screen readers. * [PublicDecks] Keep the empty and status variants correct across language changes retranslateUi unconditionally rewrote the empty label to the 'nothing published' variant, stomping the 'no decks match your filters' choice rebuildGrid had made, and a visible loading message stayed in the old language. Let retranslateUi pick the same variant rebuildGrid does and re-show the status so it retranslates. * [VDS] Share one color-identity match rule between the two deck grids The remote public decks model verbatim-copied updateColorMatches' switch, down to the ExactMatch normalization and the fact that Includes/Excludes do not normalize case. Extract colorIdentityMatches() next to the FilterMode enum and call it from both so the subtle rule cannot drift. * [DeckStorage] Drop the unused tree widget model accessor The accessor handed the model out past the wrapper methods that exist to keep it encapsulated, and nothing in the stack called it. * [DeckShare] End the public-decks files with a trailing newline keeps the final line's diff clean and stops clang-format CI from flagging the files. * [VDS] Reuse the shared quick settings widget for the public decks tab PublicDecksQuickSettingsWidget was VisualDeckStorageQuickSettingsWidget minus the folders, banner and tooltip controls, with identical wiring for the shared keys and a version of the near-identical file to keep in sync by hand. Fold the Show Upload Time checkbox into the shared widget, give it a setPublicDecksMode() that hides the controls that do not apply, and delete the duplicate. * [PublicDecks] Drop stale deck-list replies after the loading timeout A reply that lands after its own loading timeout (the reverse of the ping sweep dropping the command) could stop the newer request's timeout timer and repaint the grid with out-of-date data. Each refresh now captures a monotonically increasing request id, and only the newest request's reply updates the grid. * [PublicDecks] Re-show a displayed failure message on language changes The status label carries both the loading and the failure message, and retranslateUi hid it whenever the model was not loading, so a language change while a server-error or timeout message was on screen swapped it for the (empty) grid. The tab now keeps the last failure text and re-shows it when not loading, clearing it once a new refresh starts. * [DeckStorage] Keep the visibility refresh armed until replies land The single-shot drain was armed with the 500 ms delay at send time, so a round trip slower than that drained before the server applied the change, re-read the old state and never re-armed, leaving the column stale until a manual refresh. The timer is now armed with the full network timeout at send time (a lost reply still costs one stale refresh) and re-armed with the short delay every time a reply lands. * [DeckShare] Close public decks tabs when the client disconnects TabSupervisor::stop() built tabsToDelete from the room and game tabs only, so a public decks tab survived a disconnect, sitting with stale contents and a refresh button that kept hitting the dead client. Its values are now folded into the same cleanup. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
8ca749c07d
|
[DeckShare] Open shared decks via links with a gated preview flow (#7244)
* [DeckShare] Open shared decks via links with a gated preview flow - Serialized url-chain dispatcher in IntentUrlParser; queue-drained urlChainFinished(bool) drives the startup auto-connect fallback - Open-shared-deck intent with sequential download state machine, 15s per-item timeout, partial-success offer, livable Cancel via ApplicationModal dlg_login_prompt interactive fallback - Preview dialog: download progress label, share vocab sweep, palette-highlight selection frame, Space/Enter keyboard toggle, NoFocus checkbox, double-click tile opens immediately - Confirm-before-server-migration with one-shot restore to the previous server on failed/cancelled chains (statusChanged settle deferral), hostname-only identity comparisons - Skip credential link when already connected; arrow-key navigation in FlowWidget; card glows use palette highlight - Address code-review M1-M4 and UI/UX QA blockers 1-2 * [DeckShare] End the open-shared-deck files with a trailing newline * [DeckShare] Forward a dependency's cancellation as the owner's own * [DeckShare] Let intent chains opt into the link sign-in dialog * [DeckShare] Track link-intent chains per-run so each can restore its own session * [Settings] Match a server on the exact host and port when adding it * [DeckShare] Confirm the share link's target server before opening a deck * [DeckShare] Reformat the link sign-in intent constructor * [DeckShare] Time the share-list round trip and backstop silently-destroyed intent chains * [Client] Drain a single-instance payload before its handlers read the socket again * [Client] Treat a busy single-instance primary as alive instead of stealing its socket * [DeckShare] Keep arrow-key navigation between flow items inside a scroll area * [Client] Skip the startup connection when a macOS URL launch owns the connection * [Client] Redact share secrets from activation URL logs * [Client] Make the link-connection gates port-aware and keyboard-safe Second-pass review notes for the shared-deck link flow (Cockatrice#7244): - FlowWidget arrow-key navigation is opt-in via addNavigableWidget, so combo/spin controls on the analytics flows keep their own arrow keys - isConnectedTo and the open-deck/join-game preconditions compare the configured server port alongside the host, so a same-host/different-port link cannot resolve its share token or game id on the wrong instance - the link sign-in dialog reuses an existing server entry's saved name instead of renaming it to the raw hostname - skipStartupAutoConnect is cleared once the launch chain connects, so a later mid-session declined link cannot fire the startup fallback - the plain-launch path of SingleInstanceManager no longer blocks on the primary's ACK - link- and server-supplied text is html-escaped in the confirm prompts and shared-deck preview so markup cannot spoof the shown messages --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
ba2900dcb9
|
[DeckShare] Create temporary share links for local and server decks (#7243)
* [DeckShare] Create temporary share links for local and server decks
* [DeckShare] Address review findings and harden the share flows
Gate every share entry point on login, de-duplicate the share-link and
color-identity logic behind DeckShareUtils and an injected querier, and
replace the silent tray/status-bar notices with always-visible dialogs.
- abstract_tab_deck_editor: explain that sharing requires a connection
instead of silently doing nothing when logged out
- tab_deck_storage: disable the share action on disconnect, reject
folder/deck mixes and the root folder with clear warnings, re-enable
Create on every entry/response so a dropped connection cannot leave
the button disabled
- tab_deck_storage_visual: same login gate for the context-menu entry,
visible success/error dialogs, and a symmetric in-flight guard
- getDeckColorIdentity now takes a CardDatabaseQuerier, dropping the
CardDatabaseManager singleton access and enabling unit tests
* [DeckShare] Fix share-link expiry build on the minimum-supported Qt
QTimeZone::UTC (the Initialization enum) only exists since Qt 6.7, so
Debian 12 and Ubuntu 24.04 (Qt 6.4) fail to compile the share-link expiry
handling in the share dialog and the two deck-storage tabs. Mirror the
existing games_model guard and fall back to Qt::UTC on older Qt.
* [DeckShare] Use the stable server client for the visual deck storage tab
* [DeckShare] Extract the share-creation response handling into DeckShareUtils
* [DeckShare] Drop includes left unused by the share-response extraction
* [DeckShare] Format share expiry with the locale-aware short format
* [DeckShare] Build share links with QUrl and QUrlQuery for percent-encoding
* [DeckShare] Replace the duplicate computeColorIdentity with the shared getDeckColorIdentity
* [DeckShare] Recover the share controls when the server never answers
* [DeckShare] Provide the full share hint in each plural form
* [DeckShare] Join the selected-count label with a non-translatable separator
* [DeckShare] Retranslate the share button tooltip with the storage widget
* [DeckShare] Forward retranslateUi to the visual deck storage widget
* [DeckShare] Let the share bar owners supply the hint text
* [DeckShare] End the share-related headers and sources with a trailing newline
* [DeckShare] Keep the settings include in the project include block
* [DeckShare] Include the network settings header used by the share timeout
* [DeckShare] Resolve the share theme icon through themePixmap
QPixmap("theme:icons/share") has no file extension, so ThemeManager::assetPath()
is bypassed and the pixmap is always null. Use themePixmap(QStringLiteral("icons/share"))
like every other toolbar action, so the .svg (and dark/light variants) resolves.
* [DeckShare] Keep the share selection consistent with the visible decks
Filtered-out previews are hidden but kept alive, so selectedFilePaths() counted
them in the share and the selection highlight. Only decks the user can see are
now shared, and a deck that stops matching the filters is deselectd as the deck
pass runs, keeping the %n count and the highlight in sync with the screen.
* [DeckShare] Abandon an in-flight tree share on cancel
Leaving share mode never stopped the timeout timer, and a late response still
ran shareFromTreeFinished, copying the link and announcing success for a share
the user backed out of. Stopping the timer and tracking the outstanding request
by sequence number means a stale reply (or a timed-out one) after cancel is
ignored, and cancelling + re-entering share mode can no longer confuse the two
requests.
* [DeckShare] Abandon an in-flight tile share on cancel
exitShareMode() left shareTimeoutTimer running and did not abandon the pending
Command_DeckShareCreate, so a timer pop or a late success still reported the
share after the user cancelled. Stop the timer and ignore stale responses via a
sequence number, mirroring the tree tab.
* [DeckShare] Wire the status-changed handler after shareBar exists
handleConnectionChanged() dereferences shareBar->isVisible(), but the connection
was set up before shareBar was constructed and shareBar had no in-class
initializer. On any status change delivered before construction the slot read an
indeterminate pointer. Seed the connection (and the initial share availability)
after shareBar exists and give shareBar a = nullptr initializer.
* [DeckShare] Explain why a blank deck cannot be shared
A blank deck exited the share flow silently. The menu only disables the entry
via setSaveStatus(), a different predicate, so the path is reachable (e.g. add a
card and remove it again). Mirror the not-logged-in branch with a short
information dialog.
* [DeckShare] Restore the banner-text doc comment
Re-add the doc block above refreshBannerCardText() that was removed as part of
the share-selection work; it documents the coupling to refreshBannerCardToolTip.
* [DeckShare] Resolve the stable server client in the deck editor gate
actShareDeck went through tabSupervisor->getClient(), which hands back a
LocalClient while an offline game is running. LocalClient never sets its status,
so a logged-in user could not share from the deck editor during a local game,
and got a misleading "You must be connected" message. Expose the supervisor's
stable remote client and use it for the gate and the dialog, matching the other
share tabs.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
|
||
|
|
a289d61765
|
[VDS] Decouple tag filter and fix reordered-chips crash (#7242)
* [VDS] Decouple tag filter and fix reordered-chips crash * [VDS] Address review: dead code, chip reparenting, filter signal and sort fast-path * [VDS] Address second round of review nits --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
a5e94d8a4f
|
[Build] Keep Windows installs free of build-tree artifacts (#7316)
* [Build] Keep Windows installs free of build-tree artifacts
Several Windows packaging gaps could leak Visual Studio CMake build
output into the installed application or the NSIS installer:
- The per-app DLL sweep used
${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${CMAKE_BUILD_TYPE}, which is
empty on multi-config generators, collapsing the recursive
DIRECTORY install into the whole build tree (containing *.dir,
*_autogen, .qt, .qsb, x64, ...). Point it at the real per-config
output with $<TARGET_FILE_DIR:...> and exclude build artifacts.
- install(FILES ${OPENSSL_INCLUDE_DIRS} ...) tried to install OpenSSL
include directories as files. CMake refuses this
("install FILES given directory"); it only slipped through CI
because the vcpkg OpenSSL config leaves the variable empty. Remove it;
fixup_bundle already ships the OpenSSL runtime DLLs.
- The NSIS uninstaller only deleted *.exe/*.dll and a few known files,
so build-tree leftovers survived an uninstall/reinstall cycle. Wipe
the whole directory tree instead.
- Add a Windows CI gate that lists the packaged installer with 7-Zip
and fails the build if any build-tree artifact path is found.
* Update .ci/compile.sh
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
* [Build] Rework Windows installer artifact exclusions per review
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
|
||
|
|
db2e159dca
|
[Game] Allow judges to enter any game regardless of restrictions (#7315)
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> |
||
|
|
9acb9739b2
|
[Client] Fix spurious server room join error (#7259)
* [Client] Fix spurious server room join error The server replies RespContextError when a join command is received for a room that connection is already registered in. The client was sending such duplicate joins in benign situations - double-clicking to join a room, or clicking a room the selector was already auto-joining - and answered them with a modal telling users to restart the client. Joins for the same room are now deduplicated while one is in flight, and a remaining RespContextError is healed by leaving and rejoining the room so the tab appears without a client restart. Error dialogs are only shown for user-initiated joins, so failed auto-joins no longer spam critical popups. * [Client] Bound stale-membership room join heal to one attempt The RespContextError heal (leave + rejoin) previously recurred unconditionally, so a server that kept returning RespContextError for a reason other than stale membership would loop forever. Track room ids that already received a heal and surface the error dialog after one attempt instead of retrying indefinitely. * [Client] Scope room-join heal guard to one join attempt * [Client] Hoist room-join heal guard lookup out of response switch --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
59dd052143
|
[DeckEditor] Restore auto-scroll when adding cards (#7317)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
51365960fe
|
[Client] Create custom pawn for dev role (#7295)
* [Client] Create new pawn for dev role * lighten color |
||
|
|
7b39cf98d5
|
[Themes] Add identity default palettes for the image themes (#7280)
* [Themes] Add identity default palettes for the image themes The Fabric, Leather, Plasma and VelvetMarble themes shipped only zone art and no palette, so their chrome fell back to the bare OS palette. Each theme now ships light and dark curated defaults written in the palette editor's own conventions, with [AppColors] so the home-tab buttons keep the theme's identity over static backgrounds. - Add palette-default-light.toml / palette-default-dark.toml for the four themes; scheme resolution follows the OS since the themes declare no color scheme. - Tint the window/base/button chrome and bevels towards each theme's identity: navy cloth (Fabric), black-brown with brass accents (Leather), electric violet with cyan sparks (Plasma), charcoal velvet with slate marble (VelvetMarble). - Fill [AppColors] AccentStrong / AccentSoft per theme so the home-tab button gradient matches instead of falling back to the default greens. - Derive Disabled and Inactive groups with the same conventions as PaletteGenerator::fromAccent. * [Themes] Add home backgrounds for the image themes * [Themes] Ship Fusion style + scheme backgrounds for the image themes Address PR review: - Add theme.cfg ([Style] Name = Fusion, ColorScheme = System) to Fabric, Leather, Plasma and VelvetMarble so their curated palettes actually apply. Without it the native style paints button chrome from the OS (windowsvista on Windows has no dark mode), making the dark palettes' light ButtonText unreadable on light buttons. - Leverage the scheme-variant asset resolution: register home-dark.png and home-light.png in resources and add them to the qrc so the built-in home background also flips with the palette instead of staying static. - Fix Leather [Palette.Inactive] Accent, which copied the Active Highlight color instead of the theme accent (dark #4a5f8f -> #c9995a, light #34508c -> #a5712f) in both palette files. * [Themes] Align light plasma home background with the dark variant Recolored the Plasma light home background to mirror the dark one: brighter violet/cyan blooms, vivid azure spark arcs, and higher accent saturation against the same pale-lavender key. Brightness is unchanged so it still reads as a light scheme. * [Home] Add option to disable the home tab background dim Adds a 'Dim the home tab background' checkbox to the Home tab settings page (Appearance). When unchecked, HomeWidget skips the translucent black overlay it paints over the whole background. Default is on, preserving current behavior; the home tab repaints live on change. * Update leather backgrounds * Update velvet marble backgrounds * Update light plasma background * Update light fabric background * WIP [UI] Theme-aware onboarding banner with frosted light mode Banner colours now derive from palette tokens at ~60fps (tick-driven, equality-guarded setters) so scheme switches and live accent-picker previews apply instantly: - dark stages: byte-for-byte the original treatment (near-black stage from window hue, Highlight accent, white centre halo, vignette 0.62) - light stages: pastel accent-hue wash instead of a neutral grey copy, brightness-lifted accent for additive glow legibility, deep-Highlight halo (uGlowColor) instead of white blowout, gentler vignette (uVignetteMin 0.88) so corners don't go muddy - black logo silhouette variant selected on light stages - theme picker preseeded with brand green (brand_colors.h single source) WIP notes for next session: - real-pixel wizard screenshot check still pending (headless capture exists: Xvfb :77 + isolated XDG_DATA_HOME; shader vs fallback pixel analysis not finished) - user plans separately: promote Fusion to default theme, Default -> system * [Themes] Align Fusion accent tokens with the SVG brand gradient Align AccentStrong (#139740) and AccentSoft (#c9fd62) and the linked Link/Accent roles with cockatrice.svg's linearGradient4265-7-8 stops so the identity gradient used by the logo emulation matches the static art the icon shipped. * [Onboarding] Draw the banner logo as a static gradient plate Replace the black/white logo tint switch with a ShaderEffect plate that repaints the SVG's brand gradient (light AccentSoft -> dark AccentStrong along the baked-in userSpaceOnUse axis) clipped to the full-color logo's alpha silhouette, with the white highlight path overlaid on top — matching the home widget's QPainter composite. The plate is static: no glow or breathing. Brand colors flow from BannerShaderConfig's new brandStrong/ brandSoft pair instead of the removed logoDark flag, and the background motifs get a touch more accent so the mark keeps its coloured surround. * [Home] Draw the featured logo as a theme gradient composite Repaint cockatrice.svg in Qt instead of showing the baked-in static art: fill the full-color logo's alpha silhouette with the same brand gradient the banner plate uses (light AccentSoft grading to dark AccentStrong along the SVG's userSpaceOnUse axis), then overlay the white highlight path. Renders an explicit QPixmap so the mark stays crisp at the 200px display size, and re-seeds it on theme/palette/appearance changes so it never goes stale. * [Resources] Drop the unused black logo asset No consumer remains after the banner's logoDark toggle was replaced by the static gradient plate (unit-tested in d7529e0c6a), so remove cockatrice-logo-black.svg and its qrc entry. * [Onboarding] Seed the theme picker from the theme's identity accent Replace the hardcoded brand-green preseed with the shipped theme's own AccentStrong (Plasma seeds violet, Fusion green), resolved from the default palette so auto/user-generated palettes can't mask it, and re-seed whenever the theme changes so the swatch never goes stale. Also consult the shipped palette in maybeAutoGeneratePalette so scheme flips don't regenerate a fresh palette over curated theme colors. * Regenerate zone assets for Fabric, Leather, VelvetMarble themes Align zone textures with each theme's palette identity: - Fabric: linen weave in navy/steel tones (was generic green/blue/red/gray) - Leather: grain texture in warm brown/amber tones - VelvetMarble: smooth charcoal marble veining (replaces .jpg with .png) All zones now have dark + light scheme variants for OS color scheme adaptation. Plasma zones deferred to separate iteration. * Regenerate Plasma zone assets: horizontal-hand layout, seamless tiling Bake each zone's phase from its true world position in the horizontal-hand layout (player 0, stack 172, table 280, hand 172+406) so the diagonal sheen continues continuously across zone boundaries. All zones share one mathematically tileable (1,1) diagonal at frequency 3; playerzone gets a distinct blue-violet identity and the light scheme gets extra contrast. * Regenerate VelvetMarble zones: classic Perlin marble, seamless tiling * Regenerate VelvetMarble zones: domain-warped fractal veins, stone grain * Promote Fusion to default theme, rename Default to System Fresh installs and new profiles now default to the Fusion (dark) theme instead of the platform-native theme. The old "Default" theme is renamed "System" to better describe its purpose — using the OS-native Qt style (windowsvista, macOS, etc.). Existing users who had "Default" selected are automatically migrated to "System" so they keep their platform-native styling. Users with an empty or invalid theme name now fall back to Fusion. * Move checkbox. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
6c1d1c7b58
|
[Client] Add [AppColors] application palette roles (#7279)
* [Client] Add [AppColors] application palette roles The home-tab buttons' gradient over the static theme background was hardcoded, and accent-derived fallbacks could not be themed or edited: QPalette's role set is closed, so any application-specific color has to live in Cockatrice's own palette layer. - Add an AppColor::Role enum (AccentStrong / AccentSoft) stored on PaletteConfig and round-tripped from palette-<scheme>.toml under a new [AppColors] section. - Cache the applied app colors in ThemeManager and expose appColor(Role) with a palette-accent-derived fallback; emit paletteChanged() from applyStyleAndPalette so previews, scheme switches and OS dark mode repaint palette-driven widgets. - Fill both app roles in PaletteGenerator::fromAccent and surface them as a dedicated section in the palette editor. - Drive the home-tab buttons from appColor() whenever the background source is the theme (any theme, not just built-ins). - Ship AccentStrong / AccentSoft values in the Fusion and Default default palettes so the static home-tab buttons keep their classic greens. # Conflicts: # cockatrice/src/interface/widgets/general/home_widget.cpp * [AppColors] Address review comments - PaletteEditorDialog::onSave(): compare whole PaletteConfig (colors and appColors) so a change to only AccentStrong/AccentSoft writes the file; add PaletteConfig::operator==. - appColor(): derive both roles from QPalette::Highlight unconditionally. The Fusion palettes pin Accent to near-Window values, and QPalette::Accent only exists on Qt 6.6+, so keying on it made identical themes render very differently across Qt versions. - themeChangedSlot(): merge the theme default's [AppColors] into a custom palette that predates the section instead of all-or-nothing per file; hasPalette() now counts an appColors-only file as a palette. - Add Default/palette-default-light.toml so the Default theme's Light scheme keeps the classic greens instead of falling back to the OS accent. - home_widget: restore the isBuiltInTheme() half of the Automatic condition; non-built-in themes extract button colors from their own background art. - palette_grid_widget: use appEnum.value(i) for the role cast (3 sites), append appHeader to headerLabels, fix the 'Lighted' typo. * [Themes] Route theme writes to the user themes directory setColorScheme()/setStyleName() and the palette editor wrote directly to the resolved theme directory, which for built-in themes is the read-only system (install) location. Changes therefore landed in the install dir and were lost on upgrade. Add ThemeManager::writableThemeDir(), which always resolves to the user themes directory, and route all theme writes through it. The palette editor reuses the same helper, dropping its private writability probe. * [Home] Replace 'Automatic' button color with explicit theme colors default The Automatic option gated on isBuiltInTheme(): built-in themes used the theme's accent colors, while non-built-in themes extracted colors from their own background art. That made the result depend on the theme's origin rather than what the user actually sees. Remove Automatic and expose two explicit choices: 'From theme colors' (always the theme's identity accents, now the default) and 'Extract from background' (always sample the painted background). Drop the now-unused isBuiltInTheme() helper. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
456db56058
|
[PrintingSelector] Add Image Overrides submenu with hover preview (#7312)
* [PrintingSelector] Add Image Overrides submenu with hover preview * [PrintingSelector] Address review comments - Move QAction/QMenu forward declarations after the includes - Use the renamed installPrintingOverride API and deleteAllLocalOverrides statically - Drop the flavorName usage; Cockatrice does not use that field anywhere yet - Extract the Load Custom Image handler into loadCustomImage() - Make the preview size/offset constexpr and drop the redundant pixmap copy - Extract the preview placement into a previewPositionNear() QPoint helper --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
cca4af0ec7
|
[PictureLoader] Add local override storage and resolution with matcher tests (#7311)
* [PictureLoader] Add local override storage and resolution with matcher tests * [PictureLoader] Address review comments - Make deleteAllLocalOverrides static; it does not touch instance state - Drop the now-unused hasCustomArt dead code - Rename the override install methods to installPrintingOverride / installPrintingOverrideOnLoad * [Tests] Give loader matcher tests a writable HOME in CI Under GitHub's docker runner the process uid has no passwd entry, so HOME resolves to '/' and the test-mode qttest data dir cannot be created. SettingsCache's QSettings then drops every write, getPicsPath() comes back empty, and the loader searches a blank path. Point HOME at a QTemporaryDir for the duration of the run. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
1c93309952
|
[Client] Show localized card names, texts and pictures (#7294)
* [Client] Show localized card names, texts and pictures
Localization wiring now runs end to end: the oracle importer collects
foreignData for the configured language and the client renders it.
- [Oracle] Import localized names and rules texts for the selected cardLang
- single-face cards store their foreignData name and full text
- multi-face (split/adventure/aftermath/prepare) cards collect the joined
name once and join each face's translated text with the same separator
as the English merge; an incomplete translation falls back to English;
the joined text follows the same highest-priority-set policy as the
single-face path and is only collected when localization is enabled
- the wizard switching languages re-imports the card database
- [Client] Display localized card info throughout the client
- card info text/picture widgets and the game board re-render on language
change
- pictures resolve cardLang art through Scryfall's named endpoint using the
localized name, falling back to id-based art when no match exists
- deck editor keeps canonical English names as card identity (EditRole)
while showing localized names (DisplayRole), so decks and wire names
stay stable
- [Card] Add CardLocalization-backed name/text lookup and cards.xml v4
localization elements with a bounded-size translation cache
- [Tests] Cover oracle foreignData import (incl. multi-face joins, priority
and fallback paths), XML v4 localization parsing, deck model localized
display and the language-aware settings default
Existing installations need to re-run Oracle to see translations: localized
data only lands in cards.xml when the Oracle app is started with the
preferred language selected — launch the separate "Oracle" program that
ships with Cockatrice, pick the language in the wizard and let it re-import
the card database.
The client's database cache (cards.xml.cache) is invalidated by the cache
format bump and the source-hash checks, but a cache written before the
re-import can still hold English-only entries (the hash uses file size and
mtime, so a same-size/same-timestamp rewrite may be served as-is); delete
cards.xml.cache and relaunch if no localized names/texts show up after
re-importing.
* [Card] Pass localized card names and texts into CardInfo construction
Address review: instead of constructing the card and then calling
setLocalizedName/setLocalizedText (which emit a cardInfoChanged signal per
language), both constructors, both newInstance overloads and their callers
(cards.xml v4 parser and the binary cache reader) now pass the localized maps
as constructor arguments.
* [Client] Rename LocalizedCard:: helpers namespace to CardLocalization
The namespace now matches its header file name, as the review pointed out;
LocalizedCard reads more like a class or struct. Callers (card info text
widget, board card name rendering) are updated to match.
* [Client] Drop unused info member from the card info text widget
The CardInfoPtr member was only ever initialized to nullptr and never read;
remove it together with its initializer.
* [PictureLoader] Add the localized picture URL explicitly, not implicitly
Address review: silently prepending the Scryfall named-picture URL to the
download list whenever a non-English card language was active was surprising,
consumed quota per card when it failed, and could grab the wrong (canon) art on
name collisions, with no way to turn it off.
The insert is now opt-in and user-controlled: changing the card language adds
the template to the top of the download URLs once (persisted, documented in the
re-import prompt, and editable/removable in the deck editor settings), while the
picture loader no longer injects it at request time.
* [Card] Show card languages in the same native (English) format as the UI
Address review: the card text & images language dropdown listed bare native
names, some in inconsistent lowercase (e.g. "čeština", "español de España"),
which makes the languages easy to mix up for users that do not read the script
(e.g. 日本語 vs 한국어). It now mirrors the UI language dropdown and always pairs
the native name with its English name (e.g. "Deutsch (German)",
"日本語 (Japanese)"), using the same fixed casing.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
|
||
|
|
fd82b140a8
|
[PictureLoader] Serve cached pictures from the disk cache instead of re-fetching them (#7284)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
With picture downloads enabled, requests were issued with AlwaysNetwork cache control, which per Qt never consults the disk cache. A picture that had already been downloaded was therefore fetched from the network again on every session start, with the queue bypass letting those re-fetches skip the rate limit entirely. Treat the network cache as the intent of the 'Network Cache' storage method suggests: if the URL is already cached, serve it with AlwaysCache (no network, no quota); only a genuine miss goes to the network, and only when downloads are enabled. Cache hits skip the queue for free since they never consume the per-second request allowance. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
5d025ca0bd
|
Use capitalized app names (#7255)
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
* use capitalized app name * update urls * app description * Update main.cpp |
||
|
|
a85203e457
|
Migrate theme asset loads to scheme-variant resolution (#7209-2) (#7276)
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
Replace remaining QPixmap("theme:...") call sites with themePixmap() so icons, replay controls, card backs, and other images resolve to -dark/-light variants under theme schemes. Stem-exact 1:1 migration; behavior unchanged for non-variant themes.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
|
||
|
|
e9bf1e6e46
|
[Theme] Add scheme-variant asset resolution (#7275)
* Add scheme-variant theme asset resolution (#7209-1) ThemeManager::assetPath() and schemeVariantPath() resolve a theme asset to its scheme-variant file (prefix-light/dark.png) with fallback to the plain asset, and themePixmap()/loadBrush()/loadExtraBrush() use them. CSS files load style-dark.css or style-light.css when present. Home widget re-resolves its background on theme change. Link pixel_map_generator.cpp into the oracle target, which needs Qt6::Xml for QDomDocument. * Fix clang-format wrap of theme format probe lists --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
202a5ac958
|
[Client/Server/Protocol] Surface live metrics in the Developer tab (#7212)
* [Server] Instrument command processing, game starts, and event loops
Add a lock-free MetricsRegistry that accumulates per-command processing
times in preallocated histogram slots (one per protobuf command type,
bucketed at 1/5/10/25/50/100/250/500/1000/2500/5000 ms +Inf). The
hot-path observeCommand() uses only relaxed atomic adds — no locks,
no allocations, no cache-line ping-pong beyond the unavoidable counter
updates.
Wire the registry into AbstractServerSocketInterface::processCommandContainer()
so every processed command is attributed with its container's wall-clock
time. When a container exceeds metrics/slow_command_ms (default 500),
a warning is logged including the connected username.
Add an EventLoopWatchdog heartbeat that runs on every socket pool thread.
If a heartbeat overshoots metrics/stall_warn_ms (default 2000 ms), the
overshoot is recorded in atomic counters and a warning is logged. Both
thresholds are configurable in servatrice.ini; setting stall_warn_ms to 0
disables the watchdogs entirely.
Track game-start durations via a separate histogram in MetricsRegistry.
Server_Game::startGameNow() measures the time from zone creation through
player materialization and reports it via Server::observeGameStartDurationMs().
Add a live card-count gauge: Server_Game exposes getCardsInGame() and
Servatrice::getCardsInGamesTotal() sums across all running games under
the appropriate read locks.
Include a standalone metrics_registry_test (Google Test) that validates
empty registries, single/multi-sample histograms, kind encoding,
overflow-slot collapse, negative-duration clamping, gauge rendering,
and the game-start histogram separation.
Took 10 minutes
* [Client/Server/Protocol] Surface live metrics in the Developer tab
Extend Response_GetServerStats with live counters from the in-process
MetricsRegistry: cards in games, event loop stall totals/worst,
total commands processed, average command time, active command types,
and game-start count/duration. Add a repeated CommandStats message
carrying per-command breakdowns (kind, extension number, resolved
protobuf name, count, total ms) for every type that has seen at
least one sample.
Server-side cmdGetServerStats() populates all new fields after the
existing DB uptime snapshot query, resolving protobuf extension names
via the descriptor pool for human-readable labels like
session/Command_Ping.
Expand TabDeveloper with two tables: an overview section (existing
DB stats plus the new live metrics) and a per-command breakdown table
(Command / Count / Total ms / Avg ms) sorted by total_ms descending
so the hottest commands surface first.
Took 55 minutes
Took 47 seconds
* [Server] Drop dead Prometheus histogram, add developer command metrics, fix watchdog init order
- metrics_registry: remove toPrometheusText/appendCumulativeBuckets and the time-bucket histogram that nothing in production ever emitted (the future /metrics exporter can bring it back); keep counts/totals read by the Developer tab
- Fix +Inf bucket routing that never incremented, and its test that locked the bug in
- Instrument developer_command container (kind 6) in processCommandContainer and stats label resolution
- Read metrics/{slow_command_ms,stall_warn_ms} at the top of initServer() so stall_warn_ms=0 disables the watchdogs before pool threads start
- Shrink KindStride to 1280 (largest extension in use is 1206) with a static_assert; document scrape cost of getCardsInGamesTotal; note slow_command logging has no rate limit in servatrice.ini.example
* [Tests] Give metrics_registry_test an explicit main
* [Server] Record only the dispatched command family; drop unused totals
processCommandContainer recorded every family in a container even though
the base if/else-if dispatch processes at most one. An unauthenticated
client could batch a session command (login) with fabricated developer,
moderator, and admin entries and forge genuine-looking samples that were
never executed or authorized. Mirror the base's selection, skip when the
handler was already deleted, and skip entries whose extension number is
-1 (which would otherwise wrap into the previous kind's id range).
[Server] Drop dead process-lifetime byte/uptime counters
txBytesTotal/rxBytesTotal added an atomic RMW to every socket write and
read for counters nothing consumes (cmdGetServerStats fills tx_bytes,
rx_bytes, and uptime_secs from the DB snapshot). Remove the two atomics
and the getTxBytesTotal/getRxBytesTotal/getUptimeSeconds getters; the
incTxBytes/incRxBytes slots and mutexes remain for the ISL legacy
counters.
[Protocol] Document kind 5 as developer in CommandStats
NumKinds is 6 and the server emits kind_index = 5 for developer
commands; the comment stopped at 4.
* [Client] Togglable auto-refresh for Developer stats tab
* [Oracle] Fix clang-format alignment of card type priority list
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
|
||
|
|
d5d99e4dfb
|
[Server/Client/Protocol] Add developer staff role (#7211)
* [Server/Client/Protocol] Add developer staff role Introduce a Developer staff level (proto flag 32, DB admin bit 8) that sits between admin and moderator: no kick/ban/warn/report/admin powers, but gets server log access via a new developer command container family (GET_SERVER_STATS, VIEWLOG_HISTORY) and an idle-timeout exemption. - Protocol: IsDeveloper flag, developer_commands.proto envelope, Command_GetServerStats/Command_GetLogHistory, Response_GetServerStats, Command_AdjustMod.should_be_developer - Servatrice: fail-closed developer dispatcher, uptime snapshot handler, shared log history handler reuse, bit-8 DB mapping - Client: burgundy pawn/badge/labels/sort order, prepareDeveloperCommand, minimal Developer stats tab, log tab access, promote/demote actions Took 24 minutes Took 18 seconds * [Server/Client/Protocol] Address developer role review feedback Address ZeizaZach's review of the developer staff role: - Nudge the developer log query to exclude private chat and sender IPs (the ModeratorCommand path still sees everything). - Deduplicate Command_GetLogHistory into Command_ViewLogHistory, which now extends both ModeratorCommand (ext) and DeveloperCommand (dev_ext); the client picks the DeveloperCommand-scoped extension by extendee, and the server reads it via the extension number. - Pull the uptime snapshot SQL into Servatrice_DatabaseInterface as getLatestUptimeSnapshot() and widen the reported counters to 64-bit. - Document the admin bitfield (1 admin, 2 moderator, 4 judge, 8 developer) and add a server-side test for the developer command path. * Add missing trailing newline to user_context_menu.cpp * Remove stale includes of deleted command_get_log_history proto The Command_GetLogHistory message was folded into Command_ViewLogHistory, which deleted command_get_log_history.proto, but serversocketinterface still #included its generated header. Fresh CI builds fail on the missing file; local builds masked it by reusing a previously generated header. * [Server] Exclude chat rows when private-chat filter is bypassable A developer who omits log_location entirely — or sends only "chat" — leaves chatType, gameType, roomType all false, so getMessageLogHistory skips the target_type clause and returns every row, private messages included. When !allowPrivateChat the server now forces game+room when no surviving location was requested, guaranteeing the query always carries a target_type restriction. [Client] Demote mod+dev to moderator path in log-tab dispatch The developer command family is strictly weaker than the moderator one (no private chat, no sender_ip, ip filter ignored), so granting the developer bit to an existing moderator must not silently strip their capabilities. useDeveloperCommands is now true only when the user holds the developer bit and not the moderator bit. [Client] Hide the IP-address filter for developer log tab users The developer path ignores the ip_address query field server-side. Showing the field lets a developer type an IP and get results that are silently unfiltered by it rather than an empty result set — reads as a broken filter. Hide labelFindIPAddress/findIPAddress alongside the privateChat checkbox. * Developer pawn is silver. * [Client] Fix indentation of merged Card Art Rules / Developer tabs --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
7d867b9745
|
[Oracle/Client] Report card database download progress (#7253)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
* [Oracle/Client] Report card database download progress Card database updates ran invisibly: MTGJSON parsing spun an indeterminate bar on the UI thread and the set import blocked the window, while the onboarding wizard spawned `oracle -b` with no progress to show at all. - Add byte-level scan progress to `RawJson::scanSetRanges` via an optional callback, throttled to ~100 reports per scan. - Emit `OracleImporter::dataReadProgress` during the scan and import sets on a worker thread, driving the wizard's progress bar per set. - With `-b`, write machine-readable `PROGRESS <stage> <done> <total>` lines to stdout for the download/scan/import stages; stderr keeps the log output. - Parse the oracle stdout in `MainWindow` and forward it to the onboarding wizard, giving the card database step a determinate bar with stage-specific status text. - Guard the async workers against the wizard being closed mid-run. - Add Google Test coverage for scan progress reporting. * [Oracle/Client] Harden oracle progress workers and quit prompt Address review feedback on the download-progress change: decompress and read sets files off the UI thread, cancel the load/import workers before the wizard can tear down the importer, and show an 'Extracting file...' status plus a clean 100% tail so the poll never looks stuck. Quitting Cockatrice while a card database update runs now asks for confirmation. * Show 100% for 500ms on complete. * Disable buttons on set import until done. * Clean up progress bar. * Drop wrapper around lambda --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |