2020-01-23 07:16:09 +03:00
|
|
|
{-|
|
|
|
|
Terminal Driver
|
|
|
|
-}
|
2020-01-24 08:28:38 +03:00
|
|
|
module Urbit.Vere.Term
|
2019-09-18 08:22:19 +03:00
|
|
|
( module Term
|
2019-09-18 09:17:54 +03:00
|
|
|
, localClient
|
2019-09-18 10:24:10 +03:00
|
|
|
, connectToRemote
|
|
|
|
, runTerminalClient
|
2019-12-17 21:06:20 +03:00
|
|
|
, connClient
|
2019-09-18 08:01:44 +03:00
|
|
|
, term
|
2020-06-10 22:25:51 +03:00
|
|
|
, term'
|
2019-09-18 08:01:44 +03:00
|
|
|
) where
|
2019-08-28 01:29:11 +03:00
|
|
|
|
2019-08-29 23:19:06 +03:00
|
|
|
import Data.Char
|
2019-08-28 23:17:01 +03:00
|
|
|
import Foreign.Marshal.Alloc
|
2019-08-29 23:19:06 +03:00
|
|
|
import Foreign.Ptr
|
2019-08-29 21:12:50 +03:00
|
|
|
import Foreign.Storable
|
2019-09-18 07:41:31 +03:00
|
|
|
import RIO.FilePath
|
2019-08-28 01:29:11 +03:00
|
|
|
import System.Posix.IO
|
|
|
|
import System.Posix.Terminal
|
2020-11-03 22:03:58 +03:00
|
|
|
import Urbit.Arvo
|
2020-06-12 21:27:25 +03:00
|
|
|
import Urbit.King.App
|
2020-06-08 20:24:05 +03:00
|
|
|
import Urbit.Noun.Time
|
2020-01-24 08:28:38 +03:00
|
|
|
import Urbit.Prelude hiding (getCurrentTime)
|
|
|
|
import Urbit.Vere.Pier.Types
|
2020-01-23 07:16:09 +03:00
|
|
|
|
2020-01-24 08:28:38 +03:00
|
|
|
import Data.List ((!!))
|
|
|
|
import RIO.Directory (createDirectoryIfMissing)
|
|
|
|
import Urbit.King.API (readPortsFile)
|
2020-12-03 22:13:59 +03:00
|
|
|
import Urbit.Vere.Stat (RenderedStat)
|
2020-03-23 23:35:50 +03:00
|
|
|
import Urbit.TermSize (TermSize(TermSize))
|
2020-09-25 18:32:17 +03:00
|
|
|
import Urbit.Vere.Term.API (Client(Client), ClientTake(..))
|
2019-08-28 23:17:01 +03:00
|
|
|
|
2020-09-15 17:40:28 +03:00
|
|
|
import qualified Data.Set as S
|
2020-02-15 11:21:55 +03:00
|
|
|
import qualified Data.ByteString.Internal as BS
|
|
|
|
import qualified Data.ByteString.UTF8 as BS
|
2020-06-10 22:25:51 +03:00
|
|
|
import qualified System.Console.ANSI as ANSI
|
2020-03-23 23:35:50 +03:00
|
|
|
import qualified Urbit.TermSize as T
|
2020-02-15 11:21:55 +03:00
|
|
|
import qualified Urbit.Vere.NounServ as Serv
|
|
|
|
import qualified Urbit.Vere.Term.API as Term
|
|
|
|
import qualified Urbit.Vere.Term.Render as T
|
2019-08-28 01:29:11 +03:00
|
|
|
|
2019-09-13 21:46:03 +03:00
|
|
|
|
2019-08-28 01:29:11 +03:00
|
|
|
-- Types -----------------------------------------------------------------------
|
|
|
|
|
2020-01-23 07:16:09 +03:00
|
|
|
-- | All stateful data in the printing to stdOutput.
|
2019-09-16 23:34:55 +03:00
|
|
|
data LineState = LineState
|
2019-09-18 07:41:31 +03:00
|
|
|
{ lsLine :: Text
|
2021-04-02 14:58:13 +03:00
|
|
|
, lsCurPos :: CurPos
|
2019-09-16 23:34:55 +03:00
|
|
|
, lsSpinTimer :: Maybe (Async ())
|
2019-09-18 07:41:31 +03:00
|
|
|
, lsSpinCause :: Maybe Text
|
2019-09-16 23:34:55 +03:00
|
|
|
, lsSpinFirstRender :: Bool
|
|
|
|
, lsSpinFrame :: Int
|
|
|
|
, lsPrevEndTime :: Wen
|
|
|
|
}
|
2019-08-29 03:08:47 +03:00
|
|
|
|
2021-04-02 14:58:13 +03:00
|
|
|
data CurPos = CurPos
|
|
|
|
{ row :: Int
|
|
|
|
, col :: Int
|
|
|
|
}
|
|
|
|
|
2020-01-23 07:16:09 +03:00
|
|
|
-- | A record used in reading data from stdInput.
|
2019-08-29 23:19:06 +03:00
|
|
|
data ReadData = ReadData
|
2019-09-13 21:46:03 +03:00
|
|
|
{ rdBuf :: Ptr Word8
|
|
|
|
, rdEscape :: Bool
|
|
|
|
, rdBracket :: Bool
|
2021-04-02 15:25:29 +03:00
|
|
|
, rdMouse :: Bool
|
|
|
|
, rdMouseBut :: Word8
|
|
|
|
, rdMouseCol :: Word8
|
2019-09-13 21:46:03 +03:00
|
|
|
, rdUTF8 :: ByteString
|
|
|
|
, rdUTF8width :: Int
|
2019-08-29 23:19:06 +03:00
|
|
|
}
|
|
|
|
|
2020-01-23 07:16:09 +03:00
|
|
|
-- | Private data to the Client that we keep around for stop().
|
2019-08-30 02:35:52 +03:00
|
|
|
data Private = Private
|
2019-09-13 21:02:41 +03:00
|
|
|
{ pReaderThread :: Async ()
|
|
|
|
, pWriterThread :: Async ()
|
2019-09-17 23:37:41 +03:00
|
|
|
, pPreviousConfiguration :: TerminalAttributes
|
2019-08-30 02:35:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
-- Utils -----------------------------------------------------------------------
|
2019-08-28 01:29:11 +03:00
|
|
|
|
2020-11-03 22:03:58 +03:00
|
|
|
blewEvent :: Word -> Word -> Ev
|
2020-09-25 18:32:17 +03:00
|
|
|
blewEvent w h = EvBlip $ BlipEvTerm $ TermEvBlew (UD 1, ()) w h
|
2019-08-28 02:22:01 +03:00
|
|
|
|
2020-11-03 22:03:58 +03:00
|
|
|
initialHail :: Ev
|
2019-08-29 03:08:47 +03:00
|
|
|
initialHail = EvBlip $ BlipEvTerm $ TermEvHail (UD 1, ()) ()
|
2019-08-28 02:22:01 +03:00
|
|
|
|
2019-08-28 23:17:01 +03:00
|
|
|
-- Version one of this is punting on the ops_u.dem flag: whether we're running
|
2019-08-29 03:08:47 +03:00
|
|
|
-- in daemon mode.
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2019-09-03 21:02:54 +03:00
|
|
|
rioAllocaBytes :: (MonadIO m, MonadUnliftIO m)
|
|
|
|
=> Int -> (Ptr a -> m b) -> m b
|
|
|
|
rioAllocaBytes size action =
|
|
|
|
withRunInIO $ \run ->
|
|
|
|
allocaBytes size $ \x -> run (action x)
|
2019-08-28 23:17:01 +03:00
|
|
|
|
2020-01-23 07:16:09 +03:00
|
|
|
{-|
|
|
|
|
Because of legacy reasons, some file operations are in the terminal
|
|
|
|
driver. These should be filtered out and handled locally instead of
|
|
|
|
in any abstractly connected terminal.
|
|
|
|
-}
|
2019-08-30 21:01:37 +03:00
|
|
|
isTerminalBlit :: Blit -> Bool
|
|
|
|
isTerminalBlit (Sav _ _) = False
|
|
|
|
isTerminalBlit (Sag _ _) = False
|
|
|
|
isTerminalBlit _ = True
|
|
|
|
|
2019-08-28 01:29:11 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2020-09-25 18:32:17 +03:00
|
|
|
connClient :: Serv.Conn ClientTake [Term.Ev] -> Client
|
2019-12-17 21:06:20 +03:00
|
|
|
connClient c = Client
|
|
|
|
{ give = Serv.cSend c
|
|
|
|
, take = Serv.cRecv c
|
|
|
|
}
|
2019-09-18 10:24:10 +03:00
|
|
|
|
2020-09-25 02:43:03 +03:00
|
|
|
connectToRemote :: forall e. HasLogFunc e
|
2019-09-18 10:24:10 +03:00
|
|
|
=> Port
|
|
|
|
-> Client
|
|
|
|
-> RAcquire e (Async (), Async ())
|
|
|
|
connectToRemote port local = mkRAcquire start stop
|
|
|
|
where
|
|
|
|
stop (x, y) = cancel x >> cancel y
|
|
|
|
start = do
|
2019-12-17 21:06:20 +03:00
|
|
|
Serv.Client{..} <- Serv.wsClient "/terminal/0" (fromIntegral port)
|
2019-09-18 10:24:10 +03:00
|
|
|
|
2019-12-17 21:06:20 +03:00
|
|
|
-- TODO XX Handle disconnect more cleanly.
|
2019-09-18 10:24:10 +03:00
|
|
|
ferry <- async $ forever $ atomically $ asum
|
2019-12-17 21:06:20 +03:00
|
|
|
[ Term.take local >>= \case
|
|
|
|
Nothing -> empty
|
|
|
|
Just ev -> Serv.cSend cConn ev
|
2019-09-18 10:24:10 +03:00
|
|
|
, Serv.cRecv cConn >>= \case
|
|
|
|
Nothing -> empty
|
|
|
|
Just ev -> Term.give local ev
|
|
|
|
]
|
|
|
|
|
|
|
|
pure (ferry, cAsync)
|
|
|
|
|
2019-12-17 19:55:10 +03:00
|
|
|
data HackConfigDir = HCD { _hcdPax :: FilePath }
|
|
|
|
makeLenses ''HackConfigDir
|
2020-05-22 21:12:28 +03:00
|
|
|
instance HasPierPath HackConfigDir where pierPathL = hcdPax
|
2019-12-17 19:55:10 +03:00
|
|
|
|
2020-09-25 02:43:03 +03:00
|
|
|
runTerminalClient :: forall e. HasLogFunc e => FilePath -> RIO e ()
|
2019-12-17 19:55:10 +03:00
|
|
|
runTerminalClient pier = runRAcquire $ do
|
2019-12-17 21:06:20 +03:00
|
|
|
mPort <- runRIO (HCD pier) readPortsFile
|
|
|
|
port <- maybe (error "Can't connect") pure mPort
|
|
|
|
mExit <- io newEmptyTMVarIO
|
2020-09-25 18:32:17 +03:00
|
|
|
cli <- localClient (putTMVar mExit ())
|
2019-12-17 21:06:20 +03:00
|
|
|
(tid, sid) <- connectToRemote (Port $ fromIntegral port) cli
|
|
|
|
atomically $ waitSTM tid <|> waitSTM sid <|> takeTMVar mExit
|
|
|
|
|
2019-09-18 10:24:10 +03:00
|
|
|
where
|
|
|
|
runRAcquire :: RAcquire e () -> RIO e ()
|
|
|
|
runRAcquire act = rwith act $ const $ pure ()
|
2019-09-18 09:58:42 +03:00
|
|
|
|
2020-06-10 22:25:51 +03:00
|
|
|
|
|
|
|
-- Spinner ---------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- Call an STM action after delay of `first` microseconds and then every
|
|
|
|
-- `rest` microseconds after that.
|
|
|
|
repeatedly :: Int -> Int -> STM () -> IO ()
|
|
|
|
repeatedly first rest action = do
|
|
|
|
threadDelay first
|
|
|
|
forever $ do
|
|
|
|
atomically action
|
|
|
|
threadDelay rest
|
|
|
|
|
|
|
|
spinners :: [Text]
|
|
|
|
spinners = ["|", "/", "-", "\\"]
|
|
|
|
|
|
|
|
leftBracket, rightBracket :: Text
|
|
|
|
leftBracket = "«"
|
|
|
|
rightBracket = "»"
|
|
|
|
|
2020-11-03 22:03:58 +03:00
|
|
|
_spin_cool_us, _spin_warm_us, _spin_rate_us, _spin_idle_us :: Integral i => i
|
2020-06-10 22:25:51 +03:00
|
|
|
_spin_cool_us = 500000
|
|
|
|
_spin_warm_us = 50000
|
|
|
|
_spin_rate_us = 250000
|
|
|
|
_spin_idle_us = 500000
|
|
|
|
|
|
|
|
|
|
|
|
-- Client ----------------------------------------------------------------------
|
|
|
|
|
2020-01-23 07:16:09 +03:00
|
|
|
{-|
|
2019-09-18 09:17:54 +03:00
|
|
|
Initializes the generalized input/output parts of the terminal.
|
|
|
|
-}
|
2020-09-25 02:43:03 +03:00
|
|
|
localClient :: forall e. HasLogFunc e
|
2019-12-17 21:06:20 +03:00
|
|
|
=> STM ()
|
2020-09-25 18:32:17 +03:00
|
|
|
-> RAcquire e Client
|
2019-12-17 21:06:20 +03:00
|
|
|
localClient doneSignal = fst <$> mkRAcquire start stop
|
2019-08-28 23:17:01 +03:00
|
|
|
where
|
2020-09-25 18:32:17 +03:00
|
|
|
start :: HasLogFunc e => RIO e (Client, Private)
|
2019-08-28 23:17:01 +03:00
|
|
|
start = do
|
2020-06-10 22:25:51 +03:00
|
|
|
tsWriteQueue <- newTQueueIO :: RIO e (TQueue [Term.Ev])
|
|
|
|
spinnerMVar <- newEmptyTMVarIO :: RIO e (TMVar ())
|
2020-09-25 18:32:17 +03:00
|
|
|
|
|
|
|
-- Track the terminal size, keeping track of the size of the local
|
|
|
|
-- terminal for our own printing, as well as putting size changes into an
|
|
|
|
-- event queue so we can send changes to the terminal muxing system.
|
2020-10-01 18:10:30 +03:00
|
|
|
tsizeTVar <- newTVarIO (TermSize 80 24) -- Value doesn't matter.
|
|
|
|
tsSizeChange <- newEmptyTMVarIO
|
2020-09-25 18:32:17 +03:00
|
|
|
io $ T.liveTermSize (\ts -> atomically $ do
|
|
|
|
-- We keep track of the console's local size for
|
|
|
|
-- our own tank washing.
|
|
|
|
writeTVar tsizeTVar ts
|
|
|
|
|
|
|
|
-- We queue up changes so we can broadcast them
|
|
|
|
-- to the muxing client.
|
2020-10-01 18:10:30 +03:00
|
|
|
putTMVar tsSizeChange ts)
|
2020-09-25 18:32:17 +03:00
|
|
|
|
2021-04-02 15:25:29 +03:00
|
|
|
-- start mouse reporting
|
|
|
|
putStr "\x1b[?9h"
|
|
|
|
|
2020-09-18 20:46:43 +03:00
|
|
|
pWriterThread <- asyncBound
|
|
|
|
(writeTerminal tsWriteQueue spinnerMVar tsizeTVar)
|
2019-08-28 23:17:01 +03:00
|
|
|
|
2019-09-03 21:02:54 +03:00
|
|
|
pPreviousConfiguration <- io $ getTerminalAttributes stdInput
|
2019-08-28 23:17:01 +03:00
|
|
|
|
2019-08-30 02:35:52 +03:00
|
|
|
-- Create a new configuration where we put the terminal in raw mode and
|
|
|
|
-- disable a bunch of preprocessing.
|
2019-09-18 09:59:07 +03:00
|
|
|
let newTermSettings = flip withTime 0
|
|
|
|
$ flip withMinInput 1
|
|
|
|
$ foldl' withoutMode pPreviousConfiguration
|
|
|
|
$ disabledFlags
|
|
|
|
|
2019-09-03 21:02:54 +03:00
|
|
|
io $ setTerminalAttributes stdInput newTermSettings Immediately
|
2019-08-30 02:35:52 +03:00
|
|
|
|
|
|
|
tsReadQueue <- newTQueueIO
|
2019-09-13 21:02:41 +03:00
|
|
|
pReaderThread <- asyncBound
|
2021-04-02 15:25:29 +03:00
|
|
|
(readTerminal tsReadQueue tsWriteQueue tsizeTVar (bell tsWriteQueue))
|
2019-08-30 02:35:52 +03:00
|
|
|
|
2020-10-01 18:10:30 +03:00
|
|
|
let client = Client { take = Just <$> asum
|
|
|
|
[ readTQueue tsReadQueue <&> ClientTakeBelt,
|
|
|
|
takeTMVar tsSizeChange <&> ClientTakeSize
|
2020-09-25 18:32:17 +03:00
|
|
|
]
|
2019-09-18 08:22:19 +03:00
|
|
|
, give = writeTQueue tsWriteQueue
|
|
|
|
}
|
2019-09-04 01:17:20 +03:00
|
|
|
|
2020-09-25 18:32:17 +03:00
|
|
|
pure (client, Private{..})
|
2019-08-30 02:35:52 +03:00
|
|
|
|
2019-09-04 01:17:20 +03:00
|
|
|
stop :: HasLogFunc e
|
2020-09-25 18:32:17 +03:00
|
|
|
=> (Client, Private) -> RIO e ()
|
|
|
|
stop (Client{..}, Private{..}) = do
|
2019-09-06 22:59:56 +03:00
|
|
|
-- Note that we don't `cancel pReaderThread` here. This is a deliberate
|
|
|
|
-- decision because fdRead calls into a native function which the runtime
|
|
|
|
-- can't kill. If we were to cancel here, the internal `waitCatch` would
|
|
|
|
-- block until the next piece of keyboard input. Since this only happens
|
|
|
|
-- at shutdown, just leak the file descriptor.
|
2019-08-30 02:35:52 +03:00
|
|
|
cancel pWriterThread
|
2019-09-17 23:37:41 +03:00
|
|
|
|
2021-04-02 15:25:29 +03:00
|
|
|
-- stop mouse reporting
|
|
|
|
putStr "\x1b[?9l"
|
|
|
|
|
2019-09-17 23:37:41 +03:00
|
|
|
-- inject one final newline, as we're usually on the prompt.
|
2020-02-19 19:27:23 +03:00
|
|
|
putStr "\r\n"
|
2019-09-17 23:37:41 +03:00
|
|
|
|
2019-08-30 02:35:52 +03:00
|
|
|
-- take the terminal out of raw mode
|
2019-09-03 21:02:54 +03:00
|
|
|
io $ setTerminalAttributes stdInput pPreviousConfiguration Immediately
|
2019-08-30 02:35:52 +03:00
|
|
|
|
2019-09-18 09:17:54 +03:00
|
|
|
{-
|
|
|
|
A list of terminal flags that we disable.
|
|
|
|
|
|
|
|
TODO: Terminal library missing CSIZE?
|
|
|
|
-}
|
|
|
|
disabledFlags :: [TerminalMode]
|
|
|
|
disabledFlags = [ StartStopOutput
|
|
|
|
, KeyboardInterrupts
|
|
|
|
, EnableEcho
|
|
|
|
, EchoLF
|
|
|
|
, ProcessInput
|
|
|
|
, ExtendedFunctions
|
|
|
|
, MapCRtoLF
|
|
|
|
, CheckParity
|
|
|
|
, StripHighBit
|
|
|
|
, EnableParity
|
|
|
|
, ProcessOutput
|
|
|
|
]
|
2019-08-28 23:17:01 +03:00
|
|
|
|
2019-09-16 23:34:55 +03:00
|
|
|
|
2019-08-28 23:17:01 +03:00
|
|
|
-- Writes data to the terminal. Both the terminal reading, normal logging,
|
|
|
|
-- and effect handling can all emit bytes which go to the terminal.
|
2021-04-02 15:21:22 +03:00
|
|
|
--TODO blanks, traces and slogs should only be written into the default
|
|
|
|
-- terminal session.
|
2020-09-18 20:46:43 +03:00
|
|
|
writeTerminal :: TQueue [Term.Ev] -> TMVar () -> TVar TermSize -> RIO e ()
|
|
|
|
writeTerminal q spinner termSizeVar = do
|
2019-09-16 23:34:55 +03:00
|
|
|
currentTime <- io $ now
|
2021-04-02 14:58:13 +03:00
|
|
|
loop (LineState "" (CurPos 0 0) Nothing Nothing True 0 currentTime)
|
2019-08-29 03:08:47 +03:00
|
|
|
where
|
2019-09-18 07:41:31 +03:00
|
|
|
writeBlank :: LineState -> RIO e LineState
|
2021-04-02 15:21:22 +03:00
|
|
|
writeBlank ls = do
|
|
|
|
TermSize _ height <- readTVarIO termSizeVar
|
|
|
|
--NOTE hijack creates a blank line
|
|
|
|
T.hijack $ fromIntegral height
|
|
|
|
T.lojack
|
|
|
|
pure ls
|
2019-09-18 07:41:31 +03:00
|
|
|
|
2019-09-18 08:06:40 +03:00
|
|
|
writeTrace :: LineState -> Text -> RIO e LineState
|
|
|
|
writeTrace ls p = do
|
2021-04-02 15:21:22 +03:00
|
|
|
TermSize _ height <- readTVarIO termSizeVar
|
|
|
|
T.hijack $ fromIntegral height
|
2020-02-19 19:27:23 +03:00
|
|
|
putStr p
|
2021-04-02 15:21:22 +03:00
|
|
|
T.lojack
|
|
|
|
pure ls
|
2019-09-18 08:06:40 +03:00
|
|
|
|
2020-09-18 20:46:43 +03:00
|
|
|
writeSlog :: LineState -> (Atom, Tank) -> RIO e LineState
|
|
|
|
writeSlog ls slog = do
|
2021-04-02 15:21:22 +03:00
|
|
|
TermSize width height <- readTVarIO termSizeVar
|
|
|
|
T.hijack $ fromIntegral height
|
2020-09-28 17:56:51 +03:00
|
|
|
-- TODO: Ignoring priority for now. Priority changes the color of,
|
|
|
|
-- and adds a prefix of '>' to, the output.
|
2020-09-18 20:46:43 +03:00
|
|
|
let lines = fmap unTape $ wash (WashCfg 0 width) $ tankTree $ snd slog
|
2021-04-02 15:21:22 +03:00
|
|
|
T.putCSI 'm' [90] --NOTE print slogs in grey
|
|
|
|
forM (intersperse "\n" lines) $ \line -> putStr line
|
|
|
|
T.putCSI 'm' [0]
|
|
|
|
T.lojack
|
|
|
|
pure ls
|
2020-09-18 20:46:43 +03:00
|
|
|
|
2019-09-18 07:41:31 +03:00
|
|
|
{-
|
|
|
|
Figure out how long to wait to show the spinner. When we
|
|
|
|
don't have a vane name to display, we assume its a user
|
|
|
|
action and trigger immediately. Otherwise, if we receive an
|
|
|
|
event shortly after a previous spin, use a shorter delay to
|
|
|
|
avoid giving the impression of a half-idle system.
|
|
|
|
-}
|
2021-04-02 15:21:22 +03:00
|
|
|
--TODO this is too eager and does termRestoreLine on every keypress!
|
2019-09-18 07:41:31 +03:00
|
|
|
doSpin :: LineState -> Maybe Text -> RIO e LineState
|
|
|
|
doSpin ls@LineState{..} mTxt = do
|
2020-06-10 22:25:51 +03:00
|
|
|
maybe (pure ()) cancel lsSpinTimer
|
|
|
|
|
2019-09-18 07:41:31 +03:00
|
|
|
current <- io $ now
|
|
|
|
delay <- pure $ case mTxt of
|
|
|
|
Nothing -> 0
|
|
|
|
Just _ ->
|
|
|
|
if (gap current lsPrevEndTime ^. microSecs) < _spin_idle_us
|
|
|
|
then _spin_warm_us
|
|
|
|
else _spin_cool_us
|
|
|
|
|
2020-06-10 22:25:51 +03:00
|
|
|
spinTimer <- io $ async
|
|
|
|
$ repeatedly delay _spin_rate_us
|
|
|
|
$ void
|
|
|
|
$ tryPutTMVar spinner ()
|
2019-09-18 07:41:31 +03:00
|
|
|
|
|
|
|
pure $ ls { lsSpinTimer = Just spinTimer
|
|
|
|
, lsSpinCause = mTxt
|
|
|
|
, lsSpinFirstRender = True
|
|
|
|
}
|
|
|
|
|
|
|
|
unspin :: LineState -> RIO e LineState
|
2019-09-18 08:01:44 +03:00
|
|
|
unspin ls@LineState{..} = do
|
2019-09-16 23:34:55 +03:00
|
|
|
maybe (pure ()) cancel lsSpinTimer
|
|
|
|
-- We do a final flush of the spinner mvar to ensure we don't
|
|
|
|
-- have a lingering signal which will redisplay the spinner after
|
2021-04-02 15:21:22 +03:00
|
|
|
-- we call termRestoreLine below.
|
2019-09-16 23:34:55 +03:00
|
|
|
atomically $ tryTakeTMVar spinner
|
|
|
|
|
|
|
|
-- If we ever actually ran the spinner display callback, we need
|
|
|
|
-- to force a redisplay of the command prompt.
|
2021-04-02 15:21:22 +03:00
|
|
|
if not lsSpinFirstRender || True
|
|
|
|
then termRestoreLine ls termSizeVar
|
|
|
|
else pure ()
|
2019-09-16 23:34:55 +03:00
|
|
|
|
|
|
|
endTime <- io $ now
|
2019-09-18 07:41:31 +03:00
|
|
|
pure $ ls { lsSpinTimer = Nothing, lsPrevEndTime = endTime }
|
2019-09-16 23:34:55 +03:00
|
|
|
|
2019-09-18 08:22:19 +03:00
|
|
|
execEv :: LineState -> Term.Ev -> RIO e LineState
|
2019-09-18 07:41:31 +03:00
|
|
|
execEv ls = \case
|
2021-04-02 14:58:13 +03:00
|
|
|
Term.Blits bs -> foldM (writeBlit termSizeVar) ls bs
|
2019-09-18 09:58:42 +03:00
|
|
|
Term.Trace p -> writeTrace ls (unCord p)
|
2020-09-18 20:46:43 +03:00
|
|
|
Term.Slog s -> writeSlog ls s
|
2019-09-18 08:22:19 +03:00
|
|
|
Term.Blank -> writeBlank ls
|
2019-09-18 09:58:42 +03:00
|
|
|
Term.Spinr (Just txt) -> doSpin ls (unCord <$> txt)
|
2019-09-18 08:22:19 +03:00
|
|
|
Term.Spinr Nothing -> unspin ls
|
2019-09-16 23:34:55 +03:00
|
|
|
|
2020-06-10 22:25:51 +03:00
|
|
|
-- TODO What does this do?
|
2019-09-18 07:41:31 +03:00
|
|
|
spin :: LineState -> RIO e LineState
|
|
|
|
spin ls@LineState{..} = do
|
|
|
|
let spinner = (spinners !! lsSpinFrame) ++ case lsSpinCause of
|
|
|
|
Nothing -> ""
|
|
|
|
Just str -> leftBracket ++ str ++ rightBracket
|
|
|
|
|
2020-06-10 22:25:51 +03:00
|
|
|
putStr (spinner <> pack (ANSI.cursorBackwardCode (length spinner)))
|
2019-09-18 07:41:31 +03:00
|
|
|
|
2020-06-10 22:25:51 +03:00
|
|
|
let newFrame = (lsSpinFrame + 1) `mod` length spinners
|
2019-09-18 07:41:31 +03:00
|
|
|
|
|
|
|
pure $ ls { lsSpinFirstRender = False
|
|
|
|
, lsSpinFrame = newFrame
|
2019-09-16 23:34:55 +03:00
|
|
|
}
|
2019-08-29 03:08:47 +03:00
|
|
|
|
2019-09-18 07:41:31 +03:00
|
|
|
loop :: LineState -> RIO e ()
|
2019-09-18 08:01:44 +03:00
|
|
|
loop ls = do
|
2019-09-18 07:41:31 +03:00
|
|
|
join $ atomically $ asum
|
2019-09-18 12:11:18 +03:00
|
|
|
[ readTQueue q >>= pure . (foldM execEv ls >=> loop)
|
2019-09-18 07:41:31 +03:00
|
|
|
, takeTMVar spinner >> pure (spin ls >>= loop)
|
|
|
|
]
|
|
|
|
|
2019-08-29 03:08:47 +03:00
|
|
|
-- Writes an individual blit to the screen
|
2021-04-02 14:58:13 +03:00
|
|
|
writeBlit :: TVar TermSize -> LineState -> Blit -> RIO e LineState
|
|
|
|
writeBlit ts ls = \case
|
2020-02-19 19:27:23 +03:00
|
|
|
Bel () -> T.soundBell $> ls
|
|
|
|
Clr () -> do T.clearScreen
|
2021-04-02 14:58:13 +03:00
|
|
|
T.cursorRestore
|
|
|
|
pure ls
|
|
|
|
Hop t -> case t of
|
|
|
|
Col c -> termShowCursor ls ts 0 (fromIntegral c)
|
|
|
|
Roc r c -> termShowCursor ls ts (fromIntegral r) (fromIntegral c)
|
|
|
|
Klr s -> termShowStub ls s
|
|
|
|
Put c -> termShowLine ls (pack c)
|
|
|
|
Nel () -> termShowNewline ls
|
2019-09-18 09:17:54 +03:00
|
|
|
Sag path noun -> pure ls
|
|
|
|
Sav path atom -> pure ls
|
|
|
|
Url url -> pure ls
|
2021-04-02 14:58:13 +03:00
|
|
|
Wyp () -> termShowClear ls
|
2019-08-29 03:08:47 +03:00
|
|
|
|
2020-09-15 17:40:28 +03:00
|
|
|
termRenderDeco :: Deco -> Char
|
|
|
|
termRenderDeco = \case
|
|
|
|
DecoBr -> '1'
|
|
|
|
DecoUn -> '4'
|
|
|
|
DecoBl -> '5'
|
|
|
|
DecoNull -> '0'
|
|
|
|
|
2020-12-01 22:53:57 +03:00
|
|
|
termRenderTint :: Tint -> [Char]
|
2020-09-15 17:40:28 +03:00
|
|
|
termRenderTint = \case
|
2020-12-01 22:53:57 +03:00
|
|
|
TintK -> ['0']
|
|
|
|
TintR -> ['1']
|
|
|
|
TintG -> ['2']
|
|
|
|
TintY -> ['3']
|
|
|
|
TintB -> ['4']
|
|
|
|
TintM -> ['5']
|
|
|
|
TintC -> ['6']
|
|
|
|
TintW -> ['7']
|
|
|
|
TintNull -> ['9']
|
|
|
|
TintTrue r g b ->
|
2020-12-03 03:45:46 +03:00
|
|
|
mconcat ["8;2;", show r, ";", show g, ";", show b]
|
2020-09-15 17:40:28 +03:00
|
|
|
|
|
|
|
-- Wraps the appropriate escape sequence around a piece of styled text
|
|
|
|
termRenderStubSegment :: Stye -> [Char] -> [Char]
|
|
|
|
termRenderStubSegment Stye {..} tape =
|
|
|
|
case (S.null decoset, back, fore) of
|
|
|
|
(True, TintNull, TintNull) -> tape
|
|
|
|
_ -> styled
|
|
|
|
where
|
|
|
|
decoset = setFromHoonSet deco
|
|
|
|
escape = [chr 27, '[']
|
|
|
|
|
|
|
|
styles = intercalate ";" $ filter (not . null)
|
|
|
|
[ intersperse ';' $ fmap termRenderDeco $ toList decoset
|
|
|
|
, case back of
|
|
|
|
TintNull -> []
|
2020-12-03 03:45:46 +03:00
|
|
|
tint -> '4' : termRenderTint tint
|
2020-09-15 17:40:28 +03:00
|
|
|
, case fore of
|
|
|
|
TintNull -> []
|
2020-12-03 03:45:46 +03:00
|
|
|
tint -> '3' : termRenderTint tint
|
2020-09-15 17:40:28 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
styled = mconcat [escape, styles, "m", tape, escape, "0m"]
|
|
|
|
|
2021-04-02 14:58:13 +03:00
|
|
|
-- Displays styled text at the cursor
|
2020-09-15 17:40:28 +03:00
|
|
|
termShowStub :: LineState -> Stub -> RIO e LineState
|
2021-04-02 14:58:13 +03:00
|
|
|
termShowStub ls@LineState{lsCurPos} (Stub s) = do
|
2020-09-15 17:40:28 +03:00
|
|
|
let outText = pack $ mconcat $ fmap (uncurry termRenderStubSegment) s
|
|
|
|
putStr outText
|
2021-04-02 14:58:13 +03:00
|
|
|
T.cursorRestore
|
|
|
|
case row lsCurPos of
|
|
|
|
0 -> --TODO offset by col
|
|
|
|
pure ls { lsLine = outText }
|
|
|
|
_ -> pure ls
|
2020-09-15 17:40:28 +03:00
|
|
|
|
2019-08-29 03:08:47 +03:00
|
|
|
-- Moves the cursor to the requested position
|
2021-04-02 14:58:13 +03:00
|
|
|
termShowCursor :: LineState -> TVar TermSize -> Int -> Int -> RIO e LineState
|
|
|
|
termShowCursor ls ts row col = do
|
|
|
|
TermSize _ h <- readTVarIO ts
|
|
|
|
T.cursorMove (max 0 (fromIntegral h - row - 1)) col
|
|
|
|
T.cursorSave
|
|
|
|
pure ls { lsCurPos = CurPos row col }
|
2019-09-16 23:34:55 +03:00
|
|
|
|
|
|
|
-- Moves the cursor left without any mutation of the LineState. Used only
|
|
|
|
-- in cursor spinning.
|
2020-09-25 02:43:03 +03:00
|
|
|
_termSpinnerMoveLeft :: Int -> RIO e ()
|
2021-04-02 14:58:13 +03:00
|
|
|
_termSpinnerMoveLeft = liftIO . ANSI.cursorBackward
|
2019-08-29 03:08:47 +03:00
|
|
|
|
|
|
|
-- Displays and sets the current line
|
2020-02-19 19:27:23 +03:00
|
|
|
termShowLine :: LineState -> Text -> RIO e LineState
|
2021-04-02 14:58:13 +03:00
|
|
|
termShowLine ls@LineState{lsCurPos} newStr = do
|
2020-02-19 19:27:23 +03:00
|
|
|
putStr newStr
|
2021-04-02 14:58:13 +03:00
|
|
|
T.cursorRestore
|
|
|
|
case row lsCurPos of
|
|
|
|
0 -> --TODO offset by col
|
|
|
|
pure ls { lsLine = newStr }
|
|
|
|
_ -> pure ls
|
2019-08-29 03:08:47 +03:00
|
|
|
|
2020-02-19 19:27:23 +03:00
|
|
|
termShowClear :: LineState -> RIO e LineState
|
2021-04-02 14:58:13 +03:00
|
|
|
termShowClear ls@LineState{lsCurPos} = do
|
2020-02-19 19:27:23 +03:00
|
|
|
putStr "\r"
|
|
|
|
T.clearLine
|
2021-04-02 14:58:13 +03:00
|
|
|
T.cursorRestore
|
|
|
|
case row lsCurPos of
|
|
|
|
0 -> pure ls { lsLine = "" }
|
|
|
|
_ -> pure ls
|
2019-08-29 03:08:47 +03:00
|
|
|
|
|
|
|
-- New Current Line
|
2021-04-02 14:58:13 +03:00
|
|
|
termShowNewline :: LineState -> RIO e LineState
|
|
|
|
termShowNewline ls@LineState{lsCurPos} = do
|
2020-02-19 19:27:23 +03:00
|
|
|
putStr "\r\n"
|
2021-04-02 14:58:13 +03:00
|
|
|
case row lsCurPos of
|
|
|
|
0 -> pure ls { lsLine = "", lsCurPos = lsCurPos { col = 0 } }
|
|
|
|
r -> pure ls { lsCurPos = CurPos (r-1) 0 }
|
2019-08-29 03:08:47 +03:00
|
|
|
|
2021-04-02 15:21:22 +03:00
|
|
|
-- Redraw the bottom LineState, maintaining the current curpos
|
|
|
|
termRestoreLine :: LineState -> TVar TermSize -> RIO e ()
|
|
|
|
termRestoreLine ls@LineState{lsLine} ts = do
|
|
|
|
TermSize _ h <- readTVarIO ts
|
|
|
|
T.cursorMove (fromIntegral h - 1) 0
|
|
|
|
T.clearLine
|
|
|
|
putStr lsLine
|
|
|
|
T.cursorRestore
|
2019-08-28 23:17:01 +03:00
|
|
|
|
2019-08-30 02:35:52 +03:00
|
|
|
-- ring my bell
|
2019-09-18 12:11:18 +03:00
|
|
|
bell :: TQueue [Term.Ev] -> RIO e ()
|
|
|
|
bell q = atomically $ writeTQueue q $ [Term.Blits [Bel ()]]
|
2019-08-29 23:19:06 +03:00
|
|
|
|
2019-08-28 01:29:11 +03:00
|
|
|
-- 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-09-03 21:02:54 +03:00
|
|
|
readTerminal :: forall e. HasLogFunc e
|
2021-04-02 15:25:29 +03:00
|
|
|
=> TQueue Belt
|
|
|
|
-> TQueue [Term.Ev]
|
|
|
|
-> TVar TermSize
|
|
|
|
-> RIO e ()
|
|
|
|
-> RIO e ()
|
|
|
|
readTerminal rq wq ts bell =
|
|
|
|
rioAllocaBytes 1 $ \ buf
|
|
|
|
-> loop (ReadData buf False False False 0 0 mempty 0)
|
2019-08-29 23:19:06 +03:00
|
|
|
where
|
2019-09-03 21:02:54 +03:00
|
|
|
loop :: ReadData -> RIO e ()
|
2019-08-29 23:19:06 +03:00
|
|
|
loop rd@ReadData{..} = do
|
2019-09-13 21:46:03 +03:00
|
|
|
-- The problem with using fdRead raw is that it will text encode
|
|
|
|
-- things like \ESC instead of 27. That makes it broken for our
|
|
|
|
-- purposes.
|
2019-08-30 00:54:34 +03:00
|
|
|
--
|
2020-02-19 19:27:23 +03:00
|
|
|
io (try $ fdReadBuf stdInput rdBuf 1) >>= \case
|
2019-08-30 00:54:34 +03:00
|
|
|
Left (e :: IOException) -> do
|
2019-08-29 23:19:06 +03:00
|
|
|
-- Ignore EAGAINs when doing reads
|
|
|
|
loop rd
|
|
|
|
Right 0 -> loop rd
|
|
|
|
Right _ -> do
|
2019-09-03 21:02:54 +03:00
|
|
|
w <- io $ peek rdBuf
|
2019-08-30 00:54:34 +03:00
|
|
|
-- print ("{" ++ (show w) ++ "}")
|
2019-09-18 07:41:31 +03:00
|
|
|
let c = BS.w2c w
|
2019-08-30 02:35:52 +03:00
|
|
|
if rdEscape then
|
|
|
|
if rdBracket then do
|
2019-08-29 23:19:06 +03:00
|
|
|
case c of
|
2021-04-02 14:58:13 +03:00
|
|
|
'A' -> sendBelt $ Bol $ Aro U
|
|
|
|
'B' -> sendBelt $ Bol $ Aro D
|
|
|
|
'C' -> sendBelt $ Bol $ Aro R
|
|
|
|
'D' -> sendBelt $ Bol $ Aro L
|
2021-04-02 15:25:29 +03:00
|
|
|
'M' -> pure ()
|
2019-08-30 00:54:34 +03:00
|
|
|
_ -> bell
|
2021-04-02 15:25:29 +03:00
|
|
|
rd <- case c of
|
|
|
|
'M' -> pure rd { rdMouse = True }
|
|
|
|
_ -> pure rd
|
|
|
|
loop rd { rdEscape = False, rdBracket = False }
|
2019-08-30 00:54:34 +03:00
|
|
|
else if isAsciiLower c then do
|
2021-04-02 14:58:13 +03:00
|
|
|
sendBelt $ Mod Met $ Key c
|
2019-08-30 02:35:52 +03:00
|
|
|
loop rd { rdEscape = False }
|
2019-08-30 00:54:34 +03:00
|
|
|
else if w == 8 || w == 127 then do
|
2021-04-02 14:58:13 +03:00
|
|
|
sendBelt $ Mod Met $ Bac ()
|
2019-08-30 02:35:52 +03:00
|
|
|
loop rd { rdEscape = False }
|
2019-08-30 00:54:34 +03:00
|
|
|
else if c == '[' || c == '0' then do
|
2019-08-30 02:35:52 +03:00
|
|
|
loop rd { rdBracket = True }
|
2019-08-30 00:54:34 +03:00
|
|
|
else do
|
|
|
|
bell
|
2019-08-30 02:35:52 +03:00
|
|
|
loop rd { rdEscape = False }
|
2021-04-02 15:25:29 +03:00
|
|
|
else if rdMouse then
|
|
|
|
if rdMouseBut == 0 then do
|
|
|
|
loop rd { rdMouseBut = w - 31 }
|
|
|
|
else if rdMouseCol == 0 then do
|
|
|
|
loop rd { rdMouseCol = w - 32 }
|
|
|
|
else do
|
|
|
|
if rdMouseBut == 1 then do
|
|
|
|
let rdMouseRow = w - 32
|
|
|
|
TermSize _ h <- readTVarIO ts
|
|
|
|
sendBelt $ Bol $ Hit
|
|
|
|
(fromIntegral h - fromIntegral rdMouseRow)
|
|
|
|
(fromIntegral rdMouseCol - 1)
|
|
|
|
else do pure ()
|
|
|
|
loop rd { rdMouse = False, rdMouseBut = 0, rdMouseCol = 0 }
|
2019-09-13 21:46:03 +03:00
|
|
|
else if rdUTF8width /= 0 then do
|
|
|
|
-- continue reading into the utf8 accumulation buffer
|
|
|
|
rd@ReadData{..} <- pure rd { rdUTF8 = snoc rdUTF8 w }
|
|
|
|
if length rdUTF8 /= rdUTF8width then loop rd
|
|
|
|
else do
|
2019-09-18 07:41:31 +03:00
|
|
|
case BS.decode rdUTF8 of
|
2019-09-13 21:46:03 +03:00
|
|
|
Nothing ->
|
|
|
|
error "empty utf8 accumulation buffer"
|
|
|
|
Just (c, bytes) | bytes /= rdUTF8width ->
|
|
|
|
error "utf8 character size mismatch?!"
|
2021-04-02 14:58:13 +03:00
|
|
|
Just (c, bytes) -> sendBelt $ Bol $ Key c
|
2019-09-18 07:41:31 +03:00
|
|
|
loop rd { rdUTF8 = mempty, rdUTF8width = 0 }
|
2019-08-30 00:54:34 +03:00
|
|
|
else if w >= 32 && w < 127 then do
|
2021-04-02 14:58:13 +03:00
|
|
|
sendBelt $ Bol $ Key c
|
2019-08-30 00:54:34 +03:00
|
|
|
loop rd
|
2019-08-29 23:19:06 +03:00
|
|
|
else if w == 0 then do
|
|
|
|
bell
|
2019-08-30 00:54:34 +03:00
|
|
|
loop rd
|
2019-08-29 23:19:06 +03:00
|
|
|
else if w == 8 || w == 127 then do
|
2021-04-02 14:58:13 +03:00
|
|
|
sendBelt $ Bol $ Bac ()
|
2019-08-29 23:19:06 +03:00
|
|
|
loop rd
|
|
|
|
else if w == 13 then do
|
2021-04-02 14:58:13 +03:00
|
|
|
sendBelt $ Bol $ Ret ()
|
2019-08-29 23:19:06 +03:00
|
|
|
loop rd
|
2019-08-30 00:54:34 +03:00
|
|
|
else if w == 3 then do
|
|
|
|
-- ETX (^C)
|
2020-12-03 22:13:59 +03:00
|
|
|
logInfo $ "Ctrl-c interrupt"
|
2019-08-30 01:05:01 +03:00
|
|
|
atomically $ do
|
2021-04-02 15:21:22 +03:00
|
|
|
writeTQueue wq [Term.Trace "interrupt"]
|
2021-04-02 14:58:13 +03:00
|
|
|
writeTQueue rq $ Mod Ctl $ Key 'c'
|
2019-08-30 00:54:34 +03:00
|
|
|
loop rd
|
2019-08-29 23:19:06 +03:00
|
|
|
else if w <= 26 then do
|
2021-04-02 14:58:13 +03:00
|
|
|
case BS.w2c (w + 97 - 1) of
|
|
|
|
'd' -> atomically doneSignal
|
|
|
|
c -> do sendBelt $ Mod Ctl $ Key c
|
2019-12-17 21:06:20 +03:00
|
|
|
loop rd
|
2019-08-29 23:19:06 +03:00
|
|
|
else if w == 27 then do
|
2019-08-30 02:35:52 +03:00
|
|
|
loop rd { rdEscape = True }
|
2019-08-30 00:54:34 +03:00
|
|
|
else do
|
2019-08-29 23:19:06 +03:00
|
|
|
-- start the utf8 accumulation buffer
|
2019-09-13 21:46:03 +03:00
|
|
|
loop rd { rdUTF8 = singleton w,
|
|
|
|
rdUTF8width = if w < 224 then 2
|
|
|
|
else if w < 240 then 3
|
|
|
|
else 4 }
|
2019-08-29 23:19:06 +03:00
|
|
|
|
2019-09-03 21:02:54 +03:00
|
|
|
sendBelt :: HasLogFunc e => Belt -> RIO e ()
|
2019-08-29 23:19:06 +03:00
|
|
|
sendBelt b = do
|
2019-12-17 14:29:58 +03:00
|
|
|
-- logDebug $ displayShow ("terminalBelt", b)
|
2019-08-30 02:35:52 +03:00
|
|
|
atomically $ writeTQueue rq b
|
|
|
|
|
2020-06-10 22:25:51 +03:00
|
|
|
|
2019-08-30 02:35:52 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2020-06-10 22:25:51 +03:00
|
|
|
{-|
|
|
|
|
Terminal Driver
|
|
|
|
|
|
|
|
Until blew/hail events succeeds, ignore effects.
|
|
|
|
Wait until blew/hail event callbacks invoked.
|
|
|
|
If success, signal success.
|
|
|
|
If failure, try again several times.
|
|
|
|
If still failure, bring down ship.
|
|
|
|
Don't wait for other drivers to boot
|
|
|
|
Begin normal operation (start accepting requests)
|
|
|
|
-}
|
|
|
|
term'
|
|
|
|
:: HasPierEnv e
|
2020-06-12 21:27:25 +03:00
|
|
|
=> (TermSize, Client)
|
2020-12-03 22:13:59 +03:00
|
|
|
-> IO RenderedStat
|
2020-06-10 23:53:55 +03:00
|
|
|
-> IO ()
|
2020-06-10 22:25:51 +03:00
|
|
|
-> RIO e ([Ev], RAcquire e (DriverApi TermEf))
|
2020-12-03 22:13:59 +03:00
|
|
|
term' (tsize, client) stat serfSIGINT = do
|
2020-06-12 21:27:25 +03:00
|
|
|
let TermSize wi hi = tsize
|
2020-09-25 18:32:17 +03:00
|
|
|
initEv = [blewEvent wi hi, initialHail]
|
2020-06-02 23:48:07 +03:00
|
|
|
|
2020-06-10 22:25:51 +03:00
|
|
|
pure (initEv, runDriver)
|
|
|
|
where
|
|
|
|
runDriver = do
|
|
|
|
env <- ask
|
|
|
|
ventQ :: TQueue EvErr <- newTQueueIO
|
2020-12-03 22:13:59 +03:00
|
|
|
diOnEffect <- term env (tsize, client) (writeTQueue ventQ) stat serfSIGINT
|
2020-06-10 22:25:51 +03:00
|
|
|
|
|
|
|
let diEventSource = fmap RRWork <$> tryReadTQueue ventQ
|
|
|
|
|
|
|
|
pure (DriverApi {..})
|
2020-06-02 23:48:07 +03:00
|
|
|
|
2020-01-23 07:16:09 +03:00
|
|
|
{-|
|
|
|
|
Terminal Driver
|
|
|
|
-}
|
2020-06-07 02:03:20 +03:00
|
|
|
term :: forall e. (HasPierEnv e)
|
2020-05-13 22:35:57 +03:00
|
|
|
=> e
|
2020-06-12 21:27:25 +03:00
|
|
|
-> (TermSize, Client)
|
2020-06-02 23:48:07 +03:00
|
|
|
-> (EvErr -> STM ())
|
2020-12-03 22:13:59 +03:00
|
|
|
-> IO RenderedStat
|
2020-06-10 23:53:55 +03:00
|
|
|
-> IO ()
|
2020-06-10 22:25:51 +03:00
|
|
|
-> RAcquire e (TermEf -> IO ())
|
2020-12-03 22:13:59 +03:00
|
|
|
term env (tsize, Client{..}) plan stat serfSIGINT = runTerm
|
2019-08-30 02:35:52 +03:00
|
|
|
where
|
2020-06-07 02:34:27 +03:00
|
|
|
runTerm :: RAcquire e (TermEf -> IO ())
|
2019-08-30 02:35:52 +03:00
|
|
|
runTerm = do
|
2019-12-17 21:06:20 +03:00
|
|
|
tim <- mkRAcquire (async readLoop) cancel
|
2020-06-07 02:34:27 +03:00
|
|
|
pure (runRIO env . handleEffect)
|
2019-08-30 02:35:52 +03:00
|
|
|
|
2019-12-17 21:06:20 +03:00
|
|
|
{-
|
|
|
|
Because our terminals are always `Demux`ed, we don't have to
|
|
|
|
care about disconnections.
|
|
|
|
-}
|
|
|
|
readLoop :: RIO e ()
|
|
|
|
readLoop = forever $ do
|
|
|
|
atomically take >>= \case
|
2020-09-25 18:32:17 +03:00
|
|
|
Nothing -> pure ()
|
|
|
|
Just (ClientTakeBelt b) -> do
|
2021-04-02 14:58:13 +03:00
|
|
|
when (b == Mod Ctl (Key 'c')) $ do
|
2020-06-10 23:53:55 +03:00
|
|
|
io serfSIGINT
|
2020-06-02 23:48:07 +03:00
|
|
|
let beltEv = EvBlip $ BlipEvTerm $ TermEvBelt (UD 1, ()) $ b
|
|
|
|
let beltFailed _ = pure ()
|
|
|
|
atomically $ plan (EvErr beltEv beltFailed)
|
2020-09-25 18:32:17 +03:00
|
|
|
Just (ClientTakeSize ts@(TermSize w h)) -> do
|
|
|
|
let blewFailed _ = pure ()
|
|
|
|
atomically $ plan (EvErr (blewEvent w h) blewFailed)
|
2019-08-29 23:19:06 +03:00
|
|
|
|
2019-09-03 21:02:54 +03:00
|
|
|
handleEffect :: TermEf -> RIO e ()
|
2019-08-30 02:35:52 +03:00
|
|
|
handleEffect = \case
|
2019-12-17 21:06:20 +03:00
|
|
|
TermEfInit _ _ -> pure ()
|
|
|
|
TermEfMass _ _ -> pure ()
|
2020-06-07 02:03:20 +03:00
|
|
|
TermEfLogo _ _ -> atomically =<< view killPierActionL
|
2019-12-17 21:06:20 +03:00
|
|
|
TermEfBlit _ blits -> do
|
|
|
|
let (termBlits, fsWrites) = partition isTerminalBlit blits
|
|
|
|
atomically $ give [Term.Blits termBlits]
|
|
|
|
for_ fsWrites handleFsWrite
|
|
|
|
|
2019-09-03 21:02:54 +03:00
|
|
|
handleFsWrite :: Blit -> RIO e ()
|
2019-08-30 23:25:50 +03:00
|
|
|
handleFsWrite (Sag path noun) = performPut path (jamBS noun)
|
2020-01-23 12:22:30 +03:00
|
|
|
handleFsWrite (Sav path atom) = performPut path (atomBytes atom)
|
2019-09-13 21:02:41 +03:00
|
|
|
handleFsWrite _ = pure ()
|
2019-08-30 23:25:50 +03:00
|
|
|
|
2019-09-03 21:02:54 +03:00
|
|
|
performPut :: Path -> ByteString -> RIO e ()
|
2019-08-30 23:25:50 +03:00
|
|
|
performPut path bs = do
|
2019-12-17 17:31:50 +03:00
|
|
|
pierPath <- view pierPathL
|
2019-09-10 23:14:43 +03:00
|
|
|
let putOutFile = pierPath </> ".urb" </> "put" </> (pathToFilePath path)
|
|
|
|
createDirectoryIfMissing True (takeDirectory putOutFile)
|
2019-08-30 23:25:50 +03:00
|
|
|
writeFile putOutFile bs
|