htalkat-0.1.1: DumbClient.hs
-- This file is part of htalkat
-- Copyright (C) 2021 Martin Bays <mbays@sdf.org>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of version 3 of the GNU General Public License as
-- published by the Free Software Foundation, or any later version.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see http://www.gnu.org/licenses/.
module DumbClient (dumbClient) where
import Control.Concurrent (forkIO)
import Control.Exception (bracket)
import Control.Monad (foldM_)
import System.IO
import System.IO.Unsafe (unsafeInterleaveIO)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text.Encoding.Error as T
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.Encoding as T
import qualified Network.Socket as S
import qualified Network.Socket.ByteString as SS
import qualified Network.Socket.ByteString.Lazy as SL
import WCWidth
connectingNamedSocket :: FilePath -> (S.Socket -> IO a) -> IO a
connectingNamedSocket path =
(`bracket` S.close) $ do
sock <- S.socket S.AF_UNIX S.Stream 0
S.connect sock $ S.SockAddrUnix path
pure sock
getContentsBS :: IO [BS.ByteString]
getContentsBS = unsafeInterleaveIO $ do
s <- BS.hGetSome stdin 256
if BS.null s then pure []
else (s :) <$> getContentsBS
dumbClient :: FilePath -> IO ()
dumbClient path = connectingNamedSocket path $ \sock -> do
_ <- forkIO $ do
printWithErasures . T.decodeUtf8With T.lenientDecode . BL.drop 1 =<<
SL.getContents sock
putStrLn "\n[Connection closed; ^C to quit]"
hSetBuffering stdin NoBuffering
hSetEcho stdin False
hSetBuffering stdout NoBuffering
mapM_ (SS.sendAll sock) =<< getContentsBS
printWithErasures :: T.Text -> IO ()
printWithErasures = mapM_ (doLine . T.unpack) . T.lines where
doLine :: String -> IO ()
doLine = foldM_ go []
go [] '\b' = pure []
go (h:t) '\b' = erase (charWidth h) >> pure t
go s '\NAK' = (erase . sum $ charWidth <$> s) >> pure []
go s c = putChar c >> pure (c:s)
-- This doesn't correctly erase wide characters which are wrapped onto the
-- next line earlier than a normal char would have been.
-- I don't see how to deal with that dumbly.
erase n = putStr . concat $ replicate n "\b \b"
charWidth = max 0 . wcwidth