Skip to content

Release Omakade 1.7.0 - #36

Merged
btsouth merged 55 commits into
mainfrom
release/1.7.0-merge
Sep 7, 2026
Merged

Release Omakade 1.7.0#36
btsouth merged 55 commits into
mainfrom
release/1.7.0-merge

Conversation

@btsouth

@btsouth btsouth commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Bring console libraries, ratings and artwork, reorganized settings, manual games, bulk organization, saved filters, and personal backups into 1.7.0.

Includes the controller keyboard and focus fixes, narrow-window layout corrections, scanner cache preservation, and backup fixes from final testing. This incorporates #30, #34, and #37.

Validation:

  • 124 local tests passed; 121 executable checks also passed against the Arch package.
  • Tested the packaged UI on Wayland with mouse and keyboard, plus maintainer controller testing.
  • x86_64 package upgrade from 1.6.1, removal, reinstall, metadata, and dependency scans passed.
  • Final x86_64 and ARM64 CI and release-package checks passed (124 tests each).

Brings codex/1.7-review-fixes (PR #34 on top of PR #30) into the release
branch so one 1.7.0 carries both feature sets.

Conflict resolutions worth knowing about:

- Hero and logo artwork overrides moved into the data() switch. The merged
  code left them after it, where they were unreachable, so custom heroes and
  logos would have silently stopped working.
- Saved filters record the multi-select source list instead of one source
  name, and both the model and the archive validator accept a bare string so
  filters saved by an earlier build still load and export.
- Sort modes are five, not three. The duplicate name list is gone and the
  count is shared through PersonalDataRules with the archive validator.
- Manual games, extra GOG folders, and backup and restore moved into the
  redesigned settings sections, which had replaced the sheet they lived in.
- The Manual source chip joins the multi-select chips and has its controller
  targets wired. Pick A Game is now the first toolbar button after the source
  row, so the row hands focus to it rather than skipping to Sort.
- CI and the release workflow install wayland and wayland-protocols, so both
  build the idle inhibitor instead of quietly configuring it out.

All 94 CTest cases pass, along with the SBOM, desktop and AppStream checks.
A filter saved before sources became multi-select stored one source name as a
string. Both the model and the archive validator accept that shape, so add the
cases that hold them to it: a multi-source filter round trip, applying a
legacy single-source filter, and an archive carrying one of each. The archive
case also uses the popularity sort mode, which the old range would reject.
Sorting by rating was hard to read, because the score that decided the order
was not on the card. The metadata line now ends with the score beside the
source and the playtime, brighter than the rest of the line so a sorted grid
reads down the column.

Games without a rating show nothing rather than a placeholder, and the source
name gives up the width the score needs so it elides instead of pushing the
line off the card.
Automatic identification already existed, but it accepted a match only when the
cleaned local title equalled the catalogue title exactly, and the cleaner knew
only No-Intro style tags. Measured against a real 1,397 game set, 99% of titles
still carried dump text after cleaning, so almost nothing could ever match and
every game fell to manual identification.

The cleaner now removes a parenthesised group only when every part of it is a
region, language, release flag, revision or translation note, so "(NA, Rev 1)"
and a multi-clause translation credit both go while "(& Knuckles)" and
"(Digital Deluxe Edition)" stay. Square bracket flags go too, and a sorted
article moves back to the front, with or without a subtitle after it. Comparison
ignores a leading article, which the catalogues disagree about. That same set
now cleans completely.

Where several catalogue entries share a name and platform, the most rated one
wins instead of the match being abandoned. A regional duplicate or a
compilation beside the game no longer costs an identification.

Retro consoles keep their own box art: Libretro scans and GameTDB covers are
authentic, so those systems skip SteamGridDB entirely rather than replacing
real artwork with a fan-made portrait. Switch, Wii U and PS4 carry only a
square icon that crops badly, so they still ask for one, as do PC games.
A stored match carried only a timestamp, so an answer decided by older rules
stood until its rating aged out. A matching fix therefore reached an existing
library a month later, one entry at a time, and a shelf marked "Needs
identification" kept looking broken in the meantime. That is why Super Mario
All-Stars stayed unidentified after the cleaner learned to read its dump tag.

Entries now record the version of the rules that decided them, and an entry
decided by older rules is stale however recently it was written. Raising that
version is how a future matching change reaches everyone, with no backfill
command and nothing for anyone to run by hand.

A match the user chose is now protected from automatic re-identification. It
could previously be replaced during an ordinary rating refresh, and picking the
most rated entry among equal titles made that more likely.

Every connection in settings reports its own state through one shared rule.
RetroAchievements and IGDB showed no status at all, so a working
RetroAchievements connection looked like a failed one, and Steam said OPTIONAL
where the others said NOT CONNECTED. Steam and RetroAchievements need both
their fields before they count as connected, and a provider that answered with
an invalid key, a private profile or a rate limit says to check its settings.
A downloaded portrait replaced whatever cover a game already had, including
Steam's official 600x900 capsule. That is a downgrade: fan art in place of the
publisher's own artwork. The rule is now about the artwork rather than the
system. A portrait is fetched only when the game's own art is missing or cannot
serve as a cover, so anything already portrait shaped is left alone, retro
consoles keep their box scans, and Switch, Wii U and PS4 dumps, which carry a
square icon that crops a logo off the card, may still take one.

That decision has to read the source's own artwork, not the resolved cover, or
a portrait already downloaded would justify keeping itself. A role now exposes
what the source provides, before any user choice or download.

The queue paused half a second between games on top of a 350 ms pause before
each IGDB call, and SteamGridDB's calls were not paced at all. Both providers
now pace themselves at the request, so the gap between games only has to yield
to the event loop. Together with the games that no longer make three
SteamGridDB calls each, a library of mostly Steam and retro games finishes in a
fraction of the requests it made before.
A card under Super Nintendo read "RetroArch · Nintendo - SNES / SFC (SNES9X)",
where the heading above it already says Super Nintendo. The core name pushed
the playtime and the rating to the end of the line, and the rating was the
first thing to elide. Inside a console the card now names only its source, so
the fields that differ between games have the room.
Switching a source filter rebuilds the grid, and every cover blinked back to a
placeholder before returning. Two causes, both measured on a real library over
the same thirty second window: 8,478 image loads before this change, 5,604
after.

A delegate is created before layout gives it a size, so binding the image
source straight through started a decode at the cover's full size while the
frame was still zero, then decoded again at the size actually wanted. The
second decode missed the cache, so art that was already on screen had to be
read from disk again. The image now waits for the card to have a size, which
also stops a 600x900 cover being decoded at full size for a thumbnail.

The fade then made the reload obvious. A cover already decoded is ready within
a frame or two, and fading those in is what made a filter change look like the
library was reloading. The fade is kept for art that genuinely has to come from
disk or the network.

Portraits are also cleaned up on their own now. A portrait downloaded over
artwork a game's own source provides is dropped as the library settles, so the
Steam capsules that fan art had replaced come back without anyone running
anything. Steam is judged by source rather than by the file on disk, because
its official capsule downloads on demand and a game whose capsule had not
arrived yet would otherwise still look like it needed fan art.
Nine sources scan at startup and each one signals as it loads, and the combined
library answered every signal with a full reset. A reset destroys and recreates
every card, so covers disappeared and came back several times before the grid
settled. Measured over the first thirty seconds on a 1,464 game library:

  before   26 full resets, 18 of 25 changing nothing at all
  after     0 full resets, 7 appends, 19 rescans that said nothing

The library is now composed first and compared before anything is emitted. A
rescan that finds the same games says nothing, since the view is already right
and a real change to a game arrives through dataChanged. A source that finished
scanning and added games appends them, which keeps every card already on screen
along with its artwork and the selection. A full reset is left for what it is
meant for: games genuinely moving or disappearing.
Ratings and artwork only arrived if you pressed Update and left the window open
long enough. A library of 1,464 games needs about half an hour that way, the
queue lived only in memory, and closing the app threw away the progress and
started again from the top. Games near the end, such as the eight Nintendo 64
titles, never got their turn.

The pass now continues on its own once the library has settled, picks up games
added later, and resumes across launches, since a game that already has a
current answer is skipped. Pressing Stop keeps it stopped until the next launch
or an explicit Update, so it never fights the person using it.

It also identifies what you are looking at first. Opening a console reorders
what is still pending so those games come first, rather than reordering the
work already done. On a real library it identified 38 games in the first minute
with nothing pressed.
Scrolling a few rows and back made every card read and decode its artwork from
disk again, and show a placeholder while it did. Qt keeps only a couple of
megabytes of images no view is currently showing, roughly twenty covers at the
size a card asks for, which is less than a single screenful. So the cache was
guaranteed to miss for any library worth having, and cacheBuffer could not help:
measured against a real library, changing it moved nothing.

Covers now go through Omakade's own provider, which decodes straight to the size
the card wants and keeps the result. Measured over the first forty-five seconds
on a 1,464 game library: 136 covers decoded from disk, 1,071 served from memory,
and not one decoded twice.

The cache is bounded at 128 MB rather than sized to the library. Holding
everything would grow with both the number of games and the cover size setting,
so the largest libraries would be the ones to blow the budget, and it would be
spent on artwork nobody is near. A few hundred covers is several screens in
either direction, which is the distance people scroll and come back from. Past
that a thumbnail decodes again in a few milliseconds, which was never the
problem; the problem was a limit far below one screen.

Also orders identification by system in turn once what is on screen is done, so
eight Nintendo 64 games and two Dreamcast games cannot sit behind 1,387 SNES
ROMs. That is why those two consoles still had no ratings.
Locks in the startup fix so it cannot quietly come back. A source that rescans
and finds the same games must emit nothing, a source that finds more must append
them, and only a game genuinely moving or disappearing may reset. Reverting the
fix makes this fail with five resets where none belong.
The Couch Mode bar spent equal room on Settings and Desktop, which are rarely
touched, while Source and Sort sat inside Browse. Seeing only Steam games with
nothing on screen to say Steam was the only source selected read as a broken
library rather than a filter.

Source and Sort are now bar controls that state their current value, so the
filter is always visible and Emulated is one press away. Source opens Browse on
its source list, which is still the way to every other filter, so the bar loses
a control rather than gaining three. All, Favorites and Recent become one
cycling Show control, and the console toggle carries its state in its highlight
rather than in its label. Ten controls become eight, and the widest possible
labels still fit at 720p.

Grid cards gained the rating as a badge in a fixed corner of the artwork, which
lets the eye read a column of ratings down the grid the way a line of text under
the card cannot at television distance. Cards without a rating have no badge.
Playtime joins the source line, which had room for it.
…of filters

Narrowing the library leaves the grid holding delegates for the games that just
left. Those delegates stay in the scene, stay visible, and carry index -1, so a
move down from the organize row could land on a card nobody can see. Focusing it
set the grid's current row to -1 and opening it asked for row -1, which is the
"Unknown game" page with no title and no source.

A delegate that stands for no row now leaves the focus chain, so navigation
cannot reach it, and it refuses to be focused, activated or favourited. That is
the fix; the rest is depth. The organize row gained the explicit downward target
every other control already had, rather than falling back to picking whatever
was nearest. Activating from either grid checks the row count as well as the
sign, and openGame refuses a row outside the library instead of opening an empty
page. Reverting the delegate change fails the new test.

Back now walks out of the library the way you walked in: out of a console, then
out of a search, then out of a source, and finally out of anything else still
narrowing the view, which goes together so back always reaches the whole library
rather than asking for a press per filter. Levels that are not active are
skipped, so RetroArch inside Nintendo 64 is one press from RetroArch and two
from all sources. Both views are covered, since both watch the same library.
Retro systems were exempt from portraits on the grounds that their box scans are
authentic. Measured against a real library, that only holds for systems whose
boxes were printed portrait:

  NES         4 portrait, 0 the wrong shape
  N64         0 portrait, 9 the wrong shape
  SNES        0 portrait, 111 the wrong shape
  Dreamcast   0 portrait, 2 the wrong shape

So the exemption kept authentic artwork on NES, where it looks right, and kept
wide cartons and square cases everywhere else, where they cannot fill a card
without being cropped or letterboxed. The rule is now the artwork's shape alone.
A box printed portrait is kept whatever system it came from; a box printed wide
or square takes a portrait, which reads better on a card even when it is fan
made. Artwork arriving later is reconsidered, since the startup sweep applies
this same rule and will drop a portrait once authentic artwork turns up.

Portraits are stored as JPEG rather than PNG. Covers are photographs, and
lossless was costing about 750 KB each: a thousand of them would have filled the
artwork cache on its own and then spent its life evicting and re-downloading. On
twelve real portraits from this library JPEG at quality 92 is 4.5 times smaller,
161 KB against 726 KB, which takes a full SNES set from 983 MB to 218 MB and
well inside the default limit. Portraits already stored as PNG keep working and
are still counted by the cache trim.
Just over half the library was matching. Logging what IGDB actually returned for
every failure showed the search was rarely the problem: of 34 failures, only 5
came back empty, while 22 came back with the right game and were then thrown
away by our own platform check.

IGDB catalogues a Japanese release under its own machine. Alcahest, Accele Brid
and Ace wo Nerae are all returned by name and all listed on platform 58, the
Super Famicom, while we accepted only the Super Nintendo's 19. A system now maps
to every platform its games can be listed under, so a shelf of imports is no
longer discarded after being found.

Two smaller causes went with it. Accents are a spelling difference rather than a
different game, so a cartridge labelled Pokemon Stadium 2 now matches the
catalogue's Pokémon Stadium 2. A licensed game is often catalogued with its
publisher in front, so DuckTales matches Disney's DuckTales.

Measured by clearing every failed entry and running the pass again over the same
library: of the eighty games re-decided, sixty-seven matched, where every one of
them had failed before.
Every control in the identify panel is gated on the service being idle, and the
library pass now runs on its own, so the service was almost never idle. Search
IGDB, Not This Game and Choose Portrait sat permanently disabled, and the status
line reported on whatever game the background happened to be working through.

Opening the panel now stands the queue down and holds it, rather than dropping
it, so nothing already worked out is repeated. The status line shows the state
of the game in front of you instead of the background's. Closing the panel puts
the queue back and carries on where it left off.
A game opened from Omakade filled a fraction of the screen and had to be
toggled fullscreen by hand. RetroArch here is configured with
video_fullscreen = "false", so it opened a normal window, Hyprland tiled it
beside Omakade, and nothing asked for anything else. That is not the emulator
misbehaving and it is not the compositor: a launcher should say what it wants.

Each emulator is now asked explicitly, using the flag it actually documents,
checked against the installed binaries rather than assumed:

  RetroArch  --fullscreen
  Cemu       --fullscreen
  PCSX2      -fullscreen
  shadPS4    -f true
  Dolphin    -C Dolphin.Display.Fullscreen=True, having no flag of its own

Ryujinx is left alone. Its launcher prints no options and its fullscreen state
lives in its own configuration, so there is nothing here to pass that I could
verify rather than guess at.
Some ROM sets number their files, so a Game Boy Advance cartridge arrives as
"1636 - Pokemon Fire Red". Searching a catalogue for that finds nothing.

The number is only removed after the title as written has already failed, so a
game that genuinely begins with a number, 1080 Snowboarding or 1942 or 007
Racing, is searched for as written and never mangled. A hand search is never
affected by a retry left over from the background pass.
The regional platforms, accents, publisher prefixes and catalogue numbers all
changed how a match is decided, and none of them raised kMatchVersion. So the
575 entries already marked as needing identification stayed marked, on rules
that no longer applied, and would never have been retried. That is the exact
failure the version exists to prevent, and a comment asking the next person to
remember was not enough.

Every identification rule now folds into one fingerprint that a test pins.
Changing any of them fails that test until kMatchVersion is raised alongside,
so a library cannot be left holding answers the rules would no longer give.
Verified by adding a platform and watching the test fail.
Keys are read from the keyring on a worker thread, so the library can settle
before they get back. The pass looked once, found no connection, and had
nothing to bring it back, so a whole library sat unidentified until something
else happened to change. Measured on a real library: twelve minutes after
launch, none of the 674 entries awaiting the new rules had been looked at.

Credentials arriving now re-arms the pass, for IGDB and for SteamGridDB alike.
Nothing had been identified for hours and the cause was not the matching rules.
Four services keep credentials, Steam, RetroAchievements, IGDB and SteamGridDB,
and each read its key on its own worker thread as the app started. libsecret
builds its GObject types on first use and two threads doing that at the same
moment race, so the registration failed with "cannot register existing type"
and every schema built afterwards was invalid. Ten of those errors landed on
the same millisecond. Every lookup then failed with "the attribute 'service'
was not found in the password schema", no credential loaded, and a pass that
needs a connection quietly did nothing. Keyring calls are now serialised.

Two more faults were hiding behind that one. The pass connected to the
library's signals and waited for the next change, but sources that load from
their own database have already filled it, so those rows arrived before anything
was listening and the wait never ended. It now looks at what is already there.
And a pass that declined because a key was still being read had nothing to bring
it back, so it now tries again shortly, while wanting credentials outright still
waits to be told. A key that fails to load no longer ends the pass either, since
ratings do not depend on it.

Verified on the real library: it now settles, finds the connections, and queues
1,468 games, where before it queued none.
Two places decided it and they disagreed. Queuing asked needsIdentifying, which
knows about the rule version. Processing then judged by the timestamp alone, so
every game queued because the rules had changed was dequeued, sent straight to
the artwork step, and never identified again. Its recorded rule version never
moved, so it queued again next time, forever. The queue drained at four games a
second and wrote nothing, which is why the library looked frozen while the app
was in fact busy.

Both now ask the same question. On the real library, re-identification went from
one game to thirty-one in forty seconds.
Prioritising what is on screen only listened for rows coming and going. Opening
a console swaps the whole view in one pass and reports that as a layout change,
so walking into a console reordered nothing and its games waited behind the
whole library. That is the opposite of what the prioritising was for.

Layout changes now count, and a game whose details are open goes straight to the
front rather than taking its turn.
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 131 files, which is 31 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: dde9741b-082a-43da-afe2-13328d496c41

📥 Commits

Reviewing files that changed from the base of the PR and between 6c3572f and a109965.

⛔ Files ignored due to path filters (60)
  • resources/consoles/3do.png is excluded by !**/*.png
  • resources/consoles/3ds.png is excluded by !**/*.png
  • resources/consoles/atari2600.png is excluded by !**/*.png
  • resources/consoles/atari5200.png is excluded by !**/*.png
  • resources/consoles/atari7800.png is excluded by !**/*.png
  • resources/consoles/atarijaguar.png is excluded by !**/*.png
  • resources/consoles/atarilynx.png is excluded by !**/*.png
  • resources/consoles/cdi.png is excluded by !**/*.png
  • resources/consoles/colecovision.png is excluded by !**/*.png
  • resources/consoles/dreamcast.png is excluded by !**/*.png
  • resources/consoles/dreamcast.svg is excluded by !**/*.svg
  • resources/consoles/gamecube.png is excluded by !**/*.png
  • resources/consoles/gamecube.svg is excluded by !**/*.svg
  • resources/consoles/gamegear.png is excluded by !**/*.png
  • resources/consoles/gb.png is excluded by !**/*.png
  • resources/consoles/gb.svg is excluded by !**/*.svg
  • resources/consoles/gba.png is excluded by !**/*.png
  • resources/consoles/gba.svg is excluded by !**/*.svg
  • resources/consoles/gbc.png is excluded by !**/*.png
  • resources/consoles/gbc.svg is excluded by !**/*.svg
  • resources/consoles/genesis.png is excluded by !**/*.png
  • resources/consoles/genesis.svg is excluded by !**/*.svg
  • resources/consoles/intellivision.png is excluded by !**/*.png
  • resources/consoles/mastersystem.png is excluded by !**/*.png
  • resources/consoles/n64.png is excluded by !**/*.png
  • resources/consoles/n64.svg is excluded by !**/*.svg
  • resources/consoles/nds.png is excluded by !**/*.png
  • resources/consoles/neogeo.png is excluded by !**/*.png
  • resources/consoles/neogeocd.png is excluded by !**/*.png
  • resources/consoles/nes.png is excluded by !**/*.png
  • resources/consoles/nes.svg is excluded by !**/*.svg
  • resources/consoles/ngpc.png is excluded by !**/*.png
  • resources/consoles/odyssey2.png is excluded by !**/*.png
  • resources/consoles/pcengine.png is excluded by !**/*.png
  • resources/consoles/pcenginecd.png is excluded by !**/*.png
  • resources/consoles/ps2.png is excluded by !**/*.png
  • resources/consoles/ps2.svg is excluded by !**/*.svg
  • resources/consoles/ps3.png is excluded by !**/*.png
  • resources/consoles/ps4.png is excluded by !**/*.png
  • resources/consoles/ps4.svg is excluded by !**/*.svg
  • resources/consoles/psp.png is excluded by !**/*.png
  • resources/consoles/psvita.png is excluded by !**/*.png
  • resources/consoles/psx.png is excluded by !**/*.png
  • resources/consoles/psx.svg is excluded by !**/*.svg
  • resources/consoles/saturn.png is excluded by !**/*.png
  • resources/consoles/sega32x.png is excluded by !**/*.png
  • resources/consoles/segacd.png is excluded by !**/*.png
  • resources/consoles/snes.png is excluded by !**/*.png
  • resources/consoles/snes.svg is excluded by !**/*.svg
  • resources/consoles/switch.png is excluded by !**/*.png
  • resources/consoles/switch.svg is excluded by !**/*.svg
  • resources/consoles/vectrex.png is excluded by !**/*.png
  • resources/consoles/virtualboy.png is excluded by !**/*.png
  • resources/consoles/wii.png is excluded by !**/*.png
  • resources/consoles/wii.svg is excluded by !**/*.svg
  • resources/consoles/wiiu.png is excluded by !**/*.png
  • resources/consoles/wiiu.svg is excluded by !**/*.svg
  • resources/consoles/wonderswancolor.png is excluded by !**/*.png
  • resources/consoles/xbox.png is excluded by !**/*.png
  • resources/consoles/xbox360.png is excluded by !**/*.png
📒 Files selected for processing (131)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .gitignore
  • CHANGELOG.md
  • CMakeLists.txt
  • COPYRIGHT
  • PLAN.md
  • PRIVACY.md
  • README.md
  • RELEASING.md
  • SUPPORT.md
  • docs/1.6-REVIEW.md
  • docs/1.7-REVIEW.md
  • docs/BACKUP-FORMAT.md
  • docs/COMPATIBILITY.md
  • docs/COMPLETION-PLAN.md
  • docs/COMPLETION-PROGRESS.md
  • docs/INTEGRATION-DECISIONS.md
  • docs/releases/1.7.0-notes.md
  • packaging/arch/PKGBUILD.in
  • packaging/io.github.tsouth89.Omakade.metainfo.xml
  • qml/Main.qml
  • qml/components/CouchBrowsePanel.qml
  • qml/components/CouchKeyboard.qml
  • qml/components/CouchLibraryView.qml
  • qml/components/CoverArtwork.qml
  • qml/components/CoverSizeControl.qml
  • qml/components/FieldClearButton.qml
  • qml/components/GameCard.qml
  • qml/components/GameMetadataEditor.qml
  • qml/components/GlassButton.qml
  • qml/components/LibraryView.qml
  • qml/components/SettingsPanel.qml
  • qml/components/TextEntry.qml
  • qml/screens/ArtworkEditor.qml
  • qml/screens/BackupEditor.qml
  • qml/screens/BulkOrganizationEditor.qml
  • qml/screens/GameDetails.qml
  • qml/screens/ManualGameEditor.qml
  • qml/screens/RestoreStartup.qml
  • qml/screens/SavedFiltersEditor.qml
  • resources/consoles/ARTWORK.md
  • resources/consoles/GENERATION.json
  • src/achievements/RetroAchievementsService.cpp
  • src/achievements/SteamAccountService.cpp
  • src/app/AppSettings.cpp
  • src/app/AppSettings.h
  • src/app/IdleInhibitor.cpp
  • src/app/IdleInhibitor.h
  • src/app/SecretService.h
  • src/app/main.cpp
  • src/artwork/CoverImageProvider.cpp
  • src/artwork/CoverImageProvider.h
  • src/artwork/SwitchTitleReader.cpp
  • src/artwork/SwitchTitleReader.h
  • src/artwork/TgaImage.cpp
  • src/artwork/TgaImage.h
  • src/artwork/ZArchiveReader.cpp
  • src/artwork/ZArchiveReader.h
  • src/backup/BackupArchive.cpp
  • src/backup/BackupArchive.h
  • src/backup/BackupDatabase.cpp
  • src/backup/BackupDatabase.h
  • src/backup/BackupManager.cpp
  • src/backup/BackupManager.h
  • src/backup/BackupRecovery.cpp
  • src/backup/BackupRecovery.h
  • src/backup/BackupSnapshot.cpp
  • src/backup/BackupSnapshot.h
  • src/backup/BackupStartup.cpp
  • src/backup/BackupStartup.h
  • src/input/ControllerFocusGuard.cpp
  • src/input/ControllerFocusGuard.h
  • src/input/ControllerInput.cpp
  • src/input/ControllerInput.h
  • src/launch/GameLauncher.cpp
  • src/launch/GameLauncher.h
  • src/library/BattleNetGameModel.cpp
  • src/library/CemuGameModel.cpp
  • src/library/CemuGameModel.h
  • src/library/ConsoleCatalog.cpp
  • src/library/ConsoleCatalog.h
  • src/library/ConsolePortalModel.cpp
  • src/library/ConsolePortalModel.h
  • src/library/DatabaseTuning.h
  • src/library/DolphinGameModel.cpp
  • src/library/DolphinGameModel.h
  • src/library/FaugusGameModel.cpp
  • src/library/GameRoles.h
  • src/library/HeroicGameModel.cpp
  • src/library/HeroicGameModel.h
  • src/library/LibraryFilterModel.cpp
  • src/library/LibraryFilterModel.h
  • src/library/LutrisGameModel.cpp
  • src/library/ManualGameModel.cpp
  • src/library/ManualGameModel.h
  • src/library/Pcsx2GameModel.cpp
  • src/library/PersonalDataRules.h
  • src/library/RetroArchGameModel.cpp
  • src/library/RetroArchGameModel.h
  • src/library/RyujinxGameModel.cpp
  • src/library/Shadps4GameModel.cpp
  • src/library/Shadps4GameModel.h
  • src/library/SteamGameModel.cpp
  • src/library/SteamGameModel.h
  • src/library/UnifiedGameModel.cpp
  • src/library/UnifiedGameModel.h
  • src/metadata/GameInsightsService.cpp
  • src/metadata/GameInsightsService.h
  • src/metadata/GameMetadata.cpp
  • src/metadata/GameMetadata.h
  • src/sources/cemu/CemuScanner.cpp
  • src/sources/cemu/CemuScanner.h
  • src/sources/dolphin/DolphinScanner.cpp
  • src/sources/dolphin/DolphinScanner.h
  • src/sources/heroic/HeroicScanner.cpp
  • src/sources/heroic/HeroicScanner.h
  • src/sources/retro/RomFolderScanner.cpp
  • src/sources/retro/RomFolderScanner.h
  • src/sources/ryujinx/RyujinxScanner.cpp
  • src/sources/shadps4/Shadps4Scanner.cpp
  • src/sources/shadps4/Shadps4Scanner.h
  • src/streaming/SunshineIntegration.cpp
  • tests/BackupRecoveryTests.cpp
  • tests/CMakeLists.txt
  • tests/CoreTests.cpp
  • tests/DetailsRecoveryIsolation.cmake
  • tests/fixtures/released-v1.6.0/README.md
  • tests/fixtures/released-v1.6.0/personal-data.sql
  • tests/fixtures/released-v1.6.0/schema-harness.cpp
  • tests/fixtures/released-v1.6.0/schema.sql

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Review on demand using usage pricing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…deciding the match

Four separate reasons a matched game still showed no cover.

The scan wrote its own idea of cover_path over every row, and RetroArch only
reports thumbnails installed in its own directory. A library without the
thumbnail packs reports none, so every launch discarded the covers downloaded
from libretro and re-fetched them one visible card at a time. Covers a scan did
not find are now left alone, the cache is read back on load, and pruning clears
the stored path as well as the one in memory.

SteamGridDB was only accepted when exactly one result matched the title and the
release year agreed. The two catalogues disagree about years constantly for
older games, because one is dating the arcade original or the Japanese release
and the other the cartridge that was dumped. The year is now a tie-breaker
within three years rather than a gate, and ties are still refused rather than
guessed.

A rate limit or a dropped connection was recorded as if it were an answer, so
one refusal held a game back for a day. Cover rules now carry a version, the
way matching rules do, so a fix reaches a library that has already been
through a pass.

Wide box scans sat on a plain dark field that filled half the card. Cropping
them to fit cuts the title off, since a SNES logo spans nearly the full width,
so the card is filled with a blur of the cover's own colours instead.

Also clears the portraits that were saved, which have been JPEG since they
stopped being stored losslessly, rather than only the PNGs.
… back

IGDB catalogues a licensed game under its publisher, so a cartridge labelled
Goof Troop becomes "Disney's Goof Troop". SteamGridDB files it as plain Goof
Troop and its search matches on that prefix, so asking for the name as written
returned ten other Disney games and not the one wanted. The prefix is now
dropped and the search repeated, but only after the name as written has failed,
so a game whose name really begins that way is looked up as written first.
Matching ignores the prefix on the same terms, which keeps Kirby's Dream Land
from being free to become any game called Dream Land.

Opening a candidate in the cover panel used to store it there and then. Since
the background pass short circuits on a stored grid game, one glance at the
wrong candidate pinned the game to it for good and the pass would go on to
fetch that game's artwork by itself, with no way back. A selection is now held
until a cover is actually taken from it, and a stored selection with no
portrait to show for it is dropped on a rules change rather than trusted.

The cover panel can also be searched by hand now, which is the only way to
reach a game the two catalogues disagree about: SteamGridDB files Dragon Quest
V under Hand of the Heavenly Bride while IGDB gives its Japanese title. Clear
Cover drops what a search settled on and lets the next pass look again.
The details page navigates by an explicit chain of up and down targets, which
arrow keys follow in preference to the geometry. This section was never added
to it, so pressing down off the collections row went straight to the insights
and achievements rows and the whole section was unreachable with a controller,
however plainly it was on screen. The controls added to it for cover searching
made the dead zone bigger.

The section now names its own way in and out and the page wires it to whatever
sits either side, in both directions, so it cannot be a one-way trip either.

It could not have been caught before. The section only exists when a metadata
service is behind it, and that was built only outside demo and test modes, so
every automated run rendered the details page without it. Demo and test modes
now get a throwaway one; with no credentials it stays inert and never reaches
the network. The controller navigation test walks through the section in both
directions, and fails if it is skipped again.

Also fixes an if that guarded only the first of two connects, so a metadata
service built without insights warned instead of connecting. The compiler had
been reporting it as misleading indentation.
Left off "NOT THIS GAME" jumped to the cover sidebar four hundred pixels up the
page, up off "SEARCH IGDB" reached the BACK button at the very top, and down
landed on the scroll view itself. The spatial search took any candidate in the
half plane over no candidate at all, so with nothing genuinely to the left it
picked whatever was least bad and focus flew across the page.

A candidate that overlaps the current control across the direction of travel is
a neighbour and wins. One off to the side is taken only when nothing overlaps,
only for up and down, since columns rarely line up but left and right crossing
into another row is never what is meant. Otherwise focus stays where it is. An
ancestor is no longer a candidate either: the details page keeps its content in
a Flickable that takes focus and sat below and right of everything inside it.

The section's own controls now name their neighbours in all four directions, so
the order through it is fixed rather than inferred from where the layout
happened to put things.

Both modes are covered a control and a direction at a time, including where
focus must not move. Couch mode reaches the field beside a button, since there
the on screen keyboard opens on it; the desktop leaves fields to the mouse. The
test waits for the rows to stack rather than for a fixed delay, which passed
alone and failed when the suite ran everything at once.
…ty a field

The on screen keyboard only ever appeared in couch mode: both entry points began
by returning unless couch mode was on. Reaching a field with a pad on a desktop
therefore left no way to type into it. It now follows whether the controller is
the thing being used, so a pad plugged in for gaming does not make a keyboard
appear on a mouse click, and a pad used to reach a field does get one. Real
input from the window system arrives spontaneously and the events sent on the
controller's behalf do not, which is what tells the two apart.

Text fields joined the arrow order on couch mode alone for the same reason, so
a field could not be reached on a desktop even once there was a keyboard to
offer. They now follow the controller as well.

Fields also gained a clear button, reachable with a pad as well as the mouse.
It is declared inside the field it belongs to rather than wrapping it: every
field here has its own layout, bindings and key handling, and replacing twenty
one working fields to add one button would have been a poor trade. It sits
inside the field's own rectangle, where geometry cannot find it, so each field
points right at it and from there right carries on as before. It follows its
field into the arrow order rather than joining on its own, since a button
arrows could reach whose field they could not would be a stop with nothing
either side of it.

An earlier claim here was wrong and worth recording: eleven fields looked like
they had no keyboard because the search was for one of the two mechanisms in
use. All of them had one. What was missing was the desktop, not the wiring.
…ge in a narrow window

The keyboard still did not appear. Two of the fields asked a shared function
whether one was needed and the other seventeen tested couch mode inline, so
changing the rule reached two fields and missed the rest. There is one answer
now and every field reads it.

The flag that says the controller is what is being used follows the signals
rather than the places that raise them, since main.cpp emits them too to stand
in for a controller, and those count the same.

The details direction test was passing while doing nothing. Its mode was not in
the isolated set, so with Omakade already running it handed the arguments to
that instance and exited cleanly. It now runs on its own like every other test
mode, and it presses the controller's primary button on a field and fails if no
keyboard opens.

A pragma Singleton file is only a singleton when CMake says so, before the
module is declared. Without that the type resolved to nothing, every binding on
it evaluated to undefined, and couch text entry stopped working everywhere.

Below about four hundred and forty pixels the details page was cut off down the
right. A GridLayout does not shrink a child under the width of its own label:
it overflows and the scroll view clips it. The status buttons, the game actions
and the fixed label column all get a single column tier before that happens.
@btsouth btsouth changed the title 1.7.0: console libraries, redesigned settings, and the library organization work Release Omakade 1.7.0 Sep 7, 2026
@btsouth
btsouth merged commit a109965 into main Sep 7, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant