Skip to content

fix(flet pack): Windows taskbar identity of packaged apps - #6793

Open
ndonkoHenri wants to merge 11 commits into
mainfrom
fix/windows-pack-taskbar-identity
Open

fix(flet pack): Windows taskbar identity of packaged apps#6793
ndonkoHenri wants to merge 11 commits into
mainfrom
fix/windows-pack-taskbar-identity

Conversation

@ndonkoHenri

@ndonkoHenri ndonkoHenri commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Fixes the long-standing Windows issue where apps packaged with flet pack show a second taskbar identity named "Flet description", whose right-click entry and pin launch a blank Flet client window instead of the app. Reported in #6767 (and effectively what #6403 / #6421 attempted to fix).

Three independent defects stacked up:

  1. The AUMID fix from fix(flet pack): Windows taskbar shortcut pointing to wrong .exe #6403 never shipped. It lives in a PyInstaller runtime hook that PyInstaller discovers through rthooks.dat — a plain data file that was silently missing from the flet-cli wheel (no package-data declaration; PyInstaller ignores a missing rthooks.dat without any warning). The fix only ever worked from editable installs, which is how it passed verification back then. The wheel now ships it.

  2. A process-level AppUserModelID only fixes taskbar grouping. The taskbar name, icon, and pin target resolve through the shell's relaunch properties, and with none set they fall back to the window's process image: the cached flet.exe. (What looked like the raw-path AUMID working in earlier testing was residue in Windows' app-resolver cache from prior experiments — the same trap that made feat(flet-map): add image overlay support #6421 appear verified. Windows persists AUMID→relaunch associations across unpin and Explorer restarts, so any taskbar-identity claim must be tested from a never-used path.) flet_desktop now stamps System.AppUserModel.ID / RelaunchCommand / RelaunchDisplayNameResource / RelaunchIconResource on the client window right after launch — new flet_desktop/win_taskbar.py, pure ctypes, no new dependencies. The window is matched by its FLUTTER_RUNNER_WIN32_WINDOW class (not visibility), so hidden-start apps (AppView.FLET_APP_HIDDEN) are stamped immediately too, and the stamping worker holds a SYNCHRONIZE process handle so it stops when the client exits and a recycled PID can never be stamped. Values come from env vars set by the runtime hook (FLET_APP_RELAUNCH_*, documented as Windows-desktop-only), so packaging by other means (e.g. Nuitka) gets the same behavior by setting FLET_APP_USER_MODEL_ID manually.

  3. ~/.flet/client cache collisions. The cache was keyed by flavor+version only, and ensure_client_cached() returns any existing dir untouched — so the pack-patched client (custom icon/metadata) and the vanilla client of the same version silently shadowed each other. Bundled archives are now content-fingerprinted: flet pack writes a <archive>.sha256 sidecar (<hash> <size>; the size check detects a stale sidecar next to a replaced archive), and the cache dir is keyed by that fingerprint. To keep the cache bounded: archives are built deterministically (fixed timestamps, sorted entries) so unchanged rebuilds reuse the same fingerprint, superseded fingerprint dirs unused for 30 days are garbage-collected with a rename-before-delete guard (a running app's client can never be half-deleted), and concurrent first-run extractions no longer crash the losing process.

Verified end-to-end on a Windows 11 VM with packed apps run from fresh paths: single taskbar entry, correct right-click name and icon, pins that relaunch the packaged app — including paths with spaces, paths longer than 128 characters (the window property store accepts what SetCurrentProcessExplicitAppUserModelID documents as invalid), and FLET_APP_HIDDEN apps that show their window seconds after launch.

Fixes #6767

Test code

import flet as ft


def main(page: ft.Page):
    page.title = "TaskbarTest"
    page.add(ft.Text("Hello taskbar"))


ft.run(main)

How to test (reviewers)

Needs a Windows 10/11 machine. Two iron rules, both of which have produced false verdicts on this bug before:

  1. Run every packed exe from a fresh, never-used folder (C:\t1, C:\t2, …). Windows' app-resolver cache permanently remembers taskbar identity per AUMID — a folder that ever showed the correct identity keeps showing it forever, even for a broken build.
  2. Test from built wheels, not a repo checkout. Half of this fix is wheel packaging (rthooks.dat); pip install -e / running from source always has the file and false-passes.

Phase A — reproduce with released Flet (baseline):

Remove-Item -Recurse -Force $env:USERPROFILE\.flet\client -ErrorAction SilentlyContinue
python -m venv venv-a; venv-a\Scripts\Activate.ps1
pip install "flet[all]==0.86.5" pyinstaller
flet pack main.py --name TaskbarTest --product-name TaskbarTest --file-description "Taskbar Test App" --yes
mkdir C:\t1; copy dist\TaskbarTest.exe C:\t1\; C:\t1\TaskbarTest.exe

Expected (bug): right-clicking the taskbar icon shows "Flet description"; pin it, close the app, click the pin → a blank "Flet" window opens instead of the app.

Phase B — the fix: download the flet-cli-desktop-python-distribution artifact from this PR's CI run and unzip it, then:

Remove-Item -Recurse -Force $env:USERPROFILE\.flet\client
python -m venv venv-b; venv-b\Scripts\Activate.ps1
pip install "flet[all]==0.86.5" pyinstaller
pip install --force-reinstall --no-deps path\to\flet_cli-*.whl path\to\flet_desktop-*.whl
$env:FLET_CLIENT_URL = "https://github.com/flet-dev/flet/releases/download/v0.86.5/flet-windows.zip"
flet pack main.py --name TaskbarTest --product-name TaskbarTest --file-description "Taskbar Test App" --yes
mkdir C:\t2; copy dist\TaskbarTest.exe C:\t2\; C:\t2\TaskbarTest.exe

(FLET_CLIENT_URL is needed only in the packing venv: the PR-built flet-desktop wheel carries a dev version with no matching client release. This PR changes no client code, so the released v0.86.5 client is the correct pairing.)

Expected (fixed): pack output includes Including run-time hook 'pyi_rth_localhost_fletd.py' (absent in Phase A); right-click shows TaskbarTest; the pin relaunches the actual app; %USERPROFILE%\.flet\client contains a fingerprint-suffixed directory.

Optional extras: repeat Phase B's run for an AppView.FLET_APP_HIDDEN app that shows its window after a delay — pre-fix, hidden apps could never be stamped.

Additional details

  • An adversarial multi-agent review of the diff confirmed and led to fixes for six issues (hidden-window stamping, PID-reuse safety, stale fingerprint sidecars, fingerprint churn, cache growth, extraction races); seven other candidate findings were refuted on verification.
  • Not in scope: setting the same window properties natively in the client's main.cpp (would close the sub-second window between window creation and stamping — optional future polish); renaming the client's placeholder FileDescription ("Flet description") in Runner.rc; the bare client process lingering after its blank window is closed (pre-existing, unreachable in normal use once this fix is in).

Summary by Sourcery

Fix Windows taskbar identity and client-cache isolation for applications packaged with flet pack.

Bug Fixes:

  • Fix Windows apps packaged with flet pack so they use the packaged executable’s taskbar name, icon, grouping, and pin relaunch behavior instead of a duplicate generic Flet identity.
  • Prevent packaged and vanilla desktop clients with the same version from colliding in the shared client cache.

Enhancements:

  • Make packaged client archives deterministic and content-fingerprinted, with stale-cache cleanup and safe concurrent extraction.

Build:

  • Include PyInstaller runtime hook manifests and hook files in the flet-cli wheel so packaged applications receive the Windows taskbar configuration.

Documentation:

  • Document the Windows-only taskbar relaunch environment variables and how to configure them for alternative packaging tools.

Chores:

  • Remove the 1.0.0 release announcement link from the release-notes index.

- Ship rthooks.dat in the flet-cli wheel via package-data; without it
  PyInstaller silently never ran the Flet runtime hook, so the AUMID
  fix from #6403 was dead on arrival in wheel installs.
- Stamp System.AppUserModel ID/RelaunchCommand/DisplayName/Icon window
  properties on the desktop client window (new flet_desktop.win_taskbar,
  pure ctypes): a process-level AppUserModelID only fixes grouping, while
  the taskbar name, icon and pin target resolve through the window
  relaunch properties.
- Hashed-AUMID fallback for exe paths over 128 chars or with spaces.
- Key ~/.flet/client cache dirs by bundled-archive content fingerprint
  (flet pack writes <archive>.sha256 at build time) so pack-patched and
  vanilla clients of the same version stop shadowing each other.
- Document FLET_APP_RELAUNCH_* env vars.
The hashed-AUMID fallback for >128-char / spaced paths guarded against a
failure that was never actually isolated: in the AUMID-only test round,
every unseeded path failed identically (window relaunch props did not
exist yet), so the long path added no signal. With the window properties
stamped, a raw >128-char path with spaces was verified working on
Windows 11 - the window property store accepts it and the Relaunch*
properties drive name/pin resolution. Keep the single, tested code path.
- Match the client window by its FLUTTER_RUNNER_WIN32_WINDOW class instead
  of visibility: hidden-start apps (AppView.FLET_APP_HIDDEN) were never
  stamped because the worker waited for a visible window and gave up after
  30 s. Stamping works on hidden windows, so they now get their identity
  immediately.
- Hold a SYNCHRONIZE process handle while polling: stops when the client
  exits and prevents PID recycling from ever stamping a foreign window;
  the fixed 30 s deadline is gone.
- Fingerprint sidecar now records '<hash> <size>' and the size must match
  the archive, so a stale sidecar next to a replaced archive re-hashes
  instead of silently mapping to the old client's cache.
- Build client archives deterministically (fixed zip/tar/gzip timestamps,
  sorted entries) so identical content keeps the same fingerprint across
  rebuilds; GC superseded fingerprint dirs after 30 days of disuse with a
  rename-before-delete guard; survive concurrent first-run extraction
  races instead of crashing the loser.
Move the FLET_APP_RELAUNCH_* doc sections to their alphabetical position,
add type hints and Google-style Args/Returns sections to the new
functions, and use single backticks in docstrings.

[skip ci]
Changelog entries moved from Unreleased into the 1.0.0 section
introduced on main; env var docs re-slotted into main's fully
alphabetical ordering.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Drop verification narrative and investigation history; keep symptom,
cause, and behavior.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 28, 2026

Copy link
Copy Markdown

Deploying flet-website-v2 with  Cloudflare Pages  Cloudflare Pages

Latest commit: e302817
Status:🚫  Build failed.

View logs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Windows taskbar/pinning identity for apps packaged with flet pack by ensuring the PyInstaller runtime hook is shipped in the flet-cli wheel, stamping Windows relaunch properties on the desktop client window, and preventing patched bundled clients from colliding in the shared ~/.flet/client cache.

Changes:

  • Ship PyInstaller runtime hook manifests/files in the flet-cli wheel and extend the runtime hook to set relaunch env vars on Windows.
  • Add Windows window-property stamping (System.AppUserModel.*) in flet-desktop and invoke it after launching the client.
  • Make bundled client archives deterministic + fingerprinted, key cache dirs by fingerprint, add stale-cache GC and extraction race handling.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
website/docs/reference/environment-variables.md Documents new Windows relaunch environment variables and clarifies FLET_APP_USER_MODEL_ID.
sdk/python/packages/flet-desktop/src/flet_desktop/win_taskbar.py New ctypes-based Windows taskbar relaunch-property stamping helper.
sdk/python/packages/flet-desktop/src/flet_desktop/init.py Fingerprinted client cache dirs, GC of stale fingerprint dirs, extraction race handling, and taskbar prop application after launch.
sdk/python/packages/flet-cli/src/flet_cli/commands/pack.py Deterministic client archive creation and writing <archive>.sha256 sidecars; preserve sidecar during macOS cleanup.
sdk/python/packages/flet-cli/src/flet_cli/__pyinstaller/utils.py Adds tar entry normalization helper for deterministic tars.
sdk/python/packages/flet-cli/src/flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py Sets Windows relaunch env vars alongside FLET_APP_USER_MODEL_ID.
sdk/python/packages/flet-cli/src/flet_cli/__pyinstaller/macos_utils.py Makes macOS tar creation deterministic (fixed gzip mtime + normalized tar entries).
sdk/python/packages/flet-cli/pyproject.toml Includes rthooks.dat and runtime hook files in wheel package data.
CHANGELOG.md Adds release notes for the Windows taskbar fix and the fingerprinted client-cache fix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread sdk/python/packages/flet-desktop/src/flet_desktop/win_taskbar.py
Comment thread sdk/python/packages/flet-desktop/src/flet_desktop/win_taskbar.py
Comment thread sdk/python/packages/flet-cli/src/flet_cli/commands/pack.py Outdated
Comment thread sdk/python/packages/flet-desktop/src/flet_desktop/__init__.py Outdated
- compress_flet_client_dir() streamed each file through zf.open(zi, 'w')
  instead of reading it whole into memory with writestr(); the switch to
  a custom ZipInfo (for deterministic timestamps) had lost the streaming
  the previous zf.write() call had.
- __get_archive_fingerprint() now requires the sidecar hash to be 64
  lowercase hex digits before trusting it, so a corrupted or hand-edited
  sidecar falls back to hashing instead of producing a cache directory
  name that could contain path separators - and that the fingerprint GC,
  which only recognizes hex suffixes, would never clean up.
- Note ctypes.OleDLL's automatic HRESULT checking in win_taskbar, which
  is why COM failures surface as OSError.
`/blog/flet-1-0` has no post behind it -- the blog declares
`introducing-flet-1-0-alpha` and `flet-1-0-beta`, and the stable announcement
is not written yet. Docusaurus fails the build on broken links, so this took
down the docs workflow and the Cloudflare deploy with it.

Every other entry links to a post that exists, so the line loses just the
announcement and keeps its changelog and breaking-changes links. Put it back
when the post lands.

Arrived in #6693 and is unrelated to the Linux icon work; it rides on this
branch only because that is where it was hit.

Verified with a full `crocodocs:generate && docusaurus build`: green, and no
other reference to that slug anywhere under website/.
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.

2 participants