hbro-1.4.0.0: library/Hbro/Boot.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TupleSections #-}
module Hbro.Boot (Settings(..), hbro) where
-- {{{ Imports
import Hbro.Config as Config
import Hbro.Core as Core
import Hbro.Defaults
import Hbro.Dyre as Dyre
import Hbro.Event
import Hbro.Gui as Gui
import Hbro.Gui.MainView
import Hbro.IPC as IPC (CommandMap,
bindCommands)
import Hbro.Keys as Keys
import Hbro.Logger
import Hbro.Options as Options
import Hbro.Prelude
import Control.Concurrent.Async.Lifted
import Control.Lens hiding ((<|), (??), (|>))
import Control.Monad.Trans.Resource
import Data.Version hiding (Version)
import Graphics.UI.Gtk.General.General as Gtk
import Network.URI.Extended
import qualified Paths_hbro as Package
import System.Directory
import System.Info
import System.Posix.Process
import System.Posix.Signals
import qualified System.ZMQ4 as ZMQ (version)
-- }}}
-- | What users can configure.
data Settings = Settings
{ configuration :: Config
, commandMap :: forall m r . (God r m, MonadCatch m) => CommandMap m
, keyMap :: forall m r . (God r m, MonadCatch m) => KeyMap m
, startUp :: forall m r . (God r m, MonadCatch m) => m ()
}
instance Default Settings where
def = Settings
{ configuration = def
, commandMap = defaultCommandMap
, keyMap = defaultKeyMap
, startUp = debug "No start-up script defined"
}
getDataFileName :: (MonadIO m, Functor m) => Text -> m FilePath
getDataFileName file = io (Package.getDataFileName $ unpack file)
-- | Main function to call in the configuration file. Cf @Hbro/Main.hs@ as an example.
hbro :: Settings -> IO ()
hbro settings = do
options <- parseOptions
case options of
Left Rebuild -> Dyre.recompile >>= mapM_ putStrLn
Left Version -> printVersions
Right runOptions -> runResourceT . runThreadedLoggingT (runOptions^.logLevelL) $ Dyre.wrap (runOptions^.dyreModeL)
(withAsyncBound guiThread . mainThread)
(settings, runOptions)
-- | Gtk main loop thread.
guiThread :: (ControlIO m, MonadLogger m) => m ()
guiThread = do
io . async $ do
installHandler sigINT (Catch onInterrupt) Nothing
installHandler sigTERM (Catch onInterrupt) Nothing
io $ initGUI >> mainGUI
debug "GUI thread correctly exited."
where onInterrupt = logInterrupt >> postGUIAsync mainQuit
logInterrupt = putStrLn "Received interrupt signal."
mainThread :: (ControlIO m, MonadCatch m, MonadThreadedLogger m, Alternative m, MonadResource m)
=> (Settings, CliOptions) -> Async () -> m ()
mainThread (settings, options) uiThread = logErrors_ $ do
uiFiles <- getUIFiles options
(builder, mainView, promptBar, statusBar, notifBar) <- asum $ map Gui.initialize uiFiles
socketURI <- getSocketURI options
keySignal <- newSignal KeyMapPressed
config <- io . newTVarIO $ configuration settings
flip runReaderT (config, ())
. withReaderT (mainView, )
. withReaderT (statusBar, )
. withReaderT (promptBar, )
. withReaderT (notifBar, )
. withReaderT (builder, )
. withReaderT (keySignal, )
. withAsync (bindCommands socketURI (commandMap settings)) . const $ do
bindKeys (mainView^.keyPressedHandlerL) keySignal (keyMap settings)
addHandler (mainView^.linkClickedHandlerL) defaultLinkClickedHandler
addHandler (mainView^.loadRequestedHandlerL) defaultLoadRequestedHandler
addHandler (mainView^.newWindowHandlerL) defaultNewWindowHandler
addHandler (mainView^.titleChangedHandlerL) defaultTitleChangedHandler
startUp settings
debug . ("Start-up configuration: \n" ++) . describe =<< Config.get id
maybe goHome (load <=< getStartURI) (options^.startURIL)
io $ wait uiThread
debug "All threads correctly exited."
-- | Return the list of available UI files (from configuration and package)
getUIFiles :: (MonadIO m, Functor m) => CliOptions -> m [FilePath]
getUIFiles options = do
fileFromConfig <- getAppUserDataDirectory "hbro" >/> "ui.xml"
fileFromPackage <- getDataFileName "examples/ui.xml"
return $ catMaybes [options^.uiFileL, Just fileFromConfig, Just fileFromPackage]
-- | Return socket URI used by this instance
getSocketURI :: (MonadIO m, Functor m) => CliOptions -> m Text
getSocketURI options = maybe getDefaultSocketURI (return . normalize) $ options^.socketPathL
where
normalize = ("ipc://" ++) . pack
getDefaultSocketURI = do
dir <- pack <$> io getTemporaryDirectory
pid <- io getProcessID
return $ "ipc://" ++ dir ++ "/hbro." ++ tshow pid
-- | Parse URI passed in commandline, check whether it is a file path or an internet URI
-- and return the corresponding normalized URI (that is: prefixed with "file://" or "http://")
getStartURI :: (MonadIO m, MonadThrow m) => URI -> m URI
getStartURI uri = do
fileURI <- io . doesFileExist $ show uri
workingDir <- pack <$> io getCurrentDirectory
parseURIReference ("file://" ++ workingDir ++ "/" ++ tshow uri) <| fileURI |> return uri
-- maybe abort return =<< logErrors (parseURIReference fileURI')
printVersions :: IO ()
printVersions = do
(a, b, c) <- ZMQ.version
putStrLn $ "hbro-" ++ pack (showVersion Package.version)
putStrLn $ "compiled by " ++ pack compilerName ++ "-" ++ pack (showVersion compilerVersion)
putStrLn $ "using zeromq-" ++ intercalate "." (map tshow [a, b, c])