8

I was just writing a C++] program that uses the box characters to display information.  I ran the program on macOS and used the terminal app and it worked fine.

When I switched to Debian Linux using the same code and recompiling it, I only then encountered this problem!  I've searched online for answers, but I have found none related to this problem.  I've tested this with both LXTerminal and URXVT.

Here is my minimal reproducible example of this problem:

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

int ch;

int main() {
    setlocale(LC_CTYPE, "");
    initscr();
    noecho();
    keypad(stdscr, true);
    nodelay(stdscr, true);
    curs_set(0);
    start_color();
    use_default_colors();

    init_pair(1, COLOR_BLUE, -1);

    clear();
    refresh();

    while ((ch = getch()) != 'q' && ch != 'Q') {

        attrset(COLOR_PAIR(1));

        mvprintw(0,0,"┏━┓");
        mvprintw(1,0,"┗━┛");

    }

    refresh();
    endwin();

    return 0;
}

Compile this demo with this command below:

g++ -g -c -o main.o test.cpp && g++ -g -o test main.o -std=c++11 -Wall -pedantic -lncurses

or

LDLIBS=-lncurses make test

Instead of displaying

┏━┓
┗━┛

it displays this:

M-b~T~OM-b~T~AM-b~T~S
M-b~T~WM-b~T~AM-b~T~[

Actual screen images

Normal (desired) display on macOS:

Normal (desired) display on macOS

Abnormal display on Linux:

Abnormal display on Linux

5
  • I think it doesn't matter what terminal emulator you use but if it makes you happy I tried it with LXTerminal and URXVT. @terdon Commented Mar 6, 2023 at 10:45
  • 2
    And did you recompile? How did you compile? Please edit your question and add these details. And yes, it absolutely matters which terminal you use since you are using non-ASCII characters so you need a terminal that supports them. lxterminal does though, and your program works fine with lxterminal, so it's going to be something about how you compiled it. So please edit your question and tell us whether you compiled on Linux and, if so, how.
    – terdon
    Commented Mar 6, 2023 at 10:59
  • 1
    Odd. I ran the exact same thing and tested with lxterminal and it worked fine. If you run echo $TERM, that returns xterm-256color, right?
    – terdon
    Commented Mar 6, 2023 at 11:34
  • Wait, are you redirecting to a file or is that the direct output printed to the terminal?
    – terdon
    Commented Mar 6, 2023 at 11:35
  • Yes when I run echo $TERM , it returns xterm-256color . I am using printw() to display the box so its direct output to the terminal. Commented Mar 6, 2023 at 11:39

3 Answers 3

16

The problem is that you're using literal characters , , , , , instead of using the ncurses-independent symbols. You should use the ACS_??CORNER (for Upper/Lower and Left/Right) and ACS_HLINE symbolic names and let ncurses work out how to represent those on your terminal.

Modifying your code from this,

mvprintw(0,0,"┏━┓");
mvprintw(1,0,"┗━┛");

to this,

move(0,0); addch(ACS_ULCORNER); addch(ACS_HLINE); addch(ACS_URCORNER);
move(1,0); addch(ACS_LLCORNER); addch(ACS_HLINE); addch(ACS_LRCORNER);

should ensure that the code works portably. I also changed the end of your main() procedure from a plain return 0 to the following, so that the terminal will be correctly reset when the program ends:

endwin();
exit(0);

Compile test.cpp and execute with,

LDLIBS=-lncurses make test && ./test

Useful references, in no particular order:

9
  • 1
    Any idea why the OP's code did work for me on Arch? I tested with the exact same code, same compilation command, and terminal emulators ternminator, gnome-terminal, lxterminal, xterm, all fine. I realize you can't know exactly why, you don't have access to my system, but what kind of thing would this depend on?
    – terdon
    Commented Mar 6, 2023 at 18:43
  • [@roaima I like your answer, it's correct but is there a simpler way to display the characters with printw? Printing out one character at a time seams inefficient. I'm trying to make a terminal game and it uses the characters quite allot. Also the characters that are printed from your code look thinner. Sorry if I sound picky.] [@terdon I find It very odd how the characters display properly for lxterminal on your OS but not mine? Are you using the default setting that lxterminal comes with, no customisation. Maybe it's OS dependent, idk?] Commented Mar 6, 2023 at 23:46
  • 1
    @terdon: Arch only has ncurses 6.x, which has merged libncursesw into libncurses (or rather the opposite – /lib/libncurses.so is a linker script that redirects to "-lncursesw") so the wide-character support is always present, unlike in ncurses 5.x which appears to still be the default on Debian and which had separate wide and non-wide variants of the library, as the post by Thomas Dickey mentions. Commented Mar 7, 2023 at 8:22
  • 1
    It is highly unlikely to be a terminal emulator issue; terminal emulators that receive UTF-8 data but don't know to interpret it as such would simply show all three bytes as if they were ISO 8859 characters (with the initial byte usually showing up as â â or similar) – whereas the M- (Ctrl-) notation for 8-bit characters almost always comes from the non-wide ncurses being used, i.e. the original UTF-8 data doesn't even reach the terminal. Commented Mar 7, 2023 at 8:28
  • 1
    @user1686 I don't think it's the terminal emulator either. At best it's ncurses misunderstanding what to do with an extended unicode character, but I wanted a non-UTF8 environment in which to test Commented Mar 7, 2023 at 8:53
10

On MacOS, the -lncurses links to the wide-character configuration of ncurses, which normally is provided by -lncursesw. The setlocale call gives different results in those (normal versus wide characters), and the literal string for the box-characters (which is UTF-8) comes out differently.

Further reading:

  • Values above 128 are either meta characters (if the screen has not been initialized, or if meta(3x) has been called with a TRUE parameter), shown in the M-X notation, or are displayed as themselves. In the latter case, the values may not be printable; this follows the X/Open specification.

The underlying issue is that the UTF-8 encoding for the box characters uses byte-codes above 128, and without the locale support used in the wide-character library, you'll get just that—no reassembly of the bytes into a wide-character.

Some other platforms also present -lncurses as an alias for -lncursesw, e.g., (noting a comment), Arch Linux which has this in /usr/lib/ncurses.so:

INPUT(-lncursesw)

Another comment asked about "thick lines". There are a few points here:

I only implemented WACS_xxx symbols for thick/double-lines for the usual reasons: it's experimental, and only useful with Unicode.

  • The symbols are documented in the wadd_wch manual page.
  • The feature is not available for MacOS's bundled ncurses, since that (ncurses 5.7) is about a year older than the change in ncurses.
  • It will not work for the Linux console terminal (because that relies upon a font with only 512 glyphs).
  • The actual appearance of thick/double lines is unspecified (it depends on the fonts).
3

The problem was that box character didn't display properly in a Linux terminal. The solution is that you need to compile the code differently.

If you are using printw or some other text displaying method that uses ncurses as a library, then this will apply to you.  This is OS dependent, so if you are on macOS you would use the -lncurses flag when compiling (also according to @terdon this also applies to Arch Linux, but I haven't tested that), and if you are using Linux (I've tested it only for Debian) you need to use the -lncursesw flag.

If you want to automatically make your code cross-compatible for this issue, put this in your makefile.

ifneq ($(OS),Windows_NT)
    UNAME_S := $(shell uname -s)
    ifeq ($(UNAME_S),Linux)
        NCURSES += -lncursesw
    endif
    ifeq ($(UNAME_S),Darwin)
        NCURSES += -lncurses
    endif
endif

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .