0

I work on a game in Ruby. It uses ncurses to draw window contents and refresh the screen. This all works fine on macOS, where I develop the stuff. But on Linux, I cannot get anything besides plain ASCII to print when interfacing with ncurses either from Ruby or plain C.

For example, I get this output:

Hello, world! M-b~U~TM-b~U~PM-b~U~WM-b~U~QM-b~U~QM-b~U~ZM-b~U~PM-b~U~]M-b~U~_M-b~T~@M-b~UM-"

Instead of what I put into the source:

Hello, world! ╔═╗║║╚═╝╟─╢

File encoding is UTF-8, env or locale output reports LANG and LC_* variables to be en_US or en_US.UTF-8 where appropriate. The terminal can print these characters just fine, so it's not the font or terminal emulator setting.

Python 3 works fine, too. Ruby and C don't.

(Tested on a fresh Linux Mint 19 setup with libncurses5-dev installed.)

So, Am I missing some setting or is the Python binding somewhat special and I'm out of luck?


What it looks like

It should look like this (macOS):

macOS reference pic

But does look like this instead:

ruby on linux

Python Code

This code works just fine:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import locale
import curses

locale.setlocale(locale.LC_ALL, '')

stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.addstr(0, 0, "╔═╗║║╚═╝╟─╢") # dont even need .encode('UTF-8')
stdscr.refresh()
stdscr.getkey()
curses.endwin()

The docs for the curses module state that you have to do this since ncurses 5: https://docs.python.org/3/library/curses.html

Since version 5.4, the ncurses library decides how to interpret non-ASCII data using the nl_langinfo function. That means that you have to call locale.setlocale() in the application and encode Unicode strings using one of the system’s available encodings.

Ruby Code

# coding: utf-8
require "curses"

Curses.init_screen
Curses.start_color
Curses.stdscr.keypad(true) # enable arrow keys
Curses.cbreak # no line buffering / immediate key input
Curses.ESCDELAY = 0
Curses.curs_set(0) # Invisible cursor
Curses.noecho # Do not print keyboard input

Curses.stdscr.addstr(STYLES[:single])
Curses.stdscr.setpos(2,0)
Curses.stdscr.addstr(%Q{╔═╗║║╚═╝╟─╢})

Curses.getch

Tried with the ffi-ncurses gem instead of curses but to no avail. The output is the same.

C Code

I compile with

gcc -finput-charset=UTF-8 -fexec-charset=UTF-8 -pedantic -Wall -o main main.c -lncurses

Here's the code:

// Most of the code is sample code from "CURHELLO.C"
// (c) Copyright Paul Griffiths 1999

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <langinfo.h>
#include <locale.h>
#include <ncurses.h>

int main(void) {

    // Here I tried to copy what Python is doing:
    setlocale(LC_ALL, ""); // Also tried "C.UTF-8", "en_US.UTF-8"
    nl_langinfo(CODESET);


    WINDOW * mainwin;
    if ( (mainwin = initscr()) == NULL ) {
        fprintf(stderr, "Error initialising ncurses.\n");
        exit(EXIT_FAILURE);
    }

    start_color();
    clear();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);

    mvaddstr(1, 1, "Hello, world! ╔═╗║║╚═╝╟─╢");
    refresh();
    sleep(3);

    delwin(mainwin);
    endwin();
    refresh();

    return EXIT_SUCCESS;
}

Edit

Got it working by linking to ncursesw (note the trailing W!), but it displays the special characters at double the width of text on Linux, using Ubuntu Mono, the same font I tried on OSX with iTerm.

enter image description here

ctietze
  • 2,805
  • 25
  • 46
  • 1
    Link the C code against `ncursesw`, and it'll work. (Although ncursesw adds new types and interfaces for wide characters, it also adds UTF-8 multibyte support to the standard ncurses functions.) – Nominal Animal Sep 20 '18 at 17:46
  • 1
    Also, Ruby should prefer ncursesw over ncurses, so you probably don't have the ncursesw package installed. The library is provided by `libncursesw5` on Debian, Ubuntu, Mint (and you are probably missing that). If you want to try also the additional interfaces in ncursesw in C, also install the `libncursesw5-dev` package. Finally, I recommend you use `gcc -Wall -O2 $(pkg-config --cflags ncursesw) main.c $(pkg-config --libs ncursesw) -o main` to compile the C program. – Nominal Animal Sep 20 '18 at 17:53
  • @NominalAnimal thanks for the C compiler ideas and for pointing me towards ncursesw! Sure enough, the "wide character" support is what I should've been looking for. I accidentally found this when I peeked at the Python code last night and [changed ruby/curses to prefer ncursesw if present](https://christiantietze.de/posts/2018/09/ruby-ncurses-macos-linux/). Box drawing characters are displayed at double width, though, with the same font I use on macOS (Ubuntu Mono). What could be affecting this? – ctietze Sep 21 '18 at 08:08
  • Which OS does the double-width box-drawing character issue occur on? I don't use macOS, so unfortunately I cannot help with that.. all I know is that the curses libraries on macOS tend to be quite old, unless newer ones are installed via macports. – Nominal Animal Sep 21 '18 at 14:50
  • @NominalAnimal the double-width characters are displayed on Linux Mint, actually. See picture in the post abov – ctietze Sep 21 '18 at 19:36
  • 1
    I just checked, and noticed that the block element characters, like `▌` `▙` `▚` `█` `▞` `▟` `▐`, are not monospaced in the Ubuntu Mono, Oxygen Mono, Noto Mono, or Linux Libertine Mono O fonts: even if they claim to be monospaced fonts, they are not so for block elements! On my system, Nimbus Mono L, Monospace, Liberation Mono, FreeMono, DejaVu Sans Mono, Bitstream Vera Sans Mono, and Andale Mono fonts seem to have proper monospaced glyphs even for Box Drawing and Block Elements, so I suggest you use one of those fonts instead. (And mention the issue and fonts in your documentation!) – Nominal Animal Sep 21 '18 at 20:17
  • Also forgot to mention: If you install the `gucharmap` package, you get an accessory program named *Character Map*. You can use it to explore the Unicode blocks -- Box Drawing, Block Elements, Geometric Shapes, and Dingbats in particular --, using various fonts, and also copy them to paste into your source code. – Nominal Animal Sep 21 '18 at 20:29
  • I had no such luck changing fonts in the Terminal application Will try other terminal emulators and things like that – ctietze Sep 24 '18 at 11:51
  • 1
    You cannot change the font from a program run inside a terminal; you must instruct the user to change it. For gnome-terminal, select *Edit*, then *Profile preferences*, then *General* tab, check *Custom font* checkbox, and then select the font using the button on the right of the checkbox. In mate-terminal, it's at the same tab, but you need to uncheck the *Use system font* to be able to select the font. I have never used an X terminal emulator that did not let me select the font. – Nominal Animal Sep 24 '18 at 16:22
  • Sorry I wasn't clear: I can customize the font used in my terminal emulator, but the ones you mention do not change the width of the box drawing characters. Things do look different, but none display box drawing characters at single width. I wonder if pursuing this is worth the effort compared to simple cross-platform bitmap drawing :) – ctietze Sep 25 '18 at 17:21
  • 1
    Try running `printf '0123456789012345678\n╔═╗║║╚═╝╟─╢▌▙▚█▞▟▐|\n'` in the terminal to see if it truly is a font issue. If you ask me, I'd use SDL or GTK+3.0 instead, with tiles in SVG format. – Nominal Animal Sep 25 '18 at 22:58

1 Answers1

3

Actually this should be several questions, but the most recent one asks how the characters could be shown as double-width.

The screenshot does not match the given example program. That only draws a few sample characters, while the screenshot shows boxes.

However, the particular line-drawing characters can be the problem, versus the locale tables (and depending on the actual program used). That's because those codes are ambiguous width (see U+2550). The standard leaves it up to the implementer whether to treat them as single-width or double-width. Not everyone agrees on the actual width.

If this were a bug report, I'd get the actual program source to test with, but suspect the problem lies in Ruby.

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
  • Ah, now I see. Some monospace fonts like Ubuntu Mono, Oxygen Mono, Noto Mono, and Linux Libertine Mono O fonts use different widths for Block Elements as they do for Box Drawing glyphs, because someone on the Unicode board thought that some of them might be used in some East Asian scripts, so it'll be better to screw the monospace-width for everyone, so the font is basically useless for box drawing and block elements. Design by committee... – Nominal Animal Sep 21 '18 at 20:33
  • agreed - makes it harder to use those characters, since none of the committees gave any thought to usage. – Thomas Dickey Sep 21 '18 at 20:40