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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With