2019-08-28 01:29:11 +03:00
|
|
|
{-# OPTIONS_GHC -Wwarn #-}
|
|
|
|
|
2019-08-28 23:17:01 +03:00
|
|
|
module Vere.Term (initializeTerminal, term, VereTerminal) where
|
2019-08-28 01:29:11 +03:00
|
|
|
|
|
|
|
import UrbitPrelude
|
|
|
|
import Arvo hiding (Term)
|
|
|
|
import Vere.Pier.Types
|
|
|
|
|
2019-08-28 23:17:01 +03:00
|
|
|
import Foreign.Marshal.Alloc
|
2019-08-28 01:29:11 +03:00
|
|
|
import System.Posix.IO
|
|
|
|
import System.Posix.Terminal
|
|
|
|
|
2019-08-28 23:17:01 +03:00
|
|
|
import System.Console.Terminfo.Base
|
|
|
|
|
2019-08-28 01:29:11 +03:00
|
|
|
import qualified Urbit.Time as Time
|
|
|
|
|
|
|
|
-- Types -----------------------------------------------------------------------
|
|
|
|
|
|
|
|
data TermDrv = TermDrv
|
|
|
|
{ tdPreviousConfiguration :: TerminalAttributes
|
|
|
|
, tdReader :: Async ()
|
|
|
|
}
|
|
|
|
|
2019-08-28 23:17:01 +03:00
|
|
|
data VereTerminal = VereTerminal
|
|
|
|
{ vtWidth :: Word
|
|
|
|
, vtHeight :: Word
|
|
|
|
, vtClearScreen :: Maybe TermOutput
|
|
|
|
|
|
|
|
--
|
|
|
|
, vtWriteQueue :: TQueue TermOutput
|
|
|
|
, vtWriter :: Async ()
|
|
|
|
}
|
2019-08-28 01:29:11 +03:00
|
|
|
|
|
|
|
-- 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 -----------------------------------------------------------------------
|
|
|
|
|
2019-08-28 23:17:01 +03:00
|
|
|
-- TODO: We lie about terminal size for now and just pass 80x24 because getting
|
|
|
|
-- it is a call to ioctl() which is in IO.
|
2019-08-28 01:29:11 +03:00
|
|
|
|
2019-08-28 02:22:01 +03:00
|
|
|
-- TODO: 49 is the string "1", which is what we need to pass to dill as the
|
|
|
|
-- hard-coded terminal "1" session. Figure out how to turn this into "1" later.
|
2019-08-28 23:17:01 +03:00
|
|
|
initialBlew w h = EvBlip $ BlipEvTerm $ TermEvBlew (49, ()) w h
|
2019-08-28 02:22:01 +03:00
|
|
|
|
|
|
|
initialHail = EvBlip $ BlipEvTerm $ TermEvHail (49, ()) ()
|
|
|
|
|
2019-08-28 01:29:11 +03:00
|
|
|
|
|
|
|
-- What we need is an equivalent to _term_io_suck_char(). That's a manual, hand
|
|
|
|
-- rolled parser to deal with the escape state.
|
|
|
|
|
2019-08-28 23:17:01 +03:00
|
|
|
-- Version one of this is punting on the ops_u.dem flag: whether we're running
|
|
|
|
-- in daemon mode. This needs to
|
|
|
|
|
2019-08-28 01:29:11 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2019-08-28 23:17:01 +03:00
|
|
|
initializeTerminal :: Acquire VereTerminal
|
|
|
|
initializeTerminal = mkAcquire start stop
|
|
|
|
where
|
|
|
|
start :: IO VereTerminal
|
|
|
|
start = do
|
|
|
|
t <- setupTermFromEnv
|
|
|
|
let vtWidth = 80
|
|
|
|
let vtHeight = 24
|
|
|
|
let vtClearScreen = getCap t "clear"
|
|
|
|
|
|
|
|
vtWriteQueue <- newTQueueIO
|
|
|
|
vtWriter <- asyncBound (writeTerminal t vtWriteQueue)
|
|
|
|
|
|
|
|
pure VereTerminal{..}
|
|
|
|
|
|
|
|
stop :: VereTerminal -> IO ()
|
|
|
|
stop (VereTerminal{..}) = cancel vtWriter
|
|
|
|
|
|
|
|
getCap term cap =
|
|
|
|
getCapability term (tiGetOutput1 cap) :: Maybe TermOutput
|
|
|
|
|
|
|
|
-- Writes data to the terminal. Both the terminal reading, normal logging,
|
|
|
|
-- and effect handling can all emit bytes which go to the terminal.
|
|
|
|
writeTerminal :: Terminal -> TQueue TermOutput -> IO ()
|
|
|
|
writeTerminal terminal q = forever $ do
|
|
|
|
x <- atomically $ readTQueue q
|
|
|
|
runTermOutput terminal x
|
|
|
|
|
|
|
|
|
|
|
|
term :: VereTerminal -> KingId -> QueueEv -> ([Ev], Acquire (EffCb TermEf))
|
|
|
|
term VereTerminal{..} king enqueueEv =
|
2019-08-28 01:29:11 +03:00
|
|
|
(initialEvents, runTerm)
|
|
|
|
where
|
2019-08-28 23:17:01 +03:00
|
|
|
initialEvents = [(initialBlew vtWidth vtHeight), initialHail]
|
2019-08-28 01:29:11 +03:00
|
|
|
|
|
|
|
runTerm :: Acquire (EffCb TermEf)
|
|
|
|
runTerm = do
|
|
|
|
tim <- mkAcquire start stop
|
2019-08-28 23:17:01 +03:00
|
|
|
pure (handleEffect vtWriteQueue tim)
|
2019-08-28 01:29:11 +03:00
|
|
|
|
|
|
|
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.
|
|
|
|
let newTermSettings =
|
2019-08-28 23:17:01 +03:00
|
|
|
flip withTime 1 .
|
2019-08-28 01:29:11 +03:00
|
|
|
flip withMinInput 0 $
|
|
|
|
foldl' withoutMode tdPreviousConfiguration disabledFlags
|
|
|
|
setTerminalAttributes stdInput newTermSettings Immediately
|
|
|
|
|
|
|
|
tdReader <- asyncBound readTerminal
|
|
|
|
|
|
|
|
pure TermDrv{..}
|
|
|
|
|
|
|
|
stop :: TermDrv -> IO ()
|
|
|
|
stop (TermDrv{..}) = do
|
|
|
|
-- cancel our threads
|
|
|
|
cancel tdReader
|
2019-08-28 23:17:01 +03:00
|
|
|
--cancel tdWriter
|
2019-08-28 01:29:11 +03:00
|
|
|
-- take the terminal out of raw mode
|
|
|
|
setTerminalAttributes stdInput tdPreviousConfiguration Immediately
|
|
|
|
|
|
|
|
-- Reads data from stdInput and emit the proper effect
|
2019-08-28 23:17:01 +03:00
|
|
|
--
|
|
|
|
-- This entire path is a divergence from how term.c does things,
|
|
|
|
-- probably. First, the vtime is 0, not 1 in term.c. So (IIUC), we'll
|
|
|
|
-- always have a latency of 1/10 of a second.
|
|
|
|
--
|
|
|
|
-- A better way to do this would be to get some sort of epoll on stdInput,
|
|
|
|
-- since that's kinda closer to what libuv does?
|
2019-08-28 01:29:11 +03:00
|
|
|
readTerminal :: IO ()
|
2019-08-28 23:17:01 +03:00
|
|
|
readTerminal = allocaBytes 1 $ \ buf -> forever $ do
|
|
|
|
t <- try (fdReadBuf stdInput buf 1)
|
2019-08-28 01:29:11 +03:00
|
|
|
case t of
|
|
|
|
Left (e :: IOException) ->
|
2019-08-28 23:17:01 +03:00
|
|
|
-- Ignore EAGAINs when doing reads
|
2019-08-28 01:29:11 +03:00
|
|
|
pure ()
|
2019-08-28 23:17:01 +03:00
|
|
|
Right 0 -> pure ()
|
|
|
|
Right _ -> do
|
|
|
|
putStrLn "\r[KEY] " -- ++ str)
|
2019-08-28 01:29:11 +03:00
|
|
|
wen <- Time.now
|
|
|
|
pure ()
|
2019-08-28 23:17:01 +03:00
|
|
|
-- atomicallu $ enqueuEv $
|
|
|
|
|
|
|
|
handleEffect :: TQueue TermOutput -> TermDrv -> TermEf -> IO ()
|
|
|
|
handleEffect writeQueue TermDrv{..} = \case
|
|
|
|
TermEfBlit _ blits ->
|
|
|
|
atomically $ for_ blits (blitToScreen writeQueue)
|
|
|
|
TermEfInit _ _ -> pure ()
|
|
|
|
TermEfLogo path _ -> pure ()
|
|
|
|
TermEfMass _ _ -> pure ()
|
|
|
|
|
|
|
|
-- Write each in
|
|
|
|
blitToScreen :: TQueue TermOutput -> Blit -> STM ()
|
|
|
|
blitToScreen q = \case
|
|
|
|
(Bel ()) -> pure ()
|
|
|
|
(Clr ()) -> pure ()
|
|
|
|
(Hop w) -> pure ()
|
|
|
|
(Lin c) -> writeTQueue q $ termText $ pack c
|
|
|
|
(Mor ()) -> pure ()
|
|
|
|
(Sag path noun) -> pure ()
|
|
|
|
(Sav path atom) -> pure ()
|
|
|
|
(Url url) -> pure ()
|
2019-08-28 01:29:11 +03:00
|
|
|
|