1-- Copyright 2009-2010 Corey O'Connor
2{-# OPTIONS_GHC -Wall #-}
3{-# LANGUAGE ForeignFunctionInterface #-}
4module Graphics.Vty.LLInput ( Key(..)
13import Data.Maybe ( mapMaybe
15import Data.List( inits )
17import qualified Data.Map as M( fromList, lookup )
18import qualified Data.Set as S( fromList, member )
20import Codec.Binary.UTF8.Generic (decode)
22import Control.Monad (when)
23import Control.Concurrent
24import Control.Exception
26import System.Console.Terminfo
28import System.Posix.Signals.Exts
29import System.Posix.Terminal
30import System.Posix.IO ( stdInput
36import Foreign ( malloc, poke, peek, free )
38-- |Representations of non-modifier keys.
39data Key = KEsc | KFun Int | KBackTab | KPrtScr | KPause | KASCII Char | KBS | KIns
40 | KHome | KPageUp | KDel | KEnd | KPageDown | KBegin | KNP5 | KUp | KMenu
41 | KLeft | KDown | KRight | KEnter
42 deriving (Eq,Show,Ord)
44-- |Modifier keys. Key codes are interpreted such that users are more likely to
45-- have Meta than Alt; for instance on the PC Linux console, 'MMeta' will
46-- generally correspond to the physical Alt key.
47data Modifier = MShift | MCtrl | MMeta | MAlt
48 deriving (Eq,Show,Ord)
50-- |Mouse buttons. Not yet used.
51data Button = BLeft | BMiddle | BRight
52 deriving (Eq,Show,Ord)
55data Event = EvKey Key [Modifier] | EvMouse Int Int Button [Modifier]
57 deriving (Eq,Show,Ord)
59data KClass = Valid Key [Modifier] | Invalid | Prefix | MisPfx Key [Modifier] [Char]
62-- | Set up the terminal for input. Returns a function which reads key
63-- events, and a function for shutting down the terminal access.
64initTermInput :: Int -> Terminal -> IO (IO Event, IO ())
65initTermInput escDelay terminal = do
66 eventChannel <- newChan
67 inputChannel <- newChan
68 hadInput <- newEmptyMVar
69 oattr <- getTerminalAttributes stdInput
70 let nattr = foldl withoutMode oattr [StartStopOutput, KeyboardInterrupts,
71 EnableEcho, ProcessInput, ExtendedFunctions]
72 setTerminalAttributes stdInput nattr Immediately
74 let inputToEventThread :: IO ()
75 inputToEventThread = loop []
76 where loop kb = case (classify kb) of
77 Prefix -> do c <- readChan inputChannel
80 MisPfx k m s -> writeChan eventChannel (EvKey k m) >> loop s
81 Valid k m -> writeChan eventChannel (EvKey k m) >> loop ""
83 finishAtomicInput = writeChan inputChannel '\xFFFE'
89 setFdOption stdInput NonBlockingRead False
90 threadWaitRead stdInput
91 setFdOption stdInput NonBlockingRead True
92 _ <- try readAll :: IO (Either IOException ())
93 when (escDelay == 0) finishAtomicInput
98 bytes_read <- fdReadBuf stdInput ptr 1
99 bytes <- fmap ((: "") . chr . fromIntegral) $ peek ptr
101 when (bytes_read > 0) $ do
102 _ <- tryPutMVar hadInput () -- signal input
103 writeChan inputChannel (head bytes)
105 -- | If there is no input for some time, this thread puts '\xFFFE' in the
107 noInputThread :: IO ()
108 noInputThread = when (escDelay > 0) loop
110 takeMVar hadInput -- wait for some input
111 threadDelay escDelay -- microseconds
112 hadNoInput <- isEmptyMVar hadInput -- no input yet?
118 compile :: [[([Char],(Key,[Modifier]))]] -> [Char] -> KClass
119 compile lst = cl' where
121 pfx = S.fromList $ concatMap (init . inits . fst) $ lst'
122 mlst = M.fromList lst'
123 cl' str = case S.member str pfx of
125 False -> case M.lookup str mlst of
126 Just (k,m) -> Valid k m
127 Nothing -> case head $ mapMaybe (\s -> (,) s `fmap` M.lookup s mlst) $ init $ inits str of
128 (s,(k,m)) -> MisPfx k m (drop (length s) str)
130 -- ANSI specific bits
133 classify, classifyTab :: [Char] -> KClass
136 classify "\xFFFE" = Invalid
137 classify s@(c:_) | ord c >= 0xC2 =
138 if utf8Length (ord c) > length s then Prefix else classifyUtf8 s -- beginning of an utf8 sequence
139 classify other = classifyTab other
141 classifyUtf8 s = case decode ((map (fromIntegral . ord) s) :: [Word8]) of
142 Just (unicodeChar, _) -> Valid (KASCII unicodeChar) []
143 _ -> Invalid -- something bad happened; just ignore and continue.
145 classifyTab = compile (caps_classify_table : ansi_classify_table)
147 caps_tabls = [("khome", (KHome, [])),
148 ("kend", (KEnd, [])),
149 ("cbt", (KBackTab, [])),
150 ("kcud1", (KDown, [])),
151 ("kcuu1", (KUp, [])),
152 ("kcuf1", (KRight, [])),
153 ("kcub1", (KLeft, [])),
155 ("kLFT", (KLeft, [MShift])),
156 ("kRIT", (KRight, [MShift]))
159 caps_classify_table = [(x,y) | (Just x,y) <- map (first (getCapability terminal . tiGetStr)) $ caps_tabls]
161 ansi_classify_table :: [[([Char], (Key, [Modifier]))]]
162 ansi_classify_table =
163 [ let k c s = ("\ESC["++c,(s,[])) in [ k "G" KNP5
174 -- Support for arrows and KHome/KEnd
175 [("\ESC[" ++ charCnt ++ show mc++c,(s,m))
176 | charCnt <- ["1;", ""], -- we can have a count or not
177 (m,mc) <- [([MShift],2::Int), ([MCtrl],5), ([MMeta],3),
178 ([MShift, MCtrl],6), ([MShift, MMeta],4)], -- modifiers and their codes
179 (c,s) <- [("A", KUp), ("B", KDown), ("C", KRight), ("D", KLeft), ("H", KHome), ("F", KEnd)] -- directions and their codes
182 let k n s = ("\ESC["++show n++"~",(s,[]))
183 in zipWith k [2::Int,3,5,6,1,4]
184 [KIns,KDel,KPageUp,KPageDown,KHome,KEnd],
186 let k n s = ("\ESC["++show n++";5~",(s,[MCtrl]))
187 in zipWith k [2::Int,3,5,6,1,4]
188 [KIns,KDel,KPageUp,KPageDown,KHome,KEnd],
190 -- Support for simple characters.
191 [ (x:[],(KASCII x,[])) | x <- map toEnum [0..255] ],
193 -- Support for function keys (should use terminfo)
194 [ ("\ESC[["++[toEnum(64+i)],(KFun i,[])) | i <- [1..5] ],
195 let f ff nrs m = [ ("\ESC["++show n++"~",(KFun (n-(nrs!!0)+ff), m)) | n <- nrs ] in
196 concat [ f 6 [17..21] [], f 11 [23,24] [], f 1 [25,26] [MShift], f 3 [28,29] [MShift], f 5 [31..34] [MShift] ],
197 [ ('\ESC':[x],(KASCII x,[MMeta])) | x <- '\ESC':'\t':[' ' .. '\DEL'] ],
200 [ ([toEnum x],(KASCII y,[MCtrl]))
201 | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_']),
202 y /= 'i' -- Resolve issue #3 where CTRL-i hides TAB.
206 [ ('\ESC':[toEnum x],(KASCII y,[MMeta,MCtrl])) | (x,y) <- zip [0..31] ('@':['a'..'z']++['['..'_']) ],
209 [ -- special support for ESC
210 ("\ESC",(KEsc,[])) , ("\ESC\ESC",(KEsc,[MMeta])),
212 -- Special support for backspace
213 ("\DEL",(KBS,[])), ("\ESC\DEL",(KBS,[MMeta])),
215 -- Special support for Enter
216 ("\ESC\^J",(KEnter,[MMeta])), ("\^J",(KEnter,[])) ]
219 eventThreadId <- forkIO $ inputToEventThread
220 inputThreadId <- forkIO $ inputThread
221 noInputThreadId <- forkIO $ noInputThread
222 let pokeIO = (Catch $ do let e = error "(getsize in input layer)"
223 setTerminalAttributes stdInput nattr Immediately
224 writeChan eventChannel (EvResize e e))
225 _ <- installHandler windowChange pokeIO Nothing
226 _ <- installHandler continueProcess pokeIO Nothing
227 let uninit = do killThread eventThreadId
228 killThread inputThreadId
229 killThread noInputThreadId
230 _ <- installHandler windowChange Ignore Nothing
231 _ <- installHandler continueProcess Ignore Nothing
232 setTerminalAttributes stdInput oattr Immediately
233 return (readChan eventChannel, uninit)
235first :: (a -> b) -> (a,c) -> (b,c)
236first f (x,y) = (f x, y)
238utf8Length :: (Num t, Ord a, Num a) => a -> t
245foreign import ccall "vty_set_term_timing" set_term_timing :: IO ()
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list