Categories


Loading feed
Loading feed
Loading feed

Using Ncurses in PHP


Intended audience
Overview
Learning Objectives
Definitions
Background Information
Prerequisites
How it Works
Putting it all together
The Script
Conclusion
Links
About the author

Intended audience

This tutorial is intended for expert PHP programmers interested in extending command-line PHP applications to generate terminal-based user interfaces. You need SSH access to a UNIX server with PHP 4 compiled with ncurses.

Overview

As a programmer you are often assigned to write programs for system administration tasks. By default, command line applications in PHP lack aesthetics. By taking advantage of the ncurses functions you can create a friendly user interface for the application that will be generally uniform across the various terminal types.

PHP is a highly flexible and extensible language and can be used to augment your existing shell/Perl scripting solutions or replace them; so ncurses functionality is a natural progression for PHP.

This tutorial will give you a kick-start towards writing decent looking command-line applications that benefit the user as well as the writer. I will focus on the ‘meat’ of what is needed and hopefully give you a framework for adding ncurses functionality into your PHP application.

Learning Objectives

In this tutorial you will:
  • Learn how to create basic windows using ncurses and PHP, and write data to them.
  • Learn how to create ncurses window sets in PHP, and dynamically size the windows to fit the terminal.
  • Learn the specifics of window handles, and how to capture function keys.
  • See an example of using the ncurses functions by creating a small application that extends the Linux traceroute program while using all the functions discussed herein.

Definitions

  • Ncurses : The Ncurses (new curses) library is a free software emulation of curses in System V Release 4.0, and more. It uses Terminfo format, supports pads and color and multiple highlights and forms characters and function-key mapping, and has all the other SYSV-curses enhancements over BSD Curses. If you have used Linux you have probably seen ncurses in action. Programs such as Midnight commander, ncftp, Iptraf, trafshow and many others use ncurses for the visual aspect of the application.
  • Window: The term ‘window’ as used in this document refers to a section of a terminal that is created via ncurses in PHP.

Background Information

This tutorial is written for the advanced programmer.

It has been said that the Curses library of functions is appropriately named. The documentation for the PHP ncurses functions is very limited and several of the functions used in this tutorial do not even appear on the php.net website as of this writing. Ncurses applications follow a c-style format, as they are written to be true applications and not simply scripts with a defined start and finish.

If you have experience programming in C or C++ then you are probably already familiar with what ncurses can do, and can probably imagine how useful ncurses can be when used in conjunction with a scripting language like PHP.

Prerequisites

You will need to have access to PHP compiled with --with-ncurses on a Unix-based system. Not all terminals will support colorized ncurses output so this tutorial will not use any coloring functions.

Keep in mind that you might have to reset your terminal often while working with ncurses. If you have an error ncurses_end() is not called, so your terminal will be fried and you will have to issue the ‘reset’ command to fix it.

Order is also very important for these functions, you can segfault PHP by attempting to refresh a window that does not have an appropriate resource handle.

How it Works

Using ncurses we will create a functional window system and increase interface usability.

Lets start with a very basic ncurses introduction before moving into anything more advanced:

First let’s create an ncurses main window that uses the entire screen region it has available. Then let’s add in a smaller window into the middle of this screen and write a string to it. It will look like this.



Start ncurses with ncurses_init() which lets PHP know that you wish to enter into ncurses mode. Without making a call to ncurses_init() before calling ncurses functions, PHP will error.

The code to produce the above window:

<?
// we begin by initializing ncurses
$ncurse ncurses_init();
// let ncurses know we wish to use the whole screen
$fullscreen ncurses_newwin 0000); 
// draw a border around the whole thing.
ncurses_border(0,00,00,00,0);

// now lets create a small window
$small ncurses_newwin(1030725);
// border our small window.
ncurses_wborder($small,0,00,00,00,0);

ncurses_refresh();// paint both windows

// move into the small window and write a string
ncurses_mvwaddstr($small55"   Test  String   ");
// show our handiwork and refresh our small window
ncurses_wrefresh($small);

$pressed ncurses_getch();// wait for a user keypress

ncurses_end();// clean up our screen
?>

Try resizing the terminal and running the code again. Notice that the main window will border itself using the maximum values for the height and width.

Now you may want to see something actually happen when you press a key, or you may not want to exit the application unless a certain key is pressed. I personally prefer using the escape key (char(27)) for this. To do this, at the top of the script add in:

define("ESCAPE_KEY"27);
while(
1){
    
... #code
}

Change ncurses_end() to read

If($pressed == ESCAPE_KEY){
      
ncurses_end();
  exit;
}else{
       
ncurses_mvwaddstr($small55$pressed);
}

Now if you press any key except Escape, its integer equivalent will be placed within the window resource named “small”. This creates a little application that responds to key presses and exits only when you press Escape.

We can make this look better by adding a title-bar.

ncurses_attron(NCURSES_A_REVERSE);
ncurses_mvaddstr(0,1,"My first ncurses application");
ncurses_attroff(NCURSES_A_REVERSE);

You are not just limited to using REVERSE either; you can use DIM, UNDERLINE as well as several others.

To add intuitive usability to a program you will want to add a menu system.

Most programs have user interaction and the ability to select menu items. In the past we used number or alpha systems where the user would select which menu item he wanted by typing a number or letter on the keyboard and the item would be selected. Selection menus have been replaced by interactive menu systems. You want to have a menu that visually signals the user as to which option is currently selected.

The following code will replace “$pressed = ncurses_getch();” in the previous example

$menu = array("one","two","three","four");
    for(
$a=0;$a<count($menu);$a++){
        
$out $menu[$a];
        if(
$currently_selected == intval($a)){ 
            
ncurses_wattron($small,NCURSES_A_REVERSE);
            
ncurses_mvwaddstr ($small1+$a1$out);
            
ncurses_wattroff($small,NCURSES_A_REVERSE);
        }else{
            
ncurses_mvwaddstr ($small1+$a1$out);
        }
    }
ncurses_wrefresh($small); // otherwise we will not see

  
$pressed ncurses_getch($lower_main_window); // wait for user to press key
  
if($pressed == NCURSES_KEY_UP){
        
$currently_selected--; 
        if(
$currently_selected 0){ $currently_selected 0; }
  }elseif( 
$pressed == NCURSES_KEY_DOWN){
        
$currently_selected++;
        if(
$currently_selected >= count($menu)){ $currently_selected count($menu)-1; }
  }elseif( 
$pressed == ESCAPE_KEY){
    
ncurses_end();
    exit;
  }else{
    
ncurses_mvwaddstr($small55$pressed);
  }

When run you will have an application that looks like the following illustration. It has a menu that you can navigate through using the arrow keys and exits when you press escape.



Now that you have this knowledge of ncurses you are probably thinking of all kinds of applications that you can create that utilize these functions. So the next portion of this tutorial will focus on using ncurses to create a real world application that is both useful and an excellent starting point for further development.

Putting it all together

The online documentation for PHP currently has 119 listed functions for ncurses; the following list contains the functions used in the script below with links to the online documentation where available. The four functions listed below which do not appear in the online documentation are explained.

ncurses_init
Initialize ncurses.
ncurses_newwin
Create a new window.
ncurses_getmaxyx( resource window, int return Y, int return X);
Sets Y and X to maximum size of current terminal.
ncurses_border
Draw a border around the main window.
ncurses_refresh
Refresh the main window; use ncurses_wrefresh for refreshing individual windows.
ncurses_attron
Turn output attribute on.
ncurses_mvaddstr
Move and insert a string.
ncurses_attroff
Turn output attribute off.
ncurses_wborder (resource window, int left, int right, int top, int bottom, int tl_corner, int tr_corner, int bl_corner, int br_corner);

Identical to ncurses_border except that you pass the window resource as an additional first parameter.
ncurses_wattron(resource window, int attribute)
Identical to ncurses_attron except that you pass the window resource as an additional first parameter
ncurses_mvwaddstr
Select a window and place a string within it a coordinates.
ncurses_wattroff (resource window, int attribute)
Identical to ncurses_attroff except that you pass the window resource as an additional first parameter.
ncurses_wrefresh
Refresh named window resource.
ncurses_getch
Wait for user keyboard or mouse input.

The Script

The following code will produce a window which looks like this:



This script runs a traceroute to zend.com, limits the hops to only ten, and displays the results within a window. The items in the upper window are scrollable by using your arrow keys and when you press Enter on each one a reverse whois is done and the results are placed in the lower window. If you press the escape key the program will exit.

<?
// define some key constants.
define("ESCAPE_KEY"27);
define("ENTER_KEY"13);
// get our initial data
$tr_return traceroute("www.zend.com");

$ncurses_session ncurses_init();
$main ncurses_newwin(0000); // main window
ncurses_getmaxyx(&$main$lines$columns);
ncurses_border(0,00,00,00,0);

while(
1){
// border the main window
ncurses_attron(NCURSES_A_REVERSE);
ncurses_mvaddstr(0,1,"Traceroute example");
ncurses_attroff(NCURSES_A_REVERSE);

 
// create a lower window which is dynamically sized...
$lower_frame_window ncurses_newwin (10$columns-3$lines-111);
        
ncurses_wborder($lower_frame_window0,00,00,00,0); // border it
        
$lower_main_window ncurses_newwin (8$columns-5$lines-102);

        
$main_list_window ncurses_newwin ($lines-12$columns-311);
        
ncurses_wborder($main_list_window0,00,00,00,0); // border it

  
if($currently_selected == ""){$currently_selected 0;}
        for(
$a=0;$a<count($tr_return);$a++){
        
$out $tr_return[$a];
        if(
$currently_selected == intval($a)){ 
            
ncurses_wattron($main_list_window,NCURSES_A_REVERSE);
            
ncurses_mvwaddstr ($main_list_window1+$a1$out);
                
ncurses_wattroff($main_list_window,NCURSES_A_REVERSE);
        }else{
            
ncurses_mvwaddstr ($main_list_window1+$a1$out);
        }
    }
  if(
$y == ENTER_KEY){    
        
$newout explode(" "$check_me);
        
$rwhois_return rwhois(trim($newout[3]));
        
$a=0;
        while(list(
$key,$val)=each($rwhois_return)){
            
ncurses_mvwaddstr($lower_main_window1+$a1$key " - " $val);
            
$a++;
        }
  }elseif(
$y == ESCAPE_KEY){
        
ncurses_end();
        exit;
  }
  
ncurses_move(-1,1); // toss cursor out of sight.
  
ncurses_wrefresh($lower_frame_window); 
  
ncurses_wrefresh($lower_main_window); // show it
  
ncurses_wrefresh($main_list_window); 

  
// wait for user to press a key
  
$y ncurses_getch($lower_main_window);

  
// check for the presence of up-arrow and down-arrow keys
  
if($y == NCURSES_KEY_UP){
        
$currently_selected--; 
        if(
$currently_selected 0){ $currently_selected 0; }
  }elseif( 
$y == NCURSES_KEY_DOWN){
        
$currently_selected++;
        if(
$currently_selected >= count($tr_return)){ $currently_selected count($tr_return)-1; }
  }

}
//end main while


// the following are two helper functions for
// this ncurses example.
function traceroute($address){
    
exec("/usr/sbin/traceroute -x -n -m 10 $address",
$trreturn);
    return 
$trreturn;
}
//end function

// reverse whois
function rwhois($query){
    
$fp fsockopen ("rwhois.arin.net"4321$errno$errstr30);
    if (!
$fp) {
            
$ret[] = "$errstr ($errno)\n";
    }else{
            
fputs ($fp"$query\r\n\r\n");
        
$count=1;
            while (!
feof($fp) && $count 17) {
            
$back fgets ($fp,128);
            
$data explode(":",$back);
                
$ret[$data[1]] = $data[2];
            
$count++;
            }
//wend
          
fclose ($fp);
    }
//fi
   
return $ret;
}
//end function

?>

Conclusion

The code above is obviously a very simple framework for an ncurses application. With so many functions at your disposal there is quite a world of interfaces that can be developed.

This tutorial has not touched upon using the mouse in terminals, using colors or many of the output control functions and their defined constants.

You will want to create a library of functions for using ncurses for doing things like starting up ncurses and returning a handle to the main window or for drawing windows.

The following are two functions which show in more depth how to use the ncurses_newwin() function and how to create dynamically sized windows within terminal screens. These will be a good start for much more advanced functions. These can easily be extended to add in title bars, panes or anything else you can think up.

#
# left_window(int size) creates an ncurses window
# that is write-safe on the left-hand side of the
# screen $size is how wide it will be
# returns window handle of inner window.
#
function left_window($size=15){
    global 
$fullscreen;
    
ncurses_getmaxyx($fullscreen$MAX_Y$MAX_X);

    
$c ncurses_newwin ($MAX_Y-,$size11);
    
ncurses_wborder($c,0,00,00,00,0); // border it

// now create window overtop the other just
// slightly smaller so that we won't write over
// the border.
    
$d ncurses_newwin ($MAX_Y-,$size-21+12);

    
ncurses_wrefresh($c); // show it
    
ncurses_wrefresh($d); 
    return 
$d;
}

#
# creates an upper-right window
#
function upperr_window($size=15){
    global 
$fullscreen;
    
ncurses_getmaxyx($fullscreen$MAX_Y$MAX_X);

    
$c ncurses_newwin ($size ,$size1$MAX_X-($size+1));
    
ncurses_wborder($c,0,00,00,00,0); // border it

    
ncurses_wrefresh($c); // show it
    
return $c;
}

Happy programming!

Links

http://www.opengroup.org/onlinepubs/007908799/xcurses/curses.h.html

This is the curses.h documentation, which will be an excellent reference while working on any PHP application that uses the ncurses functions.

http://dickey.his.com/ncurses/ncurses-intro.html

A great write-up on programming with ncurses in C, much of which translates directly into PHP.

And as always, use ‘man ncurses’ as each function in C has a man page for it. (i.e. ´man wborder’´).

About the author

Joel De Gan is a seasoned developer currently working for a high-profile domain name registrar. He is obsessed with PHP and despite working with it on a daily basis he often continues late into the night to sit up and program.

Joel additionally is the CTO for Tenshimedia LLC ( http://tenshimedia.com ) a multi-faceted media company that among other things runs and maintains JOIhost ( http://www.joihost.com ).

Joel is currently, in his spare time, working on a PHP/ncurses interface for the Internet file sharing application eDonkey, and a PHP/ncurses packet logger, which logs from tcpdump in addition to a small side project (http://listbid.com/affil/) called Listbid.

Comments