2020-04-23 23:32:29 -04:00
|
|
|
module Rofi.Command
|
2023-02-13 22:19:49 -05:00
|
|
|
( RofiConf (..)
|
|
|
|
, RofiMenu (..)
|
2020-04-23 23:32:29 -04:00
|
|
|
, RofiAction
|
|
|
|
, RofiActions
|
2020-05-01 21:29:54 -04:00
|
|
|
, RofiIO
|
|
|
|
, RofiGroup
|
2023-02-13 22:19:49 -05:00
|
|
|
, Hotkey (..)
|
2020-04-23 23:32:29 -04:00
|
|
|
, 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-11-28 15:57:24 -05: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
|
2023-02-13 22:19:49 -05:00
|
|
|
)
|
|
|
|
where
|
|
|
|
|
|
|
|
import Control.Monad.IO.Unlift
|
|
|
|
import Control.Monad.Reader
|
|
|
|
import Data.Char
|
|
|
|
import qualified Data.Map.Ordered as M
|
|
|
|
import Data.Maybe
|
|
|
|
import RIO
|
|
|
|
import qualified RIO.List as L
|
|
|
|
import System.Process
|
2020-04-23 23:32:29 -04:00
|
|
|
|
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
|
2023-02-13 22:19:49 -05:00
|
|
|
{ actions :: RofiActions c
|
|
|
|
, title :: Maybe String
|
|
|
|
}
|
2020-04-23 23:32:29 -04:00
|
|
|
|
2020-05-01 21:29:54 -04:00
|
|
|
untitledGroup :: RofiActions c -> RofiGroup c
|
2023-02-13 22:19:49 -05:00
|
|
|
untitledGroup a = RofiGroup {actions = a, title = Nothing}
|
2020-04-23 23:32:29 -04:00
|
|
|
|
2020-05-01 21:29:54 -04:00
|
|
|
titledGroup :: String -> RofiActions c -> RofiGroup c
|
2023-02-13 22:19:49 -05:00
|
|
|
titledGroup t a = (untitledGroup a) {title = Just t}
|
2020-04-23 23:32:29 -04:00
|
|
|
|
2020-05-01 21:29:54 -04:00
|
|
|
data Hotkey c = Hotkey
|
2023-02-13 22:19:49 -05:00
|
|
|
{ keyCombo :: String
|
|
|
|
, -- only 1-10 are valid
|
|
|
|
keyIndex :: Int
|
|
|
|
, keyDescription :: String
|
|
|
|
, keyActions :: RofiActions c
|
|
|
|
}
|
2020-04-23 23:32:29 -04:00
|
|
|
|
2020-05-01 21:29:54 -04:00
|
|
|
hotkeyBinding :: Hotkey c -> [String]
|
2023-02-13 22:19:49 -05:00
|
|
|
hotkeyBinding Hotkey {keyIndex = e, keyCombo = c} = [k, c]
|
2020-04-23 23:32:29 -04:00
|
|
|
where
|
|
|
|
k = "-kb-custom-" ++ show e
|
|
|
|
|
2020-05-01 21:29:54 -04:00
|
|
|
hotkeyMsg1 :: Hotkey c -> String
|
2023-02-13 22:19:49 -05:00
|
|
|
hotkeyMsg1 Hotkey {keyCombo = c, keyDescription = d} =
|
2020-04-23 23:32:29 -04:00
|
|
|
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 [] = []
|
2023-02-13 22:19:49 -05:00
|
|
|
hotkeyMsg hs = ["-mesg", L.intercalate " | " $ fmap hotkeyMsg1 hs]
|
2020-04-23 23:32:29 -04:00
|
|
|
|
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
|
2023-02-13 22:19:49 -05:00
|
|
|
{ groups :: [RofiGroup c]
|
|
|
|
, prompt :: Maybe String
|
|
|
|
, hotkeys :: [Hotkey c]
|
|
|
|
}
|
2020-04-23 23:32:29 -04:00
|
|
|
|
2020-05-01 21:29:54 -04:00
|
|
|
emptyMenu :: RofiMenu c
|
2023-02-13 22:19:49 -05:00
|
|
|
emptyMenu =
|
|
|
|
RofiMenu
|
|
|
|
{ groups = []
|
|
|
|
, prompt = Nothing
|
|
|
|
, hotkeys = []
|
|
|
|
}
|
2020-04-23 23:32:29 -04:00
|
|
|
|
2020-05-01 21:29:54 -04:00
|
|
|
newtype RofiIO c a = RofiIO (ReaderT c IO a)
|
2023-02-13 22:19:49 -05:00
|
|
|
deriving (Functor, Applicative, Monad, MonadIO, MonadReader c, MonadUnliftIO)
|
2020-04-23 23:32:29 -04:00
|
|
|
|
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
|
2023-02-13 22:19:49 -05:00
|
|
|
groupEntries RofiGroup {actions = a, title = t}
|
2020-04-23 23:32:29 -04:00
|
|
|
| null a = ""
|
|
|
|
| otherwise = title' ++ rofiActionKeys a
|
|
|
|
where
|
|
|
|
title' = maybe "" (++ "\n") t
|
|
|
|
|
2020-05-01 21:29:54 -04:00
|
|
|
menuActions :: RofiMenu c -> RofiActions c
|
2023-02-13 22:19:49 -05:00
|
|
|
menuActions = L.foldr (M.<>|) M.empty . fmap actions . groups
|
2020-04-23 23:32:29 -04:00
|
|
|
|
2020-05-01 21:29:54 -04:00
|
|
|
menuEntries :: RofiMenu c -> String
|
2023-02-13 22:19:49 -05:00
|
|
|
menuEntries = L.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
|
2023-02-13 22:19:49 -05:00
|
|
|
Right key -> lookupRofiAction key $ menuActions rm
|
|
|
|
Left (n, key, _) ->
|
|
|
|
mapM_ (lookupRofiAction key . keyActions) $
|
|
|
|
L.find ((==) n . (+ 9) . keyIndex) $
|
|
|
|
hotkeys rm
|
2020-04-23 23:32:29 -04:00
|
|
|
|
|
|
|
maybeOption :: String -> Maybe String -> [String]
|
|
|
|
maybeOption switch = maybe [] (\o -> [switch, o])
|
|
|
|
|
|
|
|
dmenuArgs :: [String]
|
|
|
|
dmenuArgs = ["-dmenu"]
|
|
|
|
|
2023-02-13 22:19:49 -05:00
|
|
|
readRofi
|
|
|
|
:: RofiConf c
|
|
|
|
=> [String]
|
2020-05-01 21:29:54 -04:00
|
|
|
-> 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)
|
2023-02-13 22:19:49 -05:00
|
|
|
readCmdSuccess cmd args input =
|
|
|
|
either (const Nothing) Just
|
|
|
|
<$> readCmdEither cmd args input
|
2020-04-23 23:32:29 -04:00
|
|
|
|
2023-02-13 22:19:49 -05:00
|
|
|
readCmdEither
|
|
|
|
:: String
|
2020-05-01 21:29:54 -04:00
|
|
|
-> [String]
|
|
|
|
-> String
|
|
|
|
-> IO (Either (Int, String, String) String)
|
2023-02-13 22:19:49 -05:00
|
|
|
readCmdEither cmd args input =
|
|
|
|
resultToEither
|
|
|
|
<$> readProcessWithExitCode cmd args input
|
2020-05-01 21:29:54 -04:00
|
|
|
|
2023-02-13 22:19:49 -05:00
|
|
|
readCmdEither'
|
|
|
|
:: String
|
2020-05-01 21:29:54 -04:00
|
|
|
-> [String]
|
|
|
|
-> String
|
|
|
|
-> [(String, String)]
|
|
|
|
-> IO (Either (Int, String, String) String)
|
2023-02-13 22:19:49 -05:00
|
|
|
readCmdEither' cmd args input environ =
|
|
|
|
resultToEither
|
|
|
|
<$> readCreateProcessWithExitCode p input
|
2020-05-01 21:29:54 -04:00
|
|
|
where
|
2023-02-13 22:19:49 -05:00
|
|
|
p = (proc cmd args) {env = Just environ}
|
2020-05-01 21:29:54 -04:00
|
|
|
|
2023-02-13 22:19:49 -05:00
|
|
|
resultToEither
|
|
|
|
:: (ExitCode, String, String)
|
2020-05-01 21:29:54 -04:00
|
|
|
-> Either (Int, String, String) String
|
2023-02-13 22:19:49 -05:00
|
|
|
resultToEither (ExitSuccess, out, _) = Right $ stripWS out
|
2020-05-01 21:29:54 -04:00
|
|
|
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
|
2023-02-13 22:19:49 -05:00
|
|
|
joinNewline = L.intercalate "\n"
|
2020-05-01 21:29:54 -04:00
|
|
|
|
|
|
|
readPassword :: IO (Maybe String)
|
2020-11-28 15:57:24 -05:00
|
|
|
readPassword = readPassword' "Password"
|
|
|
|
|
|
|
|
readPassword' :: String -> IO (Maybe String)
|
|
|
|
readPassword' p = readCmdSuccess "rofi" args ""
|
2020-05-01 21:29:54 -04:00
|
|
|
where
|
2020-11-28 15:57:24 -05:00
|
|
|
args = dmenuArgs ++ ["-p", p, "-password"]
|