public
Description: Interface to ncurses using Ruby FFI (Foreign Function Interface)
Homepage:
Clone URL: git://github.com/seanohalpin/ffi-ncurses.git
name age message
file .gitignore Sun Feb 22 08:31:02 -0800 2009 removed announcement.txt [seanohalpin]
file History.txt Sun Feb 22 06:15:43 -0800 2009 fixed typo [seanohalpin]
file README.rdoc Mon Feb 16 00:39:10 -0800 2009 added installation instructions [seanohalpin]
file Rakefile Sun Feb 22 08:12:59 -0800 2009 use local config for email [seanohalpin]
directory bugs/ Sun Feb 22 06:15:59 -0800 2009 recorded bug fix [seanohalpin]
directory examples/ Sun Feb 22 06:04:07 -0800 2009 added example derived from Ncurses example.rb a... [seanohalpin]
file ffi-ncurses.gemspec Sun Feb 22 08:12:59 -0800 2009 use local config for email [seanohalpin]
directory lib/ Sun Feb 22 06:12:03 -0800 2009 added VERSION to FFI::NCurses [seanohalpin]
directory notes/ Sun Feb 22 06:05:40 -0800 2009 more notes on ncurses shim [seanohalpin]
directory tools/ Sun Feb 15 09:35:32 -0800 2009 moved sigs file into tools [seanohalpin]
README.rdoc

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

Install

ruby 1.8.6:

$ sudo gem install ffi ffi-ncurses

jruby 1.1.6:

$ jruby -S gem install ffi-ncurses

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'
  include FFI::NCurses
  begin
    initscr
    win = newwin(6, 12, 15, 15)
    box(win, 0, 0)
    inner_win = newwin(4, 10, 16, 16)
    waddstr(inner_win, (["Hello!"] * 5).join(' '))
    wrefresh(win)
    wrefresh(inner_win)
    ch = wgetch(inner_win)
    delwin(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".