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”.