ffi-ncurses

Author: Sean O’Halpin

A wrapper for ncurses 5.x. Tested on Mac OS X 10.4 (Tiger) and Ubuntu 8.04 with ruby 1.8.6 using ruby-ffi (>= 0.2.0) and JRuby 1.1.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'

FFI::NCurses methods can be called as module methods:

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

or as included methods:

require 'ffi-ncurses'
include FFI::NCurses
begin
  stdscr = initscr
  start_color
  curs_set 0
  raw
  cbreak
  noecho
  clear
  move 10, 10
  standout
  addstr("Hi!")
  standend
  refresh
  getch
ensure
  endwin
end

Set up screen

require 'ffi-ncurses'

FFI::NCurses.initscr
begin	
  ...
ensure
  FFI::NCurses.endwin
end

Typical initialization

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

Colours

start_color
init_pair(1, FFI::NCurses::COLOR_BLACK, FFI::NCurses::COLOR_RED)
attr_set FFI::NCurses::A_NORMAL, 1, nil
addch(?A)
addch(?Z | COLOR_PAIR(1))

Cursor

Turn cursor off

FFI::NCurses.curs_set 0

Turn cursor on

FFI::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
  FFI::NCurses.endwin
  puts e
ensure
  FFI::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, FFI::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 = FFI::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 FFI::NCurses::KEY_MOUSE
  if getmouse(mouse_event) == FFI::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] & FFI::NCurses::BUTTON1_PRESSED

or

if FFI::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

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

There are some macros in darwin ncurses.h which I haven’t implemented yet. I’m working on it but if there are any you desperately need let me know.

curscr and newscr for JRuby

These global variables are not often used but are required for certain situations (e.g. doing a wrefresh after shelling out and for the get/setsyx macros).

This requires the implementation of find_sym in JRuby (expected in JRuby 1.1.7).

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 (or porting rbcurses). However, in the interests of completeness, I suppose I ought to at least scope it out.

Trivia

While researching ncurses on Google, I innocently entered “curses getsx” as a search term. NSFW and definitely not one for “I’m Feeling Lucky”.