2012-10-09 21 views
7

Haskellで端末の幅を取得する方法は?端末の幅を取得するHaskell

物事は私はこれが唯一のUNIXを動作するように持っている

System.Posix.IOCtl (could not figure out how to get it to work) 

を試してみました。

おかげ

答えて

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 Platformのどちらかと一緒にパッケージ化されていると思うので、あなたはすでにそれを持っている可能性があります。

Cabalを使用している場合は、.cabalファイルにTermSize.hsを追加して、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 
          } 

は今、あなたはあなたの要求を表現するためのダミーデータ型を作成する必要があります。

最後に、要求タイプをIOControlと入力し、Winsizeデータタイプに関連付ける必要があります。

instance IOControl TIOCGWINSZ Winsize where 
    ioctlReq _ = ?? 

あなたのヘッダファイルでTIOCGWINSZ(私のシステム上の0x5413)で表される定数で??を交換する必要があります。

これでioctlを発行する準備が整いました。 1は、STDOUTを参照していること

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

注:あなたがioctl'フォームを使用したいので、このコマンドは、入力されたデータを気にしません。

Phew!

+1

これは少し過剰ですか?何も簡単ですか? –

+0

注: 'ioctl''呼び出しの' 0'はSTDINを参照しています。したがって、STDINがリダイレクトされた場合は失敗します。端末の幅を取得するという目標が出力の書式設定であると仮定すると、代わりにSTDOUTを照会するほうがよい場合があります。 – hammar

+0

良い点。私は私の答えを更新しましたが、あなたの答えははるかに堅牢です。私はあなたに1つ以上の議決権を与えることができたと思います。役に立つ情報が満載のあなたの答え! – pat

3

あなただけのUnix上でこれを必要とするので、私はお勧め:

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

そして出力の構文解析の小さなビットをしています。これは100%移植可能ではないかもしれませんが、私はあなたがresizeに引数を与えて(特に-uをチェックしてください)、かなり安定した出力を得ることができると信じています。

関連する問題