rofi-extras/lib/Rofi/Command.hs

189 lines
4.9 KiB
Haskell
Raw Normal View History

2020-04-23 23:32:29 -04:00
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Rofi.Command
( RofiConf(..)
, RofiMenu(..)
, RofiAction
, RofiActions
2020-05-01 21:29:54 -04:00
, RofiIO
, RofiGroup
2020-04-23 23:32:29 -04:00
, Hotkey(..)
, io
, emptyMenu
2020-05-01 21:29:54 -04:00
, runRofiIO
2020-04-23 23:32:29 -04:00
, toRofiActions
, rofiActionKeys
, untitledGroup
, titledGroup
, selectAction
2020-05-01 21:29:54 -04:00
, readPassword
2020-04-23 23:32:29 -04:00
, readCmdSuccess
, readCmdEither
2020-05-01 21:29:54 -04:00
, readCmdEither'
2020-04-23 23:32:29 -04:00
, dmenuArgs
, joinNewline
, stripWS
) where
2020-05-01 21:29:54 -04:00
import Control.Monad.IO.Unlift
2020-04-23 23:32:29 -04:00
import Control.Monad.Reader
import Data.Char
import Data.List
2020-05-01 21:29:54 -04:00
import qualified Data.Map.Ordered as M
2020-04-23 23:32:29 -04:00
import Data.Maybe
import System.Exit
import System.Process
2020-05-01 21:29:54 -04:00
class RofiConf c where
defArgs :: c -> [String]
2020-04-23 23:32:29 -04:00
2020-05-01 21:29:54 -04:00
type RofiAction c = (String, RofiIO c ())
2020-04-23 23:32:29 -04:00
2020-05-01 21:29:54 -04:00
type RofiActions c = M.OMap String (RofiIO c ())
2020-04-23 23:32:29 -04:00
2020-05-01 21:29:54 -04:00
data RofiGroup c = RofiGroup
{ actions :: RofiActions c
2020-04-23 23:32:29 -04:00
, title :: Maybe String
}
2020-05-01 21:29:54 -04:00
untitledGroup :: RofiActions c -> RofiGroup c
2020-04-23 23:32:29 -04:00
untitledGroup a = RofiGroup { actions = a, title = Nothing }
2020-05-01 21:29:54 -04:00
titledGroup :: String -> RofiActions c -> RofiGroup c
2020-04-23 23:32:29 -04:00
titledGroup t a = (untitledGroup a) { title = Just t }
2020-05-01 21:29:54 -04:00
data Hotkey c = Hotkey
2020-04-23 23:32:29 -04:00
{ keyCombo :: String
-- only 1-10 are valid
, keyIndex :: Int
, keyDescription :: String
2020-05-01 21:29:54 -04:00
, keyActions :: RofiActions c
2020-04-23 23:32:29 -04:00
}
2020-05-01 21:29:54 -04:00
hotkeyBinding :: Hotkey c -> [String]
2020-04-23 23:32:29 -04:00
hotkeyBinding Hotkey { keyIndex = e, keyCombo = c } = [k, c]
where
k = "-kb-custom-" ++ show e
2020-05-01 21:29:54 -04:00
hotkeyMsg1 :: Hotkey c -> String
2020-04-23 23:32:29 -04:00
hotkeyMsg1 Hotkey { keyCombo = c, keyDescription = d } =
c ++ ": <i>" ++ d ++ "</i>"
2020-05-01 21:29:54 -04:00
hotkeyMsg :: [Hotkey c] -> [String]
2020-04-23 23:32:29 -04:00
hotkeyMsg [] = []
hotkeyMsg hs = ["-mesg", intercalate " | " $ fmap hotkeyMsg1 hs]
2020-05-01 21:29:54 -04:00
hotkeyArgs :: [Hotkey c] -> [String]
2020-04-23 23:32:29 -04:00
hotkeyArgs hks = hotkeyMsg hks ++ concatMap hotkeyBinding hks
2020-05-01 21:29:54 -04:00
data RofiMenu c = RofiMenu
{ groups :: [RofiGroup c]
2020-04-23 23:32:29 -04:00
, prompt :: Maybe String
2020-05-01 21:29:54 -04:00
, hotkeys :: [Hotkey c]
2020-04-23 23:32:29 -04:00
}
2020-05-01 21:29:54 -04:00
emptyMenu :: RofiMenu c
2020-04-23 23:32:29 -04:00
emptyMenu = RofiMenu
{ groups = []
, prompt = Nothing
, hotkeys = []
}
2020-05-01 21:29:54 -04:00
newtype RofiIO c a = RofiIO (ReaderT c IO a)
deriving (Functor, Monad, MonadIO, MonadReader c, MonadUnliftIO)
2020-04-23 23:32:29 -04:00
2020-05-01 21:29:54 -04:00
instance Applicative (RofiIO c) where
2020-04-23 23:32:29 -04:00
pure = return
(<*>) = ap
2020-05-01 21:29:54 -04:00
io :: MonadIO m => IO a -> m a
2020-04-23 23:32:29 -04:00
io = liftIO
2020-05-01 21:29:54 -04:00
runRofiIO :: c -> RofiIO c a -> IO a
runRofiIO c (RofiIO r) = runReaderT r c
2020-04-23 23:32:29 -04:00
2020-05-01 21:29:54 -04:00
toRofiActions :: [(String, RofiIO c ())] -> RofiActions c
2020-04-23 23:32:29 -04:00
toRofiActions = M.fromList
2020-05-01 21:29:54 -04:00
rofiActionKeys :: RofiActions c -> String
2020-04-23 23:32:29 -04:00
rofiActionKeys = joinNewline . map fst . M.assocs
2020-05-01 21:29:54 -04:00
lookupRofiAction :: String -> RofiActions c -> RofiIO c ()
2020-04-23 23:32:29 -04:00
lookupRofiAction key ras = fromMaybe (return ()) $ M.lookup key ras
2020-05-01 21:29:54 -04:00
groupEntries :: RofiGroup c -> String
2020-04-23 23:32:29 -04:00
groupEntries RofiGroup { actions = a, title = t }
| null a = ""
| otherwise = title' ++ rofiActionKeys a
where
title' = maybe "" (++ "\n") t
2020-05-01 21:29:54 -04:00
menuActions :: RofiMenu c -> RofiActions c
2020-04-23 23:32:29 -04:00
menuActions = foldr1 (M.<>|) . fmap actions . groups
2020-05-01 21:29:54 -04:00
menuEntries :: RofiMenu c -> String
2020-05-28 22:42:55 -04:00
menuEntries = intercalate "\n\n" . filter (not . null) . fmap groupEntries . groups
2020-04-23 23:32:29 -04:00
2020-05-01 21:29:54 -04:00
selectAction :: RofiConf c => RofiMenu c -> RofiIO c ()
2020-04-23 23:32:29 -04:00
selectAction rm = do
let p = maybeOption "-p" $ prompt rm
let hArgs = hotkeyArgs $ hotkeys rm
res <- readRofi (p ++ hArgs) $ menuEntries rm
case res of
Right key -> lookupRofiAction key $ menuActions rm
Left (n, key, _) -> mapM_ (lookupRofiAction key . keyActions)
$ find ((==) n . (+ 9) . keyIndex)
$ hotkeys rm
maybeOption :: String -> Maybe String -> [String]
maybeOption switch = maybe [] (\o -> [switch, o])
dmenuArgs :: [String]
dmenuArgs = ["-dmenu"]
2020-05-01 21:29:54 -04:00
readRofi :: RofiConf c => [String]
-> String
-> RofiIO c (Either (Int, String, String) String)
2020-04-23 23:32:29 -04:00
readRofi uargs input = do
dargs <- asks defArgs
io $ readCmdEither "rofi" (dmenuArgs ++ dargs ++ uargs) input
readCmdSuccess :: String -> [String] -> String -> IO (Maybe String)
readCmdSuccess cmd args input = either (const Nothing) Just
<$> readCmdEither cmd args input
2020-05-01 21:29:54 -04:00
readCmdEither :: String
-> [String]
-> String
-> IO (Either (Int, String, String) String)
readCmdEither cmd args input = resultToEither
<$> readProcessWithExitCode cmd args input
readCmdEither' :: String
-> [String]
-> String
-> [(String, String)]
-> IO (Either (Int, String, String) String)
readCmdEither' cmd args input environ = resultToEither
<$> readCreateProcessWithExitCode p input
where
p = (proc cmd args) { env = Just environ }
resultToEither :: (ExitCode, String, String)
-> Either (Int, String, String) String
resultToEither (ExitSuccess, out, _) = Right $ stripWS out
resultToEither (ExitFailure n, out, err) = Left (n, stripWS out, stripWS err)
2020-04-23 23:32:29 -04:00
stripWS :: String -> String
stripWS = reverse . dropWhile isSpace . reverse
joinNewline :: [String] -> String
joinNewline = intercalate "\n"
2020-05-01 21:29:54 -04:00
readPassword :: IO (Maybe String)
readPassword = readCmdSuccess "rofi" args ""
where
args = dmenuArgs ++ ["-p", "Password", "-password"]