trackerkeeper 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. trackerkeeper/__init__.py +55 -0
  2. trackerkeeper/__main__.py +3 -0
  3. trackerkeeper/_version.py +24 -0
  4. trackerkeeper/app.py +445 -0
  5. trackerkeeper/assets/__init__.py +2 -0
  6. trackerkeeper/assets/trackerkeeper.svg +12 -0
  7. trackerkeeper/async_io.py +299 -0
  8. trackerkeeper/autostart/__init__.py +52 -0
  9. trackerkeeper/autostart/_linux.py +129 -0
  10. trackerkeeper/autostart/_macos.py +163 -0
  11. trackerkeeper/autostart/_msix.py +181 -0
  12. trackerkeeper/autostart/_unsupported.py +22 -0
  13. trackerkeeper/autostart/_windows.py +96 -0
  14. trackerkeeper/bake.py +217 -0
  15. trackerkeeper/blur/__init__.py +212 -0
  16. trackerkeeper/blur/_dwm.py +348 -0
  17. trackerkeeper/blur/_faux_frost.py +143 -0
  18. trackerkeeper/blur/_kwin.py +478 -0
  19. trackerkeeper/blur/_macos.py +356 -0
  20. trackerkeeper/blur/_unsupported.py +39 -0
  21. trackerkeeper/boot_timing.py +75 -0
  22. trackerkeeper/breadboard.py +1554 -0
  23. trackerkeeper/bus.py +110 -0
  24. trackerkeeper/catalog.py +176 -0
  25. trackerkeeper/color_tokens.py +697 -0
  26. trackerkeeper/credentials.py +471 -0
  27. trackerkeeper/custom_tooltip.py +254 -0
  28. trackerkeeper/dashboard.py +704 -0
  29. trackerkeeper/deliver.py +442 -0
  30. trackerkeeper/design_tokens.py +415 -0
  31. trackerkeeper/diagnostics.py +168 -0
  32. trackerkeeper/drag_repaint/__init__.py +68 -0
  33. trackerkeeper/drag_repaint/_kwin.py +192 -0
  34. trackerkeeper/drag_repaint/_unsupported.py +41 -0
  35. trackerkeeper/drag_repaint/effect/dragrepaint/contents/code/main.js +86 -0
  36. trackerkeeper/drag_repaint/effect/dragrepaint/metadata.json +17 -0
  37. trackerkeeper/frosted_dialog.py +377 -0
  38. trackerkeeper/i18n/__init__.py +101 -0
  39. trackerkeeper/i18n/fmt.py +137 -0
  40. trackerkeeper/icon_button.py +137 -0
  41. trackerkeeper/icons.py +532 -0
  42. trackerkeeper/identity.py +135 -0
  43. trackerkeeper/item_dialog.py +185 -0
  44. trackerkeeper/kde_titlebar.py +196 -0
  45. trackerkeeper/keyboard_focus.py +353 -0
  46. trackerkeeper/log.py +112 -0
  47. trackerkeeper/macos_menubar.py +282 -0
  48. trackerkeeper/macos_window.py +172 -0
  49. trackerkeeper/metadata.py +121 -0
  50. trackerkeeper/noborder/__init__.py +61 -0
  51. trackerkeeper/noborder/_kwin.py +247 -0
  52. trackerkeeper/noborder/_unsupported.py +42 -0
  53. trackerkeeper/notifications/__init__.py +84 -0
  54. trackerkeeper/notifications/_linux.py +68 -0
  55. trackerkeeper/notifications/_macos.py +159 -0
  56. trackerkeeper/notifications/_unsupported.py +22 -0
  57. trackerkeeper/notifications/_windows.py +115 -0
  58. trackerkeeper/platform_compat.py +149 -0
  59. trackerkeeper/power/__init__.py +84 -0
  60. trackerkeeper/power/_linux.py +79 -0
  61. trackerkeeper/power/_unsupported.py +18 -0
  62. trackerkeeper/power/_windows.py +52 -0
  63. trackerkeeper/rig.py +338 -0
  64. trackerkeeper/scaffold.py +310 -0
  65. trackerkeeper/selector.py +529 -0
  66. trackerkeeper/settings.py +226 -0
  67. trackerkeeper/settings_dialog.py +307 -0
  68. trackerkeeper/settings_migration.py +101 -0
  69. trackerkeeper/single_instance.py +330 -0
  70. trackerkeeper/smooth_scroll.py +155 -0
  71. trackerkeeper/sources.py +318 -0
  72. trackerkeeper/system_accent.py +363 -0
  73. trackerkeeper/terminal.py +423 -0
  74. trackerkeeper/test_bridge.py +514 -0
  75. trackerkeeper/theme.py +725 -0
  76. trackerkeeper/top_bar.py +154 -0
  77. trackerkeeper/tray.py +184 -0
  78. trackerkeeper/ui_helpers.py +2294 -0
  79. trackerkeeper/update_chip.py +119 -0
  80. trackerkeeper/updates.py +216 -0
  81. trackerkeeper/win_frameless.py +364 -0
  82. trackerkeeper/window.py +609 -0
  83. trackerkeeper/windows_shortcut.py +355 -0
  84. trackerkeeper-0.1.0.dist-info/METADATA +144 -0
  85. trackerkeeper-0.1.0.dist-info/RECORD +89 -0
  86. trackerkeeper-0.1.0.dist-info/WHEEL +5 -0
  87. trackerkeeper-0.1.0.dist-info/entry_points.txt +8 -0
  88. trackerkeeper-0.1.0.dist-info/licenses/LICENSE +338 -0
  89. trackerkeeper-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,212 @@
1
+ """Window "blur behind" — the frosted-glass effect for translucent
2
+ surfaces. This is what visually separates Frosted mode (blurred glass)
3
+ from Transparent mode (clear glass).
4
+
5
+ Public API:
6
+ is_supported() -> bool # backend can *request* blur
7
+ apply(widget, enabled) -> bool # enable/disable blur behind widget
8
+ status() -> BlurStatus # is a real backdrop VERIFIED behind us?
9
+
10
+ The status() call is the one that fixes the "frosted renders see-through"
11
+ class of bug: `apply()` is fire-and-forget (KWin gives no success signal),
12
+ so a frosted body painted at ~67% opacity goes transparent-broken wherever
13
+ blur silently no-ops (Blur effect off, missing plugin, no compositing, a
14
+ non-KDE desktop, Windows < 11). `status()` probes whether a real backdrop
15
+ is actually present so the theme layer can pick the body alpha — full glass
16
+ when blur landed, a near-opaque "frosted panel" fallback when it didn't.
17
+ See trackerkeeper/theme.body_color_for() and docs/research/portable_blur.md.
18
+
19
+ Backend (Linux): KDE's KWindowSystem — `KWindowEffects::enableBlurBehind`
20
+ to request blur and `KWindowEffects::isEffectAvailable(BlurBehind)` to
21
+ verify it, both via ctypes (no PySide6 binding exists). KWindowSystem
22
+ speaks `ext-background-effect-v1` where the compositor offers it and falls
23
+ back to the legacy `org_kde_kwin_blur` — so this covers KWin, and also niri
24
+ / COSMIC where KWindowSystem is installed.
25
+
26
+ Everywhere else (Windows, macOS, or a Linux box without KWindowSystem, or a
27
+ compositor with no blur protocol — Hyprland/Wayfire/sway/GNOME): the
28
+ backend reports UNSUPPORTED and the window paints a near-opaque body. On
29
+ compositors that blur via user config rather than a protocol (Hyprland,
30
+ Wayfire, SwayFX), the user can target trackerkeeper in their own window rules:
31
+ our Wayland app_id is the stable string "trackerkeeper" (set via
32
+ setDesktopFileName).
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import enum
38
+ import os
39
+
40
+
41
+ class BlurStatus(enum.Enum):
42
+ """Whether a real compositor/OS backdrop is verified to sit behind the
43
+ window — the signal the theme layer maps to a body opacity.
44
+
45
+ ACTIVE — blur issued AND positive evidence it landed;
46
+ frosted surfaces ride it at full glass alpha.
47
+ REQUESTED_UNVERIFIABLE — blur was issued but we can't confirm it took
48
+ (no success feedback, or a known-flaky path
49
+ like KDE X11); paint a conservative near-opaque
50
+ body so we never gamble on a see-through window.
51
+ UNSUPPORTED — the backend can't request blur here at all;
52
+ paint the near-opaque fallback body.
53
+ DISABLED — the active theme doesn't ask for blur (Solid /
54
+ Transparent); not an error. Bodies keep their
55
+ own opaque/translucent alpha unchanged.
56
+ """
57
+
58
+ ACTIVE = "active"
59
+ REQUESTED_UNVERIFIABLE = "unverifiable"
60
+ UNSUPPORTED = "unsupported"
61
+ DISABLED = "disabled"
62
+
63
+
64
+ from trackerkeeper.platform_compat import IS_LINUX, IS_MACOS, IS_WINDOWS
65
+
66
+ if IS_LINUX:
67
+ from trackerkeeper.blur import _kwin as _backend
68
+ elif IS_WINDOWS: # pragma: no cover - exercised on Windows
69
+ from trackerkeeper.blur import _dwm as _backend
70
+ elif IS_MACOS: # pragma: no cover - exercised on macOS
71
+ from trackerkeeper.blur import _macos as _backend
72
+ else: # pragma: no cover
73
+ from trackerkeeper.blur import _unsupported as _backend
74
+
75
+
76
+ # Process-wide cache. Blur availability is a per-session compositor/OS fact,
77
+ # not a per-window one (KWindowEffects::isEffectAvailable takes no window),
78
+ # so we probe once and reuse. force=True re-probes after a compositing /
79
+ # Blur-effect toggle.
80
+ _status_cache: BlurStatus | None = None
81
+
82
+ # Debug override: TRACKERKEEPER_BLUR_FORCE=active|unverifiable|unsupported pins the
83
+ # reported status, bypassing the probe. Lets you eyeball the near-opaque
84
+ # fallback body (TRACKERKEEPER_BLUR_FORCE=unsupported) without disabling the
85
+ # compositor's Blur effect, and the reverse on a box where blur is off.
86
+ # Same JT_* debug-switch family as TRACKERKEEPER_OPAQUE.
87
+ _FORCE = os.environ.get("TRACKERKEEPER_BLUR_FORCE", "").strip().lower()
88
+
89
+
90
+ def opaque_mode_active() -> bool:
91
+ """Dev diagnostic: fully-opaque chrome — no translucency, no blur — via the
92
+ ``TRACKERKEEPER_OPAQUE=1`` env switch. When on, :func:`status` reports UNSUPPORTED (so
93
+ frosted bodies + popups use their near-opaque fallback) and :func:`apply`
94
+ skips requesting compositor blur.
95
+
96
+ Env-only on purpose — there is NO user-facing setting. A frosted theme that
97
+ can't get real blur already falls back to a near-opaque body automatically
98
+ (status() → UNSUPPORTED/REQUESTED_UNVERIFIABLE), which covers the real user
99
+ need; the old Settings toggle additionally dropped WA_TranslucentBackground
100
+ and broke the window's rounded corners, so it was removed. TRACKERKEEPER_OPAQUE stays
101
+ for the screencast / streaming-flicker repro it was born for. Never raises."""
102
+ return os.environ.get("TRACKERKEEPER_OPAQUE") == "1"
103
+
104
+
105
+ def is_supported() -> bool:
106
+ """True if the backend can *request* blur (KWindowSystem present /
107
+ Windows 11 build). A True here doesn't guarantee the compositor will
108
+ actually blur — that's what status() checks."""
109
+ return _backend.is_supported()
110
+
111
+
112
+ def apply(
113
+ widget,
114
+ enabled: bool,
115
+ corner_radius: int = 0,
116
+ dark: bool | None = None,
117
+ elevated: bool = False,
118
+ ) -> bool:
119
+ """Enable (``enabled=True``) or remove (``False``) compositor blur
120
+ behind ``widget``'s window. ``widget`` is a QWidget; its QWindow must
121
+ already exist (call after ``show()``).
122
+
123
+ ``corner_radius``: when > 0 the blur region is shaped to a rounded
124
+ rectangle of that radius matching the widget's current size — pass this
125
+ for frameless rounded windows (mini player, settings dialog) so the blur
126
+ doesn't bleed into the transparent corners. ``0`` blurs the whole window
127
+ rectangle — correct for server-side-decorated windows.
128
+
129
+ ``dark``: dark vs light variant for backends whose backdrop has one
130
+ (Windows Mica's immersive dark/light tint). ``None`` (the default)
131
+ resolves it from the active theme, so every call site gets the right
132
+ variant for free; the KWin / macOS / unsupported backends ignore it.
133
+
134
+ ``elevated``: True for elevated popups (menus / dropdowns / volume
135
+ popups / tooltips) that paint their own status-aware QSS frost fill.
136
+ Backends whose blur material carries a built-in tint (Windows
137
+ Acrylic) drop it to near-zero so the popup isn't double-veiled —
138
+ KWin's blur is untinted, so on Linux this is a no-op and the QSS
139
+ fill stays the single tint source everywhere.
140
+
141
+ Returns True if the request was issued, False on any unsupported /
142
+ not-yet-shown case. The return is best-effort ("issued", not "blurred")
143
+ — use status() to learn whether blur actually landed. Never raises."""
144
+ if enabled and opaque_mode_active():
145
+ # User forced opaque chrome — never request blur (and remove any
146
+ # already-applied blur on this widget).
147
+ enabled = False
148
+ # "Never raises" is part of the contract (blur is progressive enhancement)
149
+ # — theme resolution + the backend call are best-effort, so swallow errors.
150
+ try:
151
+ if dark is None:
152
+ from trackerkeeper.theme import get_active_theme
153
+
154
+ dark = get_active_theme().dark
155
+ # On macOS this dispatches to the live NSVisualEffectView vibrancy
156
+ # backend (trackerkeeper/blur/_macos.py): it installs a "behind window" effect
157
+ # view as a SIBLING ordered strictly below Qt's content view, so the
158
+ # system frost shows through the translucent body — the mac-native
159
+ # equivalent of KWin's blur-behind. corner_radius/dark/elevated all
160
+ # forward through, same as the KWin/DWM backends.
161
+ return _backend.apply(widget, enabled, corner_radius, dark, elevated)
162
+ except Exception:
163
+ return False
164
+
165
+
166
+ def status(*, force: bool = False) -> BlurStatus:
167
+ """Whether a real backdrop is VERIFIED behind our windows on this
168
+ machine/session — the value the theme layer uses to choose body opacity.
169
+
170
+ Computed once and cached (the answer is a per-session compositor/OS
171
+ fact). Pass ``force=True`` to re-probe, e.g. after the window is mapped
172
+ or a compositing/Blur-effect toggle. Returns ACTIVE /
173
+ REQUESTED_UNVERIFIABLE / UNSUPPORTED — never DISABLED (that's the theme's
174
+ call, not the machine's). Never raises; any failure resolves to the
175
+ conservative REQUESTED_UNVERIFIABLE so a frosted body stays near-opaque
176
+ rather than risking see-through."""
177
+ global _status_cache
178
+ if opaque_mode_active():
179
+ # User forced opaque chrome — report no backdrop so frosted bodies +
180
+ # popups fall back to their near-opaque alpha.
181
+ return BlurStatus.UNSUPPORTED
182
+ if _FORCE:
183
+ try:
184
+ forced = BlurStatus(_FORCE)
185
+ except ValueError:
186
+ forced = None # unrecognised value → ignore, probe normally
187
+ # status() reports a machine capability and never returns DISABLED
188
+ # (that's the theme's call) — so TRACKERKEEPER_BLUR_FORCE=disabled is ignored.
189
+ if forced is not None and forced is not BlurStatus.DISABLED:
190
+ return forced
191
+ if _status_cache is not None and not force:
192
+ return _status_cache
193
+ try:
194
+ _status_cache = _backend.probe()
195
+ except Exception:
196
+ _status_cache = BlurStatus.REQUESTED_UNVERIFIABLE
197
+ return _status_cache
198
+
199
+
200
+ def reason() -> str:
201
+ """A short, human-readable explanation of the current blur status — for
202
+ the boot log and the Settings hint when a frosted theme can't get real
203
+ blur (e.g. "GNOME has no app-controllable window blur", "KWin's Blur
204
+ effect is off", "Windows 10 has no Mica backdrop"). Reads status()
205
+ (cached) + the environment via the active backend. Never raises."""
206
+ st = status()
207
+ try:
208
+ return _backend.reason(st)
209
+ except Exception:
210
+ if st is BlurStatus.ACTIVE:
211
+ return "compositor blur active"
212
+ return "compositor blur unavailable — using a near-opaque body"
@@ -0,0 +1,348 @@
1
+ """Windows blur backend for the Frosted theme.
2
+
3
+ The DEFAULT is real **Acrylic** blur-behind — live frosted glass — driven by
4
+ the legacy ``SetWindowCompositionAttribute`` accent policy
5
+ (``ACCENT_ENABLE_ACRYLICBLURBEHIND``); see ``apply_acrylic``. ``TRACKERKEEPER_NO_WIN_BLUR``
6
+ opts out to the DWM **Mica** system backdrop instead (an opaque, once-sampled
7
+ wallpaper tint — NOT a live blur). Both composite BEHIND the window, visible
8
+ through transparent Qt pixels — the Windows analog of KWin's blur-behind. The
9
+ main window pairs the Acrylic path with a NON-layered window (trackerkeeper/app.py's
10
+ ``_win_blur`` drops ``WA_TranslucentBackground``); the layered mini player /
11
+ dialogs keep it.
12
+
13
+ ``DwmSetWindowAttribute`` returns an HRESULT, so the Mica fallback has real
14
+ success feedback. Build gates for that fallback (verified against
15
+ learn.microsoft.com):
16
+
17
+ * Windows 11 22H2+ (build >= 22621): the documented
18
+ ``DWMWA_SYSTEMBACKDROP_TYPE`` (38) = ``DWMSBT_MAINWINDOW`` (2 = Mica).
19
+ * Windows 11 21H2 (22000..22620): the undocumented ``DWMWA_MICA_EFFECT``
20
+ (1029) = 1.
21
+ * Windows 10 / older (< 22000): no backdrop — UNSUPPORTED → near-opaque body.
22
+
23
+ Mica only renders when Windows' "Transparency effects" toggle is on; when it's
24
+ off we'd paint a translucent body over nothing (see-through), so ``probe()``
25
+ reads that setting from the registry and reports UNSUPPORTED when it's off —
26
+ the Windows analog of KDE's ``kwinrc blurEnabled`` demotion.
27
+
28
+ See docs/research/portable_blur.md §5. The DWM calls are exercised on Windows
29
+ only; the build/transparency gating is unit-tested cross-platform.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import ctypes
35
+ import os
36
+ import sys
37
+
38
+ from trackerkeeper.platform_compat import IS_WINDOWS
39
+
40
+ # ── DWM attribute ids + backdrop enum (learn.microsoft.com) ──────────────
41
+ _DWMWA_USE_IMMERSIVE_DARK_MODE = 20 # dark native titlebar
42
+ _DWMWA_SYSTEMBACKDROP_TYPE = 38 # documented; build >= 22621
43
+ _DWMWA_MICA_EFFECT = 1029 # legacy undocumented; build 22000..22620
44
+ _DWMWA_WINDOW_CORNER_PREFERENCE = 33 # round a frameless window; build >= 22000
45
+ _DWMWCP_ROUND = 2 # DWMWCP_ROUND — round the corners
46
+ _DWMSBT_NONE = 1 # remove the backdrop
47
+ _DWMSBT_MAINWINDOW = 2 # Mica
48
+
49
+ _MIN_BUILD_MICA = 22000 # Windows 11 21H2
50
+ _MIN_BUILD_DOCUMENTED = 22621 # Windows 11 22H2 (documented attr 38)
51
+
52
+
53
+ def _build() -> int:
54
+ """Windows build number, or 0 where unavailable (non-Windows / error)."""
55
+ try:
56
+ return int(sys.getwindowsversion().build)
57
+ except Exception:
58
+ return 0
59
+
60
+
61
+ def _transparency_enabled() -> bool:
62
+ """Windows "Transparency effects" toggle (Settings → Personalization →
63
+ Colors). Mica does not render when it's off, so we'd be painting a
64
+ translucent body over nothing — read it to demote to the near-opaque
65
+ fallback instead. HKCU\\…\\Themes\\Personalize\\EnableTransparency;
66
+ defaults True when unreadable (apply stays best-effort). The Windows
67
+ analog of KDE's ``kwinrc [Plugins] blurEnabled`` check."""
68
+ try:
69
+ import winreg
70
+
71
+ with winreg.OpenKey(
72
+ winreg.HKEY_CURRENT_USER,
73
+ r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
74
+ ) as key:
75
+ val, _ = winreg.QueryValueEx(key, "EnableTransparency")
76
+ return bool(val)
77
+ except Exception:
78
+ return True
79
+
80
+
81
+ def is_supported() -> bool:
82
+ """True on a Windows 11 build that can show Mica. A True here doesn't
83
+ guarantee it renders (transparency could be off) — that's probe()'s job."""
84
+ return IS_WINDOWS and _build() >= _MIN_BUILD_MICA
85
+
86
+
87
+ class _MARGINS(ctypes.Structure):
88
+ _fields_ = [
89
+ ("cxLeftWidth", ctypes.c_int),
90
+ ("cxRightWidth", ctypes.c_int),
91
+ ("cyTopHeight", ctypes.c_int),
92
+ ("cyBottomHeight", ctypes.c_int),
93
+ ]
94
+
95
+
96
+ def _set_attr(hwnd: int, attr: int, value: int) -> int:
97
+ """DwmSetWindowAttribute(hwnd, attr, &value, 4) → HRESULT.
98
+
99
+ ``restype`` is ``c_long`` (signed) so ``E_INVALIDARG`` (0x80070057, high
100
+ bit set) reads back as a negative failure rather than a huge positive."""
101
+ fn = ctypes.windll.dwmapi.DwmSetWindowAttribute
102
+ fn.restype = ctypes.c_long
103
+ fn.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p, ctypes.c_uint]
104
+ val = ctypes.c_int(value)
105
+ return fn(
106
+ ctypes.c_void_p(hwnd),
107
+ ctypes.c_uint(attr),
108
+ ctypes.byref(val),
109
+ ctypes.c_uint(ctypes.sizeof(val)),
110
+ )
111
+
112
+
113
+ def _extend_frame(hwnd: int) -> None:
114
+ """Extend the window frame across the whole client area (margins all -1)
115
+ so the Mica backdrop fills it. Required on the legacy 1029 path and
116
+ harmless-recommended on 22621+."""
117
+ fn = ctypes.windll.dwmapi.DwmExtendFrameIntoClientArea
118
+ fn.restype = ctypes.c_long
119
+ fn.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
120
+ margins = _MARGINS(-1, -1, -1, -1)
121
+ fn(ctypes.c_void_p(hwnd), ctypes.byref(margins))
122
+
123
+
124
+ # ── SetWindowCompositionAttribute accent policy (undocumented user32) ──────
125
+ # The legacy accent-policy API drives the real Acrylic blur-behind (see
126
+ # apply_acrylic). Undocumented user32, so every call is best-effort.
127
+ _WCA_ACCENT_POLICY = 19
128
+
129
+
130
+ class _ACCENT_POLICY(ctypes.Structure):
131
+ _fields_ = [
132
+ ("AccentState", ctypes.c_uint),
133
+ ("AccentFlags", ctypes.c_uint),
134
+ ("GradientColor", ctypes.c_uint),
135
+ ("AnimationId", ctypes.c_uint),
136
+ ]
137
+
138
+
139
+ class _WINCOMPATTRDATA(ctypes.Structure):
140
+ _fields_ = [
141
+ ("Attribute", ctypes.c_int),
142
+ ("Data", ctypes.c_void_p),
143
+ ("SizeOfData", ctypes.c_size_t),
144
+ ]
145
+
146
+
147
+ def _set_wca(hwnd: int, attribute: int, payload) -> bool:
148
+ """SetWindowCompositionAttribute(hwnd, &WINCOMPATTRDATA) — best-effort.
149
+ ``payload`` is any ctypes object (ACCENT_POLICY struct or a c_int).
150
+
151
+ Returns whether the call was *issued* successfully: the function returns a
152
+ BOOL (nonzero = accepted), so we propagate it (rather than always claiming
153
+ success) to give the Acrylic path the same honest 'issued' signal the Mica
154
+ branch gets from its HRESULT. The undocumented API is best-effort either
155
+ way — the visible blur is identical whatever this returns; only apply()'s
156
+ return becomes truthful. False on a zero return, a missing export, or any
157
+ error (including off-Windows, where ``ctypes.windll`` is absent)."""
158
+ try:
159
+ data = _WINCOMPATTRDATA()
160
+ data.Attribute = attribute
161
+ data.Data = ctypes.cast(ctypes.byref(payload), ctypes.c_void_p)
162
+ data.SizeOfData = ctypes.sizeof(payload)
163
+ fn = ctypes.windll.user32.SetWindowCompositionAttribute
164
+ fn.restype = ctypes.c_int
165
+ fn.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
166
+ return bool(fn(ctypes.c_void_p(hwnd), ctypes.byref(data)))
167
+ except Exception:
168
+ return False
169
+
170
+
171
+ # ── Acrylic blur-behind (real frosted glass) ──────────────────────────────
172
+ # Unlike Mica (opaque, wallpaper-sampled-once tint), Acrylic is a live
173
+ # frosted-glass blur. The maintained qframelesswindow drives it through the
174
+ # LEGACY accent-policy API (ACCENT_ENABLE_ACRYLICBLURBEHIND), NOT the modern
175
+ # DWMWA_SYSTEMBACKDROP_TYPE — the system-backdrop Acrylic (DWMSBT_TRANSIENT)
176
+ # is for transient surfaces. The GradientColor is the tint over the blur,
177
+ # packed AABBGGRR; alpha governs how much wallpaper-blur reads through.
178
+ _ACCENT_DISABLED = 0
179
+ _ACCENT_ENABLE_ACRYLICBLURBEHIND = 4
180
+ # Border/shadow flags qframelesswindow passes (draw the 4 edges).
181
+ _ACCENT_DRAW_ALL_BORDERS = 0x20 | 0x40 | 0x80 | 0x100
182
+ # Dark carries a heavier veil than light: at the shared 0x99 the dark
183
+ # theme read "too transparent — missing the weight of a dark themed
184
+ # app" over a bright wallpaper (eyeball-calibrated to 190/0xBE on the
185
+ # Windows 11 laptop, 2026-06-10; light stays at qframelesswindow's
186
+ # default, which calibrated as already right).
187
+ _ACRYLIC_TINT_DARK = 0xBE202020 # A=0xBE (190) over (32,32,32)
188
+ _ACRYLIC_TINT_LIGHT = 0x99F2F2F2 # qframelesswindow's default light tint
189
+
190
+
191
+ def _acrylic_tint(dark: bool, elevated: bool = False) -> int:
192
+ """Acrylic tint (AABBGGRR). TRACKERKEEPER_WIN_BLUR_ALPHA overrides just the alpha
193
+ (0–255): lower = more blur reads through, higher = more solid tint.
194
+
195
+ ``elevated`` (menus / dropdowns / volume popups / tooltips): these
196
+ surfaces carry their own status-aware QSS frost fill — the same veil
197
+ that KDE's UNTINTED KWin blur composites under on Linux. Acrylic's
198
+ default tint stacked a second warm veil on top, so Windows popups
199
+ read warmer + more opaque than the same popup on Linux (2026-06-10
200
+ Windows round). Elevated surfaces therefore request a near-zero
201
+ tint alpha — 0x01, not 0x00, because a fully transparent gradient
202
+ disables the Acrylic material on some builds — leaving the QSS fill
203
+ as the single tint source on every platform. TRACKERKEEPER_WIN_POPUP_BLUR_ALPHA
204
+ tunes it live for eyeball calibration."""
205
+ base = _ACRYLIC_TINT_DARK if dark else _ACRYLIC_TINT_LIGHT
206
+ if elevated:
207
+ try:
208
+ a = int(os.environ.get("TRACKERKEEPER_WIN_POPUP_BLUR_ALPHA", "1"))
209
+ except ValueError:
210
+ a = 1
211
+ return (max(1, min(255, a)) << 24) | (base & 0x00FFFFFF)
212
+ try:
213
+ a = int(os.environ.get("TRACKERKEEPER_WIN_BLUR_ALPHA", ""))
214
+ except ValueError:
215
+ return base
216
+ return (max(0, min(255, a)) << 24) | (base & 0x00FFFFFF)
217
+
218
+
219
+ def apply_acrylic(hwnd: int, dark: bool, enabled: bool = True, elevated: bool = False) -> bool:
220
+ """Apply (or remove) the legacy Acrylic blur-behind accent policy — the
221
+ qframelesswindow recipe for genuine frosted glass. Best-effort; returns
222
+ whether the accent-policy call was issued successfully (propagated from
223
+ ``_set_wca``), mirroring the Mica branch's HRESULT check."""
224
+ accent = _ACCENT_POLICY()
225
+ if enabled:
226
+ accent.AccentState = _ACCENT_ENABLE_ACRYLICBLURBEHIND
227
+ accent.AccentFlags = _ACCENT_DRAW_ALL_BORDERS
228
+ accent.GradientColor = _acrylic_tint(dark, elevated=elevated)
229
+ else:
230
+ accent.AccentState = _ACCENT_DISABLED
231
+ return _set_wca(hwnd, _WCA_ACCENT_POLICY, accent)
232
+
233
+
234
+ def apply(
235
+ widget,
236
+ enabled: bool,
237
+ corner_radius: int = 0,
238
+ dark: bool = True,
239
+ elevated: bool = False,
240
+ ) -> bool:
241
+ """Apply (``enabled``) or remove (``not enabled``) the Windows backdrop
242
+ behind ``widget`` — real Acrylic blur by default, the Mica system backdrop
243
+ when ``TRACKERKEEPER_NO_WIN_BLUR`` is set. ``corner_radius > 0`` additionally asks DWM to round
244
+ the window's corners — needed for the frameless, self-painted surfaces
245
+ (mini player + dialogs, and the main window once it goes frameless on
246
+ Windows), because Windows does NOT clip a frameless translucent HWND to
247
+ the painted rounded body, so without this the corners read square. The
248
+ pixel radius itself is DWM's choice (Win11's standard ~8 px), so the value
249
+ only acts as a "round me" flag; it's harmless on the native-framed window
250
+ (already rounded).
251
+
252
+ Must be called AFTER ``show()`` (``winId()`` needs a real HWND, and Qt
253
+ 6.8+ re-runs native window setup that would clobber a constructor-time
254
+ call). Returns True if the backdrop request was accepted (HRESULT S_OK),
255
+ False on any non-Windows / pre-22000 / not-yet-shown / error case. Never
256
+ raises — blur is progressive enhancement."""
257
+ if not IS_WINDOWS:
258
+ return False
259
+ build = _build()
260
+ if build < _MIN_BUILD_MICA:
261
+ return False
262
+ try:
263
+ # windowHandle() check BEFORE winId(): winId() force-creates the
264
+ # native window on a never-shown widget, so the bare int() call both
265
+ # violated the "returns False when not-yet-shown" contract above and
266
+ # triggered exactly the premature native-window setup the Qt 6.8 note
267
+ # warns about. Mirrors the KWin backend's guard.
268
+ if widget.windowHandle() is None:
269
+ return False # never shown — no platform window to blur
270
+ hwnd = int(widget.winId())
271
+ if not hwnd:
272
+ return False # no native window yet
273
+ # Frameless surfaces self-paint a rounded body but Windows leaves the
274
+ # HWND square; ask DWM to round it (Win11 22000+, the build we already
275
+ # gated on). Runs whether or not Mica is enabled so a Solid-theme
276
+ # frameless dialog still gets rounded corners.
277
+ if corner_radius > 0:
278
+ _set_attr(hwnd, _DWMWA_WINDOW_CORNER_PREFERENCE, _DWMWCP_ROUND)
279
+ # Match the titlebar AND the Mica backdrop variant to the theme:
280
+ # immersive-dark on for dark themes (dark Mica), off for light
281
+ # themes (light, wallpaper-tinted Mica). Follows the OS live when
282
+ # the theme_mode is "auto".
283
+ _set_attr(hwnd, _DWMWA_USE_IMMERSIVE_DARK_MODE, 1 if dark else 0)
284
+ # Real frosted-glass blur — the DEFAULT on Windows (TRACKERKEEPER_NO_WIN_BLUR
285
+ # opts out to the Mica system-backdrop below). Drive the legacy
286
+ # Acrylic accent policy instead of Mica (Mica is an opaque
287
+ # once-sampled tint, not a live blur). The main window pairs this with
288
+ # a NON-layered window (trackerkeeper/app.py `_win_blur` drops
289
+ # WA_TranslucentBackground); the accent path also blurs the layered
290
+ # mini player / dialogs. enabled=False (a Solid theme) removes it.
291
+ if not os.environ.get("TRACKERKEEPER_NO_WIN_BLUR"):
292
+ # Propagate the accent-policy result instead of an unconditional
293
+ # True — symmetric with the Mica branch below (`_set_attr(...) == 0`)
294
+ # so apply()'s "issued" return is honest on BOTH paths. Safe to
295
+ # change: no caller reads this return (it's best-effort by contract,
296
+ # see blur/__init__.py), and the visible blur is unaffected.
297
+ return apply_acrylic(hwnd, dark, enabled, elevated=elevated)
298
+ _extend_frame(hwnd)
299
+ if build >= _MIN_BUILD_DOCUMENTED:
300
+ attr = _DWMWA_SYSTEMBACKDROP_TYPE
301
+ value = _DWMSBT_MAINWINDOW if enabled else _DWMSBT_NONE
302
+ else:
303
+ attr = _DWMWA_MICA_EFFECT # legacy: 1 = Mica, 0 = off
304
+ value = 1 if enabled else 0
305
+ return _set_attr(hwnd, attr, value) == 0
306
+ except Exception:
307
+ return False
308
+
309
+
310
+ def probe():
311
+ """Verified BlurStatus for Windows. Mica availability is a build-version
312
+ fact (no window needed): Windows 11 22000+ with Transparency effects on
313
+ gets a real backdrop → ACTIVE (translucent body rides Mica). Pre-22000,
314
+ or transparency disabled, → UNSUPPORTED (near-opaque body, never
315
+ see-through). See trackerkeeper/blur/__init__.py."""
316
+ from trackerkeeper.blur import BlurStatus
317
+
318
+ if not IS_WINDOWS or _build() < _MIN_BUILD_MICA:
319
+ return BlurStatus.UNSUPPORTED
320
+ if not _transparency_enabled():
321
+ return BlurStatus.UNSUPPORTED
322
+ return BlurStatus.ACTIVE
323
+
324
+
325
+ def reason(status) -> str:
326
+ """Human-readable explanation of the Windows blur status — for the boot
327
+ log + Settings hint. Mirrors the other backends' ``reason(status)``; reads
328
+ the build + transparency facts so the message is actionable. Never raises."""
329
+ from trackerkeeper.blur import BlurStatus
330
+
331
+ if not IS_WINDOWS:
332
+ return "not running on Windows"
333
+ if _build() < _MIN_BUILD_MICA:
334
+ return "Windows 10 has no Mica backdrop — using a near-opaque body"
335
+ if not _transparency_enabled():
336
+ return (
337
+ "Windows 'Transparency effects' is off (Settings → Personalization "
338
+ "→ Colors) — using a near-opaque body"
339
+ )
340
+ if status == BlurStatus.ACTIVE:
341
+ # Default is the real Acrylic accent blur; TRACKERKEEPER_NO_WIN_BLUR falls back
342
+ # to the (flat) Mica system-backdrop.
343
+ return (
344
+ "Windows 11 Mica backdrop active"
345
+ if os.environ.get("TRACKERKEEPER_NO_WIN_BLUR")
346
+ else "Windows 11 Acrylic blur active"
347
+ )
348
+ return "blur unavailable — using a near-opaque body"