Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to QuickPods will be documented in this file. The format fol

## [Unreleased]

### Changed

- The taskbar observer now uses Win32 shell hooks, window event hooks, and registry notifications instead of a UI Automation event subscription, so browsers no longer detect QuickPods as an assistive-technology client or enable accessibility mode because of it.

## [1.0.0] - 2026-08-08

### Added
Expand Down
3 changes: 1 addition & 2 deletions build/Test-SelfContainedPayload.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ $runtimeConfigExpectations = [ordered]@{
"Microsoft.WindowsDesktop.App"
)
"QuickPods.TaskbarObserver.runtimeconfig.json" = @(
"Microsoft.NETCore.App",
"Microsoft.WindowsDesktop.App"
"Microsoft.NETCore.App"
)
"QuickPods.BluetoothWorker.runtimeconfig.json" = @(
"Microsoft.NETCore.App"
Expand Down
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ QuickPods documentation is organized by audience. The current code and automated

- [Development](development.md) — clone, restore, build, test, run, and package
- [Architecture](architecture/README.md) — process boundaries, Windows integration, and fail-closed design
- [ADR-0001](architecture/adr-0001-uia-watcher-process-boundary.md) — UI Automation observer process boundary
- [ADR-0001](architecture/adr-0001-uia-watcher-process-boundary.md) — taskbar observer process boundary
- [ADR-0002](architecture/adr-0002-shell-signal-observer.md) — shell-signal taskbar observer without UI Automation events
- [Release guide](release/README.md) — versioning, packaging, signing, and publication checklist
- [Public repository checklist](release/public-repository-checklist.md) — GitHub settings to review before changing visibility
- [Installer guide](../installer/README.md) — WiX per-user MSI details
Expand Down
6 changes: 3 additions & 3 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ QuickPods.exe (portable launcher)
├─ QuickPods.TaskbarHost.exe
│ └─ taskbar discovery, native surface, rendering, input, and versioned IPC
├─ QuickPods.TaskbarObserver.exe
│ └─ short-lived UI Automation subscription for one Explorer generation
│ └─ short-lived Win32 shell-signal subscription for one Explorer generation
└─ QuickPods.BluetoothWorker.exe
└─ bounded Bluetooth capability or mutation call
```
Expand All @@ -31,7 +31,7 @@ The app owns settings, logs, startup registration, and child-process lifetime. H
| `QuickPods.Infrastructure` | settings, logging, update metadata retrieval, single-instance, and host supervision |
| `QuickPods.Windows` | Core Audio, Bluetooth, registry, and Windows Settings adapters |
| `QuickPods.TaskbarHost` | out-of-process taskbar surface and placement policy |
| `QuickPods.TaskbarObserver` | Explorer-generation UI Automation observation |
| `QuickPods.TaskbarObserver` | Explorer-generation shell-signal observation without UI Automation events |
| `QuickPods.BluetoothWorker` | isolated Bluetooth driver call boundary |

## Core Audio
Expand All @@ -52,7 +52,7 @@ Unsupported capability, partial state, stale generation, timeout, and RDP owners

QuickPods uses a Win32 surface in verified empty taskbar space. The default policy chooses the nearest verified gap immediately left of the Windows notification area and supports center- and left-aligned horizontal taskbars. A user-selectable taskbar-left policy preserves the previous placement to the left of the center-aligned Start area. Neither policy reserves taskbar space or moves shell controls. Placement is permitted only after taskbar identity, notification-area geometry, monitor, DPI, landmarks, obstacles, and Explorer generation are verified.

Incomplete evidence hides the embedded surface and leaves notification-area access available. A dedicated short-lived observer contains UI Automation provider lifetime across Explorer restarts; see [ADR-0001](adr-0001-uia-watcher-process-boundary.md).
Incomplete evidence hides the embedded surface and leaves notification-area access available. A dedicated short-lived observer owns the per-Explorer-generation shell hook, window event hook, and registry notifications that drive re-discovery; see [ADR-0001](adr-0001-uia-watcher-process-boundary.md) for the process boundary and [ADR-0002](adr-0002-shell-signal-observer.md) for why the observer does not register UI Automation events.

## Settings and diagnostics

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/adr-0001-uia-watcher-process-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Status

Accepted, 2026-08-06.
Accepted, 2026-08-06. Rationale superseded by [ADR-0002](adr-0002-shell-signal-observer.md) on 2026-09-07; the observer process boundary remains, but the observer no longer registers a UI Automation subscription.

## Context

Expand Down
46 changes: 46 additions & 0 deletions docs/architecture/adr-0002-shell-signal-observer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ADR-0002: Shell-signal taskbar observer without UI Automation events

## Status

Accepted, 2026-09-07. Supersedes the rationale of [ADR-0001](adr-0001-uia-watcher-process-boundary.md); the observer process boundary remains.

## Context

`QuickPods.TaskbarObserver.exe` previously registered a repeating UI Automation event subscription (structure and property changes) rooted at the primary taskbar. UI Automation event registration is wired desktop-wide by the UI Automation core, and scope filtering happens on the client side. Every UI Automation provider on the desktop therefore learned that a client was listening, and applications that treat any UI Automation client as an assistive technology reacted accordingly. Google Chrome enabled its accessibility mode for the whole browser and listed the observer under `chrome://accessibility`, which produced measurable CPU load inside Chrome even though QuickPods never queried a Chrome window.

The host's one-shot UI Automation scans of the taskbar only send `WM_GETOBJECT` to Explorer's taskbar window and do not register a listener, so they are not affected.

The original reason for a separate observer process was that the UI Automation provider retained USER resources across Explorer generations. That reason disappears with the subscription, but a short-lived observer is still the right lifetime for Win32 registrations that must be recreated for every Explorer generation.

## Decision

The observer no longer uses UI Automation. It drives invalidation from Win32 signals that do not involve the accessibility infrastructure:

- A hidden, top-level, unowned window registered with `RegisterShellHookWindow` receives `WM_SHELLHOOKMESSAGE`. Window created, destroyed, and replaced notifications invalidate the taskbar structure. Redraw notifications are ignored while taskbar button labels are hidden (the default combined layout) because some applications redraw their title or icon several times per second; while labels are visible they are throttled to one invalidation per second. Activation, flash, and rude-app notifications are ignored because they do not change geometry.
- A `SetWinEventHook` registration scoped to the Explorer process and filtered to the taskbar HWND tree reports HWND-level create, destroy, reorder, show, hide, and location changes. Location changes only arm the settle timer.
- Registry change notifications on the user's taskbar settings keys (pinned items, Explorer advanced settings, taskbar placement state, and search box mode) invalidate the structure. Every registry watch is optional; a missing key or failed registration degrades silently.
- The hidden window also consumes `WM_SETTINGCHANGE`, `WM_DISPLAYCHANGE`, `WM_THEMECHANGED`, and `TaskbarCreated` broadcasts.
- A 250 ms identity poll continues to detect Explorer generation changes and retires the observer, exactly as before.
- A 400 ms settle timer, restarted by every raw signal, emits one geometry invalidation after Explorer finishes animating so the host re-discovers the final layout.

The wire contract, protocol version, host lifecycle handling, job-object containment, and authenticated pipe handshake are unchanged. The existing invalidation kinds keep their generic meaning: structure changed, geometry changed, and visibility changed. The observer never reports a process or window identity, and it never classifies a signal as owned by the host because the process-scoped hook cannot observe the host's own child window.

The host keeps its one-shot UI Automation scans and its 5-second watchdog as the last resort.

## Consequences

- QuickPods no longer appears as an assistive-technology client to browsers or other UI Automation providers.
- The observer only sees HWND-level changes inside Explorer. Windows 11 taskbar buttons are XAML elements without HWNDs, so button additions, pin changes, and alignment changes are inferred from the shell hook, registry notifications, and the settle timer rather than observed directly.
- The observer no longer needs the Windows Desktop framework and ships as a plain .NET application.
- The owned-source classification in the protocol is retained for compatibility but is no longer emitted.
- Registry key names may change between Windows releases; because every watch is optional, such a change reduces signal fidelity without breaking the observer.

## Verification

Changes to the observer must cover:

1. `chrome://accessibility` shows no `quickpods.taskbarobserver.exe` client after Chrome is restarted with QuickPods running;
2. taskbar button additions and removals, pin changes, alignment changes, auto-hide changes, notification-area changes, display and DPI changes, and theme changes re-place the surface within about one second;
3. bounded invalidation rate while a window title changes continuously and during auto-hide animations;
4. Explorer restart and generation replacement, including retirement of every old observer;
5. stable long-lived observer USER and GDI resources.
2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- Optional: Visual Studio 2022 with the .NET desktop development workload
- Optional for MSI packaging: the WiX SDK is restored through the installer project

QuickPods is Windows-specific. WPF, Win32, Core Audio, Configuration Manager, UI Automation, registry, and MSI behavior cannot be validated on Linux or macOS.
QuickPods is Windows-specific. WPF, Win32, shell hooks and window event hooks, Core Audio, Configuration Manager, UI Automation, registry, and MSI behavior cannot be validated on Linux or macOS.

## Clone and restore

Expand Down
8 changes: 8 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@

Both center- and left-aligned taskbar buttons are supported by the default **Left of notification area** placement.

## Google Chrome reports an accessibility client or uses more CPU

Releases up to 1.0.0 subscribed to Windows UI Automation events to follow taskbar changes. Chrome treats any UI Automation event client as an assistive technology, enables its accessibility mode, and lists `quickpods.taskbarobserver.exe` under **UIA clients** on `chrome://accessibility`. Later releases use Win32 shell hooks, window event hooks, and registry notifications instead and do not register a UI Automation client.

- Update QuickPods, then restart Chrome once; Chrome keeps accessibility enabled until it restarts.
- Confirm that `chrome://accessibility` no longer lists `quickpods.taskbarobserver.exe`.
- If you cannot update, exit QuickPods and restart Chrome to release the accessibility mode.

## A Bluetooth device is missing

1. Confirm that the device is paired in Windows Settings.
Expand Down
24 changes: 12 additions & 12 deletions src/QuickPods.Launcher/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,31 @@
"net10.0-windows10.0.26100": {
"Microsoft.DotNet.ILCompiler": {
"type": "Direct",
"requested": "[10.0.10, )",
"resolved": "10.0.10",
"contentHash": "tnG8ntt/Bk6odvHREnGLMo3PEiihy5iSlIFVp0JbIo00GKtNRt2k73eKZbPqR5yaJNIa3z8R86YLwbxfqpb17g=="
"requested": "[10.0.11, )",
"resolved": "10.0.11",
"contentHash": "yloFUsIQOzspIsn2cDHrbgikKaWHAZspzj0EWxnWJJpFC89IBW64OXgCECsrrnzGWQvy1iLX9r5/R4zBf7Am3g=="
},
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.10, )",
"resolved": "10.0.10",
"contentHash": "f5VCIE7AJpd5YvzNTeMGVzQIgyE9tX+AreTYwQF+REbu+DZo/2Ae+jNSwhPEYrVz6RRkd7y8ubXjk6Nn6Ka+Cg=="
"requested": "[10.0.11, )",
"resolved": "10.0.11",
"contentHash": "IBf7lbovvjGWVWXZX5cJ/cO0WXbId0Zq4BuSeT94mGZuOAP66oMeH9PTBZ9Jpp3Jb6jtK0qm/NyUbPRo1gC/wQ=="
}
},
"net10.0-windows10.0.26100/win-x64": {
"Microsoft.DotNet.ILCompiler": {
"type": "Direct",
"requested": "[10.0.10, )",
"resolved": "10.0.10",
"contentHash": "tnG8ntt/Bk6odvHREnGLMo3PEiihy5iSlIFVp0JbIo00GKtNRt2k73eKZbPqR5yaJNIa3z8R86YLwbxfqpb17g==",
"requested": "[10.0.11, )",
"resolved": "10.0.11",
"contentHash": "yloFUsIQOzspIsn2cDHrbgikKaWHAZspzj0EWxnWJJpFC89IBW64OXgCECsrrnzGWQvy1iLX9r5/R4zBf7Am3g==",
"dependencies": {
"runtime.win-x64.Microsoft.DotNet.ILCompiler": "10.0.10"
"runtime.win-x64.Microsoft.DotNet.ILCompiler": "10.0.11"
}
},
"runtime.win-x64.Microsoft.DotNet.ILCompiler": {
"type": "Transitive",
"resolved": "10.0.10",
"contentHash": "e5P00ObvdmAl49KkvGvqDLE5+EXu/IdXCDsLghgKFXKrNybyxMmH12HuGrsO7LTIGjXI58tyN3Hwxwr/MR0d8A=="
"resolved": "10.0.11",
"contentHash": "D6ZKMo3NeTnos7l5S8bMmiPm4LK+y15Q/uduyPw7FX+xKbeWVWdwlcZGkLLPjLvW4ZO6o3ACanq5HsSXJgrt0g=="
}
}
}
Expand Down
Loading