1{-# LANGUAGE NoMonomorphismRestriction #-}
2{-# LANGUAGE TemplateHaskell #-}
4-- | The input layer used to be a single function that correctly accounted for the non-threaded
5-- runtime by emulating the terminal VMIN adn VTIME handling. This has been removed and replace with
6-- a more straightforward parser. The non-threaded runtime is no longer supported.
8-- This is an example of an algorithm where code coverage could be high, even 100%, but the
9-- behavior is still under tested. I should collect more of these examples...
10module Graphics.Vty.Input.Loop where
12import Graphics.Vty.Config
13import Graphics.Vty.Input.Classify
14import Graphics.Vty.Input.Events
16import Control.Applicative
17import Control.Concurrent
19import Control.Monad (when, mzero, forM_)
20import Control.Monad.IO.Class (liftIO)
21import Control.Monad.Trans.State (StateT(..), evalStateT)
22import Control.Monad.Trans.Reader (ReaderT(..))
26import Data.Word (Word8)
28import Foreign ( allocaArray, peekArray, Ptr )
29import Foreign.C.Types (CInt(..))
31import System.Posix.IO (fdReadBuf)
32import System.Posix.Terminal
33import System.Posix.Types (Fd(..))
36 { -- | Channel of events direct from input processing. Unlike 'nextEvent' this will not refresh
37 -- the display if the next event is an 'EvResize'.
38 _eventChannel :: Chan Event
39 -- | Shuts down the input processing. This should return the terminal input state to before
40 -- the input initialized.
41 , shutdownInput :: IO ()
42 -- | Changes to this value are reflected after the next event.
43 , _configRef :: IORef Config
44 -- | File descriptor used for input.
50data InputBuffer = InputBuffer
55makeLenses ''InputBuffer
57data InputState = InputState
58 { _unprocessedBytes :: String
59 , _appliedConfig :: Config
60 , _inputBuffer :: InputBuffer
61 , _stopRequestRef :: IORef Bool
62 , _classifier :: String -> KClass
65makeLenses ''InputState
67type InputM a = StateT InputState (ReaderT Input IO) a
69-- this must be run on an OS thread dedicated to this input handling.
70-- otherwise the terminal timing read behavior will block the execution of the lightweight threads.
71loopInputProcessor :: InputM ()
72loopInputProcessor = do
73 readFromDevice >>= addBytesToProcess
74 validEvents <- many parseEvent
75 forM_ validEvents emit
77 stopIfRequested <|> loopInputProcessor
79addBytesToProcess :: String -> InputM ()
80addBytesToProcess block = unprocessedBytes <>= block
82emit :: Event -> InputM ()
83emit event = view eventChannel >>= liftIO . flip writeChan event
85-- There should be two versions of this method:
86-- 1. using VMIN and VTIME when under the threaded runtime
87-- 2. emulating VMIN and VTIME in userspace when under the non-threaded runtime.
88readFromDevice :: InputM String
90 newConfig <- view configRef >>= liftIO . readIORef
91 oldConfig <- use appliedConfig
93 when (newConfig /= oldConfig) $ do
94 liftIO $ applyTimingConfig fd newConfig
95 appliedConfig .= newConfig
96 bufferPtr <- use $ inputBuffer.ptr
97 maxBytes <- use $ inputBuffer.size
99 bytesRead <- fdReadBuf fd bufferPtr (fromIntegral maxBytes)
101 then fmap (map $ chr . fromIntegral) $ peekArray (fromIntegral bytesRead) bufferPtr
104applyTimingConfig :: Fd -> Config -> IO ()
105applyTimingConfig fd config =
106 let vtime = min 255 $ singleEscPeriod config `div` 100000
107 in setTermTiming fd 1 vtime
109parseEvent :: InputM Event
112 b <- use unprocessedBytes
114 Valid e remaining -> do
115 unprocessedBytes .= remaining
119dropInvalid :: InputM ()
122 b <- use unprocessedBytes
123 when (c b == Invalid) $ unprocessedBytes .= []
125stopIfRequested :: InputM ()
127 True <- (liftIO . readIORef) =<< use stopRequestRef
130runInputProcessorLoop :: ClassifyTable -> Input -> IORef Bool -> IO ()
131runInputProcessorLoop classifyTable input stopFlag = do
132 let bufferSize = 1024
133 allocaArray bufferSize $ \(bufferPtr :: Ptr Word8) -> do
134 s0 <- InputState [] <$> readIORef (_configRef input)
135 <*> pure (InputBuffer bufferPtr bufferSize)
137 <*> pure (classify classifyTable)
138 runReaderT (evalStateT loopInputProcessor s0) input
140attributeControl :: Fd -> IO (IO (), IO ())
141attributeControl fd = do
142 original <- getTerminalAttributes fd
143 let vtyMode = foldl withoutMode original [ StartStopOutput, KeyboardInterrupts
144 , EnableEcho, ProcessInput, ExtendedFunctions
146 let setAttrs = setTerminalAttributes fd vtyMode Immediately
147 unsetAttrs = setTerminalAttributes fd original Immediately
148 return (setAttrs,unsetAttrs)
150initInputForFd :: Config -> ClassifyTable -> Fd -> IO Input
151initInputForFd config classifyTable inFd = do
152 applyTimingConfig inFd config
153 stopFlag <- newIORef False
154 input <- Input <$> newChan
155 <*> pure (writeIORef stopFlag True)
158 _ <- forkOS $ runInputProcessorLoop classifyTable input stopFlag
161foreign 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