2012-10-09 19 views

Odpowiedz

12

Jeśli nie chcesz zależność ncurses, oto owinięcie z odpowiednim ioctl() żądanie przy użyciu FFI, na podstawie zaakceptowanej odpowiedzi z 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) 

Wykorzystuje to hsc2hs preprocessor do obliczenia poprawnych stałych i przesunięć w oparciu o nagłówki C zamiast ich kodowania na sztywno. Myślę, że jest on zapakowany albo z GHC, albo z platformy Haskell, więc prawdopodobnie już go masz.

Jeśli używasz Cabal można dodać TermSize.hs do pliku .cabal i będzie ona automatycznie wie jak wygenerować go z TermSize.hsc. W przeciwnym razie można ręcznie uruchomić hsc2hs TermSize.hsc, aby wygenerować plik .hs, który następnie można skompilować za pomocą GHC.

+0

To jest fajne, muszę spojrzeć na hsc2hs! – pat

+0

Bardzo ładne, dzięki –

9

można użyć hcurses. Po zainicjowaniu biblioteki można użyć numeru scrSize, aby uzyskać liczbę wierszy i kolumn na ekranie.

Aby korzystać System.Posix.IOCtl, trzeba określić typ danych do reprezentowania wniosek TIOCGWINSZ, która wypełnia w następującej strukturze:

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

Musisz określić typ danych Haskell trzymać tę informację, i sprawiają, że wystąpienie 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 
          } 

teraz trzeba stworzyć fikcyjny typ danych do reprezentowania żądanie:

data TIOCGWINSZ = TIOCGWINSZ 

Wreszcie, należy wprowadzić żądanie typu instancji o numerze IOControl i powiązać je z typem danych Winsize.

instance IOControl TIOCGWINSZ Winsize where 
    ioctlReq _ = ?? 

Trzeba będzie wymienić ?? ze stałą reprezentowanego przez TIOCGWINSZ w plikach nagłówkowych (0x5413 w moim systemie).

Teraz jesteś gotowy do wydania ioctl. Komenda ta nie dba o danych wejściowych, więc chcesz skorzystać z formularza ioctl':

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

Należy pamiętać, że 1 odnosi się do STDOUT.

Uff!

+1

Czy to trochę przesada? Czy nie ma nic prostszego? –

+0

Uwaga: '0' w wywołaniu' ioctl'' odnosi się do STDIN, więc to się nie powiedzie, jeśli STDIN jest przekierowany. Zakładając, że celem uzyskania szerokości terminala jest formatowanie wyjścia, lepiej zamiast tego zapytać STDOUT. – hammar

+0

Dobra uwaga. Zaktualizowałem swoją odpowiedź, ale twoja odpowiedź jest znacznie bardziej niezawodna. Chciałbym móc dać więcej niż 1 w górę; twoja odpowiedź, jeśli zakorkowana pełna przydatnych informacji! – pat

3

Ponieważ trzeba to tylko na Uniksie, polecam:

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

i wtedy robi się trochę-bitowej parsowanie wyjścia. To może nie być w 100% przenośne, ale uważam, że możesz podać resize z argumentami (szczególnie sprawdź w szczególności -u), aby uzyskać dość spójne dane wyjściowe.

Powiązane problemy