public
Description: Interface to ncurses using Ruby FFI (Foreign Function Interface)
Homepage:
Clone URL: git://github.com/seanohalpin/ffi-ncurses.git
README.rdoc

ffi-ncurses

Author: Sean O’Halpin

A wrapper for ncurses 5.x using ruby-ffi (>= 0.2.0). Tested on Mac OS X 10.4 (Tiger) and Ubuntu 8.04 with ruby 1.8.6.

The API is very much a transliteration of the C API rather than an attempt to provide an idiomatic Ruby object-oriented API. The intent is to provide a ‘close to the metal’ wrapper around the ncurses library upon which you can build your own abstractions.

This is still very much a work-in-progress, so expect some rough edges. Having said that, you can do quite a lot with it as it is. The main things left to be done are tests, access to global variables and various macros.

Below are some very preliminary notes on usage. See the examples directory for real working examples.

Usage

Load the library with:

  require 'ffi-ncurses'

NCurses methods can be called as module methods:

  begin
    stdscr = NCurses.initscr
    NCurses.clear
    NCurses.addstr("Hello world!")
    NCurses.refresh
    NCurses.getch
  ensure
    NCurses.endwin
  end

or as included methods:

  include NCurses
  begin
    stdscr = initscr
    start_color
    curs_set 0
    raw
    cbreak
    noecho
    clear
    move 10, 10
    standout
    addstr("Hi!")
    standend
  ensure
    endwin
  end

Set up screen

  require 'ffi-ncurses'

  NCurses.initscr
  begin
    ...
  ensure
    NCurses.endwin
  end

Typical initialization

  stdscr = NCurses.initscr
  NCurses.start_color
  NCurses.curs_set 0
  NCurses.raw
  NCurses.cbreak
  NCurses.noecho
  NCurses.keypad(stdscr, true)

Colours

  start_color
  init_pair(1, NCurses::Colour::BLACK, NCurses::Colour::RED)
  attr_set NCurses::A_NORMAL, 1, nil
  addch(?A)
  addch(?Z | COLOR_PAIR(1))

Cursor

Turn cursor off

  NCurses.curs_set 0

Turn cursor on

  NCurses.curs_set 1

Windows

  require 'ffi-ncurses'
  begin
    win = newwin(6, 12, 15, 15)
    box(win, 0, 0)
    inner_win = newwin(4, 10, 16, 16)
    waddstr(inner_win, (["Hello window!"] * 5).join(' '))
    wrefresh(win)
    wrefresh(inner_win)
    ch = wgetch(inner_win)

  rescue Object => e
    NCurses.endwin
    puts e
  ensure
    NCurses.endwin
  end

Mouse handling

The ncurses mouse API is defined in a separate file. To include it use:

  require 'ffi-ncurses/mouse'

You need to specify that you want keypad translation with:

  keypad stdscr, NCurses::TRUE

otherwise your program will receive the raw mouse escape codes, instead of KEY_MOUSE mouse event codes.

Specify which events you want to handle with:

    mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, nil)

and set up a mouse event structure to receive the returned values:

    mouse_event = NCurses::MEVENT.new

Receiving mouse events is a two-stage process: first, you are notified that a mouse event has taken place through a special key code, then you retrieve the event using getmouse. For example:

      ch = getch
      case ch
      when NCurses::KEY_MOUSE
        if getmouse(mouse_event) == NCurses::OK

The mouse event contains the button state (bstate) and x, y coordinates. You can test for the button state using:

  if mouse_event[:bstate] & NCurses::BUTTON1_PRESSED

or

  if NCurses.BUTTON_PRESS(mouse_event[:bstate], 1)

The possible button states are: PRESS, RELEASE, CLICK, DOUBLE_CLICK and TRIPLE_CLICK.

Experimental stuff

Specifying which curses library to use

You can specify which variant of curses you want to use by setting the environment variable RUBY_FFI_NCURSES_LIB to the one you want. For example, to use PDCurses X11 curses lib, use:

  RUBY_FFI_NCURSES_LIB=XCurses ruby examples/hello.rb

You can use this to specify ncursesw for example. Please note that only the bog standard ncurses lib has been in any way tested as of yet.

TO DO

Turn into a gem

Time to publish as a gem. Need to move files around and make sure it works as a gem.

Complete translation of core functions to Darwin (Mac OS X)

There are many macros in darwin ncurses.h which I haven’t implemented yet.

Work out how to get hold of curscr global variable

There’s no way (so far) to access (per process) global variables in DLLs (hence the hack to stash stdscr).

Tests

This is tricky - I’m not sure exactly how to properly test a wrapper for a library like ncurses. I certainly don’t want to test ncurses! Instead, I want to ensure my wrapper faithfully reproduces the functionality of the platform’s ncurses lib. To that end, I’m experimenting with a simple DSL to generate both C and Ruby versions of a test. With that I can generate equivalent programs and compare the output. However, this is not really ready for prime time yet.

Tidy up internals and examples

Things got a bit messy as I switched between the Linux and Mac versions. The examples should be more focussed.

Scope implementation of Menu and Form interface wrappers

I’m not particularly interested in the ncurses extension libraries for forms and menus. I would rather spend time implementing similar functionality on top of a portable text console library. However, in the interests of completeness, I suppose I ought to at least scope it out.