2012-10-09 68 views

回答

12

如果你不想上ncurses的依賴呢,下面是一個使用FFI適當ioctl()要求的包裝的基礎上,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) 

接受的答案這使用hsc2hs preprocessor根據C標頭找出正確的常量和偏移量,而不是對它們進行硬編碼。我認爲它與GHC或Haskell平臺打包在一起,所以你已經有機會了。

如果您使用的是Cabal,您可以將TermSize.hs添加到您的.cabal文件中,它會自動知道如何從TermSize.hsc生成它。否則,您可以手動運行hsc2hs TermSize.hsc以生成.hs文件,然後您可以使用GHC進行編譯。

+0

這很酷,我需要看看hsc2hs! – pat

+0

非常好,謝謝 –

9

你可以使用hcurses。初始化庫之後,可以使用scrSize來獲取屏幕上的行數和列數。

要使用System.Posix.IOCtl,你必須定義一個數據類型來表示TIOCGWINSZ要求,填補了以下結構:

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

你需要定義一個Haskell數據類型來保存這些信息,並使它的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 
          } 

現在,你需要創建一個虛擬的數據類型來表示您的要求:

data TIOCGWINSZ = TIOCGWINSZ 

最後,您需要將您的請求類型設置爲IOControl的實例,並將其與Winsize數據類型關聯。

instance IOControl TIOCGWINSZ Winsize where 
    ioctlReq _ = ?? 

您將需要更換??與在頭文件TIOCGWINSZ0x5413我的系統上)所代表的常數。

現在,您已準備好發出ioctl。該命令不關心輸入數據,所以要使用ioctl'形式:

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

注意,1是指標準輸出。

唷!

+1

這是不是有點矯枉過正?有沒有更簡單的? –

+0

注意:'ioctl''調用中的'0'是指STDIN,所以如果STDIN被重定向,則失敗。假設獲取終端寬度的目標是格式化輸出,則可以更好地查詢STDOUT。 – hammar

+0

好點。我已經更新了我的答案,但您的答案更加健壯。我希望我能給你一個以上的投票權;你的答案如果塞滿了有用的信息! – pat

3

因爲你需要這隻能在Unix上,我建議:

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

,然後做解析輸出的一點點位。這可能不是100%可移植的,但我確信你可以提供resize參數(特別是檢查-u),所以你會得到相當一致的輸出。