Russian version of this document was moved here
$window = ncurses_newwin(0, 0, 0, 0); ncurses_getmaxyx($window, $rows, $cols);
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.
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
#!/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";
}
?>
#!/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();
?>
#!/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";
?>
#!/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";
?>
#!/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";
?>
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