1{-# OPTIONS_HADDOCK hide #-}
2{-# LANGUAGE NoMonomorphismRestriction #-}
3{-# LANGUAGE TemplateHaskell #-}
5-- | The input layer used to be a single function that correctly accounted for the non-threaded
6-- runtime by emulating the terminal VMIN adn VTIME handling. This has been removed and replace with
7-- a more straightforward parser. The non-threaded runtime is no longer supported.
9-- This is an example of an algorithm where code coverage could be high, even 100%, but the
10-- behavior is still under tested. I should collect more of these examples...
12-- reference: http://www.unixwiz.net/techtips/termios-vmin-vtime.html
13module Graphics.Vty.Input.Loop where
15import Graphics.Vty.Config
16import Graphics.Vty.Input.Classify
17import Graphics.Vty.Input.Events
19import Control.Applicative
20import Control.Concurrent
22import Control.Monad (when, mzero, forM_)
23import Control.Monad.IO.Class (liftIO)
24import Control.Monad.Trans.State (StateT(..), evalStateT)
25import Control.Monad.Trans.Reader (ReaderT(..))
29import Data.Word (Word8)
31import Foreign ( allocaArray, peekArray, Ptr )
32import Foreign.C.Types (CInt(..))
35import System.Posix.IO (fdReadBuf, setFdOption, FdOption(..))
36import System.Posix.Terminal
37import System.Posix.Types (Fd(..))
39import Text.Printf (hPrintf)
42 { -- | Channel of events direct from input processing. Unlike 'nextEvent' this will not refresh
43 -- the display if the next event is an 'EvResize'.
44 _eventChannel :: Chan Event
45 -- | Shuts down the input processing. This should return the terminal input state to before
46 -- the input initialized.
47 , shutdownInput :: IO ()
48 -- | Changes to this value are reflected after the next event.
49 , _configRef :: IORef Config
50 -- | File descriptor used for input.
53 , _inputDebug :: Maybe Handle
58data InputBuffer = InputBuffer
63makeLenses ''InputBuffer
65data InputState = InputState
66 { _unprocessedBytes :: String
67 , _appliedConfig :: Config
68 , _inputBuffer :: InputBuffer
69 , _stopRequestRef :: IORef Bool
70 , _classifier :: String -> KClass
73makeLenses ''InputState
75type InputM a = StateT InputState (ReaderT Input IO) a
77logMsg :: String -> InputM ()
82 Just h -> liftIO $ hPutStrLn h msg >> hFlush h
84-- this must be run on an OS thread dedicated to this input handling.
85-- otherwise the terminal timing read behavior will block the execution of the lightweight threads.
86loopInputProcessor :: InputM ()
87loopInputProcessor = do
88 readFromDevice >>= addBytesToProcess
89 validEvents <- many parseEvent
90 forM_ validEvents emit
92 stopIfRequested <|> loopInputProcessor
94addBytesToProcess :: String -> InputM ()
95addBytesToProcess block = unprocessedBytes <>= block
97emit :: Event -> InputM ()
99 logMsg $ "parsed event: " ++ show event
100 view eventChannel >>= liftIO . flip writeChan event
102-- The timing requirements are assured by the VMIN and VTIME set for the device.
104-- Precondition: Under the threaded runtime. Only current use is from a forkOS thread. That case
105-- satisfies precondition.
106-- TODO: When under the non-threaded runtime emulate VMIN and VTIME
107readFromDevice :: InputM String
109 newConfig <- view configRef >>= liftIO . readIORef
110 oldConfig <- use appliedConfig
112 when (newConfig /= oldConfig) $ do
113 logMsg $ "new config: " ++ show newConfig
114 liftIO $ applyTimingConfig fd newConfig
115 appliedConfig .= newConfig
116 bufferPtr <- use $ inputBuffer.ptr
117 maxBytes <- use $ inputBuffer.size
118 stringRep <- liftIO $ do
119 bytesRead <- fdReadBuf fd bufferPtr (fromIntegral maxBytes)
121 then fmap (map $ chr . fromIntegral) $ peekArray (fromIntegral bytesRead) bufferPtr
123 when (not $ null stringRep) $ logMsg $ "input bytes: " ++ show stringRep
126applyTimingConfig :: Fd -> Config -> IO ()
127applyTimingConfig fd config = setTermTiming fd (derivedVmin config)
128 (derivedVtime config `div` 100)
130parseEvent :: InputM Event
133 b <- use unprocessedBytes
135 Valid e remaining -> do
136 logMsg $ "valid parse: " ++ show e
137 logMsg $ "remaining: " ++ show remaining
138 unprocessedBytes .= remaining
142dropInvalid :: InputM ()
145 b <- use unprocessedBytes
146 when (c b == Invalid) $ do
147 logMsg "dropping input bytes"
148 unprocessedBytes .= []
150stopIfRequested :: InputM ()
152 True <- (liftIO . readIORef) =<< use stopRequestRef
155runInputProcessorLoop :: ClassifyMap -> Input -> IORef Bool -> IO ()
156runInputProcessorLoop classifyTable input stopFlag = do
157 let bufferSize = 1024
158 allocaArray bufferSize $ \(bufferPtr :: Ptr Word8) -> do
159 s0 <- InputState [] <$> readIORef (_configRef input)
160 <*> pure (InputBuffer bufferPtr bufferSize)
162 <*> pure (classify classifyTable)
163 runReaderT (evalStateT loopInputProcessor s0) input
165attributeControl :: Fd -> IO (IO (), IO ())
166attributeControl fd = do
167 original <- getTerminalAttributes fd
168 let vtyMode = foldl withoutMode original [ StartStopOutput, KeyboardInterrupts
169 , EnableEcho, ProcessInput, ExtendedFunctions
171 let setAttrs = setTerminalAttributes fd vtyMode Immediately
172 unsetAttrs = setTerminalAttributes fd original Immediately
173 return (setAttrs,unsetAttrs)
175logInitialInputState :: Input -> String -> ClassifyMap -> IO()
176logInitialInputState input termName classifyTable = case _inputDebug input of
179 config <- readIORef $ _configRef input
180 _ <- hPrintf h "initial (vmin,vtime): %s\n" (show (vmin config, vtime config))
181 forM_ classifyTable $ \i -> case i of
182 (inBytes, EvKey k mods) -> hPrintf h "map %s %s %s %s\n" (show termName)
188initInputForFd :: Config -> String -> ClassifyMap -> Fd -> IO Input
189initInputForFd config termName classifyTable inFd = do
190 setFdOption inFd NonBlockingRead False
191 applyTimingConfig inFd config
192 stopFlag <- newIORef False
193 input <- Input <$> newChan
194 <*> pure (writeIORef stopFlag True)
197 <*> maybe (return Nothing)
198 (\f -> Just <$> openFile f AppendMode)
200 logInitialInputState input termName classifyTable
201 _ <- forkOS $ runInputProcessorLoop classifyTable input stopFlag
204foreign import ccall "vty_set_term_timing" setTermTiming :: Fd -> Int -> Int -> 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