Skip to content

Commit 6fd9fc0

Browse files
bfrksupki
authored andcommitted
Windows: depend on WIN32 package, implement and export hSize
WIN32 supports getConsoleScreenBufferInfo since version 2.6.2.0 and withHandleToHANDLE that supports console handles since 2.13.2.0. The fallback for Cygwin or MSYS shells now passes the given handle to the std_in of the "stty size" command. Since size is now implemented as `hSize stdout`, it works correctly even if stdin is redirected.
1 parent bb51a6a commit 6fd9fc0

File tree

5 files changed

+118
-61
lines changed

5 files changed

+118
-61
lines changed

README.markdown

+41
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,44 @@ Usage
1414
>>> size
1515
Just (Window {height = 60, width = 112})
1616
```
17+
18+
Test
19+
----
20+
21+
Compile test.hs and run it in a terminal. Here is what I get on Linux:
22+
23+
```
24+
> ghc test.hs
25+
> ./test
26+
With redirected stdin
27+
hSize stdin = Nothing
28+
hSize stdout = Just (Window {height = 19, width = 87})
29+
hSize stderr = Just (Window {height = 19, width = 87})
30+
With redirected stdout
31+
hSize stdin = Just (Window {height = 19, width = 87})
32+
hSize stdout = Nothing
33+
hSize stderr = Just (Window {height = 19, width = 87})
34+
With redirected stderr
35+
hSize stdin = Just (Window {height = 19, width = 87})
36+
hSize stdout = Just (Window {height = 19, width = 87})
37+
hSize stderr = Nothing
38+
```
39+
40+
On MINGW/MSYS the output is the same.
41+
42+
On Windows with cmd.exe I get
43+
44+
```
45+
With redirected stdin
46+
hSize stdin = Nothing
47+
hSize stdout = Just (Window {height = 40, width = 164})
48+
hSize stderr = Just (Window {height = 40, width = 164})
49+
With redirected stdout
50+
hSize stdin = Nothing
51+
hSize stdout = Nothing
52+
hSize stderr = Just (Window {height = 40, width = 164})
53+
With redirected stderr
54+
hSize stdin = Nothing
55+
hSize stdout = Just (Window {height = 40, width = 164})
56+
hSize stderr = Nothing
57+
```

src/System/Console/Terminal/Size.hs

+8-5
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ module System.Console.Terminal.Size
99
, size
1010
#if !defined(mingw32_HOST_OS)
1111
, fdSize
12-
, hSize
1312
#endif
13+
, hSize
1414
) where
1515

1616
import System.Console.Terminal.Common
@@ -19,8 +19,8 @@ import qualified System.Console.Terminal.Windows as Host
1919
#else
2020
import qualified System.Console.Terminal.Posix as Host
2121
import System.Posix.Types(Fd)
22-
import System.IO(Handle)
2322
#endif
23+
import System.IO(Handle)
2424

2525

2626
-- | Get terminal window width and height for @stdout@.
@@ -45,14 +45,17 @@ size = Host.size
4545
-- Nothing
4646
fdSize :: Integral n => Fd -> IO (Maybe (Window n))
4747
fdSize = Host.fdSize
48+
#endif
4849

49-
-- | /Not available on Windows:/
50-
-- Same as 'fdSize', but takes 'Handle' instead of 'Fd' (file descriptor).
50+
-- | Same as 'fdSize', but takes 'Handle' instead of 'Fd' (file descriptor).
51+
--
52+
-- Note that on Windows with shells that use the native console API (cmd.exe,
53+
-- PowerShell) this works only for output handles like 'stdout' and 'stderr';
54+
-- for input handles like 'stdin' it always returns 'Nothing'.
5155
--
5256
-- >>> import System.Console.Terminal.Size
5357
-- >>> import System.IO
5458
-- >>> hSize stdout
5559
-- Just (Window {height = 56, width = 85})
5660
hSize :: Integral n => Handle -> IO (Maybe (Window n))
5761
hSize = Host.hSize
58-
#endif
+40-55
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,48 @@
1-
2-
module System.Console.Terminal.Windows(size) where
1+
module System.Console.Terminal.Windows(size, hSize) where
32

43
import System.Console.Terminal.Common
54

6-
import Control.Monad
7-
import Data.Word
8-
import Foreign.Ptr
9-
import Foreign.Storable
10-
import Foreign.Marshal.Alloc
115
import System.Exit
126
import System.IO
7+
import System.IO.Error (catchIOError)
138
import System.Process
14-
15-
type HANDLE = Ptr ()
16-
17-
data CONSOLE_SCREEN_BUFFER_INFO
18-
19-
sizeCONSOLE_SCREEN_BUFFER_INFO :: Int
20-
sizeCONSOLE_SCREEN_BUFFER_INFO = 22
21-
22-
posCONSOLE_SCREEN_BUFFER_INFO_srWindow :: Int
23-
posCONSOLE_SCREEN_BUFFER_INFO_srWindow = 10 -- 4 x Word16 Left,Top,Right,Bottom
24-
25-
c_STD_OUTPUT_HANDLE :: Word32
26-
c_STD_OUTPUT_HANDLE = -11
27-
28-
foreign import stdcall unsafe "windows.h GetConsoleScreenBufferInfo"
29-
c_GetConsoleScreenBufferInfo :: HANDLE -> Ptr CONSOLE_SCREEN_BUFFER_INFO -> IO Bool
30-
31-
foreign import stdcall unsafe "windows.h GetStdHandle"
32-
c_GetStdHandle :: Word32 -> IO HANDLE
33-
9+
import System.Win32.Console
10+
( CONSOLE_SCREEN_BUFFER_INFO(srWindow)
11+
, SMALL_RECT(..)
12+
, getConsoleScreenBufferInfo
13+
)
14+
import System.Win32.Types (HANDLE, withHandleToHANDLE)
3415

3516
size :: Integral n => IO (Maybe (Window n))
36-
size = do
37-
hdl <- c_GetStdHandle c_STD_OUTPUT_HANDLE
38-
allocaBytes sizeCONSOLE_SCREEN_BUFFER_INFO $ \p -> do
39-
b <- c_GetConsoleScreenBufferInfo hdl p
40-
if not b
41-
then do -- This could happen on Cygwin or MSYS
42-
let stty = (shell "stty size") {
43-
std_in = UseHandle stdin
44-
, std_out = CreatePipe
45-
, std_err = CreatePipe
46-
}
47-
(_, mbStdout, _, rStty) <- createProcess stty
48-
exStty <- waitForProcess rStty
49-
case exStty of
50-
ExitFailure _ -> return Nothing
51-
ExitSuccess ->
52-
maybe (return Nothing)
53-
(\hSize -> do
54-
sizeStr <- hGetContents hSize
55-
let [r, c] = map read $ words sizeStr :: [Int]
56-
return $ Just $ Window (fromIntegral r) (fromIntegral c)
57-
)
58-
mbStdout
59-
else do
60-
[left,top,right,bottom] <- forM [0..3] $ \i -> do
61-
v <- peekByteOff p ((i*2) + posCONSOLE_SCREEN_BUFFER_INFO_srWindow)
62-
return $ fromIntegral (v :: Word16)
63-
return $ Just $ Window (1+bottom-top) (1+right-left)
17+
size = hSize stdout
18+
19+
hSize :: Integral n => Handle -> IO (Maybe (Window n))
20+
hSize hdl =
21+
withHandleToHANDLE hdl nativeSize
22+
`catchIOError` \_ -> do
23+
-- This could happen on Cygwin or MSYS
24+
let stty = (shell "stty size") {
25+
std_in = UseHandle hdl
26+
, std_out = CreatePipe
27+
, std_err = CreatePipe
28+
}
29+
(_, mbStdout, _, rStty) <- createProcess stty
30+
exStty <- waitForProcess rStty
31+
case exStty of
32+
ExitFailure _ -> return Nothing
33+
ExitSuccess ->
34+
maybe (return Nothing)
35+
(\out -> do
36+
sizeStr <- hGetContents out
37+
let [r, c] = map read $ words sizeStr :: [Int]
38+
return $ Just $ Window (fromIntegral r) (fromIntegral c)
39+
)
40+
mbStdout
41+
42+
nativeSize :: Integral n => HANDLE -> IO (Maybe (Window n))
43+
nativeSize hdl = do
44+
rect <- srWindow <$> getConsoleScreenBufferInfo hdl
45+
return $ Just $ Window
46+
{ height = fromIntegral (1 + bottomPos rect - topPos rect)
47+
, width = fromIntegral (1 + rightPos rect - leftPos rect)
48+
}

terminal-size.cabal

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ library
3434
ghc-prim
3535
if os(windows)
3636
build-depends:
37-
process
37+
process,
38+
Win32 >= 2.13.2.0 && < 2.14
3839

3940
build-tools:
4041
hsc2hs

test.hs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import System.Console.Terminal.Size
2+
import System.IO
3+
import GHC.IO.Handle
4+
import Control.Monad
5+
import Data.Maybe
6+
7+
stdHandles =
8+
[ (stdin, "stdin")
9+
, (stdout, "stdout")
10+
, (stderr, "stderr")
11+
]
12+
13+
main = do
14+
fh <- openFile "test.hs" ReadMode
15+
forM_ stdHandles $ \(h, n) -> do
16+
putStrLn $ "With redirected " ++ n
17+
-- save handle
18+
h_saved <- hDuplicate h
19+
-- redirect to a file handle
20+
hDuplicateTo fh h
21+
-- run hSize on all three std handles
22+
hSizes <- forM stdHandles (hSize . fst)
23+
-- restore redirected handle
24+
hDuplicateTo h_saved h
25+
-- report sizes
26+
forM_ (zip hSizes stdHandles) $ \(s, (h', n')) -> do
27+
putStrLn $ " hSize " ++ n' ++ " = " ++ show s

0 commit comments

Comments
 (0)