feat(preview-poc): Windows Explorer thumbnail provider (#935)#936
Closed
codemonkey85 wants to merge 11 commits into
Closed
feat(preview-poc): Windows Explorer thumbnail provider (#935)#936codemonkey85 wants to merge 11 commits into
codemonkey85 wants to merge 11 commits into
Conversation
Adds an IThumbnailProvider alongside the preview handler — Explorer/Finder-style icon
thumbnails for PKHeX files — reusing the same native-shim + worker split and bundled sprites.
- Pkmds.Preview.FileSprite: shared "representative sprite for a file" (entity -> species,
save -> lead party Pokemon, gift -> species/item, fallback), the thumbnail counterpart to
HtmlRenderer.RenderFile.
- Worker --thumbnail mode: draws the bundled sprite to a square PNG (System.Drawing), offline.
- PkmdsPreviewShim now hosts a second COM class (IThumbnailProvider, separate CLSID
{b98dbf6e-...}) that spawns the worker in --thumbnail mode and decodes its PNG into an alpha
HBITMAP via WIC. DllGetClassObject dispatches both CLSIDs (templated class factory).
- register.cs registers the thumbnail CLSID + the IThumbnailProvider IID {e357fccd-...} per
extension (same shim DLL).
Verified the worker renders correct thumbnails (.pk5 -> species, save -> lead party mon).
macOS/iOS QLThumbnailProvider extensions are deferred (need a Mac) — tracked in #935.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tasks
install.ps1 wiped+republished dist in step 2 but only stopped prevhost/Explorer in step 5, so a live prevhost/dllhost/worker kept dist DLLs locked (Access denied on dist\Accessibility.dll). Stop prevhost/dllhost/PkmdsPreviewWorker up front, before the clean+publish. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The thumbnail cache (dllhost) initializes IThumbnailProvider handlers via IInitializeWithStream and ignores a file-only handler, so thumbnails came up blank. Implement IInitializeWithStream as the primary init (keep IInitializeWithFile as a fallback); GetThumbnail spills the stream to a temp file for the worker. A stream has no extension, so the worker content-detects entities/saves (gifts, which need the extension, fall back to the placeholder in the stream path). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The thumbnail cache inits via IInitializeWithStream (no path), so the worker got a temp file with no extension and couldn't identify mystery gifts (which are detected by extension) — .wc6/.pgf/etc. fell back to the placeholder. Read the original name from IStream::Stat (pwcsName) and reuse its extension for the temp file, so gift thumbnails (e.g. a .wc6 Bulbasaur) resolve correctly. Falls back to .bin (content detection) if the stream has no name. Note: .wc3 (WonderCard3) only exposes Type, not the gifted species/item, so it still shows the placeholder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WonderCard3 only exposes Type (Pokémon / Item / Link), not the gifted species/item, so .wc3 thumbnails now return the item placeholder for Item-type cards and the Pokémon placeholder otherwise (previously always the Pokémon placeholder). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Save files now thumbnail as a compact "trainer card" (game version code, OT name, TID/SID, playtime) instead of the lead party Pokémon's sprite — identifying both the game and the specific playthrough at a glance. - SaveCard.Render draws the card with System.Drawing (no WebView2) so a folder of saves thumbnails quickly. - Per-version accent colour; the two groups the save format can't disambiguate (RB = Red/Blue, GS = Gold/Silver) use a diagonal gradient border and a horizontal gradient on the code so each letter lands on its game's colour (R red, B blue; G gold, S silver). - Thumbnail.Render branches: SaveUtil.TryGetSaveFile -> SaveCard, else the representative bundled sprite via FileSprite (unchanged for entities/gifts). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a `com.apple.quicklook.thumbnail` app extension (`PkmdsQuickLookThumbnail`) alongside the existing preview extension, completing the macOS side of #935. **What's new:** - `Exports.cs` — `pkmds_get_sprite_path` export wraps `FileSprite.GetRelativeSpritePath` so Swift can ask for a sprite path without going through HTML rendering. Also adds `playedHours`/`playedMinutes` to the save JSON for the trainer-card layout. - `ThumbnailProvider.swift` — `QLThumbnailProvider` that mirrors the Windows `Thumbnail.cs` + `SaveCard.cs` split: - **Saves** → compact trainer-card (version code in game accent colour, OT, TID/SID, playtime), drawn with AppKit (`NSBezierPath`, `NSAttributedString`) into the `QLThumbnailReply(contextSize:drawing:)` CGContext. - **Entities + wonder cards** → bundled species/item sprite scaled to the requested size. - **RB/GS ambiguous groups** → per-character colour interpolation on the version code (`"R"` red, `"B"` blue; `"G"` gold, `"S"` silver). Border remains single-colour (gradient border is noted as a cosmetic enhancement in the README). - `project.yml` — new `PkmdsQuickLookThumbnail` target (type: app-extension, extension point `com.apple.quicklook.thumbnail`), embedded in `PkmdsHost`. - `build-extension.sh` — after `xcodebuild`, copies the three sprite categories (`a/`, `ai/`, `bi/`) from `Pkmds.Rcl/wwwroot/sprites/` into the thumbnail appex `Contents/Resources/sprites/`, re-signs both extensions, and registers the thumbnail extension with `pluginkit`. Smoke tests both preview and thumbnail via `qlmanage`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Owner
Author
macOS thumbnail provider — done ✅Just pushed the macOS New files:
Updated files:
To verify locally (on a Mac with the preview extension already working): cd tools/macos-quicklook-poc
./build-extension.sh
# Then:
qlmanage -t -s 256 -o /tmp ../../TestFiles/Lucario_B06DDFAD.pk5
qlmanage -t -s 256 -o /tmp ../../TestFiles/Test-Save-Scarlet.savKnown limitations (noted in README):
|
ThumbnailsAgent refuses to register a thumbnail extension for any UTI that declares a prohibited filename extension (.sav, .dat, .fla). Because com.bondcodes.pkmds.save-file previously included these extensions, the extension was silently rejected at startup and no thumbnails appeared for .gci/.dsv/.srm files. Fix: remove .sav, .dat, and .fla from the save-file UTI declaration in project.yml, keeping only .gci, .dsv, and .srm. (.sav files require a Spotlight MDImporter to assign the save-file UTI; that is future work.) Also update build-extension.sh to: - Proactively unregister Xcode DerivedData and iOS POC builds from the LS database before installing, preventing stale prohibited-extension registrations from resurfacing after a clean build. - Kill ThumbnailsAgent after deploy so it restarts with the clean LS database (it caches extension-to-UTI mappings at startup). - Add timeout to qlmanage -p so the smoke test doesn't block forever. - Use a .gci fixture for the save thumbnail smoke test, since qlmanage cannot dispatch to sandboxed extensions via prohibited extensions. Adds TestFiles/Test-Save-Moon.gci (copy of moon.sav renamed .gci) as the non-prohibited save fixture for smoke tests and manual verification. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 tasks
- opaqueSourceRect(): scans sprite pixels via a CG-owned bitmap context (data:nil / byteOrder32Little / premultipliedFirst) to find the tight non-transparent bounding box, then returns it in NSImage (bottom-left) coordinates for use as the `from:` source rect in NSImage.draw() - Sprite load and opaque-rect computation are now done on our controlled background queue before the QLThumbnailReply drawing closure, avoiding a deadlock with AppKit's image-cache lock on the system-managed closure thread - drawSprite() is removed; its logic is inlined directly into provideThumbnail - Sprites now fill 90% of the thumbnail canvas based on visible content only - build-extension.sh: fix `timeout` (GNU coreutils, not on macOS) for both qlmanage -p and qlmanage -t smoke-test calls using a background-kill workaround; add 15s cap to qlmanage -t to prevent build from blocking Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
QLThumbnailReply's CGContext is pixel-sized (2× point size on Retina), but layout was using request.maximumSize in points, causing sprites to render at ~45% fill instead of 90%. Use ctx.width/height for all layout inside the drawing closure. opaqueSourceRect was also computing the NSImage y origin as minRow*sy, treating the raster-top buffer row as NSImage bottom-left. The correct formula is (ph - maxRow - 1)*sy. Also removes leftover debug logging and PNG/text file writes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Owner
Author
|
Superseded by #940, which contains all commits from this branch plus the subsequent macOS QL fixes (.sav/.dat/.fla split UTI, sprite/thumbnail improvements). Closing to keep the queue tidy. |
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.
Adds thumbnail providers for both Windows Explorer and macOS Finder — icon thumbnails for PKHeX files. Part of #935.
Windows (
IThumbnailProvider)Reuses the same native-shim + worker split and bundled sprites as the preview handler from #934:
Pkmds.Preview.FileSprite(shared) — picks the representative sprite for a file: entity → its species, save → lead party Pokémon (then first box slot, then placeholder), mystery gift → species/item.--thumbnail <out.png> <size> <file>— draws the sprite onto a square transparent PNG (System.Drawing), scaled/centered.PkmdsPreviewShimhosts a second COM class (IThumbnailProvider, CLSID{b98dbf6e-…}) alongside the preview handler.GetThumbnail(cx)spawns the worker, waits, and decodes the PNG into an alphaHBITMAPvia WIC.register.csregisters the thumbnail CLSID per extension (same shim DLL as the preview handler).macOS (
QLThumbnailProvider)ThumbnailProvider.swift— Quick Look extension that calls into the AOT-compiledPkmdsNative.dylibto resolve the sprite path or describe the save file..pk*,.pb*,.bk4, etc.) render the species sprite, trimmed of transparent padding viaopaqueSourceRectand scaled to fill 90% of the context..gci,.dsv,.srm) render a trainer card showing game version (accent-coloured), OT name, TID/SID, and playtime.build-extension.sh.QLThumbnailReplyclosure usesctx.width/height(pixel dimensions) rather thanrequest.maximumSize(points), doubling the effective fill on Retina displays.Known limitation
.savfiles are a prohibited extension on macOS — the system will not dispatch Quick Look or thumbnail requests to sandboxed extensions for them..gci,.dsv, and.srmwork automatically. A Spotlight MDImporter to assign thecom.bondcodes.pkmds.save-fileUTI to.savfiles is tracked separately in #938.Verified
.gci/.dsv/.srmin Finder..pk*and.gci/.dsv/.srmfiles.install.ps1+ thumbnail-cache refresh).🤖 Generated with Claude Code