summaryrefslogtreecommitdiff
path: root/libraries/base/GHC/ConsoleHandler.hs
blob: 8579c22739301ac7ade92451deed807317aabce1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE CPP #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  GHC.ConsoleHandler
-- Copyright   :  (c) The University of Glasgow
-- License     :  see libraries/base/LICENSE
--
-- Maintainer  :  cvs-ghc@haskell.org
-- Stability   :  internal
-- Portability :  non-portable (GHC extensions)
--
-- NB. the contents of this module are only available on Windows.
--
-- Installing Win32 console handlers.
--
-----------------------------------------------------------------------------

module GHC.ConsoleHandler
#if !defined(mingw32_HOST_OS)
        where

import GHC.Base ()  -- dummy dependency
#else /* whole file */
        ( Handler(..)
        , installHandler
        , ConsoleEvent(..)
        , flushConsole
        ) where

{-
#include "rts/Signals.h"

Note: this #include is inside a Haskell comment
      but it brings into scope some #defines
      that are used by CPP below (eg STG_SIG_DFL).
      Having it in a comment means that there's no
      danger that C-like crap will be misunderstood
      by GHC
-}

import GHC.Base
import Foreign
import Foreign.C
import GHC.IO.FD
import GHC.IO.Exception
import GHC.IO.Handle.Types
import GHC.IO.Handle.Internals
import GHC.Conc
import Control.Concurrent.MVar
import Data.Typeable

data Handler
 = Default
 | Ignore
 | Catch (ConsoleEvent -> IO ())

-- | Allows Windows console events to be caught and handled.  To
-- handle a console event, call 'installHandler' passing the
-- appropriate 'Handler' value.  When the event is received, if the
-- 'Handler' value is @Catch f@, then a new thread will be spawned by
-- the system to execute @f e@, where @e@ is the 'ConsoleEvent' that
-- was received.
--
-- Note that console events can only be received by an application
-- running in a Windows console.  Certain environments that look like consoles
-- do not support console events, these include:
--
--  * Cygwin shells with @CYGWIN=tty@ set (if you don't set @CYGWIN=tty@,
--    then a Cygwin shell behaves like a Windows console).
--  * Cygwin xterm and rxvt windows
--  * MSYS rxvt windows
--
-- In order for your application to receive console events, avoid running
-- it in one of these environments.
--
installHandler :: Handler -> IO Handler
installHandler handler
  | threaded =
    modifyMVar win32ConsoleHandler $ \old_h -> do
      (new_h,rc) <-
        case handler of
          Default -> do
            r <- rts_installHandler STG_SIG_DFL nullPtr
            return (no_handler, r)
          Ignore  -> do
            r <- rts_installHandler STG_SIG_IGN nullPtr
            return (no_handler, r)
          Catch h -> do
            r <- rts_installHandler STG_SIG_HAN nullPtr
            return (h, r)
      prev_handler <-
        case rc of
          STG_SIG_DFL -> return Default
          STG_SIG_IGN -> return Ignore
          STG_SIG_HAN -> return (Catch old_h)
          _           -> errorWithoutStackTrace "installHandler: Bad threaded rc value"
      return (new_h, prev_handler)

  | otherwise =
  alloca $ \ p_sp -> do
   rc <-
    case handler of
     Default -> rts_installHandler STG_SIG_DFL p_sp
     Ignore  -> rts_installHandler STG_SIG_IGN p_sp
     Catch h -> do
        v <- newStablePtr (toHandler h)
        poke p_sp v
        rts_installHandler STG_SIG_HAN p_sp
   case rc of
     STG_SIG_DFL -> return Default
     STG_SIG_IGN -> return Ignore
     STG_SIG_HAN -> do
        osptr <- peek p_sp
        oldh  <- deRefStablePtr osptr
         -- stable pointer is no longer in use, free it.
        freeStablePtr osptr
        return (Catch (\ ev -> oldh (fromConsoleEvent ev)))
     _           -> errorWithoutStackTrace "installHandler: Bad non-threaded rc value"
  where
   fromConsoleEvent ev =
     case ev of
       ControlC -> 0 {- CTRL_C_EVENT-}
       Break    -> 1 {- CTRL_BREAK_EVENT-}
       Close    -> 2 {- CTRL_CLOSE_EVENT-}
       Logoff   -> 5 {- CTRL_LOGOFF_EVENT-}
       Shutdown -> 6 {- CTRL_SHUTDOWN_EVENT-}

   toHandler hdlr ev = do
      case toWin32ConsoleEvent ev of
         -- see rts/win32/ConsoleHandler.c for comments as to why
         -- rts_ConsoleHandlerDone is called here.
        Just x  -> hdlr x >> rts_ConsoleHandlerDone ev
        Nothing -> return () -- silently ignore..

   no_handler = errorWithoutStackTrace "win32ConsoleHandler"

foreign import ccall unsafe "rtsSupportsBoundThreads" threaded :: Bool

foreign import ccall unsafe "RtsExternal.h rts_InstallConsoleEvent"
  rts_installHandler :: CInt -> Ptr (StablePtr (CInt -> IO ())) -> IO CInt
foreign import ccall unsafe "RtsExternal.h rts_ConsoleHandlerDone"
  rts_ConsoleHandlerDone :: CInt -> IO ()


flushConsole :: Handle -> IO ()
flushConsole h =
  wantReadableHandle_ "flushConsole" h $ \ Handle__{haDevice=dev} ->
    case cast dev of
      Nothing -> ioException $
                    IOError (Just h) IllegalOperation "flushConsole"
                        "handle is not a file descriptor" Nothing Nothing
      Just fd -> do
        throwErrnoIfMinus1Retry_ "flushConsole" $
           flush_console_fd (fdFD fd)

foreign import ccall unsafe "consUtils.h flush_input_console__"
        flush_console_fd :: CInt -> IO CInt

#endif /* mingw32_HOST_OS */