feat(notifications): show notifications in a YASB menu - #1070
Conversation
|
Thanks for the PR. You can get images as well and show them there, but I can't remember why I stopped working on this a few months ago. There is some genuine restriction when running from an unpacked app or from Python outside of the Store, but I can't remember what it was. Anyway, I'll check when I have time, maybe I can find some reference on the Discord server if I wrote something about it there. |
|
Just wanted to start by saying thanks for this awesome project! It keeps getting better with every release. On the restriction, I went digging. Notification access seems to be the one permission Windows doesn't track per app for non-Store apps. Mic, camera and location all keep a list of individual exes under the consent store, which is what Settings shows per app. This one has no such list at all: So YASB never shows up under Settings > Privacy & security > Notifications, it just inherits whatever the single global toggle says. I also built the exe and ran the same checks on it and on Python, expecting a difference. There wasn't one: What did bite me was app names coming back empty in the compiled build, until I added One gap on my side: the code ignores the result of the access request, so if someone has that toggle off they'd just get an empty panel saying "no new notifications", with no idea why. I can make it say access is off instead, and the counter would keep working since it comes from WNF. Small change, happy to push it if you want it here. On images, I'd rather leave those for a separate PR. The point of this one is just getting notifications out of the Windows panel and into YASB, and images feel like a new feature on top of that. That said, if you'd rather have them in before this gets merged, I'm happy to build them here too, just say the word. |
Until now the widget could only report a count: clicking it sent Win+N and handed the user off to the Windows Action Center, so the notifications themselves were never visible inside YASB. This renders them in YASB's own popup instead, the way the other popup widgets work, while keeping the Action Center one click away. - Replace the hand-off to the Windows Action Center with a popup menu listing the toasts it holds, with app icons, per-app grouping, relative timestamps and per-item dismiss - Extend the existing notification listener to read and broadcast toast contents instead of only the unread count, converting WinRT objects on its own thread and reusing its asyncio loop for refresh and removal - Add a Do Not Disturb toggle backed by the shared DndService, and a footer link that still opens the Windows Notification Center - Clicking a notification brings the sending app to the front, only the dismiss button removes it - Group senders that expose no app info under a generic header instead of leaving them untitled, since unregistered AUMIDs make the listener return E_NOTIMPL - Default callbacks.on_left is now toggle_menu, toggle_notification keeps the previous Action Center behaviour - Apply the tooltip option that the widget already accepted but never used
497cb6b to
68dfecc
Compare
The listener discarded the result of the access request, so a user who had turned off "Let apps access my notifications" saw an empty menu claiming there were no new notifications. Windows grants this permission through a single global switch for apps installed outside the Store, with no per-app entry, so nothing hinted at the cause. - Check the access status before every read rather than only at startup, since the user can revoke it at any time, and broadcast it to the widgets when it changes - Skip reading the list while access is denied instead of asking for it on every refresh - Say access is turned off in the menu, with a link to the Windows settings page that holds the switch - The count on the bar is unaffected, it still comes from WNF
Clearing announced an empty Action Center before checking whether the removals had taken effect, so a notification Windows refused to remove, or one that arrived while the loop was running, stayed on screen while the widget claimed there was nothing left. Zeroing the cached total also made the WNF callback skip the next update, because it compares against that value. - Read the notifications back after the removal loop and broadcast the real remainder - Leave the count on the bar to the WNF callback rather than forcing it to zero - Document that the count Windows reports and the number of entries in the menu can differ, and that an individual removal can fail ClearNotifications() would avoid the loop but is not usable here, it fails with ERROR_NOT_FOUND for apps installed outside the Store.
Added the access fix. While testing it I ran into something else: clear all was announcing an empty Action Center before checking whether the removals had actually gone through, so anything Windows refused to remove stayed on screen while the widget claimed there was nothing left. It now reads the list back and reports what is really there. On images I owe you a correction. The toast's own images aren't reachable from the listener. I sent a toast carrying |
Notifications from apps installed outside the Store had no icon in the menu. The shell draws icons from the Apps folder, which only holds registered apps, so anything else came back empty and the item was left blank. Windows itself falls back to the icon the app registers under its AUMID, which is how the same toast still shows a picture in the Notification Center. - Read IconUri from Software\Classes\AppUserModelId, user hive first, when the Apps folder has nothing - Trim the transparent margin before fitting it: what is registered here is often a tile asset with its own padding, which otherwise lands visibly smaller than the icons the shell hands out - Keep the shape and centre it rather than stretching it to the caller's box - Skip ms-resource:// icons, which need the package that owns them to resolve Firefox writes exactly this registration, and it is the case reported here. On this machine it takes the senders with an icon from 200 of 279 to 227.
Clearing left one notification per app behind. The listener hands out at most twenty notifications per app while the Action Center keeps one more, so removing the ones it gave us cleared the menu but not the Action Center: the leftover kept its place there and stayed in the count on the bar, which then disagreed with a menu that said there was nothing left. - Clear the notification history of each sender after removing what the listener gave us, which takes the notification it could not hand out - Clear per app rather than in one call, since ClearNotifications() fails with ERROR_NOT_FOUND for apps installed outside the Store Sending 26 toasts from one app leaves the Action Center holding 21 and the listener returning 20. Clear all now ends with both at zero instead of one still there.
A popup installs an event filter on the whole application while it is open, so every event of every widget crosses into Python and is checked against the popup geometry, even though only a mouse press can close it. Filling a popup with widgets generates thousands of those events, and each one paid for an isinstance check and two attribute lookups before being let go. Test the event type first and return before doing anything else. Rebuilding a menu of 30 notifications makes about 4,000 filter calls and they cost 39% less; the build as a whole goes from 63ms to 45ms.
…changed Opening the menu drew the list the widget already held and asked the listener for a fresh one at the same time, then rebuilt every item when the reply arrived. That reply is the same list almost every time, so opening the menu built it twice to end up with the same thing on screen, which is what made a long list feel slow to open. Compare the incoming list with the one on screen and only rebuild when it differs.
…ffer The note said the count "is not always the number of entries in the Action Center" without saying why, which reads as if the number were unreliable. It is not: it is the number Windows publishes for the shell, and it means different things on Windows 10 and 11. Say which state each version reports, and name the two cases where the count really does sit above the menu: a notification an app has past its limit, which the listener will not hand out, and a badge, which is counted but is not something the list can show.
A scroll area fills its viewport with the palette background, and setWidget turns the same fill back on for whatever is put inside it. Neither is reached by the stylesheet rule on the QScrollArea, so the list sat on an opaque slab between the popup and its own background and no amount of CSS could make it see-through.
The shell hands an app icon over COM in ten to a hundred milliseconds, and this was asking for one sender after another while building the menu, on the thread that had to draw it. A menu holding half a dozen apps stalled for a third of a second on its first open, which is most of what made the popup feel slow. The icons are now extracted by a pool and dropped into the item once they are ready, and the list is asked for them as soon as it changes, so by the time anybody clicks they are usually already there. A slot that is still waiting holds its space with a transparent stand-in, so nothing shifts when the picture lands.
The number on the bar and the list in the menu come from two different places and stop agreeing once an app has sent more notifications than Windows keeps for it, so a bar reading 22 can sit above a menu holding 21. max_count writes anything past it as 9+, which keeps the bar from advertising a number the menu cannot account for and from widening as the count grows. Off by default, and the tooltip and the menu header still carry the real number.
Thanks, that last comment was exactly what I needed, the 20-per-app limit explains the +1 completely. Before the four UI points: five commits landed after you tested, so a couple of these were already fixed on my side and you were looking at the older head. Sorry about the timing. Here is where each of your points stands. Some apps do not show icons (Firefox)Fixed. Firefox is not in the Apps folder, so
The content cannot be transparentFixed, and you were right that it was impossible rather than just unstyled. A Now the viewport and the content widget both have Worth knowing for the other popups: any widget that puts a The layout is slow with 20-30 itemsFixed, but the layout was not the problem. Building 60 fully styled items offscreen costs about 32 ms, so that was never where the time went. It was the icons: They are now extracted by a Measured on 30 notifications from 6 distinct senders, cold cache: Two smaller ones went in alongside it: the popup's application-wide event filter now leaves early for anything that is not a mouse press, since it was doing the full inside-the-popup geometry check for every event of every widget while a popup is open, and the menu no longer rebuilds itself when the listener replies with a list identical to the one it is already showing. This is worth stealing for the GitHub widget if it turns out to be the same cause there, the per-item layout cost looks fine in both, so I would check what that popup does synchronously while building before touching the layout. The countI took your suggestion, as an opt-in option rather than a behaviour change: I did not try to make the number itself agree with the menu. From what you described the count is the correct side and the listener is the capped one, and there is no API that hands out the notification the listener is holding back, so The docs note is updated with the version-dependent explanation from earlier, plus the per-app cap. Clear AllAlso fixed after you tested. ImagesStill keeping those out of this PR, but they are working on a branch of mine and I will open it separately once this lands. The short version, in case it saves you time later: the listener genuinely cannot reach them, but the payload Windows stored can. The catch is that the file the payload points at belongs to the sender and is usually deleted within seconds of the banner, a browser writes the icon a website asked for into a temp file and clears it almost immediately, while the notification itself stays in the Notification Center until it is dismissed. So the picture has to be copied aside the first time it is seen or it is gone by the next refresh, which is probably the restriction you ran into. |











Until now the notifications widget could only report a count: clicking it sent
Win+Nand handed the user off to the Windows Action Center, so the notifications themselves were never visible inside YASB. This renders them in YASB's own popup instead, the way the other popup widgets work, while keeping the Action Center one click away for anything the panel cannot do.DndService, and a footer link that still opens the Windows Notification Centermenublock and styleable through.notification-menuHow it works
The bar counter is unchanged: it still comes from the WNF state the widget already subscribed to. What is new is the list, which comes from
UserNotificationListeneron the sameQThreadthe widget already starts, so no second listener, event loop or thread is introduced.The widget and the listener talk over the existing
EventService, the same wayWindowsNotificationClearalready did. Three events are added:WindowsNotificationsChanged(listener to widgets) plusWindowsNotificationRefreshandWindowsNotificationRemove(widgets to listener). Removal is one-way: the widget asks, the listener removes and re-broadcasts the new list, and the menu redraws from that. There is no local mutation of the list in the widget.WinRT objects are bound to the listener thread, so each toast is flattened into a small
NotificationItemdataclass before it crosses to the GUI thread.The menu follows the structure of the GitHub widget's popup. App icons and click-to-activate reuse
get_icon_for_aumidandactivate_app_by_aumid, the Do Not Disturb toggle reusesDndService, and relative timestamps reuseget_relative_time.Behaviour change
callbacks.on_leftnow defaults totoggle_menuinstead oftoggle_label, so the menu is discoverable out of the box. The old behaviour is untouched and still available:toggle_notificationopens the Windows Notification Center exactly as before, andtoggle_labelis unchanged. Existing configs that seton_leftexplicitly are unaffected.Limitations worth knowing
UserNotification.app_inforaisesE_NOTIMPLfor senders whose AUMID is not registered, which covers plain Win32 apps and script-generated toasts. Those notifications have no app name, no icon and nothing to activate, so they are grouped under a genericOtherheader rather than being labelled with a name we do not have. Registered AUMIDs (packaged apps, shell shortcuts, or anAppUserModelIdregistry entry) resolve normally.Testing
Tested on Windows 11 with notifications from packaged apps (WhatsApp, Microsoft Store), shell-shortcut senders (PowerShell) and unregistered senders. Verified live updates while the menu is open, single dismiss and clear-all against the real Action Center, click-to-activate, the Do Not Disturb toggle against Focus Assist, and every combination of the new
menuoptions across all alignments and directions.ruff checkandruff formatpass, andschema.jsonwas left untouched for CI to regenerate.AI usage disclosure
Per the AI-generated code policy, AI was used as an assistive tool for a significant part of this change. I reviewed every line, verified the behaviour against the real Windows APIs on my machine, and can explain the changes.