hbro-0.7.1.0: Hbro/Core.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DoRec #-}
module Hbro.Core (
-- * Main
defaultConfig,
launchHbro,
-- * Browsing
goHome,
loadURI,
-- * Scrolling
goTop,
goBottom,
goLeft,
goRight,
-- * Misc
printPage,
executeJSFile
) where
-- {{{ Imports
import Hbro.Gui
--import Hbro.Keys
import Hbro.Socket
import Hbro.Types
--import Hbro.Util
import qualified Config.Dyre as D
import Config.Dyre.Paths
import Control.Concurrent
import Control.Monad.Reader
import qualified Data.Map as M
--import Graphics.UI.Gtk.Abstract.Widget
import Graphics.UI.Gtk.General.General hiding(initGUI)
import Graphics.UI.Gtk.Misc.Adjustment
import Graphics.UI.Gtk.Scrolling.ScrolledWindow
import Graphics.UI.Gtk.WebKit.WebDataSource
import Graphics.UI.Gtk.WebKit.WebFrame
import Graphics.UI.Gtk.WebKit.WebView
import Network.URL
import System.Console.CmdArgs
import System.Directory
import System.Environment.XDG.BaseDir
--import System.Glib.Signals
import System.IO
import System.Posix.Process
import System.Posix.Signals
import qualified System.ZMQ as ZMQ
-- }}}
-- {{{ Commandline options
cliOptions :: CliOptions
cliOptions = CliOptions {
mURI = def &= help "URI to open at start-up" &= explicit &= name "u" &= name "uri" &= typ "URI",
mVanilla = def &= help "Do not read custom configuration file." &= explicit &= name "1" &= name "vanilla",
mDenyReconf = def &= help "Deny recompilation even if the configuration file has changed." &= explicit &= name "deny-reconf",
mForceReconf = def &= help "Force recompilation even if the configuration file hasn't changed." &= explicit &= name "force-reconf",
mDyreDebug = def &= help "Force the application to use './cache/' as the cache directory, and ./ as the configuration directory. Useful to debug the program without installation." &= explicit &= name "dyre-debug",
mMasterBinary = def &= explicit &= name "dyre-master-binary"
}
getOptions :: IO CliOptions
getOptions = cmdArgs $ cliOptions
&= verbosityArgs [explicit, name "verbose", name "v"] []
&= versionArg [ignore]
&= help "A minimal KISS-compliant browser."
&= helpArg [explicit, name "help", name "h"]
&= program "hbro"
-- }}}
-- {{{ Configuration (Dyre)
dyreParameters :: D.Params (Config, CliOptions)
dyreParameters = D.defaultParams {
D.projectName = "hbro",
D.showError = showError,
D.realMain = realMain,
D.ghcOpts = ["-threaded"],
D.statusOut = hPutStrLn stderr
}
showError :: (Config, a) -> String -> (Config, a)
showError (config, x) message = (config { mError = Just message }, x)
-- | Default configuration.
-- Homepage: Google, socket directory: /tmp,
-- UI file: ~/.config/hbro/, no key/command binding.
defaultConfig :: CommonDirectories -> Config
defaultConfig directories = Config {
mCommonDirectories = directories,
mHomePage = "https://encrypted.google.com/",
mSocketDir = mTemporary directories,
mUIFile = (mConfiguration directories) ++ "/ui.xml",
mWebSettings = [],
mSetup = const (return () :: IO ()),
mCommands = [],
mError = Nothing
}
-- }}}
-- {{{ Entry point
-- | Browser's main function.
-- To be called in main function with a proper configuration.
-- See Hbro.Main for an example.
launchHbro :: (CommonDirectories -> Config) -> IO ()
launchHbro configGenerator = do
homeDir <- getHomeDirectory
tmpDir <- getTemporaryDirectory
configDir <- getUserConfigDir "hbro"
dataDir <- getUserDataDir "hbro"
options <- getOptions
let config = configGenerator (CommonDirectories homeDir tmpDir configDir dataDir)
case mVanilla options of
True -> D.wrapMain dyreParameters{ D.configCheck = False } (config, options)
_ -> D.wrapMain dyreParameters (config, options)
realMain :: (Config, CliOptions) -> IO ()
realMain (config, options) = do
-- Print configuration error, if any
maybe (return ()) putStrLn $ mError config
-- Print in-use paths
whenLoud $ getPaths dyreParameters >>= \(a,b,c,d,e) -> do
putStrLn ("Current binary: " ++ a)
putStrLn ("Custom binary: " ++ b)
putStrLn ("Config file: " ++ c)
putStrLn ("Cache directory: " ++ d)
putStrLn ("Lib directory: " ++ e)
putStrLn ""
-- Initialize GUI
gui <- initGUI (mUIFile config) (mWebSettings config)
-- Initialize IPC socket
ZMQ.withContext 1 $ realMain' config options gui
realMain' :: Config -> CliOptions -> GUI -> ZMQ.Context -> IO ()
realMain' config options gui@GUI {mWebView = webView, mWindow = window} context = let
environment = Environment options config gui context
setup = mSetup config
socketDir = mSocketDir config
commands = mCommands config
--keyEventHandler = mKeyEventHandler config
--keyEventCallback = (mKeyEventCallback config) environment
in do
-- Apply custom setup
setup environment
-- Setup key handler
--rec i <- after webView keyPressEvent $ keyEventHandler keyEventCallback i webView
-- Load homepage
case (mURI options) of
Just uri -> do
fileURI <- doesFileExist uri
case fileURI of
True -> getCurrentDirectory >>= \dir -> webViewLoadUri webView $ "file://" ++ dir ++ "/" ++ uri
_ -> webViewLoadUri webView uri
whenLoud $ putStrLn ("Loading " ++ uri ++ "...")
_ -> goHome webView config
-- Open socket
pid <- getProcessID
let commandsList = M.fromList $ defaultCommandsList ++ commands
let socketURI = "ipc://" ++ socketDir ++ "/hbro." ++ show pid
void $ forkIO (openRepSocket context socketURI (listenToCommands environment commandsList))
-- Manage POSIX signals
void $ installHandler sigINT (Catch interruptHandler) Nothing
--timeoutAdd (putStrLn "OK" >> return True) 2000
mainGUI -- Main loop
-- Make sure response socket is closed at exit
whenLoud $ putStrLn "Closing socket..."
closeSocket context socketURI
whenNormal $ putStrLn "Exiting..."
interruptHandler :: IO ()
interruptHandler = do
whenLoud $ putStrLn "Received SIGINT."
mainQuit
-- }}}
-- {{{ Browsing functions
-- | Load homepage (set from configuration file).
goHome :: WebView -> Config -> IO ()
goHome webView config = do
whenLoud $ putStrLn ("Loading homepage: " ++ uri)
loadURI webView uri
where
uri = mHomePage config
-- | Wrapper around webViewLoadUri meant to transparently add the proper protocol prefix (http:// or file://).
-- Most of the time, you want to use this function instead of webViewLoadUri.
loadURI :: WebView -> String -> IO ()
loadURI webView uri = do
whenLoud $ putStrLn ("Loading URI: " ++ uri)
case importURL uri of
Just uri'@URL {url_type = Absolute _} -> webViewLoadUri webView (exportURL uri')
Just uri'@URL {url_type = HostRelative} -> webViewLoadUri webView ("file://" ++ exportURL uri')
Just uri'@URL {url_type = _} -> webViewLoadUri webView ("http://" ++ exportURL uri')
_ -> whenNormal $ putStrLn ("WARNING: not a valid URI: " ++ uri)
-- }}}
-- {{{ Scrolling
-- | Scroll up to top of web page. Provided for convenience.
goTop :: ScrolledWindow -> IO ()
goTop window = do
adjustment <- scrolledWindowGetVAdjustment window
lower <- adjustmentGetLower adjustment
adjustmentSetValue adjustment lower
-- | Scroll down to bottom of web page. Provided for convenience.
goBottom :: ScrolledWindow -> IO ()
goBottom window = do
adjustment <- scrolledWindowGetVAdjustment window
upper <- adjustmentGetUpper adjustment
adjustmentSetValue adjustment upper
-- | Scroll to the left edge of web page. Provided for convenience.
goLeft :: ScrolledWindow -> IO ()
goLeft window = do
adjustment <- scrolledWindowGetHAdjustment window
lower <- adjustmentGetLower adjustment
adjustmentSetValue adjustment lower
-- | Scroll to the right edge of web page. Provided for convenience.
goRight :: ScrolledWindow -> IO ()
goRight window = do
adjustment <- scrolledWindowGetHAdjustment window
upper <- adjustmentGetUpper adjustment
adjustmentSetValue adjustment upper
-- }}}
-- {{{ Misc
-- | Wrapper around webFramePrint function, provided for convenience.
printPage :: WebView -> IO ()
printPage webView = do
frame <- webViewGetMainFrame webView
webFramePrint frame
-- | Execute a javascript file on current webpage.
executeJSFile :: String -> WebView -> IO ()
executeJSFile filePath webView = do
whenNormal $ putStrLn ("Executing Javascript file: " ++ filePath)
script <- readFile filePath
let script' = unwords . map (\line -> line ++ "\n") . lines $ script
webViewExecuteScript webView script'
-- }}}
-- | Save current web page to a file,
-- along with all its resources in a separated directory.
-- Doesn't work for now, because web_resource_get_data's binding is missing...
_savePage :: String -> WebView -> IO ()
_savePage _path webView = do
frame <- webViewGetMainFrame webView
dataSource <- webFrameGetDataSource frame
_mainResource <- webDataSourceGetMainResource dataSource
_subResources <- webDataSourceGetSubresources dataSource
return ()