gtk-sni-tray 0.1.12.0 → 0.1.13.0
raw patch · 7 files changed
+13/−427 lines, 7 filesdep +dbus-menudep −template-haskellPVP ok
version bump matches the API change (PVP)
Dependencies added: dbus-menu
Dependencies removed: template-haskell
API changes (from Hackage documentation)
Files
- ChangeLog.md +7/−0
- dbus-xml/com.canonical.dbusmenu.xml +0/−69
- gtk-sni-tray.cabal +3/−6
- src/StatusNotifier/DBus/Client/DBusMenu.hs +0/−15
- src/StatusNotifier/DBus/Client/Util.hs +0/−111
- src/StatusNotifier/DBusMenu.hs +0/−223
- src/StatusNotifier/Tray.hs +3/−3
ChangeLog.md view
@@ -1,5 +1,12 @@ # Changelog for gtk-sni-tray +## 0.1.13.0++- Use extracted `dbus-menu` library instead of inline DBusMenu implementation.+- Reduce default overlay icon scale from 60% to 40%.+- Fix: clamp scaled pixbuf dimensions to >= 1 to prevent GDK assertion failure.+- Bump `dbus-menu` lower bound to 0.1.1.+ ## 0.1.12.0 - Add `trayCenterIcons` field to `TrayParams` and `--center-icons` CLI flag to
@@ -1,69 +0,0 @@-<node>- <interface name="com.canonical.dbusmenu">- <property name="Version" type="u" access="read"/>- <property name="TextDirection" type="s" access="read"/>- <property name="Status" type="s" access="read"/>- <property name="IconThemePath" type="as" access="read"/>-- <method name="GetLayout">- <arg type="i" name="parentId" direction="in"/>- <arg type="i" name="recursionDepth" direction="in"/>- <arg type="as" name="propertyNames" direction="in"/>- <arg type="u" name="revision" direction="out"/>- <arg type="(ia{sv}av)" name="layout" direction="out"/>- <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DBusMenuLayout"/>- </method>- <method name="GetGroupProperties">- <arg type="ai" name="ids" direction="in"/>- <arg type="as" name="propertyNames" direction="in"/>- <arg type="a(ia{sv})" name="properties" direction="out"/>- <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="DBusMenuIdList"/>- <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusMenuItemPropertiesList"/>- </method>- <method name="GetProperty">- <arg type="i" name="id" direction="in"/>- <arg type="s" name="name" direction="in"/>- <arg type="v" name="value" direction="out"/>- </method>- <method name="Event">- <arg type="i" name="id" direction="in"/>- <arg type="s" name="eventId" direction="in"/>- <arg type="v" name="data" direction="in"/>- <arg type="u" name="timestamp" direction="in"/>- </method>- <!--<method name="EventGroup">- <arg type="a(isvu)" name="events" direction="in"/>- <arg type="ai" name="idErrors" direction="out"/>- <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="DBusMenuEventList"/>- <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusMenuIdList"/>- </method>-->- <method name="AboutToShow">- <arg type="i" name="id" direction="in"/>- <arg type="b" name="needUpdate" direction="out"/>- </method>- <method name="AboutToShowGroup">- <arg type="ai" name="ids" direction="in"/>- <arg type="ai" name="updatesNeeded" direction="out"/>- <arg type="ai" name="idErrors" direction="out"/>- <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="DBusMenuIdList"/>- <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusMenuIdList"/>- <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DBusMenuIdList"/>- </method>-- <signal name="ItemsPropertiesUpdated">- <arg type="a(ia{sv})" name="updatedProps" direction="out"/>- <arg type="a(ias)" name="removedProps" direction="out"/>- <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusMenuItemPropertiesList"/>- <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DBusMenuItemPropertyNamesList"/>- </signal>- <signal name="LayoutUpdated">- <arg type="u" name="revision" direction="out"/>- <arg type="i" name="parent" direction="out"/>- </signal>- <signal name="ItemActivationRequested">- <arg type="i" name="id" direction="out"/>- <arg type="u" name="timestamp" direction="out"/>- </signal>- </interface>-</node>-
gtk-sni-tray.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: gtk-sni-tray-version: 0.1.12.0+version: 0.1.13.0 synopsis: A standalone StatusNotifierItem/AppIndicator tray description: Please see the README on Github at <https://github.com/IvanMalison/gtk-sni-tray#readme> category: System@@ -20,7 +20,6 @@ extra-source-files: README.md ChangeLog.md- dbus-xml/com.canonical.dbusmenu.xml source-repository head type: git@@ -31,9 +30,7 @@ StatusNotifier.TransparentWindow StatusNotifier.Tray other-modules:- StatusNotifier.DBusMenu- StatusNotifier.DBus.Client.Util- StatusNotifier.DBus.Client.DBusMenu+ Paths_gtk_sni_tray hs-source-dirs: src pkgconfig-depends:@@ -43,6 +40,7 @@ , bytestring >=0.10 && <0.13 , containers >=0.5 && <0.8 , dbus ==1.*+ , dbus-menu >=0.1.1 && <0.2 , directory >=1.2 && <1.4 , enclosed-exceptions >=1.0.0.1 && <1.1 , filepath >=1.3 && <1.6@@ -59,7 +57,6 @@ , haskell-gi-base >=0.21.1 && <0.27 , hslogger >=1.2 && <1.4 , status-notifier-item >=0.3.2 && <0.4- , template-haskell >=2.10 && <2.23 , text >=1.2 && <2.2 , transformers >=0.4 && <0.7 , transformers-base ==0.4.*
− src/StatusNotifier/DBus/Client/DBusMenu.hs
@@ -1,15 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module StatusNotifier.DBus.Client.DBusMenu where--import DBus.Generation-import System.FilePath-import StatusNotifier.DBus.Client.Util---- Generates DBus client functions/signals for the com.canonical.dbusmenu--- interface from introspection XML, similar to Taffybar's approach.-generateClientFromFile- defaultRecordGenerationParams- defaultGenerationParams { genTakeSignalErrorHandler = True }- False- ("dbus-xml" </> "com.canonical.dbusmenu.xml")-
− src/StatusNotifier/DBus/Client/Util.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskellQuotes #-}-module StatusNotifier.DBus.Client.Util- ( RecordGenerationParams(..)- , GetTypeForName- , defaultRecordGenerationParams- , generateClientFromFile- ) where--import Control.Monad (forM)-import DBus (ObjectPath)-import DBus.Generation-import qualified DBus.Internal.Types as DBusTypes-import qualified DBus.Introspection as I-import qualified Data.Char as Char-import qualified Data.Coerce as Coerce-import qualified Data.Maybe as Maybe-import qualified Data.Text.IO as TIO-import Language.Haskell.TH-import Language.Haskell.TH.Syntax (addDependentFile, makeRelativeToProject)--type GetTypeForName = String -> DBusTypes.Type -> Maybe Type--data RecordGenerationParams = RecordGenerationParams- { recordName :: Maybe String- , recordPrefix :: String- , recordTypeForName :: GetTypeForName- }--defaultRecordGenerationParams :: RecordGenerationParams-defaultRecordGenerationParams = RecordGenerationParams- { recordName = Nothing- , recordPrefix = "_"- , recordTypeForName = const $ const Nothing- }--deriveShowAndEQ :: [DerivClause]-deriveShowAndEQ =- [DerivClause Nothing [ConT ''Eq, ConT ''Show]]--buildDataFromNameTypePairs :: Name -> [(Name, Type)] -> Dec-buildDataFromNameTypePairs name pairs =- DataD [] name [] Nothing [RecC name (map mkVarBangType pairs)] deriveShowAndEQ- where- mkVarBangType (fieldName, fieldType) =- ( fieldName- , Bang NoSourceUnpackedness NoSourceStrictness- , fieldType- )--generateGetAllRecord- :: RecordGenerationParams- -> GenerationParams- -> I.Interface- -> Q [Dec]-generateGetAllRecord- RecordGenerationParams- { recordName = recordNameString- , recordPrefix = prefix- , recordTypeForName = getTypeForName- }- GenerationParams { getTHType = getArgType }- I.Interface- { I.interfaceName = interfaceName- , I.interfaceProperties = properties- } = do- let theRecordName =- mkName $- maybe- (map Char.toUpper $ filter Char.isLetter $ Coerce.coerce interfaceName)- id- recordNameString- getPairFromProperty- I.Property { I.propertyName = propName, I.propertyType = propType } =- ( mkName $ prefix ++ propName- , Maybe.fromMaybe (getArgType propType) $- getTypeForName propName propType- )- getAllRecord =- buildDataFromNameTypePairs theRecordName $- map getPairFromProperty properties- pure [getAllRecord]--getIntrospectionObjectFromFile :: FilePath -> ObjectPath -> Q I.Object-getIntrospectionObjectFromFile filepath path = do- realPath <- makeRelativeToProject filepath- addDependentFile realPath- xml <- runIO (TIO.readFile realPath)- case I.parseXML path xml of- Nothing -> fail $ "Failed to parse DBus introspection XML: " <> filepath- Just obj -> pure obj--generateClientFromFile- :: RecordGenerationParams- -> GenerationParams- -> Bool- -> FilePath- -> Q [Dec]-generateClientFromFile recordGenerationParams params useObjectPath filepath = do- obj <- getIntrospectionObjectFromFile filepath "/"- let actualObjectPath = I.objectPath obj- realParams =- if useObjectPath- then params { genObjectPath = Just actualObjectPath }- else params- (<++>) = liftA2 (++)- fmap concat $ forM (I.objectInterfaces obj) $ \interface -> do- generateGetAllRecord recordGenerationParams params interface <++>- generateClient realParams interface <++>- generateSignalsFromInterface realParams interface
− src/StatusNotifier/DBusMenu.hs
@@ -1,223 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module StatusNotifier.DBusMenu- ( buildMenu- ) where--import Control.Concurrent (forkIO)-import Control.Exception.Enclosed (catchAny)-import Control.Monad (forM_, void, when)-import Data.Int (Int32)-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Maybe (fromMaybe)-import qualified Data.Text as T-import Data.Word (Word32)-import DBus-import DBus.Client-import Data.GI.Base (unsafeCastTo)-import qualified GI.Gtk as Gtk-import System.Log.Logger (Priority(..), logM)-import Text.Printf--import qualified StatusNotifier.DBus.Client.DBusMenu as DM--dbusMenuLogger :: Priority -> String -> IO ()-dbusMenuLogger = logM "StatusNotifier.DBusMenu"--addCssClass :: Gtk.Widget -> T.Text -> IO ()-addCssClass widget cssClass =- Gtk.widgetGetStyleContext widget >>= (`Gtk.styleContextAddClass` cssClass)--data LayoutNode = LayoutNode- { lnId :: Int32- , lnProps :: Map String Variant- , lnChildren :: [LayoutNode]- } deriving (Eq, Show)--type LayoutTuple = (Int32, Map String Variant, [Variant])--variantToLayout :: Variant -> Maybe LayoutNode-variantToLayout v = do- (i, props, kids) <- fromVariant v :: Maybe LayoutTuple- children <- traverse variantToLayout kids- pure LayoutNode { lnId = i, lnProps = props, lnChildren = children }--tupleToLayout :: LayoutTuple -> LayoutNode-tupleToLayout (i, props, kids) =- LayoutNode- { lnId = i- , lnProps = props- , lnChildren = [ n | v <- kids, Just n <- [variantToLayout v] ]- }---- | Unwrap an Either MethodError, failing on Left.-unwrapCall :: String -> Either MethodError a -> IO a-unwrapCall label (Left err) = fail $ label <> " failed: " <> show err-unwrapCall _ (Right a) = pure a--aboutToShow :: Client -> BusName -> ObjectPath -> Int32 -> IO Bool-aboutToShow client dest path i =- either (const False) id <$> DM.aboutToShow client dest path i--getLayout :: Client -> BusName -> ObjectPath -> Int32 -> Int32 -> [String] -> IO (Word32, LayoutNode)-getLayout client dest path parentId depth propNames = do- (rev, tup) <- unwrapCall "GetLayout" =<<- DM.getLayout client dest path parentId depth propNames- pure (rev, tupleToLayout tup)--sendClicked :: Client -> BusName -> ObjectPath -> Int32 -> Word32 -> IO ()-sendClicked client dest path itemId ts = do- dbusMenuLogger DEBUG $- printf "sendClicked: id=%d dest=%s path=%s ts=%d"- itemId (show dest) (show path) ts- let mc = DM.eventMethodCall- { methodCallDestination = Just dest- , methodCallPath = path- , methodCallBody =- [ toVariant itemId- , toVariant ("clicked" :: String)- , toVariant (toVariant (0 :: Int32))- , toVariant ts- ]- }- -- Send on a forked thread to avoid blocking GTK; use `call` instead of- -- `callNoReply` so we can detect service errors.- void $ forkIO $ catchAny- (do result <- call client mc- case result of- Left err -> dbusMenuLogger WARNING $- printf "sendClicked: Event error: %s" (show err)- Right _ -> dbusMenuLogger DEBUG "sendClicked: Event succeeded")- (\e -> dbusMenuLogger WARNING $- printf "sendClicked: Event exception: %s" (show e))--getPropS :: String -> LayoutNode -> Maybe String-getPropS key LayoutNode { lnProps = props } =- Map.lookup key props >>= fromVariant--getPropB :: String -> LayoutNode -> Maybe Bool-getPropB key LayoutNode { lnProps = props } =- Map.lookup key props >>= fromVariant--getPropI32 :: String -> LayoutNode -> Maybe Int32-getPropI32 key LayoutNode { lnProps = props } =- Map.lookup key props >>= fromVariant--menuItemType :: LayoutNode -> Maybe String-menuItemType = getPropS "type"--menuItemLabel :: LayoutNode -> String-menuItemLabel n =- -- libdbusmenu uses "label" with underscores for mnemonics; GTK3 MenuItem- -- has use-underline support, but defaulting to literal label is fine.- fromMaybe "" (getPropS "label" n)--menuItemVisible :: LayoutNode -> Bool-menuItemVisible n = fromMaybe True (getPropB "visible" n)--menuItemEnabled :: LayoutNode -> Bool-menuItemEnabled n = fromMaybe True (getPropB "enabled" n)--menuItemToggleType :: LayoutNode -> Maybe String-menuItemToggleType = getPropS "toggle-type"--menuItemToggleState :: LayoutNode -> Maybe Int32-menuItemToggleState = getPropI32 "toggle-state"--populateGtkMenu :: Client -> BusName -> ObjectPath -> Gtk.Menu -> LayoutNode -> IO ()-populateGtkMenu client dest path gtkMenu root = do- gtkMenuW <- Gtk.toWidget gtkMenu- addCssClass gtkMenuW "tray-menu"-- -- Clear existing children (for refreshes, e.g. submenus).- children <- Gtk.containerGetChildren gtkMenu- forM_ children Gtk.widgetDestroy-- forM_ (lnChildren root) $ \child -> when (menuItemVisible child) $ do- widget <- buildGtkMenuItem client dest path child- Gtk.menuShellAppend gtkMenu widget--buildGtkMenuItem :: Client -> BusName -> ObjectPath -> LayoutNode -> IO Gtk.MenuItem-buildGtkMenuItem client dest path node = do- item <- case menuItemType node of- Just "separator" -> do- sep <- Gtk.separatorMenuItemNew- unsafeCastTo Gtk.MenuItem sep- _ -> do- let label = T.pack (menuItemLabel node)- case menuItemToggleType node of- Just "checkmark" -> do- c <- Gtk.checkMenuItemNewWithMnemonic label- Gtk.checkMenuItemSetActive c (menuItemToggleState node == Just 1)- unsafeCastTo Gtk.MenuItem c- Just "radio" -> do- c <- Gtk.checkMenuItemNewWithMnemonic label- Gtk.checkMenuItemSetDrawAsRadio c True- Gtk.checkMenuItemSetActive c (menuItemToggleState node == Just 1)- unsafeCastTo Gtk.MenuItem c- _ -> Gtk.menuItemNewWithMnemonic label-- Gtk.widgetSetName item (T.pack ("tray-menu-item-" <> show (lnId node)))- itemW <- Gtk.toWidget item- addCssClass itemW "tray-menu-item"-- case menuItemType node of- Just "separator" -> addCssClass itemW "tray-menu-separator"- _ -> pure ()-- case menuItemToggleType node of- Just "checkmark" -> addCssClass itemW "tray-menu-check"- Just "radio" -> addCssClass itemW "tray-menu-radio"- _ -> pure ()-- Gtk.widgetSetSensitive item (menuItemEnabled node)-- -- Submenu handling: build children now, and refresh on show via AboutToShow/GetLayout.- if null (lnChildren node)- then do- _ <- Gtk.onMenuItemActivate item $- catchAny- (do ts <- Gtk.getCurrentEventTime- sendClicked client dest path (lnId node) ts)- (\e -> dbusMenuLogger WARNING $- printf "Menu item %d click failed (stale ID?): %s"- (lnId node) (show e))- pure ()- else do- addCssClass itemW "tray-menu-item-has-submenu"- submenu <- Gtk.menuNew- Gtk.widgetSetName submenu (T.pack ("tray-menu-submenu-" <> show (lnId node)))- submenuW <- Gtk.toWidget submenu- addCssClass submenuW "tray-menu-submenu"- -- Populate with the eagerly-fetched layout so submenus are usable even if- -- the service doesn't support/require lazy updates.- populateGtkMenu client dest path submenu node- let refresh =- catchAny- (do -- Allow the service to update the submenu content lazily.- _ <- aboutToShow client dest path (lnId node)- (_, layout) <- getLayout client dest path (lnId node) 1 []- populateGtkMenu client dest path submenu layout- Gtk.widgetShowAll submenu)- (\e -> dbusMenuLogger WARNING $- printf "Submenu %d refresh failed (stale ID?): %s"- (lnId node) (show e))- _ <- Gtk.onWidgetShow submenu refresh- Gtk.menuItemSetSubmenu item (Just submenu)-- pure item--buildMenu :: Client -> BusName -> ObjectPath -> IO Gtk.Menu-buildMenu client dest path = do- dbusMenuLogger DEBUG $- printf "buildMenu: dest=%s path=%s" (show dest) (show path)- _ <- aboutToShow client dest path 0- (_, layout) <- getLayout client dest path 0 (-1) []- dbusMenuLogger DEBUG $- printf "buildMenu: root has %d children" (length (lnChildren layout))- menu <- Gtk.menuNew- Gtk.widgetSetName menu "tray-menu-root"- menuW <- Gtk.toWidget menu- addCssClass menuW "tray-menu-root"- populateGtkMenu client dest path menu layout- pure menu
src/StatusNotifier/Tray.hs view
@@ -39,7 +39,7 @@ import GI.Gtk.Flags import GI.Gtk.Objects.IconTheme import Graphics.UI.GIGtkStrut-import qualified StatusNotifier.DBusMenu as DBusMenu+import qualified DBusMenu import StatusNotifier.Host.Service import qualified StatusNotifier.Item.Client as IC import System.Directory@@ -63,7 +63,7 @@ getRatio toScale = fromIntegral targetSize / fromIntegral toScale getOther :: Int32 -> Int32 -> Int32- getOther toScale other = floor $ getRatio toScale * fromIntegral other+ getOther toScale other = max 1 $ floor $ getRatio toScale * fromIntegral other in if shouldTargetWidth then (targetSize, getOther width height)@@ -225,7 +225,7 @@ , trayImageSize = Expand , trayIconExpand = False , trayAlignment = End- , trayOverlayScale = 3 % 5+ , trayOverlayScale = 2 % 5 , trayLeftClickAction = Activate , trayMiddleClickAction = SecondaryActivate , trayRightClickAction = PopupMenu