Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ncurses freaks out when writing wide character to certain screen locations

I am writing an application using ncurses and wish to use box drawing characters in it, specifically u/2550 and u/2551 (as of now). I have for loops setup to draw bars to two sides of the terminal, based off screen size I determine elsewhere.

For whatever reason when it comes to drawing any unicode characters horizontally (here at the bottom of the screen, but it does this on any row), it will go from printing the characters fine to printing garbage 'P's. This is a bit hard to explain, so I have some pictures showing what happens when I draw 6 characters and then 7 or more characters.

With 6 characters

With 7 characters

The part of the code responsible for drawing these characters is as such, note that the last for loop is what draws these characters, and the iterator is supposed to go farther than just 7, but it does this here and anywhere else.

void drawBorder(){ //draw the border graphics
                attron(COLOR_PAIR(3));
        for(int i = 1; i < screenSizeY - 1; i++){ //draw left side
                mvaddwstr(i, 0, L"║");
        }
        for(int i = 1; i < screenSizeY - 1; i++){ //draw right side
                mvaddwstr(i, screenSizeX - 1, L"\u2551");
        }
        for(int i = 0; i < 7; i++){ //draw bottom
                mvaddwstr(screenSizeY - 1, i, L"\u2550");
        }
                attroff(COLOR_PAIR(3));
}

I am linking against the ncursesw package and have a properly set locale. Other characters drawn in vertical lines work just fine, but it doesn't here. I am using C++ compiled with g++, running on Linux in an Alacritty terminal session.

This has nothing to do with box draw functions, or specific terminal capabilities, all of the wide characters are perfectly supported by the terminal, and work in other parts of the terminal. This will happen based on how many of these I draw in a row, and also happens for other box characters.

like image 890
Ampera Avatar asked Jan 27 '23 17:01

Ampera


1 Answers

OP sent a more complete example, which shows the problem:

#include <ncurses.h>
#include <string>
#include <iostream>

using namespace std;

int main(){

    initscr();
    setlocale(LC_ALL, "");
    raw();
    keypad(stdscr, TRUE);
    noecho();

    for(int i = 0; i < 10; i++){
        mvaddwstr(0, i, L"\u2550");
    }
    for(int i = 0; i < 6; i++){
        mvaddwstr(1, i, L"\u2550");
    }
    refresh();
    getch();
    endwin();

    return 0;

}

The problem is that the library is initialized with a different locale than is used in the mvaddwstr calls. The manual page says

The library uses the locale which the calling program has initialized. That is normally done with setlocale:

setlocale(LC_ALL, "");

If the locale is not initialized, the library assumes that characters are printable as in ISO-8859-1, to work with certain legacy programs. You should initialize the locale and not rely on specific details of the library when the locale has not been setup.

Because the call to setlocale is after initscr, rather than before, ncurses assumes the data is ISO-8859-1, and runs into an unexpected scenario. In other places such as addwstr, ncurses checks if the data is valid wchar_t, but in those, it's using the current locale. In this case, it's far away from the functions that it knows must be handled in that way (it's rendering data which has already been processed). The comparison in the library which allow this to be handled using the repeat_char capability can be improved, but the actual bug is in the sample program.

like image 52
Thomas Dickey Avatar answered Feb 05 '23 17:02

Thomas Dickey