----------------------------------------------------------------------------- -- | -- Module : System.Taffybar.Widget.XDGMenu.MenuWidget -- Copyright : 2017 Ulf Jasper -- License : BSD3-style (see LICENSE) -- -- Maintainer : Ulf Jasper -- Stability : unstable -- Portability : unportable -- -- MenuWidget provides a hierachical GTK menu containing all -- applicable desktop entries found on the system. The menu is built -- according to the version 1.1 of the XDG "Desktop Menu -- Specification", see -- https://specifications.freedesktop.org/menu-spec/menu-spec-1.1.html ----------------------------------------------------------------------------- module System.Taffybar.Widget.XDGMenu.MenuWidget ( -- * Usage -- $usage menuWidgetNew ) where import Control.Monad import Control.Monad.IO.Class import qualified Data.Text as T import GI.Gtk hiding (Menu) import GI.GdkPixbuf import System.Directory import System.FilePath.Posix import System.Process import System.Taffybar.Widget.XDGMenu.Menu -- $usage -- -- In order to use this widget add the following line to your -- @taffybar.hs@ file: -- -- > import System.Taffybar.Widget.XDGMenu.MenuWidget -- > main = do -- > let menu = menuWidgetNew $ Just "PREFIX-" -- -- The menu will look for a file named "PREFIX-applications.menu" in -- the (subdirectory "menus" of the) directories specified by the -- environment variables XDG_CONFIG_HOME and XDG_CONFIG_DIRS. (If -- XDG_CONFIG_HOME is not set or empty then $HOME/.config is used, if -- XDG_CONFIG_DIRS is not set or empty then "/etc/xdg" is used). If -- no prefix is given (i.e. if you pass Nothing) then the value of the -- environment variable XDG_MENU_PREFIX is used, if it is set. If -- taffybar is running inside a desktop environment like Mate, Gnome, -- XFCE etc. the environment variables XDG_CONFIG_DIRS and -- XDG_MENU_PREFIX should be set and you may create the menu like -- this: -- -- > let menu = menuWidgetNew Nothing -- -- Now you can use @menu@ as any other Taffybar widget. -- | Add a desktop entry to a gtk menu by appending a gtk menu item. addItem :: (IsMenuShell msc) => msc -- ^ GTK menu -> MenuEntry -- ^ Desktop entry -> IO () addItem ms de = do item <- imageMenuItemNewWithLabel (feName de) setWidgetTooltipText item (feComment de) setIcon item (T.unpack <$> feIcon de) menuShellAppend ms item _ <- onMenuItemActivate item $ do let cmd = feCommand de putStrLn $ "Launching '" ++ cmd ++ "'" _ <- spawnCommand cmd return () return () -- | Add an xdg menu to a gtk menu by appending gtk menu items and -- submenus. addMenu :: (IsMenuShell msc) => msc -- ^ GTK menu -> Menu -- ^ menu -> IO () addMenu ms fm = do let subMenus = fmSubmenus fm items = fmEntries fm when (not (null items) || not (null subMenus)) $ do item <- imageMenuItemNewWithLabel (T.pack $ fmName fm) setIcon item (fmIcon fm) menuShellAppend ms item subMenu <- menuNew menuItemSetSubmenu item (Just subMenu) mapM_ (addMenu subMenu) subMenus mapM_ (addItem subMenu) items setIcon :: ImageMenuItem -> Maybe String -> IO () setIcon _ Nothing = return () setIcon item (Just iconName) = do iconTheme <- iconThemeGetDefault hasIcon <- iconThemeHasIcon iconTheme (T.pack iconName) mImg <- if hasIcon then Just <$> imageNewFromIconName (Just $ T.pack iconName) (fromIntegral $ fromEnum IconSizeMenu) else if isAbsolute iconName then do ex <- doesFileExist iconName if ex then do let defaultSize = 24 -- FIXME should auto-adjust to font size pb <- pixbufNewFromFileAtScale iconName defaultSize defaultSize True Just <$> imageNewFromPixbuf (Just pb) else return Nothing else return Nothing case mImg of Just img -> imageMenuItemSetImage item (Just img) Nothing -> putStrLn $ "Icon not found: " ++ iconName -- | Create a new XDG Menu Widget. menuWidgetNew :: MonadIO m => Maybe String -- ^ menu name, must end with a dash, -- e.g. "mate-" or "gnome-" -> m GI.Gtk.Widget menuWidgetNew mMenuPrefix = liftIO $ do mb <- menuBarNew m <- buildMenu mMenuPrefix addMenu mb m widgetShowAll mb toWidget mb -- -- | Show XDG Menu Widget in a standalone frame. -- testMenuWidget :: IO () -- testMenuWidget = do -- _ <- initGUI -- window <- windowNew -- _ <- window `on` deleteEvent $ liftIO mainQuit >> return False -- containerAdd window =<< menuWidgetNew Nothing -- widgetShowAll window -- mainGUI