Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Restoring terminal settings on exit

Using the terminal package, my code basically looks like this:

main :: IO ()
main = withTerminal $ runTerminalT $ do
    eraseInDisplay EraseAll
    hideCursor
    setAutoWrap False

    forever $ do
        calculate
        updateScreen

If the user exits this program with Ctrl-C, the terminal settings (in this concrete case, the hidden cursor) remain. I am looking for a lightweight way to restore the terminal settings as they were before running the program. By lightweight, I mean something that I can just wrap around the whole runTerminalT shebang, instead of having to manually call checkInterrupt at various parts of my code etc.

like image 767
Cactus Avatar asked Feb 27 '21 06:02

Cactus


2 Answers

There's no need to mess around with POSIX signals to do something like Daniel Wagner's answer. Control.Exception has everything you need.

import Control.Exception (handleJust, AsyncException (UserInterrupt))
import Control.Monad (guard)
import System.Exit (exitWith, ExitCode (ExitFailure))

main = withTerminal $ \term ->
  handleJust
         (guard . (== UserInterrupt))
         (\ ~() -> do
           resetTerminalOrWhatever term
           exitWith (ExitFailure 1)) $
         ...

You may actually want something broader than that, since you probably want to reset the terminal on any abnormal termination:

main = withTerminal $ \term -> ... `onException` resetTerminalOrWhatever term

Note that this second version will be activated by a call to exitWith as well. If that's not desired, then you could catch/rethrow manually and refrain from resetting the terminal on ExitSuccess.


Note that withTerminal can actually operate within a more general context than just IO, but it always requires a MonadMask constraint, which should be sufficient for this general approach.

like image 162
dfeuer Avatar answered Sep 20 '22 17:09

dfeuer


I haven't tested to know for sure whether this works out right, but I guess you would just install a signal handler that does your cleanup, then exits just like the default one would.

import System.Exit
import System.Posix.Signals

main = do
    installHandler sigINT Nothing . Catch $ do
        resetTerminalOrWhatever
        exitWith (ExitFailure 1)
    withTerminal $ {- ... -}

The signals module is available from the unix package.

If you're paranoid, you could check what the old handler was and try to incorporate it into yours:

main = do
    mfix $ \oldHandler ->
        installHandler sigINT Nothing . CatchInfo $ \si -> case oldHandler of
            Default -> reset >> exit
            Ignore -> reset >> exit
            Catch act -> reset >> act
            CatchOnce act -> reset >> act >> exit
            CatchInfo f -> reset >> f si
            CatchInfoOnce f -> reset >> f si >> exit
    withTerminal $ {- ... -}

...or something like that.

like image 33
Daniel Wagner Avatar answered Sep 18 '22 17:09

Daniel Wagner