So, the second part. Here I will reveal how to get information from the user, as well as about the manipulation of this data. Here you can touch upon the question raised in the comments to the previous
post - “Why is it all necessary?”. Examples of the use of such interfaces in the 21st century are various applications on virtual machines that implement individual services. Most often they are a minimal Linux distribution or a downloadable kernel + busybox. Using this interface, you can implement a certain frontend for the service, allowing you to at a glance determine the status of the main nodes or perform certain operations in a user-friendly form. An example would be similar frontends for VMware ESXi (vDirector, vCenter etc), Citrix Xen, which combine both the power of a web interface and TUI as a backup interface and / or configuration / diagnostics interface. Switching over dozens of machines, you can see at a glance whether everything is in order or quickly find out the IP address, completely blocking the user from accessing the console, showing him only what he needs to know (foolproof).

In order not to look into the last post, I remind you that I implement all the “widgets” through a structure that creates a semblance of layers, each of which is responsible for something different (shadow, design, top layer, available for the user to change). And all this is combined into a panel that allows you to move, hide and delete all layers as one unit.
struct _cursed_window { WINDOW *background; WINDOW *decoration; WINDOW *overlay; PANEL *panel; }; typedef struct _cursed_window cursed;
')
Looking at the screenshots, you can see how the colors differ on some of them. Although the code is the same, the color mapping depends on the specific implementation of the terminal emulator. I left it as an example.
I will not mention working with functions such as getch (), wgetch (), scanw, and associates. These are essentially analogous to the corresponding functions from C with some additions.
Menu
The menu is implemented simply and visually. The engine supports a wide variety of menu options, including nested ones. Any menu items can be active or not. There is also support for scrolling, search by combination and other joys, greatly simplifying the creation of very complex and sophisticated menus.
We write a function that will return the value corresponding to the user's choice.
int32_t tui_make_menu (const char **choices, size_t choices_num)
At the entrance we have an array with lines of choices and the number of these options. This is about our list maybe.
const char *choices[] = { "Restore", "Quick backup", "Adv. backup", "Add machine", "Exit", NULL, };
And we will transfer it to the function like this
if(tui_make_menu(choices,(sizeof choices/sizeof choices[0]))==4) do_something();
And in the function itself, we now create a menu based on this array of strings.
ITEM **my_items = (ITEM **)calloc(choices_num, sizeof(ITEM *)); for(int32_t i = 0; i < choices_num; ++i) my_items[i] = new_item(choices[i], ""); MENU *my_menu = new_menu((ITEM **)my_items);
In the new_item call, I specified the empty line as the second argument, this is a postfix for the menu item, it will be displayed on the right as a comment (for example, to specify the corresponding hot key).
Set the window for the menu
set_menu_win(my_menu, new->overlay);
And also we will set the character which will be a marker of the selected menu item.
set_menu_mark(my_menu, " * ");
Well, let's color our menu.
set_menu_fore(my_menu, COLOR_PAIR(1) | A_REVERSE); set_menu_back(my_menu, COLOR_PAIR(2)); set_menu_grey(my_menu, COLOR_PAIR(3));
And publish it
post_menu(my_menu);

As always, we provide the livelihood of the engine in manual, but visual mode.
do { switch(user_key) { case KEY_MOUSE: menu_driver(my_menu, KEY_MOUSE); touchwin(panel_window(new->panel)); update_panels(); doupdate(); sleep(1); goto mouse_end; case KEY_DOWN: menu_driver(my_menu, REQ_DOWN_ITEM); break; case KEY_UP: menu_driver(my_menu, REQ_UP_ITEM); break; } touchwin(panel_window(new->panel)); update_panels(); doupdate(); } while((user_key = wgetch(new->overlay)) != 0xD);
Yes Yes. Let's digress and talk about working with the mouse.
Mouse
Ncurses allows us to duplicate the actions that we have already done with the keyboard, using the mouse, without much complicating and changing the code. It is enough to turn on the response to mouse events with a simple masking.
mousemask(ALL_MOUSE_EVENTS, NULL);
In this case, all events are allowed, but you may naturally want to respond only to a double click with the right button
BUTTON2_DOUBLE_CLICKED or only with the release of the left button
BUTTON1_RELEASED , etc.
After that, in our standard input processing loop, you will need to add a case KEY_MOUSE and then transfer the received event to the menu engine or make something different. In the first case, the menu slider will independently determine how to move the menu marker.
It is very important if the work goes through VNC with not always adequate mouse work.
Now that the user has defined and selected a menu item, we can find out his choice.
mouse_end: int32_t user_selection = item_index(current_item(my_menu));
Remove trash
unpost_menu(my_menu); free_menu(my_menu); for(int32_t i = 0; i < choices_num; ++i) free_item(my_items[i]); tui_del_win(new);
and return the value
return (user_selection);
Forms and fields
The input field in ncurses has a very powerful functionality that allows you to manipulate the text entered by the user over a wide range. But first, we will write a small function that will return us the text from the user and, using its example, consider how and what can be done with this field.
char * tui_make_input(const char *info, int32_t max_len);
That is, we must specify the maximum field length and some informational message explaining what we want to receive from the user.
Create a buffer with our input field. In this case, the size of the field is fixed, but the field can expand automatically,
This style of initialization is required.
FIELD *field[2]; field[0] = new_field(1, max_len, 0, 0, 0, 0); field[1] = NULL;
FORM *my_form; set_field_back(field[0], A_UNDERLINE);
The field is max_len characters long, 1 line high. We created one field for the form, but you can create as many as you like, for example, if you have a login / password input form. Then these fields can be used together, for example, to move to a new field when you press a certain key and / or when you fill out a form (for example, if this is a serial number input form).
And also included the option to highlight the input field underscore.
Types
Each field can have one or several types, which are defined as follows:
set_field_type(field[0],TYPE_ALPHA);
In this case, the filter is turned on for letters, and numbers or other non-alphabetical characters entered by the user will not be displayed. In the screenshot, an example of such an input field with the TYPE_IPV4 filter enabled. Unfortunately, he only checks for the presence of numbers and periods, but not the correctness of the address itself. But even here you can check every key pressed (past filter) and, after checking, decide whether to display it or not or display a warning.

Now we combine our fields
my_form = new_form(field); set_form_sub(my_form, new->overlay); post_form(my_form);
And also we print our invitation.
mvwprintw(new->decoration,1,1, info);
Next we need to ensure the work of the form
uint32_t user_key=0; do { switch(user_key) { case 0xD: form_driver(my_form, REQ_VALIDATION); goto check; default: form_driver(my_form, user_key); break; } touchwin(panel_window(new->panel)); update_panels(); doupdate(); } while((user_key = getch()) != KEY_F(12));
We are waiting in a cycle from the user pressing the key, in case it is not ENTER - we pass the key to the field driver, who already applies the filter and displays it on the screen, if necessary. If this is the ENTER key, we ask for the field check and exit the loop.
Now it’s easy to get what the user has entered:
check: char *result=0; asprintf(&result, "%s", field_buffer(field[0], 0));
Next, we clean up after ourselves and return the result.
unpost_form(my_form); free_field(field[0]); free_form(my_form); tui_del_win(new); return result;
Of course, you can set a preliminary value of the input field, which can be used as a prompt or a default value:
set_field_buffer(field[0], 0, «Default»);
Well, indeed many other options. I want to note that the manipulations with the removal / movement of characters or whole words lie on the user. In the same do-while loop, it is necessary to catch the key presses of the necessary keys and, through form_driver, to make requests for the removal of a mark, the transition by letters or words. Using forms, you can easily implement a simple text editor, the lion’s share of working with text in which will fall on the ncurses engine. After all, even text alignment across fields, deletion of whole words and other joys is supported.
Buttons
Since ncurses does not provide a ready-made implementation, we will do it ourselves. The simplest version of the button is a window with a shadow, which is pressed down on its shadow. In the last post, I told you that in the simplest case, the shadow will fall on the bottom window. This is due to the fact that in order to draw the same shadow, we must create a layer, and this layer is rectangular. The unused area of ​​it still exists and will occur as in the screenshot below.

The effect of "transparency" in the default implementation is absent. But it does not matter, it can be implemented independently. In most cases, we will draw the button inside the window, so we will assume that we know on which window the button is located.
In the function, we read the text in the underlying window along with its attributes (color, mode). The ASCII code + attribute occupies one byte and lies in a variable of type chtype (uint8_t). To display this type there is a special function mvwaddchstr.
Button code cursed *tui_new_button(WINDOW *bwin, int32_t sy, int32_t sx, const char *label, size_t len) { cursed *new = malloc (sizeof *new); int32_t w = len + 4; int32_t h = 5; new->background = newwin(h, w + 1, sy, sx); wbkgd(new->background, COLOR_PAIR(4)); int32_t shad_x, shad_y; getbegyx(bwin, shad_y, shad_x); chtype c[len + 7] = {0}; for(int32_t i=0; i < h; i++) { mvwinchnstr(bwin,sy-shad_y + i,sx-shad_x,c, len + 6); mvwaddchstr(new->background,i,0, c); } wattron(new->background, COLOR_PAIR(10)); for (int32_t i = 2; i < w - 1; i++) mvwaddch(new->background, h - 1, i, ' '); for (int32_t i= 2; i < h; i++) mvwprintw(new->background, i, w - 1, " "); wattroff(new->background, COLOR_PAIR(10)); new->decoration = derwin(new->background, h-2, w-2, 1, 1); wbkgd(new->decoration, COLOR_PAIR(1)); box(new->decoration, 0, 0); int32_t x, y; getmaxyx(new->decoration, y, x); new->overlay = derwin(new->decoration, y-2, x-2, 1, 1); wbkgd(new->overlay, COLOR_PAIR(1)); new->panel = new_panel(new->background); wprintw(new->overlay, label); update_panels(); doupdate(); return new; }

It remains to make the animation button. Insofar as
"The subwindow functions (subwin, derwin, mvderwin, wsyncup, wsyncdown, wcursyncup, syncok) are flaky, incompletely implemented, and not well tested",
then I realized the effect of pressing the button through such crutches. That is, I hide the original button panel, make a duplicate without a shadow, move it diagonally, pause and delete the clone. Well, of course, again showing the original button.
void tui_toggle_button (cursed *button) { int32_t x, y; getbegyx(button->background, y, x); hide_panel(button->panel); WINDOW *dup_dec = dupwin(button->decoration); PANEL *duppan = new_panel(dup_dec); move_panel(duppan, y + 2, x + 3); update_panels(); doupdate(); usleep(200000); del_panel(duppan); show_panel(button->panel); }

The remaining standard interface elements.
By combining the form and menu, you can easily make a drop-down list with autosearch. With the help of a horizontally oriented menu you can make a radiobutton.
The same progress indicator is simply implemented:

void tui_progress_bar(WINDOW *win, double progress) { int32_t height, width; getmaxyx(win, height, width); wattron(win, COLOR_PAIR(8)); for (int32_t i = 0; i < width * progress; i++) { mvwaddch(win, (height / 2), i, ' '); } wattroff(win, COLOR_PAIR(8)); wattron(win, COLOR_PAIR(7)); for (int32_t i = width * progress; i < width; i++) { mvwaddch(win, (height / 2), i, ' '); } wattroff(win, COLOR_PAIR(7)); wattron(win, A_STANDOUT); mvwprintw(win, (height / 2), (width / 2) - 2, "%.0f%%", progress*100); wattroff(win, A_STANDOUT); }
The same programbar can also be used in pure text applications. As a visual and convenient indicator of progress, although the same can be done purely on printf, but it will not be possible to update something else in other lines.
Conclusion
Text interface is easy. Many people write console utilities, in some cases this is the main working tool. Why not make it clearer and more convenient for perception? In this screenshot, nothing is used except 3 output functions without windows and panels.

If it is also used for scripts, tui can be turned on / off by key in the launch arguments. Anyway, if a program cannot use graphics, this does not mean that it should remain incomprehensible. The same conclusion of the help can be made clearer or even contextual. The same TAB autocompletions can be implemented and your users will thank you. A good example here is the xe utility from the Xen Server suite. After the user has typed a keyword, the program offers options by pressing TAB. For example, uuid = and immediately the UUID of the machines registered in the system are automatically substituted. This makes it incredibly easy and quick.
PS: If someone wants to try to work with curses, I laid out the simplest example here
github.com/Pugnator/curses_testPlease do not scold for the code - I'm just an amateur.
In the bin directory just run make
If necessary, reinstall:
sudo apt-get install ncurses-dev (for debian based)