How to get the width of the terminal in Haskell?
Things I tried
System.Posix.IOCtl (could not figure out how to get it to work)
This only has to work unix.
Thanks
If you don't want a dependency on ncurses, here's a wrapper of the appropriate ioctl()
request using the FFI, based on the accepted answer of Getting terminal width in C?
TermSize.hsc
{-# LANGUAGE ForeignFunctionInterface #-}
module TermSize (getTermSize) where
import Foreign
import Foreign.C.Error
import Foreign.C.Types
#include <sys/ioctl.h>
#include <unistd.h>
-- Trick for calculating alignment of a type, taken from
-- http://www.haskell.org/haskellwiki/FFICookBook#Working_with_structs
#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
-- The ws_xpixel and ws_ypixel fields are unused, so I've omitted them here.
data WinSize = WinSize { wsRow, wsCol :: CUShort }
instance Storable WinSize where
sizeOf _ = (#size struct winsize)
alignment _ = (#alignment struct winsize)
peek ptr = do
row <- (#peek struct winsize, ws_row) ptr
col <- (#peek struct winsize, ws_col) ptr
return $ WinSize row col
poke ptr (WinSize row col) = do
(#poke struct winsize, ws_row) ptr row
(#poke struct winsize, ws_col) ptr col
foreign import ccall "sys/ioctl.h ioctl"
ioctl :: CInt -> CInt -> Ptr WinSize -> IO CInt
-- | Return current number of (rows, columns) of the terminal.
getTermSize :: IO (Int, Int)
getTermSize =
with (WinSize 0 0) $ \ws -> do
throwErrnoIfMinus1 "ioctl" $
ioctl (#const STDOUT_FILENO) (#const TIOCGWINSZ) ws
WinSize row col <- peek ws
return (fromIntegral row, fromIntegral col)
This uses the hsc2hs
preprocessor to figure out the correct constants and offsets based on the C headers rather than hardcoding them. I think it's packaged with either GHC or the Haskell Platform, so chances are you'll have it already.
If you're using Cabal, you can add TermSize.hs
to your .cabal
file and it'll automatically know how to generate it from TermSize.hsc
. Otherwise, you can run hsc2hs TermSize.hsc
manually to generate a .hs
file which you can then compile with GHC.
You could use hcurses. Once you've initialized the library, you can use scrSize
to get the number of rows and columns on the screen.
To use System.Posix.IOCtl
, you have to define a data type to represent the TIOCGWINSZ
request, which fills in the following structure:
struct winsize {
unsigned short ws_row;
unsigned short ws_col;
unsigned short ws_xpixel; /* unused */
unsigned short ws_ypixel; /* unused */
};
You'll need to define a Haskell data type to hold this information, and make it an instance of Storable
:
{-# LANGUAGE RecordWildCards #-}
import Foreign.Storable
import Foreign.Ptr
import Foreign.C
data Winsize = Winsize { ws_row :: CUShort
, ws_col :: CUShort
, ws_xpixel :: CUShort
, ws_ypixel :: CUShort
}
instance Storable Winsize where
sizeOf _ = 8
alignment _ = 2
peek p = do { ws_row <- peekByteOff p 0
; ws_col <- peekByteOff p 2
; ws_xpixel <- peekByteOff p 4
; ws_ypixel <- peekByteOff p 6
; return $ Winsize {..}
}
poke p Winsize {..} = do { pokeByteOff p 0 ws_row
; pokeByteOff p 2 ws_col
; pokeByteOff p 4 ws_xpixel
; pokeByteOff p 6 ws_ypixel
}
Now, you need to create a dummy data type to represent your request:
data TIOCGWINSZ = TIOCGWINSZ
Finally, you need to make your request type an instance of IOControl
, and associate it with the Winsize
data type.
instance IOControl TIOCGWINSZ Winsize where
ioctlReq _ = ??
You will need to replace the ??
with the constant represented by TIOCGWINSZ
in your header files (0x5413
on my system).
Now, you are ready to issue the ioctl
. This command does not care about the input data, so you want to use the ioctl'
form:
main = do { ws <- ioctl' 1 TIOCGWINSZ
; putStrLn $ "My terminal is " ++ show (ws_col ws) ++ " columns wide"
}
Note that the 1 refers to STDOUT.
Phew!
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