PHP-ncurses FAQ

Anton Povarov, Alexey Rybak

v. 0.3ENG (alexey rybak, alexey d0t rybak N0-sp@m-pleaze gmail d0t com)

Russian version of this document was moved here

Table of contents:

What is ncurses?
What is PHP-ncurses?
How to initialize pseudographical mode?
How to determine terminal window size?
How to process user input?
Why there are no key codes for some commonly used keys (ENTER and ESC)?
Is it possible to read input in nonblocking mode?
How to process escape sequences?
What happens if i resize the terminal window while program is still running?
How to hide cursor?
How to work with overlapping windows?
How to debug ncurses applications and handle errors?
Where can I find examples of dialogs?
Where can I find examples of using panel-functions (overlapping windows)?
Where can I find examples of reading user input?
Where can I find examples of simple menus?
Where can I find examples of listbox (checkbox-list)?
Where can i find more PHP examples and read more about ncurses?


sponsor link

What is ncurses?

Ncurses is a C library enabling you to develop applications with a pseudographical user-interface: menus, windows, dialogs and so on. The infamous mc and vim are good examples of apps using this library.

What is PHP-ncurses?

PHP-ncurses is a thin wrapper around the original ncurses interface, it brings the power of ncurses to the world of PHP programming. Although ncurses is a very powerful library it's usually considered to be a 'low-level' tool. And PHP-ncurses implements only part of original ncurses functionality: no menu and forms related stuff. Therefore we're left with: window drawing, putting text anywhere on the screen, reading user input and terminal tricks. There is no predefined function enabling a one-liner that builds a dialog with several buttons, grabs user choice and returns the result to the caller :) But you can easily write it using the primitives supplied by the library. See examples below.

How to initialize pseudographical mode?

The functions ncurses_init() and ncurses_end() initialize and exit pseudographical mode respectively. Your program must always call ncurses_end() before exiting, otherwise you risk breaking the terminal settings and environment you program was started in. If it has happened already try to reset the terminal (unix reset command), this helps in most cases. Finally using ncurses is incorrect when output is not directed to interactive terminal, therefore using posix_isatty(STDOUT) is a good coding style.

How to determine terminal window size?

Just simple create a window with height and width zero(this means the window is full-screen). After that you can get terminal window dimensions as a dimensions of the created window:
$window = ncurses_newwin(0, 0, 0, 0);
ncurses_getmaxyx($window, $rows, $cols);

How to process user input?

User getch() and wgetch() functions. In echo mode the input is printed back on the screen, in noecho, obviously, not. If you want the echo output it's better to use wgetch() that takes a window handle as a first argument and keeps the output inside the window. But in practice the echo mode is not very useful, mostly because there's no automatic support for moving the cursor when special keys are pressed. There are more annoying quirks however: wgetch() doesn't respect window borders and erases it when input is long enough. Therefore in most cases you'll end up processing user input manually in noecho mode, taking care of cursor position, window borders and printing characters on the screen.

Why there are no key codes for some commonly used keys (ENTER and ESC)?

Yes, actually there are no key codes for ENTER or ESC and probably some other widely used keys. The reason is that there's no universal set of codes sutable for all terminals out there, but in most cases ESC is 27 and ENTER is 13. On some terminals pressing enter produces \r\n(13,10). Therefore in most cases 10 is treated as ENTER as well, just because you cannot produce 10 by pressing any other button on the keyboard.

It is possible to read input in nonblocking mode?

We need nonblocking read when we only need to check if any key has been pressed by the user. The functions ncurses_timeout() and ncurses_halfdelay() are designed for this task. The first one ncurses_timeout() is the most advanced one and can operate in on of three modes depending on the argument In halfdelayed mode getch() function will wait for data for the specfied time and return -1 if no data arrives during that period. ncurses_halfdelay() is similar to ncurses_timeout() but accepts the timeout value in 1/10 of a second and when passed 0 returns to fully blocking mode. You'll probably use ncurses_timeout() in most cases and forget about ncurses_halfdelay, but there is one annoying problem: ncurses_timeout() affects getch() only while ncurses_halfdelay() affects both getch() and wgetch(). There is a wtimeout() function in the original ncurses library, which is the window counterpart of the ncurses_timeout() function, but it's not implemented in PHP-ncurses at this moment.

How to process escape sequences?

The short answer is: you shouldn't. Use the ncurses keypad mode:
ncurses_keypad($window, TRUE);
After this wgetch will return unique code for each of the functional keys pressed (see NCURSES_KEY_* constants in module documentation).

Even if you don't need to handle functional keys in your program, but your program terminates when ESC is pressed you still need the keypad mode, or your program will exit on many other keypresses as well :)

There is one more thing worth mentioning: when ESC is pressed the 1 second timer is activated to wait for the rest of the escape sequence, if the escape sequence is read during this second it is returned and if not - your program will hang for a second before returning the ESC code. Therefore it's recommended to exit the application by double ESC, when second ESC is pressed ncurses returns ESC immediately and you program exits without delay.

What happens if i resize the terminal window while program is still running?

Experimens show that when the terminal window is resized getch() returns 410 or -1 (one should remember that -1 means 'timeout' in halfdelay mode). And to check wether the window size has really changed use ncurses_getmaxyx() function.

How to hide cursor?

Say ncurses_curs_set(0) to hide or ncurses_curs_set(1) to show.

How to manage overlapping windows?

Unfortunately when your program windows overlap each other you have to restore the lower hidden window(or part of it) when the upper ones are moved, closed, etc. To solve these problems you can use several approaches. The first and the most right way is to use ncurses panel functions(see the example below). The other one is to dump the whole terminal into some file and then restore it with ncurses_scr_* functions. Finally, you can store the contents of every window somewhere in memory and redraw windows on demand.

How to debug ncurses based applications and handle errors?

After ncurses mode is initialized you have to make sure that output doesn't break the pseudographics. Debugging is simple: you redirect debugging output to some file or you can write your own alert function that creates new window and prints the debugging information right there. You also have to forget about popular functions that write to stdout like var_dump() or print_r(), but that's easy: you replace them with something like error_log(var_export($foo, TRUE)) or my_ncurses_alert(var_export($foo, TRUE)) and live happily ever after :)

Personal error function is a neat solution but two things must be noted when your stderr is directed to stdout:

The most correct way is logging everything to stderr, and redirecting stderr to some file but this is not useful for the end user at all. But you can play with error hanlders a make this little more flexible: trigger_error with a custom error_handler that calls my_ncurses_alert() in production environment and logging to stderr redirected to file when debugging. Anyway, this is just one of the solutions and probably you can find many others.

Examples:

1)
bash> ./your_script 2>errlog.txt
bash> tail -f errlog.txt

2)
function my_error_handler($errno, $errstr, $errfile, $errline) {
   return dialog(
       array(
           'message' => 'ERROR #'.$errno.': '.$errstr.'(FILE:'.$errfile.',LINE:'.$errline.')',
           'buttons' => array('OK')
       )
   );
}
set_error_handler('my_error_handler');

the example dialog function can be found below

Where can I find examples of dialogs?

Welcome.

#!/usr/local/bin/php -q

<? 

define('XCURSES_KEY_LF',    13);
define('XCURSES_KEY_CR',    10);
define('XCURSES_KEY_ESC',   27);

###############################################################################################
function dialog($params) {
###############################################################################################
    if (empty($params) || !is_array($params) ) {
        trigger_error('params must be non-empty array');
        return NULL;
    }

    $message = isset($params['message']) ? $params['message'] : ''; 

    $buttons = (!empty($params['buttons']) && is_array($params['buttons'])) ? 
        $params['buttons'] : array('OK');  
    $n_buttons = count($buttons);
    for ($i=0;$i<$n_buttons;$i++) {
        $buttons[$i] = ' '.$buttons[$i].' ';
    }
 
    $parent_rows = (isset($params['rows']) && ($params['rows']>0)) ? 
        (int)$params['rows'] : 25;
    $parent_cols = (isset($params['cols']) && ($params['cols']>0)) ? 
        (int)$params['cols'] : 80;

    if (empty($message) || empty($buttons) || $parent_rows<=0 || $parent_cols<=0) {
        trigger_error('wrong params');
        return NULL;
    }

    $message_lines = split("\n",$message);
    $message_width = 0;
    $n_message_lines = count($message_lines);
    for ($i=0; $i<$n_message_lines; $i++) {
        $message_width = max(strlen($message_lines[$i]),$message_width);
    }

    $buttons_delim = '  ';
    $buttons_delim_len = strlen($buttons_delim);
    $buttons_len = strlen(implode($buttons_delim, $buttons));

    $width  = 4 + max($buttons_len + 2*$buttons_delim_len,$message_width);
    $height = 4 + $n_message_lines;
    $dlg_y = ($parent_rows > $height) ? 
	(($parent_rows - $height) >> 1) : 1;
    $dlg_x = ($parent_cols > $width) ? 
        (($parent_cols - $width) >> 1) : 1;

    $window = ncurses_newwin($height, $width, $dlg_y, $dlg_x);
    if (empty($window)) {
        trigger_error('unable to create window');
        return NULL;
    }

    ncurses_wborder($window, 0,0, 0,0, 0,0, 0,0);
    $i_x = 0;
    $i_y = 0;
    for ($i = 0; $i<$n_message_lines; $i++) {
        $i_y = 1 + $i;
        $i_x = 1 + (($width - 2 - strlen($message_lines[$i])) >> 1);
        ncurses_mvwaddstr($window, $i_y, $i_x, rtrim($message_lines[$i]));
    }

    $buttons_data = array();
    $buttons_shift_x = 1 + (($width - 1 - $buttons_len) >> 1);
    $buttons_shift_y = 2 + $n_message_lines;
    $i_title = '';
    $i_x = $buttons_shift_x;
    for ($i = 0; $i < $n_buttons; $i++) {
        $i_title = $buttons[$i];
        $buttons_data[] = array(
            'x' => $i_x, 
            's' => $i_title
        );
        if (0 == $i) ncurses_wattron($window, NCURSES_A_REVERSE);
        ncurses_mvwaddstr($window, $buttons_shift_y, $i_x, $i_title);
        if (0 == $i) ncurses_wattroff($window, NCURSES_A_REVERSE);
        $i_x += strlen($i_title) + $buttons_delim_len;
    }

    ncurses_wrefresh($window);
    ncurses_keypad($window,TRUE);  
    ncurses_curs_set(0);
    ncurses_noecho();

    $result = -1;
    $do_loop = 1;
    $move = 0;
    $current = 0;

    while ($do_loop) {
        $key = ncurses_wgetch($window);
        $move = 0;
        switch ($key) {
            case NCURSES_KEY_LEFT:
                if ($current > 0) $move = -1;
                break;
            case NCURSES_KEY_RIGHT:
                if ($current < $n_buttons-1) $move = 1;
                break;
            case XCURSES_KEY_LF:
            case XCURSES_KEY_CR:
                $result = $current;
                $do_loop = 0;
                break;
            case XCURSES_KEY_ESC:
		$do_loop = 0;
                break;
        }
        
        if (0 == $do_loop) {
            ncurses_flushinp();
        } elseif ($move) {
            ncurses_mvwaddstr($window, $buttons_shift_y, 
                $buttons_data[$current]['x'], $buttons_data[$current]['s']);
            $current += $move;
            ncurses_wattron($window, NCURSES_A_REVERSE);
            ncurses_mvwaddstr($window, $buttons_shift_y, 
                $buttons_data[$current]['x'], $buttons_data[$current]['s']);
            ncurses_wattroff($window, NCURSES_A_REVERSE);
            ncurses_wrefresh($window);
        }
    }

    ncurses_delwin($window);

    return $result;
}

###################################################################################################
function my_error_handler($errno, $errstr, $errfile, $errline) {
###################################################################################################
    global $errors;
    $errors[] = 'ERROR #'.$errno.': '.$errstr.' ('.$errfile.', line '.$errline.')';
    return TRUE;
}

###################################################################################################
# main
###################################################################################################

if (!posix_isatty(STDOUT)) {
    trigger_error('wrong terminal');
    exit;
}

ncurses_init();
error_reporting(E_ALL);
$errors = array();
set_error_handler('my_error_handler');

$main_window = ncurses_newwin(0, 0, 0, 0);
ncurses_getmaxyx($main_window, $rows, $cols);
ncurses_keypad($main_window,TRUE);

$message = < < < EOM
Mr. Treehorn draws a lot of water in this town. You don't draw shit, Lebowski. 
Now we got a nice, quiet little beach community here, and I aim to keep it 
nice and quiet. So let me make something plain. I don't like 
you sucking around, bothering our citizens, Lebowski. 
I don't like your jerk-off name. I don't like your jerk-off face. 
I don't like your jerk-off behavior, and I don't like you, jerk-off. 
Do I make myself clear?
EOM;
$result = dialog(
    array (
        'message' => $message,
        'rows' => -1,
        'cols' => -1, 
	'buttons' => array ('Yes','No','I\'m sorry, I wasn\'t listening')
    )
);

ncurses_end();

if (!empty($errors)) {
    print join("\n",$errors)."\n";
}

?>

Where can I find examples of using panel-functions (overlapping windows)?

#!/usr/local/bin/php -q

<? 

define('XCURSES_KEY_LF',    13);
define('XCURSES_KEY_CR',    10);
define('XCURSES_KEY_ESC',   27);

if (!posix_isatty(STDOUT)) {
    trigger_error('wrong terminal');
    exit;
}

ncurses_init();

$quotes = <<<EOT
----------------------------------------------
Usage: 
    ESC - exit
    any other key - browse windows
----------------------------------------------

We will encourage you to develop
the three great virtues of a programmer:
laziness, impatience, and hubris (Larry Wall)

A good programmer is someone who looks
both ways before crossing a one-way
street (Doug Linder)

Managing senior programmers is like
herding cats (Dave Platt)
EOT;

$lines = explode("\n",$quotes);
$n_lines = count($lines);
$window_width = 0;
for ($i=0; $i<$n_lines; $i++) {
    $window_width = max($window_width,strlen($lines[$i]));
}
$window_width += 4;

$x_coords = array(10,14,18);
$y_coords = array(10,12,8);
for ($i=0; $i<3; $i++) {
    $windows[$i] = ncurses_newwin(4+$n_lines, $window_width, $y_coords[$i], $x_coords[$i]);
    ncurses_wborder($windows[$i], 0,0, 0,0, 0,0, 0,0);
    ncurses_wattron($windows[$i], NCURSES_A_REVERSE);
    ncurses_mvwaddstr($windows[$i], 0, 2, ' window #'.$i.' ');
    ncurses_wattroff($windows[$i], NCURSES_A_REVERSE);
    for ($j=0; $j<$n_lines; $j++) {
        ncurses_mvwaddstr($windows[$i], 2+$j, 2, $lines[$j]);
    }
    ncurses_wrefresh($windows[$i]);
    $panels[$i] = ncurses_new_panel($windows[$i]);
}

ncurses_update_panels();

ncurses_curs_set(0);
ncurses_noecho();
$i = 0;
$k = NULL;
while(XCURSES_KEY_ESC != $k) {
    $k = ncurses_getch();
    ncurses_top_panel($panels[$i%3]);
    ncurses_update_panels();
    ncurses_doupdate();
    $i++;
}

ncurses_end();

?>

Where can I find examples of reading user input?


#!/usr/local/bin/php -q

<? 

define('XCURSES_KEY_LF',                 13);
define('XCURSES_KEY_CR',                 10);
define('XCURSES_KEY_ESC',                27);
define('XCURSES_KEY_PRINTABLE_MIN',      32);
define('XCURSES_KEY_PRINTABLE_MAX',     127);

###############################################################################################
function dlg_input($params = array()) {
###############################################################################################
    $title = isset($params['title']) ? $params['title'] : NULL;
    $max_length = isset($params['max_len']) ? (int)$params['max_len'] : 10;        
    $dlg_rows = isset($params['dlg_cols']) ? (int)$params['dlg_cols'] : 3;
    $dlg_cols = isset($params['dlg_cols']) ? (int)$params['dlg_cols'] : 40;
    $parent_cols = isset($params['cols']) ? (int)$params['cols'] : NULL;
    $parent_rows = isset($params['rows']) ? (int)$params['rows'] : NULL;

    $dlg_x = (int)(($parent_cols - $dlg_cols)/2);
    if($dlg_x<0) $dlg_x = 0;
    $dlg_y = (int)(($parent_rows - $dlg_rows)/2);
    if($dlg_y<0) $dlg_y = 0;
   
    if ($max_length<=0 || $dlg_rows<=0 || $dlg_cols<=0) {
        trigger_error('wrong params');
        return NULL;
    }
 
    $dlg_window = ncurses_newwin($dlg_rows, $dlg_cols, $dlg_y, $dlg_x);
    
    if (empty($dlg_window)) { 
        return NULL;
    }
    
    ncurses_wborder($dlg_window, 0, 0, 0, 0, 0, 0, 0, 0);
    if ($title) {
        ncurses_wattron($dlg_window, NCURSES_A_REVERSE);
        ncurses_mvwaddstr($dlg_window, 0, 2,' '.$title.' ');
        ncurses_wattroff($dlg_window, NCURSES_A_REVERSE);
    }
    ncurses_curs_set(1);
    ncurses_wmove($dlg_window, 2, 2);
    ncurses_wrefresh($dlg_window);
    
    $do_getch = 1;
    $input_val = '';
    $input_char = '';
    $input_len = 0;
    $cursor_x = 2;
    $cursor_y = 1;
    ncurses_wmove($dlg_window, $cursor_y, $cursor_x);
    ncurses_noecho();
    ncurses_keypad($dlg_window,TRUE);
    
    while($do_getch){
        $key_code = ncurses_wgetch($dlg_window);
        if (($key_code == XCURSES_KEY_CR) || ($key_code == XCURSES_KEY_LF)) {
    	    $do_getch = 0;
        } elseif ($key_code == NCURSES_KEY_BACKSPACE) {
            if($input_len>0) {
	        $input_len--;
	        $input_val = substr($input_val,0,$input_len);
	        $cursor_x--;
	        ncurses_mvwaddstr($dlg_window, $cursor_y, $cursor_x,' ');
	        ncurses_wmove($dlg_window, $cursor_y, $cursor_x);
            }
        } elseif ($key_code < XCURSES_KEY_PRINTABLE_MIN || $key_code > XCURSES_KEY_PRINTABLE_MAX) {
    	    continue;
        } elseif($input_len<$max_length) {
    	    $input_val .= $input_char = chr($key_code);
    	    $input_len++;
    	    $cursor_x++;
    	    ncurses_waddstr($dlg_window, $input_char);
        }
    }
    
    ncurses_delwin($dlg_window);
    
    return $input_val;
}

###################################################################################################
# main
###################################################################################################

if (!posix_isatty(STDOUT)) {
    trigger_error('wrong terminal');
    exit;
}

error_reporting(E_ALL);
ncurses_init();
$main_window = ncurses_newwin(0, 0, 0, 0);
ncurses_getmaxyx($main_window, $rows, $cols);

$input = dlg_input(
    array(
        'title' => 'sample input',
        'rows' => $rows, 
        'cols' => $cols
    )
);

ncurses_end();
echo 'Input was: '.$input."\n";

?>

Where can I find examples of simple menus?

#!/usr/local/bin/php -q

<? 

define('XCURSES_KEY_LF',    13);
define('XCURSES_KEY_CR',    10);
define('XCURSES_KEY_ESC',   27);

###################################################################################################
function menu_select($params) {
###################################################################################################
    if(!is_array($params) || empty($params)) {
        trigger_error('wrong params');
        return NULL;
    }

    $menu = isset($params['items']) ? $params['items'] : NULL;
    $rows = isset($params['rows']) ? (int)$params['rows'] : 0;
    $cols = isset($params['cols']) ? (int)$params['cols'] : 0;
    $selected = isset($params['selected']) ? (int)$params['selected'] : 0;
    $centered = empty($params['centered']) ? 0 : 1;
    $y_menu = isset($params['y']) ? (int)$params['y'] : 0;
    $x_menu = isset($params['x']) ? (int)$params['x'] : 0;
   
    if(!is_array($menu) || empty($menu) || $rows<=0 || $cols<=0 || $y_menu<0 || $x_menu<0) {
        trigger_error('wrong params');
        return NULL;
    }

    $keys = array_keys($menu);
    $values = array();

    $current = 0;
    $width = 0;
    $height = count($menu) + 2;

    foreach ($menu as $value) {
        $width = max($width, strlen($value));
    }

    $i = 0;
    foreach ($menu as $k => $v) {
        $values[$i] = ' '.$v.str_repeat(' ',1 + $width - strlen($v));
        if ($k == $selected) $current = $i;
        $i++;
    }

    $width += 4;

    if ($centered) {
        $y_menu = ($rows - $height) >> 1;
        $x_menu = ($cols - $width) >> 1;
    }

    $window = ncurses_newwin($height, $width, $y_menu, $x_menu);
    if (empty($window)) {
        trigger_error('unable to create window');
        return NULL;
    }

    ncurses_wborder($window, 0,0, 0,0, 0,0, 0,0);

    for ($a = 0; $a < count($values); $a++) {
        if ($a == $current) ncurses_wattron($window, NCURSES_A_REVERSE);
        ncurses_mvwaddstr($window, 1 + $a, 1, $values[$a]);
        if ($a == $current) ncurses_wattroff($window, NCURSES_A_REVERSE);
    }

    ncurses_wrefresh($window);
    ncurses_keypad($window,TRUE);
    ncurses_curs_set(0);

    do {
        $key = ncurses_wgetch($window);
        $move = 0;
        switch ($key) {
            case NCURSES_KEY_UP :
                if ($current > 0) $move = -1;
                break;
            case NCURSES_KEY_DOWN :
                if ($current < count($values) - 1) $move = 1;
                break;
            case XCURSES_KEY_LF:
            case XCURSES_KEY_CR :
                $result = $keys[$current];
                break;
            case XCURSES_KEY_ESC :
                ncurses_flushinp();
                $result = '';
                break;
        }

        if ($move) {
            ncurses_mvwaddstr($window, 1 + $current, 1, $values[$current]);
            $current += $move;
            ncurses_wattron($window, NCURSES_A_REVERSE);
            ncurses_mvwaddstr($window, 1 + $current, 1, $values[$current]);
            ncurses_wattroff($window, NCURSES_A_REVERSE);
            ncurses_wrefresh($window);
        }
    } while (!isset($result));

    ncurses_delwin($window);
    return $result;
}

###################################################################################################
# main
###################################################################################################

if (!posix_isatty(STDOUT)) {
    trigger_error('wrong terminal');
    exit;
}

ncurses_init();
error_reporting(E_ALL);

$main_window = ncurses_newwin(0, 0, 0, 0);
ncurses_getmaxyx($main_window, $rows, $cols);

$result = menu_select(
    array (
        'items' => array (
            'dude'    => 'The Dude',
            'walter'  => 'Walter Sobchak',
            'donny'   => 'Donny',
            'maude'   => 'Maude Lebowski',
            'jesus'   => 'Jesus Quintana',
            'smokey'  => 'Smokey'
         ),
        'rows' => $rows,
        'cols' => $cols,
        'selected' => 3,
        'centered' => 1
    )
);

ncurses_end();

echo 'Result is:'.$result."\n";

?>

Where can I find examples of listbox (checkbox-list)?

#!/usr/local/bin/php -q

<? 

define('XCURSES_KEY_LF',    13);
define('XCURSES_KEY_CR',    10);
define('XCURSES_KEY_ESC',   27);
define('XCURSES_KEY_SPACE',  32);

###############################################################################################
function menu_check_list($params) {
###############################################################################################
    if(!is_array($params) || empty($params)) {
        trigger_error('wrong_params');
        return NULL;
    }

    $menu = isset($params['items']) ? $params['items'] : NULL;
    $rows = isset($params['rows']) ? (int)$params['rows'] : 0;
    $cols = isset($params['cols']) ? (int)$params['cols'] : 0;
    $centered = empty($params['centered']) ? 0 : 1;
    $y_menu = isset($params['y']) ? (int)$params['y'] : 0;
    $x_menu = isset($params['x']) ? (int)$params['x'] : 0;

    if(!is_array($menu) || empty($menu) || $rows<=0 || $cols<=0 || $y_menu<0 || $x_menu<0) {
        trigger_error('wrong params');
        return NULL;
    }

    $keys = array_keys($menu);
    $n_menu = count($keys);

    $items = array();
    $checked = array();
    $current = 0;
    $width = 0;
    $height = $n_menu + 2;
    $i = 0;
    $k = NULL;
    $i_checked = NULL;

    for($i=0; $i<$n_menu; $i++) {
        $k = $keys[$i];
        $i_checked = (isset($menu[$k][1]) && $menu[$k][1] == 1) ? 1 : 0;
        $items[$i] = ' ['. ($i_checked ? '*' : ' ').'] '.$menu[$k][0];
        $width = max($width, strlen($items[$i]));
        $checked[$i] = $i_checked;
    }

    for ($i=0; $i<$n_menu; $i++) {
        $items[$i] = $items[$i].str_repeat(' ', 2 + $width - strlen($items[$i]));
    }

    $width += 4;

    if ($centered) {
        $r = ($rows - $height) >> 1;
        $c = ($cols - $width) >> 1;
    }

    $window = ncurses_newwin($height, $width, $r, $c);
    if (empty($window)) {
        trigger_error('unable to create window');
        return NULL;
    }

    ncurses_wborder($window, 0,0, 0,0, 0,0, 0,0);
    $n_items = count($items);
    for ($i = 0; $i < $n_items; $i++) {
        if ($i == $current) ncurses_wattron($window, NCURSES_A_REVERSE);
        ncurses_mvwaddstr($window, 1 + $i, 1, $items[$i]);
        if ($i == $current) ncurses_wattroff($window, NCURSES_A_REVERSE);
    }

    ncurses_wrefresh($window);
    ncurses_keypad($window,TRUE);
    ncurses_noecho();
    ncurses_curs_set(0);

    $do_loop = 1;
    $save_result = 0;
    while($do_loop) {
        $key = ncurses_wgetch($window);
        $move = 0;
        switch ($key) {
            case NCURSES_KEY_UP :
                if ($current > 0) $move = -1;
                break;
            case NCURSES_KEY_DOWN :
                if ($current < $n_menu - 1) $move = 1;
                break;
            case XCURSES_KEY_LF:
            case XCURSES_KEY_CR:
                $do_loop = 0;
                $save_result = 1;
                break;
            case XCURSES_KEY_SPACE:
                if ($checked[$current]) {
                    $checked[$current] = 0;
                    $items[$current] = ' [ ] '.substr($items[$current],5);
                } else {
                    $checked[$current] = 1;
                    $items[$current] = ' [*] '.substr($items[$current],5);
                }
                ncurses_wattron($window, NCURSES_A_REVERSE);
                ncurses_mvwaddstr($window, 1 + $current, 1, $items[$current]);
                ncurses_wattroff($window, NCURSES_A_REVERSE);
                ncurses_wrefresh($window);
                break;
            case XCURSES_KEY_ESC :
                ncurses_flushinp();
                $do_loop = 0;
                break;
        }

        if ($move) {
            ncurses_mvwaddstr($window, 1 + $current, 1, $items[$current]);
            $current += $move;
            ncurses_wattron($window, NCURSES_A_REVERSE);
            ncurses_mvwaddstr($window, 1 + $current, 1, $items[$current]);
            ncurses_wattroff($window, NCURSES_A_REVERSE);
            ncurses_wrefresh($window);
        }
    }

    ncurses_delwin($window);
    $result = NULL;
    if ($save_result) {
        for ($i=0; $i<$n_menu; $i++) {
            $result[$keys[$i]] = $checked[$i];
        }
    }

    return $result;
}


###################################################################################################
# main
###################################################################################################

if (!posix_isatty(STDOUT)) {
    trigger_error('wrong terminal');
    exit;
}

ncurses_init();
error_reporting(E_ALL);

$main_window = ncurses_newwin(0, 0, 0, 0);
ncurses_getmaxyx($main_window, $rows, $cols);

$result = menu_check_list(
    array (
        'items' => array (
            'dude'    => array ('The Dude',        1),
            'walter'  => array ('Walter Sobchak',  0),
            'donny'   => array ('Donny',           1),
            'maude'   => array ('Maude Lebowski',  1),
            'jesus'   => array ('Jesus Quintana',  0),
            'smokey'  => array ('Smokey',          0)
         ),
        'rows' => $rows,
        'cols' => $cols,
        'selected' => 3,
        'centered' => 1
    )
);

ncurses_end();

echo 'Result is:'.var_export($result,TRUE)."\n";

?>

Where can i find more PHP examples and read more about ncurses?

When I was writing this document, there was quite few places to see PHP examples:

A very good ncurses review can be found in ncurses-HOWTO and in "Writing Programs with NCURSES".

PHP ncurses-extention source code can be a good source of information as well. Developers familiar to perl can look at the source of several ncurses modules from CPAN: Curses::UI, Curses::Application, Curses::Forms, Curses::Widgets

wbr, fisher

see also:blitz templates, fast php-template engine written in C