mirror of
https://github.com/urbit/shrub.git
synced 2025-01-01 17:16:47 +03:00
king: set initial terminal size and react to resizes
This changes startup so we get the size of the current terminal to send to Urbit on startup. We then subscribe to terminal size change notifications and send those to your Urbit via the terminal muxing system. In the case where there are multiple terminal connections to your Urbit, set the terminal size to the minimum of the widths.
This commit is contained in:
parent
e9f09e32c1
commit
20a6c0331c
@ -18,7 +18,6 @@ import Urbit.Prelude
|
|||||||
|
|
||||||
import Network.Socket (Socket)
|
import Network.Socket (Socket)
|
||||||
import Prelude (read)
|
import Prelude (read)
|
||||||
import Urbit.Arvo (Belt)
|
|
||||||
import Urbit.King.App (HasPierPath(..))
|
import Urbit.King.App (HasPierPath(..))
|
||||||
|
|
||||||
import qualified Network.HTTP.Types as H
|
import qualified Network.HTTP.Types as H
|
||||||
@ -32,7 +31,7 @@ import qualified Urbit.Vere.Term.API as Term
|
|||||||
|
|
||||||
-- Types -----------------------------------------------------------------------
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
type TermConn = NounServ.Conn Belt [Term.Ev]
|
type TermConn = NounServ.Conn Term.ClientTake [Term.Ev]
|
||||||
|
|
||||||
type TermConnAPI = TVar (Maybe (TermConn -> STM ()))
|
type TermConnAPI = TVar (Maybe (TermConn -> STM ()))
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ import System.Posix.Files (ownerModes, setFileMode)
|
|||||||
import Urbit.EventLog.LMDB (EventLog)
|
import Urbit.EventLog.LMDB (EventLog)
|
||||||
import Urbit.King.API (TermConn)
|
import Urbit.King.API (TermConn)
|
||||||
import Urbit.Noun.Time (Wen)
|
import Urbit.Noun.Time (Wen)
|
||||||
import Urbit.TermSize (TermSize(..))
|
import Urbit.TermSize (TermSize(..), termSize)
|
||||||
import Urbit.Vere.Serf (Serf)
|
import Urbit.Vere.Serf (Serf)
|
||||||
|
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
@ -283,8 +283,10 @@ pier (serf, log) vSlog startedSig = do
|
|||||||
writeTVar (King.kTermConn kingApi) (Just $ writeTQueue q)
|
writeTVar (King.kTermConn kingApi) (Just $ writeTQueue q)
|
||||||
pure q
|
pure q
|
||||||
|
|
||||||
|
initialTermSize <- io $ termSize
|
||||||
|
|
||||||
(demux :: Term.Demux, muxed :: Term.Client) <- atomically $ do
|
(demux :: Term.Demux, muxed :: Term.Client) <- atomically $ do
|
||||||
res <- Term.mkDemux
|
res <- Term.mkDemux initialTermSize
|
||||||
pure (res, Term.useDemux res)
|
pure (res, Term.useDemux res)
|
||||||
|
|
||||||
void $ acquireWorker "TERMSERV Listener" $ forever $ do
|
void $ acquireWorker "TERMSERV Listener" $ forever $ do
|
||||||
@ -312,7 +314,7 @@ pier (serf, log) vSlog startedSig = do
|
|||||||
(bootEvents, startDrivers) <- do
|
(bootEvents, startDrivers) <- do
|
||||||
env <- ask
|
env <- ask
|
||||||
let err = atomically . Term.trace muxed . (<> "\r\n")
|
let err = atomically . Term.trace muxed . (<> "\r\n")
|
||||||
let siz = TermSize { tsWide = 80, tsTall = 24 }
|
siz <- atomically $ Term.curDemuxSize demux
|
||||||
let fak = isFake logId
|
let fak = isFake logId
|
||||||
drivers env ship fak compute (siz, muxed) err sigint
|
drivers env ship fak compute (siz, muxed) err sigint
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ import Data.List ((!!))
|
|||||||
import RIO.Directory (createDirectoryIfMissing)
|
import RIO.Directory (createDirectoryIfMissing)
|
||||||
import Urbit.King.API (readPortsFile)
|
import Urbit.King.API (readPortsFile)
|
||||||
import Urbit.TermSize (TermSize(TermSize))
|
import Urbit.TermSize (TermSize(TermSize))
|
||||||
import Urbit.Vere.Term.API (Client(Client))
|
import Urbit.Vere.Term.API (Client(Client), ClientTake(..))
|
||||||
|
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import qualified Data.ByteString.Internal as BS
|
import qualified Data.ByteString.Internal as BS
|
||||||
@ -71,7 +71,7 @@ data Private = Private
|
|||||||
|
|
||||||
-- Utils -----------------------------------------------------------------------
|
-- Utils -----------------------------------------------------------------------
|
||||||
|
|
||||||
initialBlew w h = EvBlip $ BlipEvTerm $ TermEvBlew (UD 1, ()) w h
|
blewEvent w h = EvBlip $ BlipEvTerm $ TermEvBlew (UD 1, ()) w h
|
||||||
|
|
||||||
initialHail = EvBlip $ BlipEvTerm $ TermEvHail (UD 1, ()) ()
|
initialHail = EvBlip $ BlipEvTerm $ TermEvHail (UD 1, ()) ()
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ isTerminalBlit _ = True
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
connClient :: Serv.Conn Belt [Term.Ev] -> Client
|
connClient :: Serv.Conn ClientTake [Term.Ev] -> Client
|
||||||
connClient c = Client
|
connClient c = Client
|
||||||
{ give = Serv.cSend c
|
{ give = Serv.cSend c
|
||||||
, take = Serv.cRecv c
|
, take = Serv.cRecv c
|
||||||
@ -135,7 +135,7 @@ runTerminalClient pier = runRAcquire $ do
|
|||||||
mPort <- runRIO (HCD pier) readPortsFile
|
mPort <- runRIO (HCD pier) readPortsFile
|
||||||
port <- maybe (error "Can't connect") pure mPort
|
port <- maybe (error "Can't connect") pure mPort
|
||||||
mExit <- io newEmptyTMVarIO
|
mExit <- io newEmptyTMVarIO
|
||||||
(siz, cli) <- localClient (putTMVar mExit ())
|
cli <- localClient (putTMVar mExit ())
|
||||||
(tid, sid) <- connectToRemote (Port $ fromIntegral port) cli
|
(tid, sid) <- connectToRemote (Port $ fromIntegral port) cli
|
||||||
atomically $ waitSTM tid <|> waitSTM sid <|> takeTMVar mExit
|
atomically $ waitSTM tid <|> waitSTM sid <|> takeTMVar mExit
|
||||||
|
|
||||||
@ -175,14 +175,28 @@ _spin_idle_us = 500000
|
|||||||
-}
|
-}
|
||||||
localClient :: ∀e. HasLogFunc e
|
localClient :: ∀e. HasLogFunc e
|
||||||
=> STM ()
|
=> STM ()
|
||||||
-> RAcquire e (TermSize, Client)
|
-> RAcquire e Client
|
||||||
localClient doneSignal = fst <$> mkRAcquire start stop
|
localClient doneSignal = fst <$> mkRAcquire start stop
|
||||||
where
|
where
|
||||||
start :: HasLogFunc e => RIO e ((TermSize, Client), Private)
|
start :: HasLogFunc e => RIO e (Client, Private)
|
||||||
start = do
|
start = do
|
||||||
tsWriteQueue <- newTQueueIO :: RIO e (TQueue [Term.Ev])
|
tsWriteQueue <- newTQueueIO :: RIO e (TQueue [Term.Ev])
|
||||||
spinnerMVar <- newEmptyTMVarIO :: RIO e (TMVar ())
|
spinnerMVar <- newEmptyTMVarIO :: RIO e (TMVar ())
|
||||||
tsizeTVar <- io $ T.termSize >>= newTVarIO
|
|
||||||
|
-- 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.
|
||||||
|
tsizeTVar <- newTVarIO (TermSize 80 24) -- Value doesn't matter.
|
||||||
|
tsSizeChangeQueue <- newTQueueIO
|
||||||
|
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.
|
||||||
|
writeTQueue tsSizeChangeQueue ts)
|
||||||
|
|
||||||
pWriterThread <- asyncBound
|
pWriterThread <- asyncBound
|
||||||
(writeTerminal tsWriteQueue spinnerMVar tsizeTVar)
|
(writeTerminal tsWriteQueue spinnerMVar tsizeTVar)
|
||||||
|
|
||||||
@ -201,17 +215,18 @@ localClient doneSignal = fst <$> mkRAcquire start stop
|
|||||||
pReaderThread <- asyncBound
|
pReaderThread <- asyncBound
|
||||||
(readTerminal tsReadQueue tsWriteQueue (bell tsWriteQueue))
|
(readTerminal tsReadQueue tsWriteQueue (bell tsWriteQueue))
|
||||||
|
|
||||||
let client = Client { take = Just <$> readTQueue tsReadQueue
|
let client = Client { take = Just <$> asum [
|
||||||
|
readTQueue tsReadQueue <&> ClientTakeBelt,
|
||||||
|
readTQueue tsSizeChangeQueue <&> ClientTakeSize
|
||||||
|
]
|
||||||
, give = writeTQueue tsWriteQueue
|
, give = writeTQueue tsWriteQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
tsize <- io $ T.liveTermSize (\ts -> atomically $ writeTVar tsizeTVar ts)
|
pure (client, Private{..})
|
||||||
|
|
||||||
pure ((tsize, client), Private{..})
|
|
||||||
|
|
||||||
stop :: HasLogFunc e
|
stop :: HasLogFunc e
|
||||||
=> ((TermSize, Client), Private) -> RIO e ()
|
=> (Client, Private) -> RIO e ()
|
||||||
stop ((_, Client{..}), Private{..}) = do
|
stop (Client{..}, Private{..}) = do
|
||||||
-- Note that we don't `cancel pReaderThread` here. This is a deliberate
|
-- Note that we don't `cancel pReaderThread` here. This is a deliberate
|
||||||
-- decision because fdRead calls into a native function which the runtime
|
-- decision because fdRead calls into a native function which the runtime
|
||||||
-- can't kill. If we were to cancel here, the internal `waitCatch` would
|
-- can't kill. If we were to cancel here, the internal `waitCatch` would
|
||||||
@ -583,7 +598,7 @@ term'
|
|||||||
-> RIO e ([Ev], RAcquire e (DriverApi TermEf))
|
-> RIO e ([Ev], RAcquire e (DriverApi TermEf))
|
||||||
term' (tsize, client) serfSIGINT = do
|
term' (tsize, client) serfSIGINT = do
|
||||||
let TermSize wi hi = tsize
|
let TermSize wi hi = tsize
|
||||||
initEv = [initialBlew wi hi, initialHail]
|
initEv = [blewEvent wi hi, initialHail]
|
||||||
|
|
||||||
pure (initEv, runDriver)
|
pure (initEv, runDriver)
|
||||||
where
|
where
|
||||||
@ -620,12 +635,15 @@ term env (tsize, Client{..}) plan serfSIGINT = runTerm
|
|||||||
readLoop = forever $ do
|
readLoop = forever $ do
|
||||||
atomically take >>= \case
|
atomically take >>= \case
|
||||||
Nothing -> pure ()
|
Nothing -> pure ()
|
||||||
Just b -> do
|
Just (ClientTakeBelt b) -> do
|
||||||
when (b == Ctl (Cord "c")) $ do
|
when (b == Ctl (Cord "c")) $ do
|
||||||
io serfSIGINT
|
io serfSIGINT
|
||||||
let beltEv = EvBlip $ BlipEvTerm $ TermEvBelt (UD 1, ()) $ b
|
let beltEv = EvBlip $ BlipEvTerm $ TermEvBelt (UD 1, ()) $ b
|
||||||
let beltFailed _ = pure ()
|
let beltFailed _ = pure ()
|
||||||
atomically $ plan (EvErr beltEv beltFailed)
|
atomically $ plan (EvErr beltEv beltFailed)
|
||||||
|
Just (ClientTakeSize ts@(TermSize w h)) -> do
|
||||||
|
let blewFailed _ = pure ()
|
||||||
|
atomically $ plan (EvErr (blewEvent w h) blewFailed)
|
||||||
|
|
||||||
handleEffect :: TermEf -> RIO e ()
|
handleEffect :: TermEf -> RIO e ()
|
||||||
handleEffect = \case
|
handleEffect = \case
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
-}
|
-}
|
||||||
module Urbit.Vere.Term.API (Ev(..),
|
module Urbit.Vere.Term.API (Ev(..),
|
||||||
Client(..),
|
Client(..),
|
||||||
|
ClientTake(..),
|
||||||
trace,
|
trace,
|
||||||
slog,
|
slog,
|
||||||
spin,
|
spin,
|
||||||
@ -12,6 +13,7 @@ import Urbit.Prelude hiding (trace)
|
|||||||
|
|
||||||
import Urbit.Arvo (Belt, Blit)
|
import Urbit.Arvo (Belt, Blit)
|
||||||
|
|
||||||
|
import Urbit.TermSize
|
||||||
|
|
||||||
-- External Types --------------------------------------------------------------
|
-- External Types --------------------------------------------------------------
|
||||||
|
|
||||||
@ -31,8 +33,31 @@ data Ev = Blits [Blit]
|
|||||||
| Spinr (Maybe (Maybe Cord))
|
| Spinr (Maybe (Maybe Cord))
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
|
data ClientTake
|
||||||
|
= ClientTakeBelt Belt
|
||||||
|
| ClientTakeSize TermSize
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
instance ToNoun ClientTake where
|
||||||
|
toNoun = \case
|
||||||
|
ClientTakeBelt b -> toNoun $ (Cord "belt", b)
|
||||||
|
ClientTakeSize (TermSize w h) -> toNoun $ (Cord "size", (w, h))
|
||||||
|
|
||||||
|
instance FromNoun ClientTake where
|
||||||
|
parseNoun n = named "ClientTake" $ do
|
||||||
|
(Cord name, rest) <- parseNoun n
|
||||||
|
case name of
|
||||||
|
"belt" -> do
|
||||||
|
b <- parseNoun rest
|
||||||
|
pure (ClientTakeBelt b)
|
||||||
|
"size" -> do
|
||||||
|
(w, h) <- parseNoun rest
|
||||||
|
pure (ClientTakeSize (TermSize w h))
|
||||||
|
_ -> fail "weird client take"
|
||||||
|
|
||||||
|
|
||||||
data Client = Client
|
data Client = Client
|
||||||
{ take :: STM (Maybe Belt)
|
{ take :: STM (Maybe ClientTake)
|
||||||
, give :: [Ev] -> STM ()
|
, give :: [Ev] -> STM ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,12 +4,15 @@
|
|||||||
given full event history since the creation of the demuxer.
|
given full event history since the creation of the demuxer.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Urbit.Vere.Term.Demux (Demux, mkDemux, addDemux, useDemux) where
|
module Urbit.Vere.Term.Demux (Demux,
|
||||||
|
mkDemux,
|
||||||
|
addDemux,
|
||||||
|
useDemux,
|
||||||
|
curDemuxSize) where
|
||||||
|
|
||||||
import Urbit.Prelude
|
import Urbit.Prelude
|
||||||
|
import Urbit.TermSize
|
||||||
import Urbit.Arvo (Belt)
|
import Urbit.Vere.Term.API (Client(Client), ClientTake(..))
|
||||||
import Urbit.Vere.Term.API (Client(Client))
|
|
||||||
|
|
||||||
import qualified Urbit.Vere.Term.API as Term
|
import qualified Urbit.Vere.Term.API as Term
|
||||||
import qualified Urbit.Vere.Term.Logic as Logic
|
import qualified Urbit.Vere.Term.Logic as Logic
|
||||||
@ -42,11 +45,17 @@ ksDelete k (KeyedSet t n) = KeyedSet (deleteMap k t) n
|
|||||||
|
|
||||||
data Demux = Demux
|
data Demux = Demux
|
||||||
{ dConns :: TVar (KeyedSet Client)
|
{ dConns :: TVar (KeyedSet Client)
|
||||||
|
, dSizes :: TVar (IntMap TermSize)
|
||||||
, dStash :: TVar Logic.St
|
, dStash :: TVar Logic.St
|
||||||
|
, dMinSize :: TVar TermSize
|
||||||
}
|
}
|
||||||
|
|
||||||
mkDemux :: STM Demux
|
mkDemux :: TermSize -> STM Demux
|
||||||
mkDemux = Demux <$> newTVar mempty <*> newTVar Logic.init
|
mkDemux ts = Demux <$>
|
||||||
|
newTVar mempty <*>
|
||||||
|
newTVar mempty <*>
|
||||||
|
newTVar Logic.init <*>
|
||||||
|
newTVar ts
|
||||||
|
|
||||||
addDemux :: Client -> Demux -> STM ()
|
addDemux :: Client -> Demux -> STM ()
|
||||||
addDemux conn Demux{..} = do
|
addDemux conn Demux{..} = do
|
||||||
@ -57,6 +66,8 @@ addDemux conn Demux{..} = do
|
|||||||
useDemux :: Demux -> Client
|
useDemux :: Demux -> Client
|
||||||
useDemux d = Client { give = dGive d, take = dTake d }
|
useDemux d = Client { give = dGive d, take = dTake d }
|
||||||
|
|
||||||
|
curDemuxSize :: Demux -> STM TermSize
|
||||||
|
curDemuxSize Demux{..} = readTVar dMinSize
|
||||||
|
|
||||||
-- Internal --------------------------------------------------------------------
|
-- Internal --------------------------------------------------------------------
|
||||||
|
|
||||||
@ -77,16 +88,45 @@ dGive Demux{..} evs = do
|
|||||||
If there are no attached clients, this will not return until one
|
If there are no attached clients, this will not return until one
|
||||||
is attached.
|
is attached.
|
||||||
-}
|
-}
|
||||||
dTake :: Demux -> STM (Maybe Belt)
|
dTake :: Demux -> STM (Maybe ClientTake)
|
||||||
dTake Demux{..} = do
|
dTake Demux{..} = do
|
||||||
conns <- readTVar dConns
|
conns <- readTVar dConns
|
||||||
waitForBelt conns >>= \case
|
waitForTake conns >>= \case
|
||||||
(_, Just b ) -> pure (Just b)
|
(_, Just (ClientTakeBelt b)) -> pure (Just (ClientTakeBelt b))
|
||||||
(k, Nothing) -> do writeTVar dConns (ksDelete k conns)
|
|
||||||
pure Nothing
|
(k, Just (ClientTakeSize s)) -> do
|
||||||
|
newSizeTree <- modifyAndReturnTVar dSizes (insertMap k s)
|
||||||
|
maybeUpdateTerminalSize newSizeTree
|
||||||
|
|
||||||
|
(k, Nothing) -> do
|
||||||
|
writeTVar dConns (ksDelete k conns)
|
||||||
|
newSizeTree <- modifyAndReturnTVar dSizes (deleteMap k)
|
||||||
|
maybeUpdateTerminalSize newSizeTree
|
||||||
|
|
||||||
where
|
where
|
||||||
waitForBelt :: KeyedSet Client -> STM (Int, Maybe Belt)
|
waitForTake :: KeyedSet Client -> STM (Int, Maybe ClientTake)
|
||||||
waitForBelt ks = asum
|
waitForTake ks = asum
|
||||||
$ fmap (\(k,c) -> (k,) <$> Term.take c)
|
$ fmap (\(k,c) -> (k,) <$> Term.take c)
|
||||||
$ mapToList
|
$ mapToList
|
||||||
$ _ksTable ks
|
$ _ksTable ks
|
||||||
|
|
||||||
|
maybeUpdateTerminalSize :: IntMap TermSize -> STM (Maybe ClientTake)
|
||||||
|
maybeUpdateTerminalSize newSizeTree = do
|
||||||
|
let termSize = foldr minTermSize (TermSize 1024 1024) newSizeTree
|
||||||
|
curSize <- readTVar dMinSize
|
||||||
|
if curSize == termSize
|
||||||
|
then pure Nothing
|
||||||
|
else do
|
||||||
|
writeTVar dMinSize termSize
|
||||||
|
pure $ Just (ClientTakeSize termSize)
|
||||||
|
|
||||||
|
modifyAndReturnTVar :: TVar a -> (a -> a) -> STM a
|
||||||
|
modifyAndReturnTVar var fun = do
|
||||||
|
pre <- readTVar var
|
||||||
|
let post = fun pre
|
||||||
|
writeTVar var post
|
||||||
|
pure post
|
||||||
|
|
||||||
|
minTermSize :: TermSize -> TermSize -> TermSize
|
||||||
|
minTermSize (TermSize wa ha) (TermSize wb hb) =
|
||||||
|
TermSize (min wa wb) (min ha hb)
|
||||||
|
@ -37,4 +37,6 @@ termSize = size <&> \case
|
|||||||
liveTermSize :: (TermSize -> IO ()) -> IO TermSize
|
liveTermSize :: (TermSize -> IO ()) -> IO TermSize
|
||||||
liveTermSize cb = do
|
liveTermSize cb = do
|
||||||
Sys.installHandler Sys.sigWINCH (Sys.Catch (termSize >>= cb)) Nothing
|
Sys.installHandler Sys.sigWINCH (Sys.Catch (termSize >>= cb)) Nothing
|
||||||
termSize
|
ts <- termSize
|
||||||
|
cb ts
|
||||||
|
pure ts
|
||||||
|
Loading…
Reference in New Issue
Block a user