Skip to content

Add favicon support for Windows native#5841

Open
DearRude wants to merge 4 commits intozauberzeug:mainfrom
DearRude:fix/windows-native-icon
Open

Add favicon support for Windows native#5841
DearRude wants to merge 4 commits intozauberzeug:mainfrom
DearRude:fix/windows-native-icon

Conversation

@DearRude
Copy link
Copy Markdown

Motivation

pywebview's icon parameter works only on Linux (GTK/Qt). On Windows and macOS, the window icon must typically be set at packaging time (via PyInstaller, Nuitka, etc.), so native NiceGUI windows on Windows show the default Python/executable icon instead of the app icon.

Related issues and discussions:

  • NiceGUI Discussion #1745 – Main discussion requesting custom window icon support for native mode.
  • NiceGUI Discussion #1005 Feature request for changing the default app icon; packaging (PyInstaller/Nuitka) discussed as the usual approach.
  • pywebview #1411 pywebview states icon must be specified when freezing on Windows/macOS; community workaround used LoadImageW + WM_SETICON to set the icon from a file path.

This PR adds support for using the existing favicon parameter from ui.run() as the native window icon on Windows when running in native mode. Users can pass a file path (e.g. favicon='icon.ico') and it will be applied to the taskbar and title bar at runtime, without requiring packaging changes.


Implementation

  • Reuse of favicon: No new API. When native=True and favicon is a file path, it is passed to the native process.
  • ui_run.py: Resolves favicon to an absolute path when it's a file and passes it to native_module.activate().
  • native_mode.py: activate() and _open_window() accept a favicon argument. On Windows, a window.events.shown callback finds the window by title and sets the icon.
  • window_icon.py (new): Windows-only helpers using ctypes:
    • find_window_by_title() – uses EnumWindows to get the HWND
    • set_window_icon_windows() – uses LoadImageW and WM_SETICON to set the icon
  • Scope: Windows only. Linux behavior unchanged (pywebview already supports icon there).
  • Trade-off: Icon is set after the window is shown, so there may be a brief moment where the default icon appears before the custom one.

Progress

  • I chose a meaningful title that completes the sentence: "If applied, this PR will..."
  • The implementation is complete.
  • If this PR addresses a security issue, it has been coordinated via the security advisory process.
  • Pytests have been added (or are not necessary).
  • Documentation has been added (or is not necessary).

@DearRude DearRude force-pushed the fix/windows-native-icon branch from 723057f to 616141c Compare February 26, 2026 13:28
### Implementation

- Updated the `activate` function to accept a `favicon` parameter.
- Modified the `_open_window` function to set the native window icon on Windows if a valid favicon file is provided.
- Updated documentation to reflect the new favicon usage for native window icons.

This change improves the user experience by allowing custom icons for native windows on Windows platforms.
@DearRude DearRude force-pushed the fix/windows-native-icon branch from 616141c to 3d42cdb Compare February 26, 2026 13:30
@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Feb 26, 2026

@DearRude Wow. I nominate this for "top 3 first-time PR". I'm grabbing my Windows laptop for this one.

@falkoschindler falkoschindler added feature Type/scope: New or intentionally changed behavior review Status: PR is open and needs review labels Feb 26, 2026
@falkoschindler falkoschindler added this to the 3.10 milestone Feb 26, 2026
@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Feb 26, 2026

@DearRude Works for titlebar, not for taskbar though.

{F3131DEA-6344-4B11-B326-822D335190BA}

Still, pretty good. Is taskbar functionality intended?

The previous favicon support for native mode only set the window icon
(title bar and Alt+Tab). This extends it to also set the taskbar icon
using Windows IPropertyStore COM interface, which is required for
proper taskbar icon display on Windows 7+.

The implementation:
- Adds shell32 access and COM GUID/PROPERTYKEY structures
- Sets AppUserModelID to give the window a unique identity
- Sets RelaunchIconResource to specify the taskbar icon path
- Uses SHGetPropertyStoreForWindow to access the window's property store

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Feb 26, 2026

{BBABF106-2FA4-4DB1-A48A-FA7792F030CC}

@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Feb 26, 2026

@DearRude Can you fix the pipeline? mypy and pylint isn't very happy...

@DearRude
Copy link
Copy Markdown
Author

@evnchn Done. Let me know if I should squash the commits.

@falkoschindler
Copy link
Copy Markdown
Contributor

Thanks, @DearRude!
No need to squash now. We'll do that when we merge on main.

@DearRude DearRude force-pushed the fix/windows-native-icon branch from 3bb748a to 209beb1 Compare February 27, 2026 08:10
@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Feb 27, 2026

@DearRude I am guessing you came from GitLab which encourages force pushes? Because we don't really do force pushes for GitHub because the GitHub UI naturally encourages against it. Please don't do it unless it's necessary. Thanks.

Example: GitLab you rebase a branch for latest main; GitHub you merge.

@DearRude
Copy link
Copy Markdown
Author

Nice. Thank you for specifying that.

@DearRude
Copy link
Copy Markdown
Author

@falkoschindler @evnchn Anything I have to do for now or do you think it is ready to be merged?

@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Feb 27, 2026

@DearRude since this is schedule for 3.10, it is likely that we will look at this after at least 2 weeks from now.

I'd consider this PR done for now. Please be on the watch out when we come around to review it. Meanwhile you can consider to work on items on the NiceGUI wishlist if you feel like it. MCP integration is a big one but not on the list just now.

@evnchn evnchn requested a review from falkoschindler March 28, 2026 16:05
@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Mar 28, 2026

@falkoschindler I think it's your turn. Just find me if you want to test on Windows hardware.

@falkoschindler
Copy link
Copy Markdown
Contributor

Thanks for the contribution, @DearRude! This is a nice addition for Windows users.

I'm not very familiar with the Windows side of things, so I used Claude to help review the code. Here are some points that came up — it would be great if you could have a look:

  1. Silent error swallowing in set_window_property_store (window_icon.py:128) — The bare except Exception: return False around the entire function body hides any failure with no logging. Catching arbitrary exceptions is generally bad practice — tighter try-blocks with more specific exception types would be better, if possible. At minimum, could you log the exception? Otherwise debugging taskbar icon issues will be very hard.

  2. Redundant sys.platform == 'win32' checks (window_icon.py:34,54,67) — The caller in _open_window (line 57) already guards on win32. The three helper functions each re-check internally. Could we check once at the entry point and let the helpers assume Windows?

  3. Duplicate Path(icon_path).resolve() — The path gets resolved in ui_run.py:243, then again in _open_window at line 65, and a third time in set_window_icon_windows at line 56. Once should be enough.

  4. Non-file favicon passed through unnecessarily (ui_run.py:243) — When favicon is a URL or data URI, the raw value gets passed all the way to _open_window where helpers.is_file() catches it. Passing None for the non-file case would be cleaner.

  5. Docs could clarify supported formats — The doc note recommends .ico, but it might help to mention that LoadImageW with IMAGE_ICON only supports .ico (and .cur/.ani). A user passing .png would get a silent failure.

  6. Tests — The tests check function signatures and platform guard early returns, but none of the actual Win32 logic. Since the real behavior can only be verified with an actual native window on Windows, I'd suggest removing them rather than keeping tests that give a false sense of coverage.

  7. Icon handle not freed (window_icon.py:57-62) — LoadImageW returns an HICON that is never destroyed with DestroyIcon. Probably negligible for a single-window app, but wanted to flag it.

  8. app_id not sanitized (native_mode.py:70) — f'nicegui.{title.replace(" ", "_")}' could exceed the Windows AppUserModelID limit of 128 characters with a very long title. Edge case, but worth considering.

@evnchn evnchn added in progress Status: Someone is working on it and removed review Status: PR is open and needs review labels Mar 29, 2026
@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Apr 3, 2026

@DearRude @falkoschindler I got some fixes at https://github.com/evnchn/nicegui/tree/evnchn/windows-native-icon but I don't have the time to review them yet.

@falkoschindler falkoschindler modified the milestones: 3.10, 3.11 Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Type/scope: New or intentionally changed behavior in progress Status: Someone is working on it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants