shrub/pkg/king/lib/Vere/Term.hs

114 lines
3.2 KiB
Haskell
Raw Normal View History

{-# OPTIONS_GHC -Wwarn #-}
module Vere.Term (term) where
import UrbitPrelude
import Arvo hiding (Term)
import Vere.Pier.Types
import System.Posix.IO
import System.Posix.Terminal
import qualified Urbit.Time as Time
-- Types -----------------------------------------------------------------------
data TermDrv = TermDrv
{ tdPreviousConfiguration :: TerminalAttributes
, tdReader :: Async ()
, tdWriter :: Async ()
, tdWriteQueue :: TQueue ByteString
}
-- A list of terminal flags that we disable
disabledFlags = [
-- lflag
EnableEcho, EchoLF, ProcessInput, ExtendedFunctions,
-- iflag
MapCRtoLF, CheckParity, StripHighBit,
-- cflag, todo: Terminal library missing CSIZE?
EnableParity,
-- oflag
ProcessOutput
]
-- Utils -----------------------------------------------------------------------
-- TODO: We lie about terminal size for now because getting it is a call to
-- ioctl().
initialBlew = EvBlip $ BlipEvTerm $ TermEvBlew (1, ()) 80 24
initialHail = EvBlip $ BlipEvTerm $ TermEvHail (1, ()) ()
-- What we need is an equivalent to _term_io_suck_char(). That's a manual, hand
-- rolled parser to deal with the escape state.
--------------------------------------------------------------------------------
term :: KingId -> QueueEv -> ([Ev], Acquire (EffCb TermEf))
term king enqueueEv =
(initialEvents, runTerm)
where
initialEvents = [initialBlew, initialHail]
runTerm :: Acquire (EffCb TermEf)
runTerm = do
tim <- mkAcquire start stop
pure (handleEffect tim)
start :: IO TermDrv
start = do
putStrLn "term start"
tdPreviousConfiguration <- getTerminalAttributes stdInput
-- Create a new configuration where we put the terminal in raw mode and
-- disable a bunch of preprocessing.
--
-- This is a departure from vere's term.c, which set vmin=0 and vtime=0.
let newTermSettings =
flip withTime 0 .
flip withMinInput 0 $
foldl' withoutMode tdPreviousConfiguration disabledFlags
setTerminalAttributes stdInput newTermSettings Immediately
tdWriteQueue <- newTQueueIO
tdReader <- asyncBound readTerminal
tdWriter <- asyncBound (writeTerminal tdWriteQueue)
pure TermDrv{..}
stop :: TermDrv -> IO ()
stop (TermDrv{..}) = do
-- cancel our threads
cancel tdReader
-- cancel tdWriter
-- take the terminal out of raw mode
setTerminalAttributes stdInput tdPreviousConfiguration Immediately
-- Reads data from stdInput and emit the proper effect
readTerminal :: IO ()
readTerminal = forever $ do
t <- try (fdRead stdInput 1)
case t of
Left (e :: IOException) ->
-- Ignore EOFs when doing raw reads
pure ()
Right (str, bytes) -> do
print ("[KEY] " ++ (show str))
wen <- Time.now
pure ()
handleEffect :: TermDrv -> TermEf -> IO ()
handleEffect TermDrv{..} ef =
print ("[TERM]" ++ (show ef))
-- Writes data to the terminal. Both the terminal reading, normal logging,
-- and effect handling can all emit bytes which go to the terminal.
writeTerminal :: TQueue ByteString -> IO ()
writeTerminal q = forever $ do
x <- atomically $ readTQueue q
pure ()