cadancurseswidechar

Adding wide character support breaks ncurses menu


I'm currently trying to create an ada program (this will be relevante later on) with a basic TUI. To do so, I'm using ncurses, and more specifically, I'm interfacing with C as the ada bindings, ncursesada, didn't work for me [1].

What I'm trying to do is a basic ncurses menu. It is a simple menu with three options, two of which allowing the user to input a sentence which I then use in my main program. As my input sentences are in french, I searched how to add support for wide characters and stumbled upon this post which made me add some compiler/linker flags to my config file. In my case, they are :

Compiler : -I/usr/include/ncursesw -D_DEFAULT_SOURCE -D_XOPEN_SOURCE_EXTENDED=600
Linker : -Wl,-Bsymbolic-functions -lncursesw -ltinfo

Also, I added setlocale (LC_ALL, "") before initsrc and I redefined the relevant macro NCURSES_WIDECHAR to 1, which I understood I should do.

With all of this, I can now get wide characters with no problems, however, my main menu is now broken. Indeed, instead of displaying the full options, it would seem like it is somewhat cutted.

While searching for a solution, I told myself that maybe the issue was with the setlocale function; which didn't seem to be the problem. I also took a look at this post which describes eight issues with ncurses related to unicode. I should be good with the eight points [2]. In a last-ditch effort, I tried to remove both the compiler and the linker flags and rolled back from wint_t(*) to char(*) wherever applicable. This, to my surprise, solved the issue.

So now I'm in a stalemate. I want to use ncursesw to get access to wide characters, but if I do this, it would seem that my menu breaks. What can I do to solve this ? I thought about using an alternative to ncurses, but :

Thanks a lot,

[1] Installed with alire with command, but when compiling, it complains some libraires are not present.

[2] Maybe not with the 4th point which says to be " very very careful about not ever using the library versions that don't actually have the wide character functions in them. ", but I didn't find how to use a menu with ncursesw (I tried to import <ncursesw/menu.h> but it didn't seem to do anything).

EDIT : here should be a minimal reproducible example :

with this, I obtain this menu (which, interestingly, is a different broken menu from the one above)

compiled with gcc ncurses_external.c -I/usr/include/ncursesw -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -o main -lmenu -Wl,-Bsymbolic-functions -lncursesw -ltinfo

ncurses_external.h

#include <ncurses.h>
#include <menu.h>

#define INVISIBLE 0
#define VISIBLE 1
#define ENTER_CODE 10
#define SENTENCE 0
#define FILE_STR 1

char* Choices[] = {
   "Traduire (FR->NO)",
   "Traduire (NO->FR)",
   "Quitter"
};

int InitScr_Wrp ();

void Refresh_Wrp ();

void Colored_Line (char Line[], short Color, int y);

int Menu (int y);

wint_t* Get (int type, int y);
// the wint_t errors (underlined in an IDE) are IDE's specific error. Program
// compile.

int main (int argc, char *argv[]);

void EndScr_Wrp ();

// private in regards to Ada

ITEM** Allocate_List (int N, char* choices[]);

void Free (int N, ITEM** List, MENU* menu);

ncurses_external.c

#include "ncurses_external.h"
#include <ncursesw/ncurses.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>

#define NCURSES_WIDECHAR 1

int InitScr_Wrp () {

   setlocale (LC_ALL, "");

   initscr ();

   if (has_colors () == FALSE) {
      endwin ();
      return -1;
   }

   curs_set (INVISIBLE);
   cbreak ();
   noecho ();
   start_color ();
   keypad (stdscr, TRUE);

   return 0;
}

void Refresh_Wrp () {
   refresh ();
}

void Colored_Line (char Line[], short Color, int y) {

   init_pair (y, Color, COLOR_BLACK);
   attron (COLOR_PAIR (y));
   mvwprintw (stdscr, y - 1, 0, "%s", Line);
   attroff (COLOR_PAIR (y));
}


ITEM** Allocate_List (int N, char* choices[]) {

   ITEM** List;

   List = (ITEM**)calloc (N + 1, sizeof (ITEM*));
   for (int i = 0; i < N; ++i) {
      List[i] = new_item (choices[i], "");
   }

   return List;
}

void Free (int N, ITEM** List, MENU* menu) {

   unpost_menu (menu);
   free_menu (menu);
   for (int i = 0; i < N; i++) {
      free_item (List[i]);
   }
}

int Menu (int y) {

   ITEM** Item_List;

   WINDOW* menu_win;
   MENU* Option_Menu;

   menu_win = newwin (16, 50, y, 0);
   keypad (menu_win, TRUE);

   int N = sizeof (Choices) / sizeof (Choices[0]);
   Item_List = Allocate_List (N, Choices);
   Option_Menu = new_menu (Item_List);

   set_menu_win (Option_Menu, menu_win);
   set_menu_sub (Option_Menu, menu_win);
   post_menu (Option_Menu);

   wrefresh (menu_win);

   int character;
   int Choosen_Option = 0;

   menu_driver (Option_Menu, REQ_FIRST_ITEM);
   do {

      character = wgetch (menu_win);

      switch (character) {

      case KEY_UP:
         Choosen_Option = Choosen_Option > 0 ? Choosen_Option - 1 : 0;
         menu_driver (Option_Menu, REQ_UP_ITEM);
         break;

      case KEY_DOWN:
         Choosen_Option = Choosen_Option < N ? Choosen_Option + 1 : N;
         menu_driver (Option_Menu, REQ_DOWN_ITEM);
         break;
      }

   } while (character != ENTER_CODE);

   Free (N, Item_List, Option_Menu);
   wclear (menu_win);
   wrefresh (menu_win);

   delwin (menu_win);

   return Choosen_Option;

}

wint_t* Get (int type, int y) {

   if (type == SENTENCE) {
      mvwprintw (stdscr, y, 0, "Entrer une phrase à traduire :");
   }
   else {
      mvwprintw (stdscr, y, 0, "Entrer un fichier à décoder :");
   }

   mvwprintw (stdscr, y + 1, 0, "> ");
   wrefresh (stdscr);
   curs_set (VISIBLE);
   echo ();

   wint_t* response = NULL;
   size_t N = 0;
   size_t curr_end_size = 255;
   size_t Block = 16;

   response = (wint_t*)realloc (NULL, sizeof (*response) * curr_end_size);

   if (!response) return response;

   wint_t character;
   do {

      get_wch (&character);

      response[N] = character;
      N++;

      if (N == curr_end_size) {
         curr_end_size += Block;
         response = (wint_t*)realloc (response, sizeof (*response) * curr_end_size);
         if (!response) return response;
      }

   } while (character != ENTER_CODE);

   return response;

}

void EndScr_Wrp () {
   endwin ();
}

int main (int argc, char *argv[]) {


   int y1 = 10;
   int option;

   InitScr_Wrp ();
   option = Menu (y1);

   switch (option) {

   case (SENTENCE):
      Get (SENTENCE, y1);
      break;

   case (FILE_STR):
      Get (FILE_STR, y1);
      break;

   }

   EndScr_Wrp ();
}

Solution

  • You need the wide-character configuration of the menu library, e.g, using -lmenuw rather than -lmenu.