summaryrefslogtreecommitdiff
path: root/.xmonad/lib/XMonad/Prompt/MySsh.hs
diff options
context:
space:
mode:
Diffstat (limited to '.xmonad/lib/XMonad/Prompt/MySsh.hs')
-rw-r--r--.xmonad/lib/XMonad/Prompt/MySsh.hs241
1 files changed, 241 insertions, 0 deletions
diff --git a/.xmonad/lib/XMonad/Prompt/MySsh.hs b/.xmonad/lib/XMonad/Prompt/MySsh.hs
new file mode 100644
index 0000000..e6ea042
--- /dev/null
+++ b/.xmonad/lib/XMonad/Prompt/MySsh.hs
@@ -0,0 +1,241 @@
1module XMonad.Prompt.MySsh
2 ( -- * Usage
3 -- $usage
4 sshPrompt,
5 Ssh,
6 Override (..),
7 mkOverride,
8 Conn (..),
9 moshCmd,
10 moshCmd',
11 sshCmd,
12 inTmux,
13 withEnv
14 ) where
15
16import XMonad
17import XMonad.Util.Run
18import XMonad.Prompt
19
20import System.Directory
21import System.Environment
22import qualified Control.Exception as E
23
24import Control.Monad
25import Data.Maybe
26
27import Text.Parsec.String
28import Text.Parsec
29import Data.Char (isSpace)
30
31econst :: Monad m => a -> E.IOException -> m a
32econst = const . return
33
34-- $usage
35-- 1. In your @~\/.xmonad\/xmonad.hs@:
36--
37-- > import XMonad.Prompt
38-- > import XMonad.Prompt.Ssh
39--
40-- 2. In your keybindings add something like:
41--
42-- > , ((modm .|. controlMask, xK_s), sshPrompt defaultXPConfig)
43--
44-- Keep in mind, that if you want to use the completion you have to
45-- disable the "HashKnownHosts" option in your ssh_config
46--
47-- For detailed instruction on editing the key binding see
48-- "XMonad.Doc.Extending#Editing_key_bindings".
49
50data Override = Override
51 { oUser :: Maybe String
52 , oHost :: String
53 , oPort :: Maybe Int
54 , oCommand :: Conn -> String
55 }
56
57mkOverride = Override { oUser = Nothing, oHost = "", oPort = Nothing, oCommand = sshCmd }
58sshCmd c = concat
59 [ "ssh -t "
60 , if isJust $ cUser c then (fromJust $ cUser c) ++ "@" else ""
61 , cHost c
62 , if isJust $ cPort c then " -p " ++ (show $ fromJust $ cPort c) else ""
63 , " -- "
64 , cCommand c
65 ]
66moshCmd c = concat
67 [ "mosh "
68 , if isJust $ cUser c then (fromJust $ cUser c) ++ "@" else ""
69 , cHost c
70 , if isJust $ cPort c then " --ssh=\"ssh -p " ++ (show $ fromJust $ cPort c) ++ "\"" else ""
71 , " -- "
72 , cCommand c
73 ]
74moshCmd' p c = concat
75 [ "mosh "
76 , "--server=" ++ p ++ " "
77 , if isJust $ cUser c then (fromJust $ cUser c) ++ "@" else ""
78 , cHost c
79 , if isJust $ cPort c then " --ssh=\"ssh -p " ++ (show $ fromJust $ cPort c) ++ "\"" else ""
80 , " -- "
81 , cCommand c
82 ]
83inTmux c = c { cCommand = "tmux new-session \"" ++ (cCommand c) ++ "\"" }
84withEnv :: [(String, String)] -> Conn -> Conn
85withEnv envs c = c { cCommand = "env" ++ (concat $ map (\(n, v) -> ' ' : (n ++ "=" ++ v)) envs) ++ " " ++ (cCommand c) }
86
87data Conn = Conn
88 { cUser :: Maybe String
89 , cHost :: String
90 , cPort :: Maybe Int
91 , cCommand :: String
92 } deriving (Eq, Show, Read)
93
94data Ssh = Ssh
95
96instance XPrompt Ssh where
97 showXPrompt Ssh = "SSH to: "
98 commandToComplete _ c = c
99 nextCompletion _ = getNextCompletion
100
101toConn :: String -> Maybe Conn
102toConn = toConn' . parse connParser "(unknown)"
103toConn' :: Either ParseError Conn -> Maybe Conn
104toConn' (Left _) = Nothing
105toConn' (Right a) = Just a
106
107connParser :: Parser Conn
108connParser = do
109 spaces
110 user' <- optionMaybe $ try $ do
111 str <- many1 $ satisfy (\c -> (not $ isSpace c) && (c /= '@'))
112 char '@'
113 return str
114 host' <- many1 $ satisfy (not . isSpace)
115 port' <- optionMaybe $ try $ do
116 space
117 string "-p"
118 spaces
119 int <- many1 digit
120 (space >> return ()) <|> eof
121 return $ (read int :: Int)
122 spaces
123 command' <- many anyChar
124 eof
125 return $ Conn
126 { cHost = host'
127 , cUser = user'
128 , cPort = port'
129 , cCommand = command'
130 }
131
132sshPrompt :: [Override] -> XPConfig -> X ()
133sshPrompt o c = do
134 sc <- io sshComplList
135 mkXPrompt Ssh c (mkComplFunFromList sc) $ ssh o
136
137ssh :: [Override] -> String -> X ()
138ssh overrides str = do
139 let cmd = applyOverrides overrides str
140 --liftIO $ putStr "SSH Command: "
141 --liftIO $ putStrLn cmd
142 runInTerm "" cmd
143
144applyOverrides :: [Override] -> String -> String
145applyOverrides [] str = "ssh " ++ str
146applyOverrides (o:os) str = case (applyOverride o str) of
147 Just str -> str
148 Nothing -> applyOverrides os str
149
150applyOverride :: Override -> String -> Maybe String
151applyOverride o str = let
152 conn = toConn str
153 in
154 if isNothing conn then Nothing else
155 case (fromJust conn) `matches` o of
156 True -> Just $ (oCommand o) (fromJust conn)
157 False -> Nothing
158
159matches :: Conn -> Override -> Bool
160a `matches` b = and
161 [ justBool (cUser a) (oUser b) (==)
162 , (cHost a) == (oHost b)
163 , justBool (cPort a) (oPort b) (==)
164 ]
165
166justBool :: Eq a => Maybe a -> Maybe a -> (a -> a -> Bool) -> Bool
167justBool Nothing _ _ = True
168justBool _ Nothing _ = True
169justBool (Just a) (Just b) match = a `match` b
170
171sshComplList :: IO [String]
172sshComplList = uniqSort `fmap` liftM2 (++) sshComplListLocal sshComplListGlobal
173
174sshComplListLocal :: IO [String]
175sshComplListLocal = do
176 h <- getEnv "HOME"
177 s1 <- sshComplListFile $ h ++ "/.ssh/known_hosts"
178 s2 <- sshComplListConf $ h ++ "/.ssh/config"
179 return $ s1 ++ s2
180
181sshComplListGlobal :: IO [String]
182sshComplListGlobal = do
183 env <- getEnv "SSH_KNOWN_HOSTS" `E.catch` econst "/nonexistent"
184 fs <- mapM fileExists [ env
185 , "/usr/local/etc/ssh/ssh_known_hosts"
186 , "/usr/local/etc/ssh_known_hosts"
187 , "/etc/ssh/ssh_known_hosts"
188 , "/etc/ssh_known_hosts"
189 ]
190 case catMaybes fs of
191 [] -> return []
192 (f:_) -> sshComplListFile' f
193
194sshComplListFile :: String -> IO [String]
195sshComplListFile kh = do
196 f <- doesFileExist kh
197 if f then sshComplListFile' kh
198 else return []
199
200sshComplListFile' :: String -> IO [String]
201sshComplListFile' kh = do
202 l <- readFile kh
203 return $ map (getWithPort . takeWhile (/= ',') . concat . take 1 . words)
204 $ filter nonComment
205 $ lines l
206
207sshComplListConf :: String -> IO [String]
208sshComplListConf kh = do
209 f <- doesFileExist kh
210 if f then sshComplListConf' kh
211 else return []
212
213sshComplListConf' :: String -> IO [String]
214sshComplListConf' kh = do
215 l <- readFile kh
216 return $ map (!!1)
217 $ filter isHost
218 $ map words
219 $ lines l
220 where
221 isHost ws = take 1 ws == ["Host"] && length ws > 1
222
223fileExists :: String -> IO (Maybe String)
224fileExists kh = do
225 f <- doesFileExist kh
226 if f then return $ Just kh
227 else return Nothing
228
229nonComment :: String -> Bool
230nonComment [] = False
231nonComment ('#':_) = False
232nonComment ('|':_) = False -- hashed, undecodeable
233nonComment _ = True
234
235getWithPort :: String -> String
236getWithPort ('[':str) = host ++ " -p " ++ port
237 where (host,p) = break (==']') str
238 port = case p of
239 ']':':':x -> x
240 _ -> "22"
241getWithPort str = str