{-# LANGUAGE NamedFieldPuns #-}
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
module System.Taffybar
(
getTaffyFile
, startTaffybar
, dyreTaffybar
, dyreTaffybarMain
, taffybarDyreParams
) where
import qualified Control.Concurrent.MVar as MV
import qualified Config.Dyre as Dyre
import qualified Config.Dyre.Params as Dyre
import Control.Exception ( finally )
import Data.Function ( on )
import Control.Monad
import qualified Data.GI.Gtk.Threading as GIThreading
import Data.List ( groupBy, sort, isPrefixOf )
import qualified Data.Text as T
import Data.Word (Word32)
import qualified GI.Gdk as Gdk
import qualified GI.Gtk as Gtk
import qualified GI.GLib as G
import Graphics.X11.Xlib.Misc ( initThreads )
import System.Directory
import System.Environment.XDG.BaseDir ( getUserConfigFile )
import System.Exit ( exitFailure )
import System.FilePath ( (</>), normalise, takeDirectory, takeFileName )
import System.FSNotify ( startManager, watchDir, stopManager, EventIsDirectory (..), Event (..) )
import qualified System.IO as IO
import System.Log.Logger
import System.Taffybar.Context
import System.Taffybar.Hooks
import System.Taffybar.Util ( onSigINT, maybeHandleSigHUP, rebracket_ )
import Paths_taffybar ( getDataDir )
taffybarDyreParams :: Params TaffybarConfig ()
taffybarDyreParams =
(String
-> (TaffybarConfig -> IO ())
-> (TaffybarConfig -> String -> TaffybarConfig)
-> Params TaffybarConfig ()
forall cfg a.
String -> (cfg -> IO a) -> (cfg -> String -> cfg) -> Params cfg a
Dyre.newParams String
"taffybar" TaffybarConfig -> IO ()
dyreTaffybarMain TaffybarConfig -> String -> TaffybarConfig
showError)
{ Dyre.ghcOpts = ["-threaded", "-rtsopts"]
, Dyre.rtsOptsHandling = Dyre.RTSAppend ["-I0", "-V0"]
}
dyreTaffybar :: TaffybarConfig -> IO ()
dyreTaffybar :: TaffybarConfig -> IO ()
dyreTaffybar = Params TaffybarConfig () -> TaffybarConfig -> IO ()
forall cfgType a. Params cfgType a -> cfgType -> IO a
Dyre.wrapMain Params TaffybarConfig ()
taffybarDyreParams
showError :: TaffybarConfig -> String -> TaffybarConfig
showError :: TaffybarConfig -> String -> TaffybarConfig
showError TaffybarConfig
cfg String
msg = TaffybarConfig
cfg { errorMsg = Just msg }
dyreTaffybarMain :: TaffybarConfig -> IO ()
dyreTaffybarMain :: TaffybarConfig -> IO ()
dyreTaffybarMain TaffybarConfig
cfg =
case TaffybarConfig -> Maybe String
errorMsg TaffybarConfig
cfg of
Maybe String
Nothing -> TaffybarConfig -> IO ()
startTaffybar TaffybarConfig
cfg
Just String
err -> do
Handle -> String -> IO ()
IO.hPutStrLn Handle
IO.stderr (String
"Error: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
err)
IO ()
forall a. IO a
exitFailure
getDataFile :: String -> IO FilePath
getDataFile :: String -> IO String
getDataFile String
name = do
String
dataDir <- IO String
getDataDir
String -> IO String
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> String
normalise (String
dataDir String -> String -> String
</> String
name))
getTaffyFile :: String -> IO FilePath
getTaffyFile :: String -> IO String
getTaffyFile = String -> String -> IO String
getUserConfigFile String
"taffybar"
getCSSPaths :: TaffybarConfig -> IO [FilePath]
getCSSPaths :: TaffybarConfig -> IO [String]
getCSSPaths TaffybarConfig{[String]
cssPaths :: [String]
cssPaths :: TaffybarConfig -> [String]
cssPaths} = [IO String] -> IO [String]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
forall (m :: * -> *) a. Monad m => [m a] -> m [a]
sequence (IO String
defaultCSSIO String -> [IO String] -> [IO String]
forall a. a -> [a] -> [a]
:[IO String]
userCSS)
where
defaultCSS :: IO String
defaultCSS = String -> IO String
getDataFile String
"taffybar.css"
userCSS :: [IO String]
userCSS | [String] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [String]
cssPaths = [String -> IO String
getTaffyFile String
"taffybar.css"]
| Bool
otherwise = (String -> IO String) -> [String] -> [IO String]
forall a b. (a -> b) -> [a] -> [b]
map String -> IO String
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return [String]
cssPaths
startCSS :: [FilePath] -> IO (IO (), Gtk.CssProvider)
startCSS :: [String] -> IO (IO (), CssProvider)
startCSS = Word32 -> [String] -> IO (IO (), CssProvider)
startCSS' Word32
800
startCSS' :: Word32 -> [FilePath] -> IO (IO (), Gtk.CssProvider)
startCSS' :: Word32 -> [String] -> IO (IO (), CssProvider)
startCSS' Word32
prio [String]
cssFilePaths = do
CssProvider
provider <- IO CssProvider
forall (m :: * -> *). (HasCallStack, MonadIO m) => m CssProvider
Gtk.cssProviderNew
(String -> IO ()) -> [String] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (CssProvider -> String -> IO ()
forall {a}.
(IsDescendantOf CssProvider a, GObject a) =>
a -> String -> IO ()
logLoadCSSFile CssProvider
provider) ([String] -> IO ()) -> IO [String] -> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (String -> IO Bool) -> [String] -> IO [String]
forall (m :: * -> *) a.
Applicative m =>
(a -> m Bool) -> [a] -> m [a]
filterM String -> IO Bool
doesFileExist [String]
cssFilePaths
IO ()
uninstall <- CssProvider -> Maybe Screen -> IO (IO ())
install CssProvider
provider (Maybe Screen -> IO (IO ())) -> IO (Maybe Screen) -> IO (IO ())
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IO (Maybe Screen)
forall (m :: * -> *). (HasCallStack, MonadIO m) => m (Maybe Screen)
Gdk.screenGetDefault
(IO (), CssProvider) -> IO (IO (), CssProvider)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (IO ()
uninstall, CssProvider
provider)
where
logLoadCSSFile :: a -> String -> IO ()
logLoadCSSFile a
p String
f = Priority -> String -> IO ()
logTaffy Priority
INFO (String
"Loading stylesheet " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
f) IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> a -> String -> IO ()
forall {a} {m :: * -> *}.
(IsDescendantOf CssProvider a, MonadIO m, GObject a) =>
a -> String -> m ()
loadCSSFile a
p String
f
loadCSSFile :: a -> String -> m ()
loadCSSFile a
p = a -> Text -> m ()
forall (m :: * -> *) a.
(HasCallStack, MonadIO m, IsCssProvider a) =>
a -> Text -> m ()
Gtk.cssProviderLoadFromPath a
p (Text -> m ()) -> (String -> Text) -> String -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
T.pack
install :: CssProvider -> Maybe Screen -> IO (IO ())
install CssProvider
provider (Just Screen
scr) = do
Screen -> CssProvider -> Word32 -> IO ()
forall (m :: * -> *) a b.
(HasCallStack, MonadIO m, IsScreen a, IsStyleProvider b) =>
a -> b -> Word32 -> m ()
Gtk.styleContextAddProviderForScreen Screen
scr CssProvider
provider Word32
prio
IO () -> IO (IO ())
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Screen -> CssProvider -> IO ()
forall (m :: * -> *) a b.
(HasCallStack, MonadIO m, IsScreen a, IsStyleProvider b) =>
a -> b -> m ()
Gtk.styleContextRemoveProviderForScreen Screen
scr CssProvider
provider)
install CssProvider
_ Maybe Screen
Nothing = IO () -> IO (IO ())
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (() -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ())
withCSSReloadable :: [FilePath] -> IO () -> IO ()
withCSSReloadable :: [String] -> IO () -> IO ()
withCSSReloadable [String]
css IO ()
action = IO (IO ()) -> (IO () -> IO ()) -> IO ()
forall a. IO (IO ()) -> (IO () -> IO a) -> IO a
rebracket_ ((IO (), CssProvider) -> IO ()
forall a b. (a, b) -> a
fst ((IO (), CssProvider) -> IO ())
-> IO (IO (), CssProvider) -> IO (IO ())
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [String] -> IO (IO (), CssProvider)
startCSS [String]
css) ((IO () -> IO ()) -> IO ()) -> (IO () -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \IO ()
reload -> do
let reload' :: IO ()
reload' = IO ()
noteReload IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO ()
reload
IO (IO ()) -> (IO () -> IO ()) -> IO ()
forall a. IO (IO ()) -> (IO () -> IO a) -> IO a
rebracket_ ([String] -> IO () -> IO (IO ())
watchCSS [String]
css IO ()
reload') ((IO () -> IO ()) -> IO ()) -> (IO () -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \IO ()
rewatch -> do
let rewatch' :: IO ()
rewatch' = IO ()
noteRewatch IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO ()
rewatch IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO ()
reload
IO () -> IO () -> IO ()
forall a. IO () -> IO a -> IO a
maybeHandleSigHUP IO ()
rewatch' IO ()
action
where
noteReload :: IO ()
noteReload = Priority -> String -> IO ()
logTaffy Priority
NOTICE String
"Reloading CSS..."
noteRewatch :: IO ()
noteRewatch = Priority -> String -> IO ()
logTaffy Priority
NOTICE String
"SIGHUP received - restarting file watchers..."
watchCSS :: [FilePath] -> IO () -> IO (IO ())
watchCSS :: [String] -> IO () -> IO (IO ())
watchCSS [String]
css IO ()
notifier = do
Event -> IO ()
callback <- Word32 -> IO () -> IO (Event -> IO ())
forall {a}. Word32 -> IO () -> IO (a -> IO ())
debounce Word32
100 IO ()
notifier
WatchManager
mgr <- IO WatchManager
startManager
[(String, [String])]
cssDirs <- [String] -> IO [(String, [String])]
getDirs [String]
css
((String, [String]) -> IO (IO ())) -> [(String, [String])] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\(String
dir, [String]
fs) -> WatchManager
-> String -> ActionPredicate -> (Event -> IO ()) -> IO (IO ())
watchDir WatchManager
mgr String
dir ([String] -> ActionPredicate
forall {t :: * -> *}. Foldable t => t String -> ActionPredicate
eventP [String]
fs) Event -> IO ()
callback) [(String, [String])]
cssDirs
IO () -> IO (IO ())
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (WatchManager -> IO ()
stopManager WatchManager
mgr)
where
getDirs :: [String] -> IO [(String, [String])]
getDirs = ((String, [String]) -> IO Bool)
-> [(String, [String])] -> IO [(String, [String])]
forall (m :: * -> *) a.
Applicative m =>
(a -> m Bool) -> [a] -> m [a]
filterM (String -> IO Bool
doesDirectoryExist (String -> IO Bool)
-> ((String, [String]) -> String) -> (String, [String]) -> IO Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, [String]) -> String
forall a b. (a, b) -> a
fst)
([(String, [String])] -> IO [(String, [String])])
-> ([String] -> [(String, [String])])
-> [String]
-> IO [(String, [String])]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((String, [String]) -> Bool)
-> [(String, [String])] -> [(String, [String])]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool)
-> ((String, [String]) -> Bool) -> (String, [String]) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isPrefixOf String
"/nix/store/" (String -> Bool)
-> ((String, [String]) -> String) -> (String, [String]) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, [String]) -> String
forall a b. (a, b) -> a
fst)
([(String, [String])] -> [(String, [String])])
-> ([String] -> [(String, [String])])
-> [String]
-> [(String, [String])]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [String] -> [(String, [String])]
dirGroups
([String] -> [(String, [String])])
-> ([String] -> [String]) -> [String] -> [(String, [String])]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [String] -> [String]
forall a. Ord a => [a] -> [a]
sort
dirGroups :: [String] -> [(String, [String])]
dirGroups [String]
xs = [ (String -> String
takeDirectory String
f, (String -> String) -> [String] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map String -> String
takeFileName (String
fString -> [String] -> [String]
forall a. a -> [a] -> [a]
:[String]
fs))
| (String
f:[String]
fs) <- (String -> String -> Bool) -> [String] -> [[String]]
forall a. (a -> a -> Bool) -> [a] -> [[a]]
groupBy (String -> String -> Bool
forall a. Eq a => a -> a -> Bool
(==) (String -> String -> Bool)
-> (String -> String) -> String -> String -> Bool
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` String -> String
takeDirectory) [String]
xs]
eventP :: t String -> ActionPredicate
eventP t String
fs Event
ev = Event -> EventIsDirectory
eventIsDirectory Event
ev EventIsDirectory -> EventIsDirectory -> Bool
forall a. Eq a => a -> a -> Bool
== EventIsDirectory
IsFile
Bool -> Bool -> Bool
&& String -> String
takeFileName (Event -> String
eventPath Event
ev) String -> t String -> Bool
forall a. Eq a => a -> t a -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t String
fs
debounce :: Word32 -> IO () -> IO (a -> IO ())
debounce Word32
msec IO ()
cb = do
MVar [a]
buffer <- [a] -> IO (MVar [a])
forall a. a -> IO (MVar a)
MV.newMVar []
let mainLoopCallback :: IO Bool
mainLoopCallback = do
[a]
evs <- MVar [a] -> ([a] -> IO ([a], [a])) -> IO [a]
forall a b. MVar a -> (a -> IO (a, b)) -> IO b
MV.modifyMVar MVar [a]
buffer (([a], [a]) -> IO ([a], [a])
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (([a], [a]) -> IO ([a], [a]))
-> ([a] -> ([a], [a])) -> [a] -> IO ([a], [a])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([],))
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ([a] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [a]
evs) IO ()
cb
Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
G.SOURCE_REMOVE
(a -> IO ()) -> IO (a -> IO ())
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ((a -> IO ()) -> IO (a -> IO ()))
-> (a -> IO ()) -> IO (a -> IO ())
forall a b. (a -> b) -> a -> b
$ \a
ev -> do
MVar [a] -> ([a] -> IO [a]) -> IO ()
forall a. MVar a -> (a -> IO a) -> IO ()
MV.modifyMVar_ MVar [a]
buffer ([a] -> IO [a]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([a] -> IO [a]) -> ([a] -> [a]) -> [a] -> IO [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a
eva -> [a] -> [a]
forall a. a -> [a] -> [a]
:))
IO Word32 -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO Word32 -> IO ()) -> IO Word32 -> IO ()
forall a b. (a -> b) -> a -> b
$ Int32 -> Word32 -> IO Bool -> IO Word32
forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Int32 -> Word32 -> IO Bool -> m Word32
G.timeoutAdd Int32
G.PRIORITY_LOW Word32
msec IO Bool
mainLoopCallback
startTaffybar :: TaffybarConfig -> IO ()
startTaffybar :: TaffybarConfig -> IO ()
startTaffybar TaffybarConfig
config = do
String -> (Logger -> Logger) -> IO ()
updateGlobalLogger String
"" Logger -> Logger
removeHandler
String -> IO ()
setTaffyLogFormatter String
"System.Taffybar"
String -> IO ()
setTaffyLogFormatter String
"StatusNotifier"
Status
_ <- IO Status
initThreads
Maybe [Text]
_ <- Maybe [Text] -> IO (Maybe [Text])
forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Maybe [Text] -> m (Maybe [Text])
Gtk.init Maybe [Text]
forall a. Maybe a
Nothing
IO ()
GIThreading.setCurrentThreadAsGUIThread
[String]
cssPathsToLoad <- TaffybarConfig -> IO [String]
getCSSPaths TaffybarConfig
config
Context
context <- TaffybarConfig -> IO Context
buildContext TaffybarConfig
config
[String] -> IO () -> IO ()
withCSSReloadable [String]
cssPathsToLoad (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ IO ()
forall (m :: * -> *). (HasCallStack, MonadIO m) => m ()
Gtk.main
IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO a
`finally` Priority -> String -> IO ()
logTaffy Priority
DEBUG String
"Finished main loop"
IO () -> IO () -> IO ()
forall a. IO a -> IO () -> IO a
`onSigINT` do
Priority -> String -> IO ()
logTaffy Priority
INFO String
"Interrupted"
Context -> IO ()
exitTaffybar Context
context
Priority -> String -> IO ()
logTaffy Priority
DEBUG String
"Exited normally"
logTaffy :: Priority -> String -> IO ()
logTaffy :: Priority -> String -> IO ()
logTaffy = String -> Priority -> String -> IO ()
logM String
"System.Taffybar"