Release Omakade 1.7.0 - #36
Merged
Merged
Conversation
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.
|
Important Review skippedToo 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: ⛔ Files ignored due to path filters (60)
📒 Files selected for processing (131)
You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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: