reflex-vty-1.3.0.0: internal/Reflex/Vty/Host/Signal.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Module: Reflex.Vty.Host.Signal
-- Description: Cross-platform OS signal handling for reflex-vty.
module Reflex.Vty.Host.Signal
( AppSignal (..)
, installAppSignalHandlers
) where
import Control.Exception (SomeException, mask, throwIO, try)
import Control.Monad (forM_, void)
import Data.IORef (IORef, modifyIORef', newIORef, readIORef)
import Foreign.C.Types (CInt)
#if defined(mingw32_HOST_OS)
import qualified GHC.ConsoleHandler as Console
#else
import System.Posix.Signals (Handler (Catch), installHandler, sigHUP, sigINT, sigTERM)
#endif
-- | An OS-level signal delivered to a running application.
--
-- * 'AppSignal_Interrupt': POSIX @SIGINT@ or Windows @ControlC@
-- * 'AppSignal_Terminate': POSIX @SIGTERM@ or Windows @Shutdown@
-- * 'AppSignal_Hangup': POSIX @SIGHUP@ or Windows @Close@ or Windows @Logoff@
--
-- All other signals go through the 'AppSignal_Platform' constructor.
data AppSignal
= AppSignal_Interrupt
| AppSignal_Terminate
| AppSignal_Hangup
| AppSignal_Platform CInt
deriving (Eq, Show)
-- | Install handlers for OS signals. Returns an action that restores
-- the previous handlers. Acquisition is masked. If any registration
-- fails, every handler installed so far is restored before the
-- exception propagates.
installAppSignalHandlers :: (AppSignal -> IO ()) -> IO (IO ())
#if defined(mingw32_HOST_OS)
-- via 'GHC.ConsoleHandler'
installAppSignalHandlers fire =
mask $ \_ -> do
r <- try (Console.installHandler (Console.Catch (fire . toAppSignal)))
case r of
Left (e :: SomeException) -> throwIO e
Right previous -> pure (void (Console.installHandler previous))
where
toAppSignal :: Console.ConsoleEvent -> AppSignal
toAppSignal ev = case ev of
Console.ControlC -> AppSignal_Interrupt -- Ctrl-C: same intent as SIGINT.
Console.Break -> AppSignal_Interrupt -- Ctrl-Break: closest to an interrupt.
Console.Close -> AppSignal_Hangup -- Console window closed: the terminal is going away.
Console.Logoff -> AppSignal_Hangup -- User logging off: the session is going away.
Console.Shutdown -> AppSignal_Terminate -- System shutting down: clean termination.
#else
installAppSignalHandlers fire =
mask $ \_ -> do
installed <- newIORef ([] :: [(Handler, Handler -> IO ())])
r <-
try $
forM_ signals $ \(sig, appSignal) -> do
previous <- installHandler sig (Catch (fire appSignal)) Nothing
modifyIORef' installed ((previous, \h -> void (installHandler sig h Nothing)) :)
case r of
Left (e :: SomeException) -> do
rollback installed
throwIO e
Right () -> pure (rollback installed)
where
rollback :: IORef [(Handler, Handler -> IO ())] -> IO ()
rollback installed =
readIORef installed >>= \ps -> forM_ ps (\(previous, restoreOne) -> restoreOne previous)
signals =
[ (sigINT, AppSignal_Interrupt)
, (sigTERM, AppSignal_Terminate)
, (sigHUP, AppSignal_Hangup)
]
#endif