Получить ширину терминала Haskell

Как получить ширину терминала в Haskell?

Вещи, которые я пробовал

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

Это только должно работать unix.

Спасибо


person Bilal Syed Hussain    schedule 09.10.2012    source источник


Ответы (3)


Если вам не нужна зависимость от ncurses, вот оболочка соответствующего запроса ioctl() с использованием FFI на основе принятого ответа Getting ширина терминала в 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 препроцессор для определения правильного константы и смещения на основе заголовков C, а не их жесткое кодирование. Я думаю, что он поставляется либо с GHC, либо с платформой Haskell, так что, скорее всего, он у вас уже есть.

Если вы используете Cabal, вы можете добавить TermSize.hs в свой файл .cabal, и он автоматически узнает, как сгенерировать его из TermSize.hsc. В противном случае вы можете запустить hsc2hs TermSize.hsc вручную, чтобы сгенерировать файл .hs, который затем можно скомпилировать с помощью GHC.

person hammar    schedule 09.10.2012

Вы можете использовать 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 _ = ??

Вам нужно будет заменить ?? на константу, представленную TIOCGWINSZ в ваших заголовочных файлах (0x5413 в моей системе).

Теперь вы готовы выпустить ioctl. Эта команда не заботится о входных данных, поэтому вы хотите использовать форму ioctl':

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

Обратите внимание, что 1 относится к STDOUT.

Фу!

person pat    schedule 09.10.2012
comment
Разве это не перебор? Нет ничего проще? - person Joachim Breitner; 09.10.2012
comment
Примечание. 0 в вызове ioctl' относится к STDIN, поэтому это не удается, если STDIN перенаправляется. Предполагая, что целью получения ширины терминала является форматирование вывода, вместо этого может быть лучше запросить STDOUT. - person hammar; 10.10.2012
comment
Хорошая точка зрения. Я обновил свой ответ, но ваш ответ гораздо надежнее. Хотел бы я дать вам более 1 голоса; ваш ответ, если он полон полезной информации! - person pat; 10.10.2012

Поскольку вам это нужно только в Unix, я бы рекомендовал:

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

А затем сделать небольшой разбор вывода. Это может быть не на 100% переносимым, но я верю, что вы можете предоставить resize аргументы (в частности, проверьте -u), так что вы получите довольно последовательный вывод.

person alias    schedule 10.10.2012
comment
команда /usr/X11/bin/resize даже не существует в арке ^^ - person Xerus; 20.10.2020