taffybar 3.1.2 → 3.2.0
raw patch · 39 files changed
+495/−235 lines, 39 filesdep +ansi-terminaldep +broadcast-chandep ~scotty
Dependencies added: ansi-terminal, broadcast-chan
Dependency ranges changed: scotty
Files
- CHANGELOG.md +42/−0
- README.md +6/−0
- src/System/Taffybar.hs +19/−6
- src/System/Taffybar/Context.hs +2/−2
- src/System/Taffybar/DBus/Toggle.hs +13/−5
- src/System/Taffybar/Hooks.hs +14/−5
- src/System/Taffybar/Information/Battery.hs +31/−11
- src/System/Taffybar/Information/Chrome.hs +7/−5
- src/System/Taffybar/Information/Memory.hs +1/−1
- src/System/Taffybar/Information/Network.hs +3/−1
- src/System/Taffybar/Information/SafeX11.hs +8/−5
- src/System/Taffybar/Information/XDG/DesktopEntry.hs +17/−30
- src/System/Taffybar/Information/XDG/Protocol.hs +5/−4
- src/System/Taffybar/LogFormatter.hs +48/−0
- src/System/Taffybar/SimpleConfig.hs +13/−8
- src/System/Taffybar/Util.hs +7/−2
- src/System/Taffybar/Widget.hs +5/−4
- src/System/Taffybar/Widget/CommandRunner.hs +1/−2
- src/System/Taffybar/Widget/Decorators.hs +2/−2
- src/System/Taffybar/Widget/FSMonitor.hs +1/−1
- src/System/Taffybar/Widget/FreedesktopNotifications.hs +18/−15
- src/System/Taffybar/Widget/Generic/ChannelGraph.hs +7/−5
- src/System/Taffybar/Widget/Generic/ChannelWidget.hs +5/−3
- src/System/Taffybar/Widget/Generic/Graph.hs +1/−1
- src/System/Taffybar/Widget/Generic/Icon.hs +1/−1
- src/System/Taffybar/Widget/Generic/PollingLabel.hs +27/−17
- src/System/Taffybar/Widget/Generic/VerticalBar.hs +1/−1
- src/System/Taffybar/Widget/Layout.hs +3/−0
- src/System/Taffybar/Widget/MPRIS2.hs +1/−1
- src/System/Taffybar/Widget/SimpleClock.hs +92/−44
- src/System/Taffybar/Widget/SimpleCommandButton.hs +46/−0
- src/System/Taffybar/Widget/Text/CPUMonitor.hs +1/−2
- src/System/Taffybar/Widget/Text/MemoryMonitor.hs +1/−2
- src/System/Taffybar/Widget/Util.hs +2/−2
- src/System/Taffybar/Widget/Weather.hs +6/−5
- src/System/Taffybar/Widget/Workspaces.hs +15/−14
- src/System/Taffybar/Widget/XDGMenu/MenuWidget.hs +11/−20
- taffybar.cabal +9/−5
- taffybar.hs.example +3/−3
CHANGELOG.md view
@@ -1,3 +1,45 @@+# 3.2.0++## New Features++ * The Layout widget can now be styled with the css class "layout-label".++ * A new polling label function `pollingLabelWithVariableDelay` that allows for+ variable poll times was added.++ * A new widget `System.Taffybar.Widget.SimpleCommandButton` was added.++ * Taffybar now outputs colorized and annotated logs by default.++## Breaking Changes++ * The file specified in the cssPath parameter in config is now used instead of,+ rather than in addition to the default user config file.++ * All parameters are now passed to `textClockNewWith` as part of the+ ClockConfig it receives. A new mechanism for rounded variable polling should+ allow the clock to always remain accurate (to the precision selected by the+ user) without having a very high polling rate, thus reducing CPU usage.++ * The polling label functions no longer accept a default text parameter.++## Miscellaneous++ * Battery updates are only triggered when a more limited number of UPower+ properties are changed. This can be customized by manually calling+ `setupDisplayBatteryChanVar` as a hook.++## Bug Fixes++ * Calendar pops up below bar without hiding any other widget #261.++ * Avoid failing when parsing XDG Desktop files with unrecognized application+ type, which previously resulted in "Prelude.read: no parse" #447.++ * Use XDG data dir so that taffybar dbus toggling functions correctly when+ taffybar is installed in a location that is not writable by the user. This is+ the case with nix when it is installed in the nix store #452.+ # 3.1.2 ## Updates
README.md view
@@ -72,6 +72,12 @@ list of available widgets [here](http://hackage.haskell.org/package/taffybar-2.0.0/docs/System-Taffybar-Widget.html) +FAQ+---++For the time being, taffybar's frequently asked questions page lives in [this+github issue](https://github.com/taffybar/taffybar/issues/332).+ Contributing ------------
src/System/Taffybar.hs view
@@ -117,7 +117,9 @@ import System.Exit ( exitFailure ) import System.FilePath ( (</>) ) import qualified System.IO as IO+import System.Log.Logger import System.Taffybar.Context+import System.Taffybar.Hooks import Paths_taffybar ( getDataDir ) @@ -154,23 +156,29 @@ dataDir <- getDataDir return (dataDir </> name) -startCSS :: Maybe FilePath -> IO Gtk.CssProvider-startCSS configCssPath = do+startCSS :: [FilePath] -> IO Gtk.CssProvider+startCSS cssPaths = do -- Override the default GTK theme path settings. This causes the -- bar (by design) to ignore the real GTK theme and just use the -- provided minimal theme to set the background and text colors. -- Users can override this default. taffybarProvider <- Gtk.cssProviderNew+ let loadIfExists filePath = doesFileExist filePath >>= flip when (Gtk.cssProviderLoadFromPath taffybarProvider (T.pack filePath))- loadIfExists =<< getDefaultConfigFile "taffybar.css"- mapM_ loadIfExists configCssPath- loadIfExists =<< getUserConfigFile "taffybar" "taffybar.css"++ mapM_ loadIfExists cssPaths+ Just scr <- Gdk.screenGetDefault Gtk.styleContextAddProviderForScreen scr taffybarProvider 800 return taffybarProvider +getDefaultCSSPaths :: IO [FilePath]+getDefaultCSSPaths = do+ defaultUserConfig <- getUserConfigFile "taffybar" "taffybar.css"+ return [defaultUserConfig]+ -- | Start taffybar with the provided 'TaffybarConfig'. Because this function -- will not handle recompiling taffybar automatically when taffybar.hs is -- updated, it is generally recommended that end users use 'dyreTaffybar'@@ -179,10 +187,15 @@ -- perfectly fine to use this function. startTaffybar :: TaffybarConfig -> IO () startTaffybar config = do+ updateGlobalLogger "" $ removeHandler+ setTaffyLogFormatter "System.Taffybar"+ setTaffyLogFormatter "StatusNotifier" _ <- initThreads _ <- Gtk.init Nothing GIThreading.setCurrentThreadAsGUIThread- _ <- startCSS $ cssPath config+ defaultConfig <- getDefaultConfigFile "taffybar.css"+ cssPaths <- maybe getDefaultCSSPaths (return . return) $ cssPath config+ _ <- startCSS $ defaultConfig:cssPaths _ <- buildContext config Gtk.main
src/System/Taffybar/Context.hs view
@@ -167,9 +167,9 @@ show $ strutConfig barConfig window <- Gtk.windowNew Gtk.WindowTypeToplevel- box <- Gtk.hBoxNew False $ fromIntegral $ widgetSpacing barConfig+ box <- Gtk.boxNew Gtk.OrientationHorizontal $ fromIntegral $ widgetSpacing barConfig _ <- widgetSetClassGI box "taffy-box"- centerBox <- Gtk.hBoxNew False $ fromIntegral $ widgetSpacing barConfig+ centerBox <- Gtk.boxNew Gtk.OrientationHorizontal $ fromIntegral $ widgetSpacing barConfig Gtk.boxSetCenterWidget box (Just centerBox) setupStrutWindow (strutConfig barConfig) window
src/System/Taffybar/DBus/Toggle.hs view
@@ -17,6 +17,7 @@ import Control.Applicative import qualified Control.Concurrent.MVar as MV+import Control.Exception import Control.Monad import Control.Monad.Trans.Class import Control.Monad.Trans.Maybe@@ -29,9 +30,9 @@ import qualified GI.Gdk as Gdk import qualified GI.Gtk as Gtk import Graphics.UI.GIGtkStrut-import Paths_taffybar ( getDataDir ) import Prelude import System.Directory+import System.Environment.XDG.BaseDir import System.FilePath.Posix import System.Log.Logger import System.Taffybar.Context hiding (logIO, logT)@@ -88,8 +89,11 @@ taffybarToggleInterface :: InterfaceName taffybarToggleInterface = "taffybar.toggle" +taffyDir :: IO FilePath+taffyDir = getUserDataDir "taffybar"+ toggleStateFile :: IO FilePath-toggleStateFile = (</> "toggleState.hs") <$> getDataDir+toggleStateFile = (</> "toggle_state.dat") <$> taffyDir newtype TogglesMVar = TogglesMVar (MV.MVar (M.Map Int Bool)) @@ -110,13 +114,17 @@ exportTogglesInterface = do TogglesMVar enabledVar <- getTogglesVar ctx <- ask+ lift $ taffyDir >>= createDirectoryIfMissing True+ stateFile <- lift toggleStateFile let toggleTaffyOnMon fn mon = flip runReaderT ctx $ do lift $ MV.modifyMVar_ enabledVar $ \numToEnabled -> do let current = fromMaybe True $ M.lookup mon numToEnabled result = M.insert mon (fn current) numToEnabled- logIO DEBUG $ printf "Toggle state before: %s" $ show numToEnabled- logIO DEBUG $ printf "Toggle state after: %s" $ show result- flip writeFile (show result) =<< toggleStateFile+ logIO DEBUG $ printf "Toggle state before: %s, after %s"+ (show numToEnabled) (show result)+ catch (writeFile stateFile (show result)) $ \e ->+ logIO WARNING $ printf "Unable to write to toggle state file %s, error: %s"+ (show stateFile) (show (e :: SomeException)) return result refreshTaffyWindows toggleTaffy = do
src/System/Taffybar/Hooks.hs view
@@ -2,12 +2,14 @@ ( module System.Taffybar.DBus , module System.Taffybar.Hooks , ChromeTabImageData(..)- , refreshBatteriesOnPropChange- , getX11WindowToChromeTabId , getChromeTabImageDataChannel , getChromeTabImageDataTable+ , getX11WindowToChromeTabId+ , refreshBatteriesOnPropChange+ , setTaffyLogFormatter ) where +import BroadcastChan import Control.Applicative import Control.Concurrent import Control.Monad@@ -16,24 +18,31 @@ import Data.Maybe import qualified Data.MultiMap as MM import System.FilePath+import System.Log.Logger import System.Taffybar.Context import System.Taffybar.DBus import System.Taffybar.Information.Battery import System.Taffybar.Information.Chrome import System.Taffybar.Information.Network import System.Taffybar.Information.XDG.DesktopEntry+import System.Taffybar.LogFormatter import System.Taffybar.Util -newtype NetworkInfoChan = NetworkInfoChan (Chan [(String, (Rational, Rational))])+newtype NetworkInfoChan = NetworkInfoChan (BroadcastChan In [(String, (Rational, Rational))]) buildInfoChan :: Double -> IO NetworkInfoChan buildInfoChan interval = do- chan <- newChan- _ <- forkIO $ monitorNetworkInterfaces interval $ writeChan chan+ chan <- newBroadcastChan+ _ <- forkIO $ monitorNetworkInterfaces interval (void . writeBChan chan) return $ NetworkInfoChan chan getNetworkChan :: TaffyIO NetworkInfoChan getNetworkChan = getStateDefault $ lift $ buildInfoChan 2.0++setTaffyLogFormatter :: String -> IO ()+setTaffyLogFormatter loggerName = do+ handler <- taffyLogHandler+ updateGlobalLogger loggerName $ setHandlers [handler] withBatteryRefresh :: TaffybarConfig -> TaffybarConfig withBatteryRefresh = appendHook refreshBatteriesOnPropChange
src/System/Taffybar/Information/Battery.hs view
@@ -11,6 +11,7 @@ , module System.Taffybar.Information.Battery ) where +import BroadcastChan import Control.Concurrent import Control.Monad import Control.Monad.IO.Class@@ -140,24 +141,33 @@ return $ filter isBattery paths newtype DisplayBatteryChanVar =- DisplayBatteryChanVar (Chan BatteryInfo, MVar BatteryInfo)+ DisplayBatteryChanVar (BroadcastChan In BatteryInfo, MVar BatteryInfo) getDisplayBatteryInfo :: TaffyIO BatteryInfo getDisplayBatteryInfo = do DisplayBatteryChanVar (_, theVar) <- getDisplayBatteryChanVar lift $ readMVar theVar +defaultMonitorDisplayBatteryProperties :: [String]+defaultMonitorDisplayBatteryProperties = [ "IconName", "State", "Percentage" ]++-- | Start the monitoring of the display battery, and setup the associated+-- channel and mvar for the current state.+setupDisplayBatteryChanVar :: [String] -> TaffyIO DisplayBatteryChanVar+setupDisplayBatteryChanVar properties = getStateDefault $+ DisplayBatteryChanVar <$> monitorDisplayBattery properties+ getDisplayBatteryChanVar :: TaffyIO DisplayBatteryChanVar getDisplayBatteryChanVar =- getStateDefault $ DisplayBatteryChanVar <$> monitorDisplayBattery+ setupDisplayBatteryChanVar defaultMonitorDisplayBatteryProperties -getDisplayBatteryChan :: TaffyIO (Chan BatteryInfo)+getDisplayBatteryChan :: TaffyIO (BroadcastChan In BatteryInfo) getDisplayBatteryChan = do DisplayBatteryChanVar (chan, _) <- getDisplayBatteryChanVar return chan updateBatteryInfo- :: Chan BatteryInfo+ :: BroadcastChan In BatteryInfo -> MVar BatteryInfo -> ObjectPath -> TaffyIO ()@@ -166,27 +176,37 @@ where doWrites info = batteryLogF DEBUG "Writing info %s" info >>- swapMVar var info >> writeChan chan info+ swapMVar var info >> void (writeBChan chan info) warnOfFailure = batteryLogF WARNING "Failed to update battery info %s" registerForAnyUPowerPropertiesChanged :: (Signal -> String -> Map String Variant -> [String] -> IO ()) -> ReaderT Context IO SignalHandler-registerForAnyUPowerPropertiesChanged signalHandler = do+registerForAnyUPowerPropertiesChanged = registerForUPowerPropertyChanges []++registerForUPowerPropertyChanges+ :: [String]+ -> (Signal -> String -> Map String Variant -> [String] -> IO ())+ -> ReaderT Context IO SignalHandler+registerForUPowerPropertyChanges properties signalHandler = do client <- asks systemDBusClient lift $ DBus.registerForPropertiesChanged client matchAny { matchInterface = Just uPowerDeviceInterfaceName }- signalHandler+ handleIfPropertyMatches+ where handleIfPropertyMatches rawSignal n propertiesMap l =+ let propertyPresent prop = isJust $ M.lookup prop propertiesMap+ in when (any propertyPresent properties || null properties) $+ signalHandler rawSignal n propertiesMap l -- | Monitor the DisplayDevice for changes, writing a new "BatteryInfo" object -- to returned "MVar" and "Chan" objects-monitorDisplayBattery :: TaffyIO (Chan BatteryInfo, MVar BatteryInfo)-monitorDisplayBattery = do+monitorDisplayBattery :: [String] -> TaffyIO (BroadcastChan In BatteryInfo, MVar BatteryInfo)+monitorDisplayBattery propertiesToMonitor = do lift $ batteryLog DEBUG "Starting Battery Monitor" client <- asks systemDBusClient infoVar <- lift $ newMVar $ infoMapToBatteryInfo M.empty- chan <- lift newChan+ chan <- newBroadcastChan taffyFork $ do ctx <- ask let warnOfFailedGetDevice err =@@ -199,7 +219,7 @@ do batteryLogF DEBUG "Battery changed properties: %s" changedProps runReaderT doUpdate ctx- _ <- registerForAnyUPowerPropertiesChanged signalCallback+ _ <- registerForUPowerPropertyChanges propertiesToMonitor signalCallback doUpdate return () return (chan, infoVar)
src/System/Taffybar/Information/Chrome.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} module System.Taffybar.Information.Chrome where +import BroadcastChan import Control.Concurrent import Control.Monad import Control.Monad.Trans.Class@@ -29,14 +30,14 @@ newtype ChromeTabImageDataState = ChromeTabImageDataState- (MVar (M.Map Int ChromeTabImageData), Chan ChromeTabImageData)+ (MVar (M.Map Int ChromeTabImageData), BroadcastChan Out ChromeTabImageData) getChromeTabImageDataState :: TaffyIO ChromeTabImageDataState getChromeTabImageDataState = do ChromeFaviconServerPort port <- fromMaybe (ChromeFaviconServerPort 5000) <$> getState getStateDefault (listenForChromeFaviconUpdates port) -getChromeTabImageDataChannel :: TaffyIO (Chan ChromeTabImageData)+getChromeTabImageDataChannel :: TaffyIO (BroadcastChan Out ChromeTabImageData) getChromeTabImageDataChannel = do ChromeTabImageDataState (_, chan) <- getChromeTabImageDataState return chan@@ -51,7 +52,8 @@ listenForChromeFaviconUpdates :: Int -> TaffyIO ChromeTabImageDataState listenForChromeFaviconUpdates port = do infoVar <- lift $ newMVar M.empty- chan <- lift newChan+ inChan <- newBroadcastChan+ outChan <- newBChanListener inChan _ <- lift $ forkIO $ scotty port $ post "/setTabImageData/:tabID" $ do tabID <- param "tabID"@@ -69,10 +71,10 @@ in modifyMVar_ infoVar $ \currentMap -> do- writeChan chan chromeTabImageData+ _ <- writeBChan inChan chromeTabImageData return $ M.insert tabID chromeTabImageData currentMap Gdk.pixbufLoaderGetPixbuf loader >>= maybe (return ()) updateChannelAndMVar- return $ ChromeTabImageDataState (infoVar, chan)+ return $ ChromeTabImageDataState (infoVar, outChan) newtype X11WindowToChromeTabId = X11WindowToChromeTabId (MVar (M.Map X11Window Int))
src/System/Taffybar/Information/Memory.hs view
@@ -15,7 +15,7 @@ , memorySwapFree :: Double , memorySwapUsed :: Double -- swapTotal - swapFree , memorySwapUsedRatio :: Double -- swapUsed / swapTotal- , memoryAvailable :: Double -- An estimate of how much memory is available for starting new apps+ , memoryAvailable :: Double -- An estimate of how much memory is available , memoryRest :: Double -- free + buffer + cache , memoryUsed :: Double -- total - rest , memoryUsedRatio :: Double -- used / total
src/System/Taffybar/Information/Network.hs view
@@ -106,7 +106,9 @@ doUpdate = MV.modifyMVar_ samplesVar ((>>= doOnUpdate) . updateSamples) foreverWithDelay interval doUpdate -updateSamples :: [(String, (TxSample, TxSample))] -> IO [(String, (TxSample, TxSample))]+updateSamples ::+ [(String, (TxSample, TxSample))] ->+ IO [(String, (TxSample, TxSample))] updateSamples currentSamples = do let getLast sample@TxSample { sampleDevice = device } = maybe sample fst $ lookup device currentSamples
src/System/Taffybar/Information/SafeX11.hs view
@@ -35,8 +35,12 @@ import Prelude import System.IO import System.IO.Unsafe+import System.Log.Logger import System.Timeout+import Text.Printf +logHere = logM "System.Taffybar.Information.SafeX11"+ foreign import ccall safe "XlibExtras.h XGetWMHints" safeXGetWMHints :: Display -> Window -> IO (Ptr WMHints) @@ -124,9 +128,9 @@ startHandlingX11Requests = withErrorHandler handleError handleX11Requests where handleError _ xerrptr = do- putStrLn "Got error" ee <- getErrorEvent xerrptr- print ee+ logHere WARNING $+ printf "Handling X11 error with error handler: %s" $ show ee handleX11Requests :: IO () handleX11Requests = do@@ -136,9 +140,8 @@ catch (maybe (Left SafeX11Exception) Right <$> timeout 500000 action) (\e -> do- putStrLn "Got error on X11 thread"- hFlush stdout- print (e :: IOException)+ logHere WARNING $ printf "Handling X11 error with catch: %s" $+ show (e :: IOException) return $ Left SafeX11Exception) writeChan responseChannel res handleX11Requests
src/System/Taffybar/Information/XDG/DesktopEntry.hs view
@@ -46,16 +46,15 @@ import System.Log.Logger import System.Posix.Files import Text.Printf+import Text.Read (readMaybe) +logHere = logM "System.Taffybar.Information.XDG.DesktopEntry"+ data DesktopEntryType = Application | Link | Directory deriving (Read, Show, Eq) existingDirs :: [FilePath] -> IO [FilePath]-existingDirs dirs = do- exs <- mapM fileExist dirs- let exDirs = nub $ map fst $ filter snd $ zip dirs exs- mapM_ (putStrLn . ("Directory does not exist: " ++)) $ dirs \\ exDirs- return exDirs+existingDirs dirs = filterM fileExist dirs getDefaultConfigHome :: IO FilePath getDefaultConfigHome = do@@ -182,8 +181,7 @@ -- | Retrieve a desktop entry with a specific name. getDirectoryEntry :: [FilePath] -> String -> IO (Maybe DesktopEntry) getDirectoryEntry dirs name = do- liftIO $ logM "System.Taffybar.Information.XDG.DesktopEntry" DEBUG $- printf "Searching %s for %s" (show dirs) name+ liftIO $ logHere DEBUG $ printf "Searching %s for %s" (show dirs) name exFiles <- filterM doesFileExist $ map ((</> name) . normalise) dirs if null exFiles then return Nothing@@ -206,27 +204,16 @@ -- | Read a desktop entry from a file. readDesktopEntry :: FilePath -> IO (Maybe DesktopEntry)-readDesktopEntry fp = do- ex <- doesFileExist fp- if ex- then doReadDesktopEntry fp- else do- putStrLn $ "File does not exist: '" ++ fp ++ "'"- return Nothing+readDesktopEntry filePath = doReadDesktopEntry >>= either logWarning (return . Just) where- doReadDesktopEntry :: FilePath -> IO (Maybe DesktopEntry)- doReadDesktopEntry f = do- eResult <-- runExceptT $ do- cp <- join $ liftIO $ CF.readfile CF.emptyCP f- CF.items cp sectionMain- case eResult of- Left _ -> return Nothing- Right r ->- return $- Just- DesktopEntry- { deType = maybe Application read (lookup "Type" r)- , deFilename = f- , deAttributes = r- }+ logWarning error =+ logHere WARNING (printf "Failed to parse desktop entry at %s" filePath) >>+ return Nothing+ doReadDesktopEntry = runExceptT $ do+ result <- (join $ liftIO $ CF.readfile CF.emptyCP filePath) >>=+ flip CF.items sectionMain+ return DesktopEntry+ { deType = fromMaybe Application $ lookup "Type" result >>= readMaybe+ , deFilename = filePath+ , deAttributes = result+ }
src/System/Taffybar/Information/XDG/Protocol.hs view
@@ -39,6 +39,7 @@ import System.Directory import System.Environment import System.FilePath.Posix+import System.Log.Logger import System.Taffybar.Information.XDG.DesktopEntry import System.Taffybar.Util import Text.XML.Light@@ -250,13 +251,13 @@ maybeMenu filename = ifM (doesFileExist filename) (do- putStrLn $ "Reading " ++ filename contents <- readFile filename langs <- getPreferredLanguages runMaybeT $ do m <- MaybeT $ return $ parseXMLDoc contents >>= parseMenu des <- lift $ getApplicationEntries langs m return (m, des))- (do- putStrLn $ "Error: menu file '" ++ filename ++ "' does not exist!"- return Nothing)+ (do+ logM "System.Taffybar.Information.XDG.Protocol" WARNING $+ "Menu file '" ++ filename ++ "' does not exist!"+ return Nothing)
+ src/System/Taffybar/LogFormatter.hs view
@@ -0,0 +1,48 @@+-----------------------------------------------------------------------------+-- |+-- Module : System.Taffybar.LogFormatter+-- Copyright : (c) Ivan A. Malison+-- License : BSD3-style (see LICENSE)+--+-- Maintainer : Ivan A. Malison+-- Stability : unstable+-- Portability : unportable+-----------------------------------------------------------------------------+module System.Taffybar.LogFormatter where++import System.Console.ANSI+import System.Log.Formatter+import System.Log.Handler.Simple+import System.Log.Logger+import Text.Printf+import System.IO+import Data.Monoid++setColor :: Color -> String+setColor color = setSGRCode [SetColor Foreground Vivid color]++priorityToColor :: Priority -> Color+priorityToColor CRITICAL = Red+priorityToColor ALERT = Red+priorityToColor EMERGENCY = Red+priorityToColor ERROR = Red+priorityToColor WARNING = Yellow+priorityToColor NOTICE = Magenta+priorityToColor INFO = Blue+priorityToColor DEBUG = Green++reset :: String+reset = setSGRCode [Reset]++colorize color txt = setColor color <> txt <> reset++taffyLogFormatter :: LogFormatter a+taffyLogFormatter _ (priority, msg) name =+ return $ printf "%s %s - %s" colorizedPriority colorizedName msg+ where priorityColor = priorityToColor priority+ colorizedPriority = colorize priorityColor+ ("[" <> show priority <> "]")+ colorizedName = colorize Green name++taffyLogHandler = setFormatter <$> streamHandler stderr DEBUG+ where setFormatter h = h { formatter = taffyLogFormatter }
src/System/Taffybar/SimpleConfig.hs view
@@ -30,7 +30,7 @@ import System.Taffybar.Information.X11DesktopInfo import System.Taffybar import qualified System.Taffybar.Context as BC (BarConfig(..), TaffybarConfig(..))-import System.Taffybar.Context hiding (BarConfig(..), cssPath)+import System.Taffybar.Context hiding (TaffybarConfig(..), BarConfig(..)) import System.Taffybar.Util -- | The side of the monitor at which taffybar should be displayed.@@ -51,15 +51,17 @@ , barPosition :: Position -- | The number of pixels between widgets , widgetSpacing :: Int- -- | Widget constructors whose results are placed at the beginning of the bar+ -- | Widget constructors whose output are placed at the beginning of the bar , startWidgets :: [TaffyIO Gtk.Widget]- -- | Widget constructors whose results will be placed in the center of the bar+ -- | Widget constructors whose output are placed in the center of the bar , centerWidgets :: [TaffyIO Gtk.Widget]- -- | Widget constructors whose results are placed at the end of the bar+ -- | Widget constructors whose output are placed at the end of the bar , endWidgets :: [TaffyIO Gtk.Widget] -- | Optional path to CSS stylesheet (loaded in addition to stylesheet found -- in XDG data directory). , cssPath :: Maybe FilePath+ -- | Hook to run at taffybar startup.+ , startupHook :: TaffyIO () } -- | Sensible defaults for most of the fields of 'SimpleTaffyConfig'. You'll@@ -76,6 +78,7 @@ , centerWidgets = [] , endWidgets = [] , cssPath = Nothing+ , startupHook = return () } toStrutConfig :: SimpleTaffyConfig -> Int -> StrutConfig@@ -111,11 +114,13 @@ newtype SimpleBarConfigs = SimpleBarConfigs (MV.MVar [(Int, BC.BarConfig)]) -toTaffyConfig :: SimpleTaffyConfig -> TaffybarConfig+toTaffyConfig :: SimpleTaffyConfig -> BC.TaffybarConfig toTaffyConfig conf =- defaultTaffybarConfig { getBarConfigsParam = configGetter- , BC.cssPath = cssPath conf- }+ defaultTaffybarConfig+ { BC.getBarConfigsParam = configGetter+ , BC.cssPath = cssPath conf+ , BC.startupHook = startupHook conf+ } where configGetter = do SimpleBarConfigs configsVar <-
src/System/Taffybar/Util.hs view
@@ -85,9 +85,14 @@ ExitFailure exitCode -> Left $ printf "Exit code %s: %s " (show exitCode) stderr -- | Execute the provided IO action at the provided interval.-foreverWithDelay :: RealFrac a1 => a1 -> IO a -> IO ThreadId+foreverWithDelay :: RealFrac d => d -> IO a -> IO ThreadId foreverWithDelay delay action =- forkIO $ forever $ action >> threadDelay (floor $ delay * 1000000)+ foreverWithVariableDelay $ action >> return delay++foreverWithVariableDelay :: RealFrac d => IO d -> IO ThreadId+foreverWithVariableDelay action = forkIO $ action >>= delayThenAction+ where delayThenAction delay =+ threadDelay (floor $ delay * 1000000) >> action >>= delayThenAction liftActionTaker :: (Monad m)
src/System/Taffybar/Widget.hs view
@@ -39,11 +39,11 @@ , module System.Taffybar.Widget.SNITray -- * "System.Taffybar.Widget.SimpleClock"- , textClockNew- , textClockNewWith- , defaultClockConfig- , ClockConfig(..)+ , module System.Taffybar.Widget.SimpleClock + -- * "System.Taffybar.Widget.SimpleCommandButton"+ , simpleCommandButtonNew+ -- * "System.Taffybar.Widget.Text.CPUMonitor" , module System.Taffybar.Widget.Text.CPUMonitor @@ -87,6 +87,7 @@ import System.Taffybar.Widget.NetworkGraph import System.Taffybar.Widget.SNITray import System.Taffybar.Widget.SimpleClock+import System.Taffybar.Widget.SimpleCommandButton import System.Taffybar.Widget.Text.CPUMonitor import System.Taffybar.Widget.Text.MemoryMonitor import System.Taffybar.Widget.Text.NetworkMonitor
src/System/Taffybar/Widget/CommandRunner.hs view
@@ -34,8 +34,7 @@ -> T.Text -- ^ If command fails this will be displayed. -> m GI.Gtk.Widget commandRunnerNew interval cmd args defaultOutput =- pollingLabelNew "" interval $- runCommandWithDefault cmd args defaultOutput+ pollingLabelNew interval $ runCommandWithDefault cmd args defaultOutput runCommandWithDefault :: FilePath -> [String] -> T.Text -> IO T.Text runCommandWithDefault cmd args def =
src/System/Taffybar/Widget/Decorators.hs view
@@ -11,7 +11,7 @@ -- for the purpose of displaying a different background behind the widget. buildPadBox :: MonadIO m => Gtk.Widget -> m Gtk.Widget buildPadBox contents = liftIO $ do- innerBox <- Gtk.hBoxNew False 0+ innerBox <- Gtk.boxNew Gtk.OrientationHorizontal 0 outerBox <- Gtk.eventBoxNew Gtk.containerAdd innerBox contents Gtk.containerAdd outerBox innerBox@@ -23,7 +23,7 @@ buildContentsBox :: MonadIO m => Gtk.Widget -> m Gtk.Widget buildContentsBox widget = liftIO $ do- contents <- Gtk.hBoxNew False 0+ contents <- Gtk.boxNew Gtk.OrientationHorizontal 0 Gtk.containerAdd contents widget _ <- widgetSetClassGI contents "contents" Gtk.widgetShowAll contents
src/System/Taffybar/Widget/FSMonitor.hs view
@@ -32,7 +32,7 @@ -> [String] -- ^ Names of the partitions to monitor (e.g. [\"\/\", \"\/home\"]) -> m GI.Gtk.Widget fsMonitorNew interval fsList = liftIO $ do- label <- pollingLabelNew "" interval $ showFSInfo fsList+ label <- pollingLabelNew interval $ showFSInfo fsList GI.Gtk.widgetShowAll label GI.Gtk.toWidget label
src/System/Taffybar/Widget/FreedesktopNotifications.hs view
@@ -3,7 +3,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} -- | This widget listens on DBus for freedesktop notifications--- (http://developer.gnome.org/notification-spec/). Currently it is+-- (<http://developer.gnome.org/notification-spec/>). Currently it is -- somewhat ugly, but the format is somewhat configurable. A visual -- overhaul of the widget is coming. --@@ -28,6 +28,7 @@ , notifyAreaNew ) where +import BroadcastChan import Control.Concurrent import Control.Concurrent.STM import Control.Monad ( forever, void )@@ -64,14 +65,14 @@ , noteConfig :: NotificationConfig -- ^ The associated configuration , noteQueue :: TVar (Seq Notification) -- ^ The queue of active notifications , noteIdSource :: TVar Word32 -- ^ A source of fresh notification ids- , noteChan :: Chan () -- ^ Writing to this channel wakes up the display thread+ , noteChan :: BroadcastChan In () -- ^ Writing to this channel wakes up the display thread } initialNoteState :: Widget -> Label -> NotificationConfig -> IO NotifyState initialNoteState wrapper l cfg = do m <- newTVarIO 1 q <- newTVarIO S.empty- ch <- newChan+ ch <- newBroadcastChan return NotifyState { noteQueue = q , noteIdSource = m , noteWidget = l@@ -172,19 +173,21 @@ -------------------------------------------------------------------------------- wakeupDisplayThread :: NotifyState -> IO ()-wakeupDisplayThread s = writeChan (noteChan s) ()+wakeupDisplayThread s = void $ writeBChan (noteChan s) () -- | Refreshes the GUI displayThread :: NotifyState -> IO ()-displayThread s = forever $ do- () <- readChan (noteChan s)- ns <- readTVarIO (noteQueue s)- postGUIASync $- if S.length ns == 0- then widgetHide (noteContainer s)- else do- labelSetMarkup (noteWidget s) $ formatMessage (noteConfig s) (toList ns)- widgetShowAll (noteContainer s)+displayThread s = do+ chan <- newBChanListener (noteChan s)+ forever $ do+ _ <- readBChan chan+ ns <- readTVarIO (noteQueue s)+ postGUIASync $+ if S.length ns == 0+ then widgetHide (noteContainer s)+ else do+ labelSetMarkup (noteWidget s) $ formatMessage (noteConfig s) (toList ns)+ widgetShowAll (noteContainer s) where formatMessage NotificationConfig {..} ns = T.take notificationMaxLength $ notificationFormatter ns@@ -234,7 +237,7 @@ notifyAreaNew :: MonadIO m => NotificationConfig -> m Widget notifyAreaNew cfg = liftIO $ do frame <- frameNew Nothing- box <- hBoxNew False 3+ box <- boxNew OrientationHorizontal 3 textArea <- labelNew (Nothing :: Maybe Text) button <- eventBoxNew sep <- separatorNew OrientationHorizontal@@ -259,7 +262,7 @@ s <- initialNoteState w textArea cfg _ <- onWidgetButtonReleaseEvent button (userCancel s) - realizableWrapper <- hBoxNew False 0+ realizableWrapper <- boxNew OrientationHorizontal 0 boxPackStart realizableWrapper frame False False 0 widgetShow realizableWrapper
src/System/Taffybar/Widget/Generic/ChannelGraph.hs view
@@ -1,20 +1,22 @@ module System.Taffybar.Widget.Generic.ChannelGraph where +import BroadcastChan import Control.Concurrent import Control.Monad import Control.Monad.IO.Class+import Data.Foldable (traverse_) import GI.Gtk import System.Taffybar.Widget.Generic.Graph channelGraphNew :: MonadIO m- => GraphConfig -> Chan a -> (a -> IO [Double]) -> m GI.Gtk.Widget+ => GraphConfig -> BroadcastChan In a -> (a -> IO [Double]) -> m GI.Gtk.Widget channelGraphNew config chan sampleBuilder = do (graphWidget, graphHandle) <- graphNew config _ <- onWidgetRealize graphWidget $ do- ourChan <- dupChan chan- sampleThread <- forkIO $ forever $ do- value <- readChan ourChan- sampleBuilder value >>= graphAddSample graphHandle+ ourChan <- newBChanListener chan+ sampleThread <- forkIO $ forever $+ readBChan ourChan >>=+ traverse_ (graphAddSample graphHandle <=< sampleBuilder) void $ onWidgetUnrealize graphWidget $ killThread sampleThread return graphWidget
src/System/Taffybar/Widget/Generic/ChannelWidget.hs view
@@ -1,16 +1,18 @@ module System.Taffybar.Widget.Generic.ChannelWidget where +import BroadcastChan import Control.Concurrent import Control.Monad import Control.Monad.IO.Class+import Data.Foldable (traverse_) import GI.Gtk -channelWidgetNew :: (MonadIO m, IsWidget w) => w -> Chan a -> (a -> IO ()) -> m w+channelWidgetNew :: (MonadIO m, IsWidget w) => w -> BroadcastChan In a -> (a -> IO ()) -> m w channelWidgetNew widget channel updateWidget = do void $ onWidgetRealize widget $ do- ourChan <- dupChan channel+ ourChan <- newBChanListener channel processingThreadId <- forkIO $ forever $- readChan ourChan >>= updateWidget+ readBChan ourChan >>= traverse_ updateWidget void $ onWidgetUnrealize widget $ killThread processingThreadId widgetShowAll widget return widget
src/System/Taffybar/Widget/Generic/Graph.hs view
@@ -228,7 +228,7 @@ Gtk.widgetSetSizeRequest drawArea (fromIntegral $ graphWidth cfg) (-1) _ <- Gtk.onWidgetDraw drawArea (\ctx -> renderWithContext (drawGraph mv drawArea) ctx >> return True)- box <- Gtk.hBoxNew False 1+ box <- Gtk.boxNew Gtk.OrientationHorizontal 1 case graphLabel cfg of Nothing -> return ()
src/System/Taffybar/Widget/Generic/Icon.hs view
@@ -51,7 +51,7 @@ putInBox :: IsWidget child => child -> IO Widget putInBox icon = do- box <- hBoxNew False 0+ box <- boxNew OrientationHorizontal 0 boxPackStart box icon False False 0 widgetShowAll box toWidget box
src/System/Taffybar/Widget/Generic/PollingLabel.hs view
@@ -1,18 +1,17 @@ -- | This is a simple text widget that updates its contents by calling -- a callback at a set interval.-module System.Taffybar.Widget.Generic.PollingLabel- ( pollingLabelNew- , pollingLabelNewWithTooltip- ) where+module System.Taffybar.Widget.Generic.PollingLabel where import Control.Concurrent import Control.Exception.Enclosed as E import Control.Monad import Control.Monad.IO.Class-import System.Taffybar.Util import qualified Data.Text as T import GI.Gtk+import System.Log.Logger+import System.Taffybar.Util import System.Taffybar.Widget.Util+import Text.Printf -- | Create a new widget that updates itself at regular intervals. The -- function@@ -29,31 +28,42 @@ -- not update until the update interval expires. pollingLabelNew :: MonadIO m- => T.Text -- ^ Initial value for the label- -> Double -- ^ Update interval (in seconds)+ => Double -- ^ Update interval (in seconds) -> IO T.Text -- ^ Command to run to get the input string -> m GI.Gtk.Widget-pollingLabelNew initialString interval cmd =- pollingLabelNewWithTooltip initialString interval $ (, Nothing) <$> cmd+pollingLabelNew interval cmd =+ pollingLabelNewWithTooltip interval $ (, Nothing) <$> cmd pollingLabelNewWithTooltip :: MonadIO m- => T.Text -- ^ Initial value for the label- -> Double -- ^ Update interval (in seconds)+ => Double -- ^ Update interval (in seconds) -> IO (T.Text, Maybe T.Text) -- ^ Command to run to get the input string -> m GI.Gtk.Widget-pollingLabelNewWithTooltip initialString interval cmd =+pollingLabelNewWithTooltip interval action =+ pollingLabelWithVariableDelay $ withInterval <$> action+ where withInterval (a, b) = (a, b, interval)++pollingLabelWithVariableDelay+ :: MonadIO m+ => IO (T.Text, Maybe T.Text, Double)+ -> m GI.Gtk.Widget+pollingLabelWithVariableDelay action = liftIO $ do grid <- gridNew- label <- labelNew $ Just initialString+ label <- labelNew Nothing - let updateLabel (labelStr, tooltipStr) =+ let updateLabel (labelStr, tooltipStr, delay) = do postGUIASync $ do- labelSetMarkup label labelStr- widgetSetTooltipMarkup label tooltipStr+ labelSetMarkup label labelStr+ widgetSetTooltipMarkup label tooltipStr+ logM "System.Taffybar.Widget.Generic.PollingLabel" DEBUG $+ printf "Polling label delay was %s" $ show delay+ return delay+ updateLabelHandlingErrors =+ E.tryAny action >>= either (const $ return 1) updateLabel _ <- onWidgetRealize label $ do- sampleThread <- foreverWithDelay interval $ E.tryAny cmd >>= either (const $ return ()) updateLabel+ sampleThread <- foreverWithVariableDelay updateLabelHandlingErrors void $ onWidgetUnrealize label $ killThread sampleThread vFillCenter label
src/System/Taffybar/Widget/Generic/VerticalBar.hs view
@@ -174,7 +174,7 @@ } widgetSetSizeRequest drawArea (fromIntegral $ barWidth cfg) (-1) _ <- onWidgetDraw drawArea $ \ctx -> renderWithContext (drawBar mv drawArea) ctx >> return True- box <- hBoxNew False 1+ box <- boxNew OrientationHorizontal 1 boxPackStart box drawArea True True 0 widgetShowAll box giBox <- toWidget box
src/System/Taffybar/Widget/Layout.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedStrings #-} ----------------------------------------------------------------------------- -- | -- Module : System.Taffybar.Widget.Layout@@ -31,6 +32,7 @@ import System.Taffybar.Context import System.Taffybar.Information.X11DesktopInfo import System.Taffybar.Util+import System.Taffybar.Widget.Util -- $usage --@@ -69,6 +71,7 @@ layoutNew config = do ctx <- ask label <- lift $ Gtk.labelNew (Nothing :: Maybe T.Text)+ _ <- widgetSetClassGI label "layout-label" -- This callback is run in a separate thread and needs to use -- postGUIASync
src/System/Taffybar/Widget/MPRIS2.hs view
@@ -11,7 +11,7 @@ -- -- This is a "Now Playing" widget that listens for MPRIS2 events on DBus. You -- can find the MPRIS2 specification here at--- (https://specifications.freedesktop.org/mpris-spec/latest/).+-- (<https://specifications.freedesktop.org/mpris-spec/latest/>). ----------------------------------------------------------------------------- module System.Taffybar.Widget.MPRIS2 ( mpris2New ) where
src/System/Taffybar/Widget/SimpleClock.hs view
@@ -1,37 +1,45 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE ForeignFunctionInterface #-} {-# LANGUAGE OverloadedStrings #-}---- | This module implements a very simple text-based clock widget.--- The widget also toggles a calendar widget when clicked. This--- calendar is not fancy at all and has no data backend. module System.Taffybar.Widget.SimpleClock ( textClockNew , textClockNewWith , defaultClockConfig , ClockConfig(..)+ , ClockUpdateStrategy(..) ) where import Control.Monad.IO.Class+import Data.Maybe+import qualified Data.Text as T import Data.Time.Calendar ( toGregorian ) import qualified Data.Time.Clock as Clock import Data.Time.Format import Data.Time.LocalTime import qualified Data.Time.Locale.Compat as L-import GI.Gtk-import qualified GI.Gdk as D+import qualified GI.Gdk as Gdk+import GI.Gtk import System.Taffybar.Widget.Generic.PollingLabel import System.Taffybar.Widget.Util-import qualified Data.Text as T +diffLocalTime :: LocalTime -> LocalTime -> Clock.NominalDiffTime+diffLocalTime a b = Clock.diffUTCTime (localTimeToUTC utc a) (localTimeToUTC utc b)++-- | addLocalTime a b = a + b+addLocalTime :: Clock.NominalDiffTime -> LocalTime -> LocalTime+addLocalTime x = utcToLocalTime utc . Clock.addUTCTime x . localTimeToUTC utc++-- | This module implements a very simple text-based clock widget. The widget+-- also toggles a calendar widget when clicked. This calendar is not fancy at+-- all and has no data backend.+ makeCalendar :: IO TimeZone -> IO Window makeCalendar tzfn = do container <- windowNew WindowTypeToplevel cal <- calendarNew containerAdd container cal- -- update the date on show _ <- onWidgetShow container $ resetCalendarDate cal tzfn- -- prevent calendar from being destroyed, it can be only hidden:+ -- Hide the calendar instead of destroying it _ <- onWidgetDeleteEvent container $ \_ -> widgetHide container >> return True return container @@ -55,36 +63,48 @@ -- | Create the widget. I recommend passing @Nothing@ for the TimeLocale -- parameter. The format string can include Pango markup--- (http://developer.gnome.org/pango/stable/PangoMarkupFormat.html).-textClockNew :: MonadIO m => Maybe L.TimeLocale -> String -> Double -> m GI.Gtk.Widget-textClockNew userLocale =+-- (<http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>).+textClockNew ::+ MonadIO m => Maybe L.TimeLocale -> String -> Double -> m GI.Gtk.Widget+textClockNew userLocale format interval = textClockNewWith cfg where- cfg = defaultClockConfig { clockTimeLocale = userLocale }+ cfg = defaultClockConfig { clockTimeLocale = userLocale+ , clockFormatString = format+ , clockUpdateStrategy = ConstantInterval interval+ } -data ClockConfig = ClockConfig { clockTimeZone :: Maybe TimeZone- , clockTimeLocale :: Maybe L.TimeLocale- }- deriving (Eq, Ord, Show)+data ClockUpdateStrategy+ = ConstantInterval Double+ | RoundedTargetInterval Int Double+ deriving (Eq, Ord, Show) +data ClockConfig = ClockConfig+ { clockTimeZone :: Maybe TimeZone+ , clockTimeLocale :: Maybe L.TimeLocale+ , clockFormatString :: String+ , clockUpdateStrategy :: ClockUpdateStrategy+ } deriving (Eq, Ord, Show)+ -- | A clock configuration that defaults to the current locale defaultClockConfig :: ClockConfig-defaultClockConfig = ClockConfig Nothing Nothing--data TimeInfo = TimeInfo { getTZ :: IO TimeZone- , getLocale :: IO L.TimeLocale- }+defaultClockConfig = ClockConfig+ { clockTimeZone = Nothing+ , clockTimeLocale = Nothing+ , clockFormatString = "%a %b %_d %r"+ , clockUpdateStrategy = RoundedTargetInterval 5 0.0+ } systemGetTZ :: IO TimeZone systemGetTZ = setTZ >> getCurrentTimeZone --- | Old versions of time do not call localtime_r properly. We set--- the time zone manually, if required.+-- | Old versions of time do not call localtime_r properly. We set the time zone+-- manually, if required. setTZ :: IO () #if MIN_VERSION_time(1, 4, 2) setTZ = return () #else-setTZ = c_tzset+setTZ = c_tzsetp foreign import ccall unsafe "time.h tzset" c_tzset :: IO ()@@ -94,27 +114,55 @@ -- a configurable time zone through the 'ClockConfig'. -- -- See also 'textClockNew'.-textClockNewWith :: MonadIO m => ClockConfig -> String -> Double -> m Widget-textClockNewWith cfg fmt updateSeconds = liftIO $ do- let ti = TimeInfo { getTZ = maybe systemGetTZ return userZone- , getLocale = maybe (return L.defaultTimeLocale) return userLocale- }- l <- pollingLabelNew "" updateSeconds (getCurrentTime' ti fmt)+textClockNewWith :: MonadIO m => ClockConfig -> m Widget+textClockNewWith ClockConfig+ { clockTimeZone = userZone+ , clockTimeLocale = userLocale+ , clockFormatString = formatString+ , clockUpdateStrategy = updateStrategy+ } = liftIO $ do+ let getTZ = maybe systemGetTZ return userZone+ locale = fromMaybe L.defaultTimeLocale userLocale++ let getUserZonedTime =+ utcToZonedTime <$> getTZ <*> Clock.getCurrentTime++ doTimeFormat zonedTime = T.pack $ formatTime locale formatString zonedTime++ getRoundedTimeAndNextTarget = do+ zonedTime <- getUserZonedTime+ return $ case updateStrategy of+ ConstantInterval interval ->+ (doTimeFormat zonedTime, Nothing, interval)+ RoundedTargetInterval roundSeconds offset ->+ let roundSecondsDiffTime = fromIntegral roundSeconds+ addTheRound = addLocalTime roundSecondsDiffTime+ localTime = zonedTimeToLocalTime zonedTime+ ourLocalTimeOfDay = localTimeOfDay localTime+ seconds = round $ todSec ourLocalTimeOfDay+ secondsFactor = seconds `div` roundSeconds+ displaySeconds = secondsFactor * roundSeconds+ baseLocalTimeOfDay =+ ourLocalTimeOfDay { todSec = fromIntegral displaySeconds }+ ourLocalTime =+ localTime { localTimeOfDay = baseLocalTimeOfDay }+ roundedLocalTime =+ if seconds `mod` roundSeconds > roundSeconds `div` 2+ then addTheRound ourLocalTime+ else ourLocalTime+ roundedZonedTime =+ zonedTime { zonedTimeToLocalTime = roundedLocalTime }+ nextTarget = addTheRound ourLocalTime+ amountToWait = realToFrac $ diffLocalTime nextTarget localTime+ in (doTimeFormat roundedZonedTime, Nothing, amountToWait - offset)++ label <- pollingLabelWithVariableDelay getRoundedTimeAndNextTarget ebox <- eventBoxNew- containerAdd ebox l+ containerAdd ebox label eventBoxSetVisibleWindow ebox False- cal <- makeCalendar $ getTZ ti- _ <- onWidgetButtonPressEvent ebox $ onClick [D.EventTypeButtonPress] (toggleCalendar l cal)+ cal <- makeCalendar getTZ+ _ <- onWidgetButtonPressEvent ebox $ onClick [Gdk.EventTypeButtonPress] $+ toggleCalendar label cal widgetShowAll ebox toWidget ebox- where- userZone = clockTimeZone cfg- userLocale = clockTimeLocale cfg- -- alternate getCurrentTime that takes a specific TZ- getCurrentTime' :: TimeInfo -> String -> IO T.Text- getCurrentTime' ti f = do- l <- getLocale ti- z <- getTZ ti- t <- Clock.getCurrentTime- return $ T.pack $ formatTime l f $ utcToZonedTime z t
+ src/System/Taffybar/Widget/SimpleCommandButton.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE OverloadedStrings #-}+--------------------------------------------------------------------------------+-- |+-- Module : System.Taffybar.Widget.SimpleCommandButton+-- Copyright : (c) Ulf Jasper+-- License : BSD3-style (see LICENSE)+--+-- Maintainer : Ulf Jasper <ulf.jasper@web.de>+-- Stability : unstable+-- Portability : unportable+--+-- Simple button which runs a user defined command when being clicked+--------------------------------------------------------------------------------++module System.Taffybar.Widget.SimpleCommandButton (+ -- * Usage+ -- $usage+ simpleCommandButtonNew)+where++import Control.Monad.IO.Class+import GI.Gtk+import System.Process+import qualified Data.Text as T++-- $usage+--+-- In order to use this widget add the following line to your+-- @taffybar.hs@ file:+--+-- > import System.Taffybar.Widget+-- > main = do+-- > let cmdButton = simpleCommandButtonNew "Hello World!" "xterm -e \"echo Hello World!; read x\""+--+-- Now you can use @cmdButton@ like any other Taffybar widget.++-- | Creates a new simple command button.+simpleCommandButtonNew :: MonadIO m =>+ T.Text -- ^ Contents of the button's label.+ -> T.Text -- ^ Command to execute. Should be in $PATH or an absolute path+ -> m Widget+simpleCommandButtonNew txt cmd = do+ but <- buttonNewWithLabel txt+ _ <- onButtonClicked but $ spawnCommand (T.unpack cmd) >> return ()+ toWidget but+
src/System/Taffybar/Widget/Text/CPUMonitor.hs view
@@ -5,7 +5,6 @@ import System.Taffybar.Information.CPU import System.Taffybar.Widget.Generic.PollingLabel ( pollingLabelNew ) import qualified GI.Gtk-import qualified Data.Text as T -- | Creates a simple textual CPU monitor. It updates once every polling -- period (in seconds).@@ -13,7 +12,7 @@ -> Double -- ^ Polling period (in seconds) -> IO GI.Gtk.Widget textCpuMonitorNew fmt period = do- label <- pollingLabelNew (T.pack fmt) period callback+ label <- pollingLabelNew period callback GI.Gtk.widgetShowAll label return label where
src/System/Taffybar/Widget/Text/MemoryMonitor.hs view
@@ -4,7 +4,6 @@ import System.Taffybar.Information.Memory import System.Taffybar.Widget.Generic.PollingLabel ( pollingLabelNew ) import qualified GI.Gtk-import qualified Data.Text as T -- | Creates a simple textual memory monitor. It updates once every polling -- period (in seconds).@@ -12,7 +11,7 @@ -> Double -- ^ Polling period in seconds. -> IO GI.Gtk.Widget textMemoryMonitorNew fmt period = do- label <- pollingLabelNew (T.pack fmt) period callback+ label <- pollingLabelNew period callback GI.Gtk.widgetShowAll label return label where
src/System/Taffybar/Widget/Util.hs view
@@ -17,7 +17,6 @@ import Control.Concurrent ( forkIO ) import Control.Monad import Control.Monad.IO.Class-import Control.Monad.Trans.Maybe import Data.Functor ( ($>) ) import Data.Int import qualified Data.Text as T@@ -79,7 +78,8 @@ displayPopup widget window = do windowSetPosition window WindowPositionMouse (x, y ) <- windowGetPosition window- (_, y') <- widgetGetSizeRequest widget+ (_, natReq) <- widgetGetPreferredSize =<< widgetGetToplevel widget+ y' <- getRequisitionHeight natReq widgetShowAll window if y > y' then windowMove window x (y - y')
src/System/Taffybar/Widget/Weather.hs view
@@ -73,16 +73,17 @@ import qualified Data.ByteString.Lazy as LB import Data.List (stripPrefix) import Data.Maybe (fromMaybe)-import GI.Gtk+import qualified Data.Text as T+import qualified Data.Text.Encoding as T import GI.GLib(markupEscapeText)+import GI.Gtk import Network.HTTP.Client import Network.HTTP.Client.TLS (tlsManagerSettings) import Network.HTTP.Types.Status+import System.Log.Logger import Text.Parsec import Text.Printf import Text.StringTemplate-import qualified Data.Text as T-import qualified Data.Text.Encoding as T import System.Taffybar.Widget.Generic.PollingLabel @@ -243,7 +244,7 @@ lbl <- markupEscapeText rawLabel (-1) return (lbl, Just lbl) Left err -> do- putStrLn err+ logM "System.Taffybar.Widget.Weather" ERROR $ "Error in weather: " <> show err return ("N/A", Nothing) -- | The NOAA URL to get data from@@ -329,7 +330,7 @@ let labelTpl' = newSTMP labelTpl tooltipTpl' = newSTMP tooltipTpl - l <- pollingLabelNewWithTooltip "N/A" (delayMinutes * 60)+ l <- pollingLabelNewWithTooltip (delayMinutes * 60) (getCurrentWeather getter labelTpl' tooltipTpl' formatter) GI.Gtk.widgetShowAll l
src/System/Taffybar/Widget/Workspaces.hs view
@@ -88,7 +88,7 @@ data WorkspacesContext = WorkspacesContext { controllersVar :: MV.MVar (M.Map WorkspaceIdx WWC) , workspacesVar :: MV.MVar (M.Map WorkspaceIdx Workspace)- , workspacesWidget :: Gtk.HBox+ , workspacesWidget :: Gtk.Box , workspacesConfig :: WorkspacesConfig , taffyContext :: Context }@@ -206,7 +206,8 @@ workspacesRef <- asks workspacesVar updateVar workspacesRef buildWorkspaceData -getWorkspaceToWindows :: [X11Window] -> X11Property (MM.MultiMap WorkspaceIdx X11Window)+getWorkspaceToWindows ::+ [X11Window] -> X11Property (MM.MultiMap WorkspaceIdx X11Window) getWorkspaceToWindows = foldM (\theMap window ->@@ -285,7 +286,7 @@ -- XXX: This hbox exists to (hopefully) prevent the issue where workspace -- widgets appear out of order, in the switcher, by acting as an empty -- place holder when the actual widget is hidden.- hbox <- Gtk.hBoxNew False 0+ hbox <- Gtk.boxNew Gtk.OrientationHorizontal 0 parent <- Gtk.widgetGetParent workspaceWidget if isJust parent then Gtk.widgetReparent workspaceWidget hbox@@ -294,7 +295,7 @@ workspacesNew :: WorkspacesConfig -> TaffyIO Gtk.Widget workspacesNew cfg = ask >>= \tContext -> lift $ do- cont <- Gtk.hBoxNew False $ fromIntegral (widgetGap cfg)+ cont <- Gtk.boxNew Gtk.OrientationHorizontal $ fromIntegral (widgetGap cfg) controllersRef <- MV.newMVar M.empty workspacesRef <- MV.newMVar M.empty let context =@@ -464,7 +465,7 @@ controllers <- mapM ($ ws) constructors ctx <- ask tempController <- lift $ do- cons <- Gtk.hBoxNew False 0+ cons <- Gtk.boxNew Gtk.OrientationHorizontal 0 mapM_ (flip runReaderT ctx . getWidget >=> Gtk.containerAdd cons) controllers outerBox <- Gtk.toWidget cons >>= buildPadBox _ <- widgetSetClassGI cons "contents"@@ -570,7 +571,7 @@ } data IconController = IconController- { iconsContainer :: Gtk.HBox+ { iconsContainer :: Gtk.Box , iconImages :: [IconWidget] , iconWorkspace :: Workspace }@@ -579,7 +580,7 @@ buildIconController ws = do tempController <- lift $ do- hbox <- Gtk.hBoxNew False 0+ hbox <- Gtk.boxNew Gtk.OrientationHorizontal 0 return IconController {iconsContainer = hbox, iconImages = [], iconWorkspace = ws}@@ -593,9 +594,8 @@ updateWidget ic (IconUpdate updatedIcons) = updateWindowIconsById ic updatedIcons >> return ic -updateWindowIconsById :: IconController- -> [X11Window]- -> WorkspacesIO ()+updateWindowIconsById ::+ IconController -> [X11Window] -> WorkspacesIO () updateWindowIconsById ic windowIds = mapM_ maybeUpdateWindowIcon $ iconImages ic where@@ -610,9 +610,8 @@ getter size >=> lift . traverse (scalePixbufToSize size Gtk.OrientationHorizontal) -constantScaleWindowIconPixbufGetter :: Int32- -> WindowIconPixbufGetter- -> WindowIconPixbufGetter+constantScaleWindowIconPixbufGetter ::+ Int32 -> WindowIconPixbufGetter -> WindowIconPixbufGetter constantScaleWindowIconPixbufGetter constantSize getter = const $ scaledWindowIconPixbufGetter getter constantSize @@ -670,7 +669,9 @@ sortWindowsByPosition wins = do let getGeometryWorkspaces w = getDisplay >>= liftIO . (`safeGetGeometry` w) getGeometries = mapM- (forkM return ((((sel2 &&& sel3) <$>) .) getGeometryWorkspaces) . windowId)+ (forkM return+ ((((sel2 &&& sel3) <$>) .) getGeometryWorkspaces) .+ windowId) wins windowGeometries <- liftX11Def [] getGeometries let getLeftPos wd =
src/System/Taffybar/Widget/XDGMenu/MenuWidget.hs view
@@ -23,15 +23,16 @@ ) where -import Control.Monad-import Control.Monad.IO.Class+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+import GI.GdkPixbuf+import GI.Gtk hiding (Menu)+import System.Directory+import System.FilePath.Posix+import System.Log.Logger+import System.Process+import System.Taffybar.Widget.XDGMenu.Menu -- $usage --@@ -71,7 +72,7 @@ menuShellAppend ms item _ <- onMenuItemActivate item $ do let cmd = feCommand de- putStrLn $ "Launching '" ++ cmd ++ "'"+ logM "System.Taffybar.Widget.XDGMenu.MenuWidget" DEBUG $ "Launching '" ++ cmd ++ "'" _ <- spawnCommand cmd return () return ()@@ -114,7 +115,7 @@ else return Nothing case mImg of Just img -> imageMenuItemSetImage item (Just img)- Nothing -> putStrLn $ "Icon not found: " ++ iconName+ Nothing -> logM "System.Taffybar.Widget.XDGMenu.MenuWidget" WARNING $ "Icon not found: " ++ iconName -- | Create a new XDG Menu Widget. menuWidgetNew :: MonadIO m => Maybe String -- ^ menu name, must end with a dash,@@ -126,13 +127,3 @@ 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
taffybar.cabal view
@@ -1,10 +1,10 @@ name: taffybar-version: 3.1.2+version: 3.2.0 synopsis: A desktop bar similar to xmobar, but with more GUI license: BSD3 license-file: LICENSE-author: Tristan Ravitch-maintainer: tristan@nochair.net+author: Ivan Malison+maintainer: IvanMalison@gmail.com category: System build-type: Simple cabal-version: >=1.10@@ -42,6 +42,8 @@ , ConfigFile , HStringTemplate >= 0.8 && < 0.9 , X11 >= 1.5.0.1+ , ansi-terminal+ , broadcast-chan >= 0.2.0.0 , bytestring , containers , dbus >= 1.2.1 && < 2.0.0@@ -52,8 +54,8 @@ , enclosed-exceptions >= 1.0.0.1 , filepath , gi-cairo- , gi-cairo-render , gi-cairo-connector+ , gi-cairo-render , gi-gdk , gi-gdkpixbuf >= 2.0.18 , gi-gdkx11@@ -76,7 +78,7 @@ , rate-limit >= 1.1.1 , regex-compat , safe >= 0.3 && < 1- , scotty >= 0.11.2 && < 0.12.0+ , scotty >= 0.11.0 && < 0.12.0 , split >= 0.1.4.2 , status-notifier-item >= 0.3.0.1 , stm@@ -121,6 +123,7 @@ , System.Taffybar.Information.X11DesktopInfo , System.Taffybar.Information.XDG.DesktopEntry , System.Taffybar.Information.XDG.Protocol+ , System.Taffybar.LogFormatter , System.Taffybar.SimpleConfig , System.Taffybar.Support.PagerHints , System.Taffybar.Util@@ -147,6 +150,7 @@ , System.Taffybar.Widget.NetworkGraph , System.Taffybar.Widget.SNITray , System.Taffybar.Widget.SimpleClock+ , System.Taffybar.Widget.SimpleCommandButton , System.Taffybar.Widget.Text.CPUMonitor , System.Taffybar.Widget.Text.MemoryMonitor , System.Taffybar.Widget.Text.NetworkMonitor
taffybar.hs.example view
@@ -63,11 +63,11 @@ cpu = pollingGraphNew cpuCfg 0.5 cpuCallback mem = pollingGraphNew memCfg 1 memCallback net = networkGraphNew netCfg Nothing- clock = textClockNew Nothing "%a %b %_d %r" 1+ clock = textClockNewWith defaultClockConfig layout = layoutNew defaultLayoutConfig windows = windowsNew defaultWindowsConfig- -- See https://github.com/taffybar/gtk-sni-tray#statusnotifierwatcher- -- for a better way to set up the sni tray+ -- See https://github.com/taffybar/gtk-sni-tray#statusnotifierwatcher+ -- for a better way to set up the sni tray tray = sniTrayThatStartsWatcherEvenThoughThisIsABadWayToDoIt myConfig = defaultSimpleTaffyConfig { startWidgets =