@@ -1,3 +1,6 @@
1+{-# LANGUAGE NoMonomorphismRestriction #-}
2+{-# LANGUAGE TemplateHaskell #-}
1-- Much of the input layer used to be in a single module with a few large functions. I've refactored
4-- Much of the input layer used to be in a single module with a few large functions. I've refactored
2-- the input layer into many small bits. Now, I think, the code is a better position to be
5-- the input layer into many small bits. Now, I think, the code is a better position to be
3-- incrementally refined. Still, until there are proper tests in place much of the refinement must
6-- incrementally refined. Still, until there are proper tests in place much of the refinement must
@@ -7,18 +10,25 @@ module Graphics.Vty.Input.Internal where
7import Graphics.Vty.Input.Events
10import Graphics.Vty.Input.Events
9import Codec.Binary.UTF8.Generic (decode)
12import Codec.Binary.UTF8.Generic (decode)
13+import Control.Applicative
10import Control.Concurrent
14import Control.Concurrent
11import Control.Exception (try, IOException)
16import Control.Exception (try, IOException)
12−import Control.Monad (when, void)
17+import Control.Monad (when, void, mzero)
18+import Control.Monad.IO.Class (liftIO)
19+import Control.Monad.Trans.State (StateT(..))
20+import Control.Monad.Trans.Reader (ReaderT(..))
14import Data.Char
22import Data.Char
15import Data.List( inits )
25import Data.List( inits )
16import qualified Data.Map as M( fromList, lookup )
26import qualified Data.Map as M( fromList, lookup )
17import Data.Maybe ( mapMaybe )
27import Data.Maybe ( mapMaybe )
18import qualified Data.Set as S( fromList, member )
28import qualified Data.Set as S( fromList, member )
19import Data.Word
29import Data.Word
21−import Foreign ( alloca, poke, peek, Ptr )
31+import Foreign ( alloca, poke, peek, peekArray, Ptr )
22import Foreign.C.Types (CInt(..))
32import Foreign.C.Types (CInt(..))
24import System.Posix.IO ( fdReadBuf
34import System.Posix.IO ( fdReadBuf
@@ -27,48 +37,164 @@ import System.Posix.IO ( fdReadBuf
28import System.Posix.Types (Fd(..))
38import System.Posix.Types (Fd(..))
41+ { controlSeqPeriod :: Int -- ^ default control sequence period is 10000 microseconds. Which is
42+ -- assumed to be well above the sampling rate required to detect a
43+ -- keyup for a person typing 200 wpm.
44+ , metaComboPeriod :: Int -- ^ The default meta combo period is 100000 microseconds.
45+ } deriving (Show, Eq)
47+instance Default Config where
49+ { controlSeqPeriod = 10000
50+ , metaComboPeriod = 100000
30data Input = Input
53data Input = Input
31 { -- | Channel of events direct from input processing. Unlike 'next_event' this will not refresh
54 { -- | Channel of events direct from input processing. Unlike 'next_event' this will not refresh
32 -- the display if the next event is an 'EvResize'.
55 -- the display if the next event is an 'EvResize'.
33− event_channel :: Chan Event
56+ _event_channel :: Chan Event
34 -- | Shuts down the input processing. This should return the terminal input state to before
57 -- | Shuts down the input processing. This should return the terminal input state to before
35 -- the input initialized.
58 -- the input initialized.
36 , shutdown_input :: IO ()
59 , shutdown_input :: IO ()
60+ -- | Changes to this value are reflected after the next event.
61+ , _config_ref :: IORef Config
62+ -- | File descriptor used for input.
39data KClass
68data KClass
40− = Valid Key [Modifier]
69+ = Valid Key [Modifier] [Char]
44− | MisPfx Key [Modifier] [Char]
45− deriving(Show)
72+ deriving(Show, Eq)
74+data InputBuffer = InputBuffer
79+makeLenses ''InputBuffer
81+data InputState = InputState
82+ { _unprocessed_bytes :: String
83+ , _applied_config :: Config
84+ , _input_buffer :: InputBuffer
85+ , _stop_request_ref :: IORef Bool
86+ , _classifier :: String -> KClass
89+makeLenses ''InputState
91+type InputM a = StateT InputState (ReaderT Input IO) a
93+loop_input_processor :: InputM ()
94+loop_input_processor = do
95+ read_from_device >>= add_bytes_to_process
96+ _ <- many $ parse_event >>= emit
98+ stop_requested <|> loop_input_processor
100+add_bytes_to_process :: String -> InputM ()
101+add_bytes_to_process block = unprocessed_bytes <>= block
103+emit :: Event -> InputM ()
104+emit event = view event_channel >>= liftIO . flip writeChan event
106+read_from_device :: InputM String
107+read_from_device = do
108+ new_config <- view config_ref >>= liftIO . readIORef
109+ old_config <- use applied_config
111+ when (new_config /= old_config) $ do
112+ let vtime = min 255 $ metaComboPeriod new_config `div` 100000
113+ liftIO $ set_term_timing fd 0 vtime
114+ applied_config .= new_config
115+ buffer_ptr <- use $ input_buffer.ptr
116+ max_bytes <- use $ input_buffer.size
118+ bytes_read <- fdReadBuf fd buffer_ptr (fromIntegral max_bytes)
119+ fmap (map $ chr . fromIntegral) $ peekArray (fromIntegral bytes_read) buffer_ptr
121+parse_event :: InputM Event
124+ b <- use unprocessed_bytes
126+ Valid k m remaining -> do
127+ unprocessed_bytes .= remaining
131+drop_invalid :: InputM ()
134+ b <- use unprocessed_bytes
135+ when (c b == Invalid) $ unprocessed_bytes .= []
137+stop_requested :: InputM ()
139+ True <- (liftIO . readIORef) =<< use stop_request_ref
47-- The input uses two magic character:
148-- The input uses two magic character:
49-- * '\xFFFD' for "stop processing"
150-- * '\xFFFD' for "stop processing"
50-- * '\xFFFE' for "previous input chunk is complete" Which is used to differentiate a single ESC
151-- * '\xFFFE' for "previous input chunk is complete" Which is used to differentiate a single ESC
51-- from using ESC as meta or to indicate a control sequence.
152-- from using ESC as meta or to indicate a control sequence.
154+-- | Read 'InputChunk' from the input channel, parse into one or more 'Event's, output to event
157+-- Each input chunk is added to the current chunk being processed. The current chunk is processed
158+-- with the provided function to determine events.
52−inputToEventThread :: (String -> KClass) -> Chan Char -> Chan Event -> IO ()
159+inputToEventThread :: (String -> KClass) -> Chan InputChunk -> Chan Event -> IO ()
53inputToEventThread classifier inputChannel eventChannel = loop []
160inputToEventThread classifier inputChannel eventChannel = loop []
163+ c <- readChan inputChannel
165+ StopInput -> return ()
166+ -- the prefix is missing bytes and there are bytes available.
167+ InputChunk chunk -> parse_until_prefix (current ++ chunk)
169+ -- if there are bytes remaining after all events are parsed then the spacing after
170+ InputBreak -> parse_until_prefix current
54− where loop kb = case classifier kb of
172+ parse_until_prefix current = case classifier current of
173+ Prefix -> return current
174+ Invalid -> return []
175+ Valid k m remaining -> do
176+ writeChan eventChannel (EvKey k m)
177+ parse_until_prefix remaining
55 Prefix -> do
179 Prefix -> do
56 c <- readChan inputChannel
180 c <- readChan inputChannel
57− if c == '\xFFFD'
181+ case c of
58− then return ()
182+ StopInput -> return ()
59− else loop (kb ++ [c])
183+ -- the prefix is missing bytes and there are bytes available.
184+ InputChunk chunk -> loop (current ++ chunk)
185+ -- the prefix is a control sequence that is unknown. Drop and move on to the next
186+ MetaShiftChunk chunk -> loop chunk
60 -- drop the entirety of invalid sequences. Probably better to drop only the first
187 -- drop the entirety of invalid sequences. Probably better to drop only the first
61 -- character. However, the read behavior of terminals (a single read corresponds to the
188 -- character. However, the read behavior of terminals (a single read corresponds to the
62 -- bytes of a single event) might mean this results in quicker error recovery from a
189 -- bytes of a single event) might mean this results in quicker error recovery from a
63 -- users perspective.
190 -- users perspective.
64 Invalid -> do
191 Invalid -> do
65 c <- readChan inputChannel
192 c <- readChan inputChannel
66− if c == '\xFFFD'
193+ case c of
67− then return ()
194+ EndChunk -> return ()
68− else loop [c]
195+ InputChunk chunk -> loop chunk
70− MisPfx k m s -> writeChan eventChannel (EvKey k m) >> loop s
71− Valid k m -> writeChan eventChannel (EvKey k m) >> loop ""
196+ Valid k m remaining -> writeChan eventChannel (EvKey k m) >> loop remaining
73-- This makes a kind of tri. Has space efficiency issues with large input blocks.
199-- This makes a kind of tri. Has space efficiency issues with large input blocks.
74-- Likely building a parser and just applying that would be better.
200-- Likely building a parser and just applying that would be better.
@@ -87,22 +213,20 @@ compile table = cl' where
87 -- if the input_block is exactly what is expected for an event then consume the whole
213 -- if the input_block is exactly what is expected for an event then consume the whole
88 -- block and return the event
214 -- block and return the event
89 False -> case event_for_input input_block of
215 False -> case event_for_input input_block of
90− Just (EvKey k m) -> Valid k m
216+ Just (EvKey k m) -> Valid k m []
91 -- look up progressively large prefixes of the input block until an event is found
217 -- look up progressively large prefixes of the input block until an event is found
92 -- H: There will always be one match. The prefix_set contains, by definition, all
218 -- H: There will always be one match. The prefix_set contains, by definition, all
93 -- prefixes of an event.
219 -- prefixes of an event.
94 Nothing ->
220 Nothing ->
95 let input_prefixes = init $ inits input_block
221 let input_prefixes = init $ inits input_block
96 in case mapMaybe (\s -> (,) s `fmap` event_for_input s) input_prefixes of
222 in case mapMaybe (\s -> (,) s `fmap` event_for_input s) input_prefixes of
97− (s,EvKey k m) : _ -> MisPfx k m (drop (length s) input_block)
223+ (s,EvKey k m) : _ -> Valid k m (drop (length s) input_block)
98 -- neither a prefix or a full event. Might be interesting to log.
224 -- neither a prefix or a full event. Might be interesting to log.
99− [] -> error $ show input_block
225+ [] -> Invalid
101classify, classifyTab :: ClassifyTable -> [Char] -> KClass
227classify, classifyTab :: ClassifyTable -> [Char] -> KClass
103-- As soon as
229-- As soon as
104−classify _table "\xFFFD" = EndLoop
105−classify _table "\xFFFE" = Invalid
106classify _table s@(c:_) | ord c >= 0xC2
230classify _table s@(c:_) | ord c >= 0xC2
107 = if utf8Length (ord c) > length s then Prefix else classifyUtf8 s -- beginning of an utf8 sequence
231 = if utf8Length (ord c) > length s then Prefix else classifyUtf8 s -- beginning of an utf8 sequence
108classify table other
232classify table other
@@ -110,7 +234,7 @@ classify table other
111classifyUtf8 :: [Char] -> KClass
235classifyUtf8 :: [Char] -> KClass
112classifyUtf8 s = case decode ((map (fromIntegral . ord) s) :: [Word8]) of
236classifyUtf8 s = case decode ((map (fromIntegral . ord) s) :: [Word8]) of
113− Just (unicodeChar, _) -> Valid (KChar unicodeChar) []
237+ Just (unicodeChar, _) -> Valid (KChar unicodeChar) [] []
114 _ -> Invalid -- something bad happened; just ignore and continue.
238 _ -> Invalid -- something bad happened; just ignore and continue.
116classifyTab table = compile table
240classifyTab table = compile table
@@ -133,15 +257,20 @@ utf8Length c
134-- This is an example of an algorithm where code coverage could be high, even 100%, but the
258-- This is an example of an algorithm where code coverage could be high, even 100%, but the
135-- algorithm still under tested. I should collect more of these examples...
259-- algorithm still under tested. I should collect more of these examples...
136−initInputForFd :: Int -> ClassifyTable -> Fd -> IO (Chan Event, IO ())
260+initInputForFd :: IORef Config -> ClassifyTable -> Fd -> IO (Chan Event, IO ())
137−initInputForFd escDelay classify_table input_fd = do
261+initInputForFd config_ref classify_table input_fd = do
138− inputChannel <- newChan
139 eventChannel <- newChan
262 eventChannel <- newChan
140− hadInput <- newEmptyMVar
263+#if 0
141− let finishAtomicInput = writeChan inputChannel '\xFFFE'
264+ should_exit <- newIORef False
265+ let -- initial state: read bytes until a ESC. Emit bytes. Go to possible-control-seq state.
266+ -- possible-control-seq: read available bytes.
267+ -- If time is < controlSeqPeriod then emit as chunk with ESC prefix.
268+ -- If time is < metaComboPeriod then emit chunk with Meta shift.
269+ -- If time is > metaComboPeriod then emit ESC event then chunk of bytes.
142 inputThread :: IO ()
270 inputThread :: IO ()
143 inputThread = do
271 inputThread = do
144− _ <- alloca $ \(input_buffer :: Ptr Word8) -> do
273+ _ <- allocaArray k $ \(input_buffer :: Ptr Word8) -> do
145 let loop = do
274 let loop = do
146 setFdOption input_fd NonBlockingRead False
275 setFdOption input_fd NonBlockingRead False
147 threadWaitRead input_fd
276 threadWaitRead input_fd
@@ -172,15 +301,16 @@ initInputForFd escDelay classify_table input_fd = do
173 classifier = classify classify_table
302 classifier = classify classify_table
174 eventThreadId <- forkIO $ void $ inputToEventThread classifier inputChannel eventChannel
303 eventThreadId <- forkIO $ void $ inputToEventThread classifier inputChannel eventChannel
175− inputThreadId <- forkIO $ inputThread
304+ inputThreadId <- forkIO $ input_thread
176 noInputThreadId <- forkIO $ noInputThread
305 noInputThreadId <- forkIO $ noInputThread
177 -- TODO(corey): killThread is a bit risky for my tastes.
306 -- TODO(corey): killThread is a bit risky for my tastes.
178 -- H - somewhat mitigated by sending a magic terminate character?
307 -- H - somewhat mitigated by sending a magic terminate character?
179 let shutdown_event_processing = do
308 let shutdown_event_processing = do
180− writeChan inputChannel '\xFFFD'
181− killThread noInputThreadId
182 killThread eventThreadId
309 killThread eventThreadId
183 killThread inputThreadId
310 killThread inputThreadId
312+ let shutdown_event_processing = return ()
184 return (eventChannel, shutdown_event_processing)
314 return (eventChannel, shutdown_event_processing)
186−foreign import ccall "vty_set_term_timing" set_term_timing :: Fd -> IO ()
316+foreign import ccall "vty_set_term_timing" set_term_timing :: Fd -> Int -> Int -> IO ()