{-# LANGUAGE CPP #-}
-- | This module provides a thin portability layer for handling user
-- interrupts.
--
-- The reason is that in the standard Haskell library, this
-- functionality is only available in operating system specific
-- modules, namely "System.Posix.Signals" (for POSIX systems,
-- including Linux) and "GHC.ConsoleHandler" (for Windows).
--
-- Note that despite this compatibility layer, there are some
-- operating system specific quirks:
--
-- * In Windows, console events (such as Control-C) can only be
-- received by an application running in a Windows console. Certain
-- environments that look like consoles do not support console events,
-- such as xterm and rxvt windows, and Cygwin shells with @CYGWIN=tty@
-- set.
--
-- * In Windows, setting a handler for any one signal automatically
-- overrides the handlers for all signals (effectively ignoring them).
-- Also, if the 'Default' or 'Ignore' handler is specified, it
-- applies to all signals. We do not currently provide a way to
-- specify handlers for multiple signals.
module Quipper.Utils.PortableSignals (
Signal(..),
Handler(Default,Ignore,Catch,CatchOnce),
installHandler,
with_handler
) where
#ifdef mingw32_HOST_OS
import qualified GHC.ConsoleHandler as OS
#else
import qualified System.Posix.Signals as OS
#endif
-- ----------------------------------------------------------------------
-- * Common interface
-- | A data type for signals. This can be extended as needed.
data Signal =
Interrupt -- ^ Control-C event.
| Close -- ^ TERM signal (POSIX) or Close event (Windows).
-- | A data type for handlers.
data Handler =
Default -- ^ Default action.
| Ignore -- ^ Ignore the signal.
| Catch (IO ()) -- ^ Handle the signal in a new thread when the signal is received.
| CatchOnce (IO ()) -- ^ Like 'Catch', but only handle the first such signal.
| OSHandler OS.Handler -- ^ An operating system specific handler.
-- | Install a handler for the given signal. The old handler is
-- returned.
installHandler :: Signal -> Handler -> IO Handler
#ifdef mingw32_HOST_OS
installHandler = installHandler_windows
#else
installHandler = installHandler_posix
#endif
-- | Run a block of code with a given signal handler. The previous
-- handler is restored when the block terminates.
with_handler :: Signal -> Handler -> IO a -> IO a
with_handler signal handler body = do
oldhandler <- installHandler signal handler
a <- body
installHandler signal oldhandler
return a
-- ----------------------------------------------------------------------
-- * Windows specific code
#ifdef mingw32_HOST_OS
-- | Check if the Windows 'ConsoleEvent' matches the given abstract
-- 'Signal'. We implement this as a relation, rather than a function,
-- to allow for more than one 'ConsoleEvent' to match the same
-- 'Signal', or for more than one 'Signal' to match the same
-- 'ConsoleEvent'.
signal_matches :: OS.ConsoleEvent -> Signal -> Bool
signal_matches OS.ControlC Interrupt = True
signal_matches OS.Close Close = True
signal_matches _ _ = False
-- | Windows implementation of 'installHandler'.
installHandler_windows :: Signal -> Handler -> IO Handler
installHandler_windows signal handler = do
oldhandler <- OS.installHandler (oshandler handler)
return (OSHandler oldhandler)
where
oshandler Default = OS.Default
oshandler Ignore = OS.Ignore
oshandler (Catch body) = OS.Catch $ \event -> do
if signal_matches event signal
then body
else return ()
oshandler (CatchOnce body) = OS.Catch $ \event -> do
if signal_matches event signal
then do
-- uninstall the handler
OS.installHandler OS.Default
body
else return ()
oshandler (OSHandler h) = h
-- ----------------------------------------------------------------------
-- * POSIX specific code
#else
-- | Map an abstract 'Signal' to a POSIX specific 'OS.Signal'.
ossignal :: Signal -> OS.Signal
ossignal Interrupt = OS.keyboardSignal
ossignal Close = OS.softwareTermination
-- | Map a 'Handler' to a POSIX specific handler.
oshandler :: Handler -> OS.Handler
oshandler Default = OS.Default
oshandler Ignore = OS.Ignore
oshandler (Catch body) = OS.Catch body
oshandler (CatchOnce body) = OS.CatchOnce body
oshandler (OSHandler h) = h
-- | POSIX implementation of 'installHandler'.
installHandler_posix :: Signal -> Handler -> IO Handler
installHandler_posix signal handler = do
oldhandler <- OS.installHandler (ossignal signal) (oshandler handler) Nothing
return (OSHandler oldhandler)
#endif