2012-10-09 31 views

Respuesta

12

Si no desea que una dependencia de ncurses, aquí es un envoltorio de la ioctl() solicitud apropiada utilizando la FFI, basado en la respuesta aceptada de 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) 

Utiliza el hsc2hs preprocessor para descubrir las constantes y compensaciones correctas basadas en los encabezados C en lugar de codificarlos en duro. Creo que está empaquetado con GHC o la plataforma Haskell, así que es probable que ya lo tengas.

Si está utilizando Cabal, puede agregar TermSize.hs a su archivo .cabal y automáticamente sabrá cómo generarlo desde TermSize.hsc. De lo contrario, puede ejecutar hsc2hs TermSize.hsc manualmente para generar un archivo .hs que luego puede compilar con GHC.

+0

¡Es genial, necesito ver hsc2hs! – pat

+0

Muy bien, gracias –

9

usted podría utilizar hcurses. Una vez que haya inicializado la biblioteca, puede usar scrSize para obtener el número de filas y columnas en la pantalla.

Para utilizar System.Posix.IOCtl, hay que definir un tipo de datos para representar la solicitud TIOCGWINSZ, que rellena la siguiente estructura:

struct winsize { 
    unsigned short ws_row; 
    unsigned short ws_col; 
    unsigned short ws_xpixel; /* unused */ 
    unsigned short ws_ypixel; /* unused */ 
}; 

Tendrá que definir un tipo de datos Haskell para mantener esta información, y que sea una instancia de 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 
          } 

Ahora, es necesario crear un tipo de datos ficticios para representar su solicitud:

data TIOCGWINSZ = TIOCGWINSZ 

Finalmente, debe hacer que su solicitud escriba una instancia de IOControl y asociarlo con el tipo de datos Winsize.

instance IOControl TIOCGWINSZ Winsize where 
    ioctlReq _ = ?? 

Usted tendrá que reemplazar el ?? con la constante representada por TIOCGWINSZ en sus archivos de cabecera (0x5413 en mi sistema).

Ahora, está listo para emitir el ioctl. Este comando no se preocupa por los datos de entrada, por lo que desea utilizar el ioctl' formulario:

main = do { ws <- ioctl' 1 TIOCGWINSZ 
      ; putStrLn $ "My terminal is " ++ show (ws_col ws) ++ " columns wide" 
      } 

Tenga en cuenta que el 1 se refiere a la salida estándar.

¡Uf!

+1

¿No es esto un poco exagerado? ¿No hay nada más simple? –

+0

Nota: El '0' en la llamada' ioctl'' se refiere a STDIN, por lo que esto falla si se redirige STDIN. Suponiendo que el objetivo para obtener el ancho del terminal es dar formato a la salida, puede ser mejor consultar STDOUT en su lugar. – hammar

+0

Buen punto. He actualizado mi respuesta, pero tu respuesta es mucho más sólida. Desearía poder darte más de 1 voto favorable; ¡Su respuesta si está lleno de información útil! – pat

3

Ya que se necesita esto sólo en Unix, me gustaría recomendar:

resizeOutput <- readProcess "/usr/X11/bin/resize" [] ""

Y luego hacer un poco de bits de análisis sintáctico de la salida. Puede que no sea 100% portátil, pero creo que puedes proporcionar resize con argumentos (echa un vistazo a -u en particular), por lo que obtendrás resultados bastante consistentes.

Cuestiones relacionadas