(n)Curses magic: are you a magician?
by BenV on Aug.07, 2009, under Morons, Software
Hej readers,
I decided to work on making slackbuild.pl a bit more acceptable in terms of usability and looks. The reason for this is that the dialog program doesn’t exactly do what I want.
That is, it works…. but that’s pretty much all I can say about it. It doesn’t allow me to make a comined checkbox/menu thing that I want for my configure options part. Next to that it’s tedious to fork and parse its output all the time, and let’s not even get started about looks and input issues.
So I figured: hmm, dialog is made in ncurses, perl has a Curses lib…. let’s play!
First off I read through most of NCURSES Programming HOWTO. Somehow it felt familiar, probably because I already went through it when messing with iotop. Next, I realized that google is pretty much useless when you try to search for ‘curses’ and perl, probably because it’s the misspelled version of ‘cursus’ which means ‘course’ in dutch. However, ncurses is something you can search for and find -some- (but few) bits and pieces of information.
Time for a hello world.
#!/usr/bin/perl
use Curses;
# init curses
initscr();
noecho();
start_color();
init_pair(1, COLOR_CYAN, COLOR_BLACK);
# Print some stuff and wait for user input
attron(COLOR_PAIR(1));
addstr(1, 5, "Hello, world!\n");
getch();
# Cleanup and end
endwin();
Well, that was easy enough. Of course I already knew all this shit, I made iotop after all…
Now time for more advanced trickery: windows.
What I want is simple: tailbox, yesnobox, checkboxconfigureoptionsthingy and all that with some good style.
“YOU SAID SIMPLE!”
Well, I never said it would be easy 😉
So my idea was to make the stdscr the backtitle, should be easy enough to always paint that the same way right?
And then make the other things windows above that. Now the mysteries start.
A little code to test the windowing concepts:
#!/usr/bin/perl
use Curses;
# init again
initscr();
noecho();
cbreak(); # no waiting for enter anymore on getch
start_color();
init_pair(1, COLOR_CYAN, COLOR_BLACK);
# make some windows that overlap
my $win = newwin(20, 40, 10, 10);
my $win2 = newwin(20, 40, 5, 5);
# some text in them
addstr($win, 1, 1, "World!");
addstr($win2, 1, 1, "Hello");
# give them borders
attrset($win, COLOR_PAIR(1));
box($win, '|', '-');
attroff($win, COLOR_PAIR(1));
box($win2, '|', '-');
# Show them! (in correct overlapping order!)
refresh($win2);
refresh($win);
# Make user press 'q' this time
my $key;
do {} while (($key = getch()) ne ERR && $key ne 'q');
# cleanup and exit
delwin($win2);
delwin($win);
endwin();
And here’s our beautiful result:
…. what the.
Our window is empty?
But how could this be?
MAGIC!!!!
However, it does wait for the ‘q’ button before exiting, so not everything failed…
This sort of nonsense is exactly the reason why I started this post. Right now I’m having some serious doubts about continuing this curse*COUGH*s project, but on the other hand, it’s a nice challenge. Probably one of the reasons dialog is such a piece of junk… the library is garbage! 🙂
Note that there are a few other options, such as Curses::UI, but they seem to be a bit more junk than I want, not to mention another depedency. Nah, I’ll figure it out using Curses for as long as I can manage to not go crazy.
Some pointers I found out:
- Printing stuff outside a window (because the window is too small) makes it go bonkers. It might not appear at all, overwrite other stuff… you name it.
- A big pitfall is the y,x syntax. Where one would expect coordinates in the x,y format, curses makes you curse and reverse.
- Another pitfall is forgetting the refresh, or messing up the order of the refreshes. I’m writing some wrappers for that in slackbuild.pl.
- Overlapping windows are another source of fun, in order to make sure the overlapped window gets repainted properly you can use
touchwin($win);
on it.
Oh yeah, if you ever decide to write a curses program:
Make sure to set a __DIE__ handler that RESTORES THE TERMINAL PROPERLY! If one thing annoys people it’s messing up their stuff…. and not closing Curses properly does exactly that.
For those who wonder how to get out of such a broken terminal, type in ‘reset‘ and hit enter. (or twice if you’re a bad typist). No, this won’t reset your pc, it will simply reset your terminal to sanity.
“But what about the broken code in the second example?”
Oh yeah… another case of forgetting to call refresh. What we did there was to properly refresh the imaginary windows.
However, we forgot to refresh the real stdscr window that contains them! So nothing was output to the screen. Lovely.
It’s still a bit fuzzy here though, so don’t mind me if I screwed up the reasoning, but that’s what I understand about it now. It’ll get better 😉
Anyway, insert a refresh();
at line 25 and it’ll do what you’d expect. It’ll look like this:
Good luck with your cursed rabbits!
***followup here:ncurses magic part 2***