status-notifier-item 0.3.2.15 → 0.3.2.16
raw patch · 4 files changed
+74/−5 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- ChangeLog.md +8/−0
- src/StatusNotifier/Host/Service.hs +22/−4
- status-notifier-item.cabal +1/−1
- test/HostSpec.hs +43/−0
ChangeLog.md view
@@ -2,6 +2,14 @@ ## Unreleased +## 0.3.2.16 - 2026-07-15+- Host: normalize the random `Id`, item path, and menu path generated by+ Ayatana StatusNotifierItem implementations when building logical duplicate+ keys. Multiple instances of the same application now collapse to one tray+ item even when each instance advertises a different generated identity.+- Add regression coverage for generated Ayatana identities while preserving+ distinct identities for ordinary StatusNotifierItems.+ ## 0.3.2.15 - 2026-07-02 - Bound every DBus call made during host and watcher startup with a timeout so a single unresponsive item (a process that owns its `StatusNotifierItem` bus
src/StatusNotifier/Host/Service.hs view
@@ -23,6 +23,7 @@ import Data.Coerce import Data.Either import Data.Int+import Data.List (stripPrefix) import qualified Data.Map.Strict as Map import Data.Maybe import Data.String@@ -133,14 +134,31 @@ logicalDuplicateKey :: ItemInfo -> Maybe LogicalDuplicateKey logicalDuplicateKey ItemInfo {..} =- let key =+ let generatedAyatanaIdentity = do+ suffix <- stripPrefix "/org/ayatana/NotificationItem/" (coerce itemServicePath :: String)+ normalizedId <- itemId >>= normalizeOptionalString+ itemMenuPath <- menuPath+ guard $ normalizedId == suffix+ guard $ (coerce itemMenuPath :: String) == (coerce itemServicePath :: String) <> "/Menu"+ pure ()+ hasGeneratedAyatanaIdentity = isJust generatedAyatanaIdentity+ key = LogicalDuplicateKey- { duplicateItemId = itemId >>= normalizeOptionalString,+ { duplicateItemId =+ if hasGeneratedAyatanaIdentity+ then Nothing+ else itemId >>= normalizeOptionalString, duplicateIconTitle = normalizeOptionalString iconTitle, duplicateIconName = normalizeOptionalString iconName, duplicateCategory = itemCategory >>= normalizeOptionalString,- duplicateMenuPath = menuPath,- duplicatePath = itemServicePath,+ duplicateMenuPath =+ if hasGeneratedAyatanaIdentity+ then Nothing+ else menuPath,+ duplicatePath =+ if hasGeneratedAyatanaIdentity+ then objectPath_ "/org/ayatana/NotificationItem"+ else itemServicePath, duplicateIsMenu = itemIsMenu } hasStableIdentity =
status-notifier-item.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: status-notifier-item-version: 0.3.2.15+version: 0.3.2.16 synopsis: A wrapper over the StatusNotifierItem/libappindicator dbus specification description: Please see the README on Github at <https://github.com/taffybar/status-notifier-item#readme> category: Desktop
test/HostSpec.hs view
@@ -33,6 +33,49 @@ spec :: Spec spec = around withIsolatedSessionBus $ do+ describe "logicalDuplicateKey" $ do+ it "normalizes generated Ayatana identities so duplicate app instances collapse" $ \() -> do+ let localSendItem token =+ ItemInfo+ { itemServiceName = busName_ ("org.test.LocalSend." <> token),+ itemServicePath = objectPath_ ("/org/ayatana/NotificationItem/" <> token),+ itemId = Just token,+ itemStatus = Just "Active",+ itemCategory = Just "ApplicationStatus",+ itemToolTip = Nothing,+ iconTitle = "localsend_app",+ iconName = "/nix/store/example-localsend/logo.png",+ overlayIconName = Nothing,+ iconThemePath = Nothing,+ iconPixmaps = [],+ overlayIconPixmaps = [],+ menuPath = Just $ objectPath_ ("/org/ayatana/NotificationItem/" <> token <> "/Menu"),+ itemIsMenu = True+ }+ logicalDuplicateKey (localSendItem "generatedA")+ `shouldBe` logicalDuplicateKey (localSendItem "generatedB")++ it "retains non-Ayatana item ids as part of logical identity" $ \() -> do+ let itemWithId itemIdValue =+ ItemInfo+ { itemServiceName = busName_ "org.test.RegularItem",+ itemServicePath = objectPath_ defaultPath,+ itemId = Just itemIdValue,+ itemStatus = Just "Active",+ itemCategory = Just "ApplicationStatus",+ itemToolTip = Nothing,+ iconTitle = "shared-title",+ iconName = "shared-icon",+ overlayIconName = Nothing,+ iconThemePath = Nothing,+ iconPixmaps = [],+ overlayIconPixmaps = [],+ menuPath = Nothing,+ itemIsMenu = False+ }+ logicalDuplicateKey (itemWithId "first")+ `shouldNotBe` logicalDuplicateKey (itemWithId "second")+ describe "StatusNotifier.Host.Service integration" $ do it "receives ItemAdded then ItemRemoved for a registered item" $ \() -> do _watcher <- startWatcher