{-# 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", mKeyEventHandler = simpleKeyEventHandler, mKeyEventCallback = \_ -> simpleKeyEventCallback (keysListToMap []), 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 ()