@@ -19,6 +19,8 @@ import Graphics.Vty.Input.Events
20import Control.Applicative
20import Control.Applicative
21import Control.Concurrent
21import Control.Concurrent
22+import Control.Concurrent.MVar
23+import Control.Exception (mask, try, SomeException)
22import Control.Lens
24import Control.Lens
23import Control.Monad (when, mzero, forM_)
25import Control.Monad (when, mzero, forM_)
24import Control.Monad.IO.Class (liftIO)
26import Control.Monad.IO.Class (liftIO)
@@ -65,7 +67,6 @@ data InputState = InputState
65 { _unprocessedBytes :: String
67 { _unprocessedBytes :: String
66 , _appliedConfig :: Config
68 , _appliedConfig :: Config
67 , _inputBuffer :: InputBuffer
69 , _inputBuffer :: InputBuffer
68− , _stopRequestRef :: IORef Bool
69 , _classifier :: String -> KClass
70 , _classifier :: String -> KClass
@@ -88,7 +89,7 @@ loopInputProcessor = do
88 validEvents <- many parseEvent
89 validEvents <- many parseEvent
89 forM_ validEvents emit
90 forM_ validEvents emit
90 dropInvalid
91 dropInvalid
91− stopIfRequested <|> loopInputProcessor
92+ loopInputProcessor
93addBytesToProcess :: String -> InputM ()
94addBytesToProcess :: String -> InputM ()
94addBytesToProcess block = unprocessedBytes <>= block
95addBytesToProcess block = unprocessedBytes <>= block
@@ -115,6 +116,11 @@ readFromDevice = do
115 bufferPtr <- use $ inputBuffer.ptr
116 bufferPtr <- use $ inputBuffer.ptr
116 maxBytes <- use $ inputBuffer.size
117 maxBytes <- use $ inputBuffer.size
117 stringRep <- liftIO $ do
118 stringRep <- liftIO $ do
119+ -- The killThread used in shutdownInput will not interrupt the foreign call fdReadBuf uses
120+ -- this provides a location to be interrupted prior to the foreign call. If there is input
121+ -- on the FD then the fdReadBuf will return in a finite amount of time due to the vtime
122+ -- terminal setting.
118 bytesRead <- fdReadBuf fd bufferPtr (fromIntegral maxBytes)
124 bytesRead <- fdReadBuf fd bufferPtr (fromIntegral maxBytes)
119 if bytesRead > 0
125 if bytesRead > 0
120 then fmap (map $ chr . fromIntegral) $ peekArray (fromIntegral bytesRead) bufferPtr
126 then fmap (map $ chr . fromIntegral) $ peekArray (fromIntegral bytesRead) bufferPtr
@@ -147,18 +153,12 @@ dropInvalid = do
147 logMsg "dropping input bytes"
153 logMsg "dropping input bytes"
148 unprocessedBytes .= []
154 unprocessedBytes .= []
150−stopIfRequested :: InputM ()
152− True <- (liftIO . readIORef) =<< use stopRequestRef
155−runInputProcessorLoop :: ClassifyMap -> Input -> IORef Bool -> IO ()
156+runInputProcessorLoop :: ClassifyMap -> Input -> IO ()
156−runInputProcessorLoop classifyTable input stopFlag = do
157+runInputProcessorLoop classifyTable input = do
157 let bufferSize = 1024
158 let bufferSize = 1024
158 allocaArray bufferSize $ \(bufferPtr :: Ptr Word8) -> do
159 allocaArray bufferSize $ \(bufferPtr :: Ptr Word8) -> do
159 s0 <- InputState [] <$> readIORef (_configRef input)
160 s0 <- InputState [] <$> readIORef (_configRef input)
160 <*> pure (InputBuffer bufferPtr bufferSize)
161 <*> pure (InputBuffer bufferPtr bufferSize)
162 <*> pure (classify classifyTable)
162 <*> pure (classify classifyTable)
163 runReaderT (evalStateT loopInputProcessor s0) input
163 runReaderT (evalStateT loopInputProcessor s0) input
@@ -192,15 +192,23 @@ initInput config classifyTable = do
192 let Just fd = inputFd config
192 let Just fd = inputFd config
193 setFdOption fd NonBlockingRead False
193 setFdOption fd NonBlockingRead False
194 applyConfig fd config
194 applyConfig fd config
195− stopFlag <- newIORef False
195+ stopSync <- newEmptyMVar
196 input <- Input <$> newChan
196 input <- Input <$> newChan
197− <*> pure (writeIORef stopFlag True)
197+ <*> pure (return ())
198 <*> newIORef config
198 <*> newIORef config
199 <*> maybe (return Nothing)
199 <*> maybe (return Nothing)
200 (\f -> Just <$> openFile f AppendMode)
200 (\f -> Just <$> openFile f AppendMode)
201 (debugLog config)
201 (debugLog config)
202 logInitialInputState input classifyTable
202 logInitialInputState input classifyTable
203− _ <- forkOS $ runInputProcessorLoop classifyTable input stopFlag
203+ inputThread <- forkOSFinally (runInputProcessorLoop classifyTable input)
204+ (\_ -> putMVar stopSync ())
205+ let killAndWait = do
206+ killThread inputThread
204− return input
208+ return $ input { shutdownInput = killAndWait }
206foreign import ccall "vty_set_term_timing" setTermTiming :: Fd -> Int -> Int -> IO ()
210foreign import ccall "vty_set_term_timing" setTermTiming :: Fd -> Int -> Int -> IO ()
212+forkOSFinally :: IO a -> (Either SomeException a -> IO ()) -> IO ThreadId
213+forkOSFinally action and_then =
214+ mask $ \restore -> forkOS $ try (restore action) >>= and_then