ui: better unicode support for curses, v2.

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2.0.22 (GNU/Linux)
 
 iQIcBAABAgAGBQJciLFoAAoJEEy22O7T6HE4mI8P/AknxbxN0IROgo/GX9aZMnIQ
 JiqbYxj4SSXL8P+0IqgsdGXwggGlcnpM8XQa/I1e6yEl7jHQv7sccMb4EZotRJOR
 k1zeXv4ie8SY7vMOW4MfdbjZ22FKwQ4kTAlRGUAmk1NcuqpmWTH9qwBWgegfiHYP
 DVqroDf34U32kuDmo4T+m4I5Sgn0uugrD525Z9M6yN3V0dCuPbncb297X9aPd+Ou
 xrZ50iAT3sfMNIBJU4JjEBQm+jxt2JOupWhsqLwiT7jwzo65vooLgYm4MdF3iGFv
 hvFEUkE5XdauC7eRuEsmLAtWQ7BzzEFPBZKgexDDRMDQ4ROWxbnUDNqfRmIzxHfG
 AeCuHn2/iuE4IycoDqE919LOBm/TnPb08Xe9ly7tMXS7NQGsctgruI3DOA7CCjLo
 ZoTNSHElVmmjDTS5yMWyrYMEkO+W4pjC2+7vAKfj3KvW0RYG+kG1tkWIIsZogZ7C
 XmcLAKLH9RQVH0UBC0wgHHs36fGnr3DP4WsLKdSrh3OhYXSPClNM+RD7YwTu5c3j
 +vnCgpqU3yW6Bk9oBP+tiG+KgltaS+tieoGNsvvE41pqV152WuUx9sqiK4ItA9i3
 /aEN+YE2bbWnqPAKGpzC8JiNyD75VopkxkuvKkq7TERvC+2ew0USeqfmS1GSedyg
 pYnwJoJGQa1bCBUmf/S/
 =gcY1
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kraxel/tags/ui-20190313-pull-request' into staging

ui: better unicode support for curses, v2.

# gpg: Signature made Wed 13 Mar 2019 07:29:44 GMT
# gpg:                using RSA key 4CB6D8EED3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [full]
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>" [full]
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [full]
# Primary key fingerprint: A032 8CFF B93A 17A7 9901  FE7D 4CB6 D8EE D3E8 7138

* remotes/kraxel/tags/ui-20190313-pull-request:
  curses: add option to specify VGA font encoding
  iconv: detect and make curses depend on it

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2019-03-13 20:11:06 +00:00
commit 85ce84489a
6 changed files with 348 additions and 52 deletions

60
configure vendored
View File

@ -1224,6 +1224,10 @@ for opt do
;; ;;
--enable-curses) curses="yes" --enable-curses) curses="yes"
;; ;;
--disable-iconv) iconv="no"
;;
--enable-iconv) iconv="yes"
;;
--disable-curl) curl="no" --disable-curl) curl="no"
;; ;;
--enable-curl) curl="yes" --enable-curl) curl="yes"
@ -1713,6 +1717,7 @@ disabled with --disable-FEATURE, default is enabled if available:
gtk gtk UI gtk gtk UI
vte vte support for the gtk UI vte vte support for the gtk UI
curses curses UI curses curses UI
iconv font glyph conversion support
vnc VNC UI support vnc VNC UI support
vnc-sasl SASL encryption for VNC server vnc-sasl SASL encryption for VNC server
vnc-jpeg JPEG lossy compression for VNC server vnc-jpeg JPEG lossy compression for VNC server
@ -3434,8 +3439,52 @@ EOF
fi fi
fi fi
##########################################
# iconv probe
if test "$iconv" != "no" ; then
cat > $TMPC << EOF
#include <iconv.h>
int main(void) {
iconv_t conv = iconv_open("WCHAR_T", "UCS-2");
return conv != (iconv_t) -1;
}
EOF
iconv_prefix_list="/usr/local:/usr"
iconv_lib_list=":-liconv"
IFS=:
for iconv_prefix in $iconv_prefix_list; do
IFS=:
iconv_cflags="-I$iconv_prefix/include"
iconv_ldflags="-L$iconv_prefix/lib"
for iconv_link in $iconv_lib_list; do
unset IFS
iconv_lib="$iconv_ldflags $iconv_link"
echo "looking at iconv in '$iconv_cflags' '$iconv_lib'" >> config.log
if compile_prog "$iconv_cflags" "$iconv_lib" ; then
iconv_found=yes
break
fi
done
if test "$iconv_found" = yes ; then
break
fi
done
if test "$iconv_found" = "yes" ; then
iconv=yes
else
if test "$iconv" = "yes" ; then
feature_not_found "iconv" "Install iconv devel"
fi
iconv=no
fi
fi
########################################## ##########################################
# curses probe # curses probe
if test "$iconv" = "no" ; then
# curses will need iconv
curses=no
fi
if test "$curses" != "no" ; then if test "$curses" != "no" ; then
if test "$mingw32" = "yes" ; then if test "$mingw32" = "yes" ; then
curses_inc_list="$($pkg_config --cflags ncurses 2>/dev/null):" curses_inc_list="$($pkg_config --cflags ncurses 2>/dev/null):"
@ -3449,14 +3498,17 @@ if test "$curses" != "no" ; then
#include <locale.h> #include <locale.h>
#include <curses.h> #include <curses.h>
#include <wchar.h> #include <wchar.h>
#include <langinfo.h>
int main(void) { int main(void) {
const char *codeset;
wchar_t wch = L'w'; wchar_t wch = L'w';
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
resize_term(0, 0); resize_term(0, 0);
addwstr(L"wide chars\n"); addwstr(L"wide chars\n");
addnwstr(&wch, 1); addnwstr(&wch, 1);
add_wch(WACS_DEGREE); add_wch(WACS_DEGREE);
return 0; codeset = nl_langinfo(CODESET);
return codeset != 0;
} }
EOF EOF
IFS=: IFS=:
@ -6251,6 +6303,7 @@ echo "libgcrypt $gcrypt"
echo "nettle $nettle $(echo_version $nettle $nettle_version)" echo "nettle $nettle $(echo_version $nettle $nettle_version)"
echo "libtasn1 $tasn1" echo "libtasn1 $tasn1"
echo "PAM $auth_pam" echo "PAM $auth_pam"
echo "iconv support $iconv"
echo "curses support $curses" echo "curses support $curses"
echo "virgl support $virglrenderer $(echo_version $virglrenderer $virgl_version)" echo "virgl support $virglrenderer $(echo_version $virglrenderer $virgl_version)"
echo "curl support $curl" echo "curl support $curl"
@ -6586,6 +6639,11 @@ fi
if test "$cocoa" = "yes" ; then if test "$cocoa" = "yes" ; then
echo "CONFIG_COCOA=y" >> $config_host_mak echo "CONFIG_COCOA=y" >> $config_host_mak
fi fi
if test "$iconv" = "yes" ; then
echo "CONFIG_ICONV=y" >> $config_host_mak
echo "ICONV_CFLAGS=$iconv_cflags" >> $config_host_mak
echo "ICONV_LIBS=$iconv_lib" >> $config_host_mak
fi
if test "$curses" = "yes" ; then if test "$curses" = "yes" ; then
echo "CONFIG_CURSES=m" >> $config_host_mak echo "CONFIG_CURSES=m" >> $config_host_mak
echo "CURSES_CFLAGS=$curses_inc" >> $config_host_mak echo "CURSES_CFLAGS=$curses_inc" >> $config_host_mak

View File

@ -1080,6 +1080,19 @@
{ 'enum' : 'DisplayGLMode', { 'enum' : 'DisplayGLMode',
'data' : [ 'off', 'on', 'core', 'es' ] } 'data' : [ 'off', 'on', 'core', 'es' ] }
##
# @DisplayCurses:
#
# Curses display options.
#
# @charset: Font charset used by guest (default: CP437).
#
# Since: 4.0
#
##
{ 'struct' : 'DisplayCurses',
'data' : { '*charset' : 'str' } }
## ##
# @DisplayType: # @DisplayType:
# #
@ -1142,6 +1155,7 @@
'*gl' : 'DisplayGLMode' }, '*gl' : 'DisplayGLMode' },
'discriminator' : 'type', 'discriminator' : 'type',
'data' : { 'gtk' : 'DisplayGTK', 'data' : { 'gtk' : 'DisplayGTK',
'curses' : 'DisplayCurses',
'egl-headless' : 'DisplayEGLHeadless'} } 'egl-headless' : 'DisplayEGLHeadless'} }
## ##

View File

@ -1446,7 +1446,7 @@ DEF("display", HAS_ARG, QEMU_OPTION_display,
" [,window_close=on|off][,gl=on|core|es|off]\n" " [,window_close=on|off][,gl=on|core|es|off]\n"
"-display gtk[,grab_on_hover=on|off][,gl=on|off]|\n" "-display gtk[,grab_on_hover=on|off][,gl=on|off]|\n"
"-display vnc=<display>[,<optargs>]\n" "-display vnc=<display>[,<optargs>]\n"
"-display curses\n" "-display curses[,charset=<encoding>]\n"
"-display none\n" "-display none\n"
"-display egl-headless[,rendernode=<file>]" "-display egl-headless[,rendernode=<file>]"
" select display type\n" " select display type\n"
@ -1478,6 +1478,9 @@ support a text mode, QEMU can display this output using a
curses/ncurses interface. Nothing is displayed when the graphics curses/ncurses interface. Nothing is displayed when the graphics
device is in graphical mode or if the graphics device does not support device is in graphical mode or if the graphics device does not support
a text mode. Generally only the VGA device models support text mode. a text mode. Generally only the VGA device models support text mode.
The font charset used by the guest can be specified with the
@code{charset} option, for example @code{charset=CP850} for IBM CP850
encoding. The default is @code{CP437}.
@item none @item none
Do not display video output. The guest will still see an emulated Do not display video output. The guest will still see an emulated
graphics card, but its output will not be displayed to the QEMU graphics card, but its output will not be displayed to the QEMU

View File

@ -46,8 +46,8 @@ endif
common-obj-$(CONFIG_CURSES) += curses.mo common-obj-$(CONFIG_CURSES) += curses.mo
curses.mo-objs := curses.o curses.mo-objs := curses.o
curses.mo-cflags := $(CURSES_CFLAGS) curses.mo-cflags := $(CURSES_CFLAGS) $(ICONV_CFLAGS)
curses.mo-libs := $(CURSES_LIBS) curses.mo-libs := $(CURSES_LIBS) $(ICONV_LIBS)
common-obj-$(call land,$(CONFIG_SPICE),$(CONFIG_GIO)) += spice-app.mo common-obj-$(call land,$(CONFIG_SPICE),$(CONFIG_GIO)) += spice-app.mo
spice-app.mo-objs := spice-app.o spice-app.mo-objs := spice-app.o

View File

@ -27,6 +27,10 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <termios.h> #include <termios.h>
#endif #endif
#include <locale.h>
#include <wchar.h>
#include <langinfo.h>
#include <iconv.h>
#include "qapi/error.h" #include "qapi/error.h"
#include "qemu-common.h" #include "qemu-common.h"
@ -54,25 +58,30 @@ static WINDOW *screenpad = NULL;
static int width, height, gwidth, gheight, invalidate; static int width, height, gwidth, gheight, invalidate;
static int px, py, sminx, sminy, smaxx, smaxy; static int px, py, sminx, sminy, smaxx, smaxy;
static chtype vga_to_curses[256]; static const char *font_charset = "CP437";
static cchar_t vga_to_curses[256];
static void curses_update(DisplayChangeListener *dcl, static void curses_update(DisplayChangeListener *dcl,
int x, int y, int w, int h) int x, int y, int w, int h)
{ {
console_ch_t *line; console_ch_t *line;
chtype curses_line[width]; cchar_t curses_line[width];
line = screen + y * width; line = screen + y * width;
for (h += y; y < h; y ++, line += width) { for (h += y; y < h; y ++, line += width) {
for (x = 0; x < width; x++) { for (x = 0; x < width; x++) {
chtype ch = line[x] & 0xff; chtype ch = line[x] & 0xff;
chtype at = line[x] & ~0xff; chtype at = line[x] & ~0xff;
if (vga_to_curses[ch]) { if (vga_to_curses[ch].chars[0]) {
ch = vga_to_curses[ch]; curses_line[x] = vga_to_curses[ch];
} else {
curses_line[x].chars[0] = ch;
curses_line[x].chars[1] = 0;
curses_line[x].attr = 0;
} }
curses_line[x] = ch | at; curses_line[x].attr |= at;
} }
mvwaddchnstr(screenpad, y, 0, curses_line, width); mvwadd_wchnstr(screenpad, y, 0, curses_line, width);
} }
pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1); pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1);
@ -391,6 +400,254 @@ static void curses_atexit(void)
endwin(); endwin();
} }
/* Setup wchar glyph for one UCS-2 char */
static void convert_ucs(int glyph, uint16_t ch, iconv_t conv)
{
wchar_t wch;
char *pch, *pwch;
size_t sch, swch;
pch = (char *) &ch;
pwch = (char *) &wch;
sch = sizeof(ch);
swch = sizeof(wch);
if (iconv(conv, &pch, &sch, &pwch, &swch) == (size_t) -1) {
fprintf(stderr, "Could not convert 0x%04x from UCS-2 to WCHAR_T: %s\n",
ch, strerror(errno));
} else {
vga_to_curses[glyph].chars[0] = wch;
}
}
/* Setup wchar glyph for one font character */
static void convert_font(unsigned char ch, iconv_t conv)
{
wchar_t wch;
char *pch, *pwch;
size_t sch, swch;
pch = (char *) &ch;
pwch = (char *) &wch;
sch = sizeof(ch);
swch = sizeof(wch);
if (iconv(conv, &pch, &sch, &pwch, &swch) == (size_t) -1) {
fprintf(stderr, "Could not convert 0x%02x from %s to WCHAR_T: %s\n",
ch, font_charset, strerror(errno));
} else {
vga_to_curses[ch].chars[0] = wch;
}
}
/* Convert one wchar to UCS-2 */
static uint16_t get_ucs(wchar_t wch, iconv_t conv)
{
uint16_t ch;
char *pch, *pwch;
size_t sch, swch;
pch = (char *) &ch;
pwch = (char *) &wch;
sch = sizeof(ch);
swch = sizeof(wch);
if (iconv(conv, &pwch, &swch, &pch, &sch) == (size_t) -1) {
fprintf(stderr, "Could not convert 0x%02x from WCHAR_T to UCS-2: %s\n",
wch, strerror(errno));
return 0xFFFD;
}
return ch;
}
/*
* Setup mapping for vga to curses line graphics.
*/
static void font_setup(void)
{
/*
* Control characters are normally non-printable, but VGA does have
* well-known glyphs for them.
*/
static uint16_t control_characters[0x20] = {
0x0020,
0x263a,
0x263b,
0x2665,
0x2666,
0x2663,
0x2660,
0x2022,
0x25d8,
0x25cb,
0x25d9,
0x2642,
0x2640,
0x266a,
0x266b,
0x263c,
0x25ba,
0x25c4,
0x2195,
0x203c,
0x00b6,
0x00a7,
0x25ac,
0x21a8,
0x2191,
0x2193,
0x2192,
0x2190,
0x221f,
0x2194,
0x25b2,
0x25bc
};
iconv_t ucs_to_wchar_conv;
iconv_t wchar_to_ucs_conv;
iconv_t font_conv;
int i;
ucs_to_wchar_conv = iconv_open("WCHAR_T", "UCS-2");
if (ucs_to_wchar_conv == (iconv_t) -1) {
fprintf(stderr, "Could not convert font glyphs from UCS-2: '%s'\n",
strerror(errno));
exit(1);
}
wchar_to_ucs_conv = iconv_open("UCS-2", "WCHAR_T");
if (wchar_to_ucs_conv == (iconv_t) -1) {
fprintf(stderr, "Could not convert font glyphs to UCS-2: '%s'\n",
strerror(errno));
exit(1);
}
font_conv = iconv_open("WCHAR_T", font_charset);
if (font_conv == (iconv_t) -1) {
fprintf(stderr, "Could not convert font glyphs from %s: '%s'\n",
font_charset, strerror(errno));
exit(1);
}
/* Control characters */
for (i = 0; i <= 0x1F; i++) {
convert_ucs(i, control_characters[i], ucs_to_wchar_conv);
}
for (i = 0x20; i <= 0xFF; i++) {
convert_font(i, font_conv);
}
/* DEL */
convert_ucs(0x7F, 0x2302, ucs_to_wchar_conv);
if (strcmp(nl_langinfo(CODESET), "UTF-8")) {
/* Non-Unicode capable, use termcap equivalents for those available */
for (i = 0; i <= 0xFF; i++) {
switch (get_ucs(vga_to_curses[i].chars[0], wchar_to_ucs_conv)) {
case 0x00a3:
vga_to_curses[i] = *WACS_STERLING;
break;
case 0x2591:
vga_to_curses[i] = *WACS_BOARD;
break;
case 0x2592:
vga_to_curses[i] = *WACS_CKBOARD;
break;
case 0x2502:
vga_to_curses[i] = *WACS_VLINE;
break;
case 0x2524:
vga_to_curses[i] = *WACS_RTEE;
break;
case 0x2510:
vga_to_curses[i] = *WACS_URCORNER;
break;
case 0x2514:
vga_to_curses[i] = *WACS_LLCORNER;
break;
case 0x2534:
vga_to_curses[i] = *WACS_BTEE;
break;
case 0x252c:
vga_to_curses[i] = *WACS_TTEE;
break;
case 0x251c:
vga_to_curses[i] = *WACS_LTEE;
break;
case 0x2500:
vga_to_curses[i] = *WACS_HLINE;
break;
case 0x253c:
vga_to_curses[i] = *WACS_PLUS;
break;
case 0x256c:
vga_to_curses[i] = *WACS_LANTERN;
break;
case 0x256a:
vga_to_curses[i] = *WACS_NEQUAL;
break;
case 0x2518:
vga_to_curses[i] = *WACS_LRCORNER;
break;
case 0x250c:
vga_to_curses[i] = *WACS_ULCORNER;
break;
case 0x2588:
vga_to_curses[i] = *WACS_BLOCK;
break;
case 0x03c0:
vga_to_curses[i] = *WACS_PI;
break;
case 0x00b1:
vga_to_curses[i] = *WACS_PLMINUS;
break;
case 0x2265:
vga_to_curses[i] = *WACS_GEQUAL;
break;
case 0x2264:
vga_to_curses[i] = *WACS_LEQUAL;
break;
case 0x00b0:
vga_to_curses[i] = *WACS_DEGREE;
break;
case 0x25a0:
vga_to_curses[i] = *WACS_BULLET;
break;
case 0x2666:
vga_to_curses[i] = *WACS_DIAMOND;
break;
case 0x2192:
vga_to_curses[i] = *WACS_RARROW;
break;
case 0x2190:
vga_to_curses[i] = *WACS_LARROW;
break;
case 0x2191:
vga_to_curses[i] = *WACS_UARROW;
break;
case 0x2193:
vga_to_curses[i] = *WACS_DARROW;
break;
case 0x23ba:
vga_to_curses[i] = *WACS_S1;
break;
case 0x23bb:
vga_to_curses[i] = *WACS_S3;
break;
case 0x23bc:
vga_to_curses[i] = *WACS_S7;
break;
case 0x23bd:
vga_to_curses[i] = *WACS_S9;
break;
}
}
}
}
static void curses_setup(void) static void curses_setup(void)
{ {
int i, colour_default[8] = { int i, colour_default[8] = {
@ -420,47 +677,7 @@ static void curses_setup(void)
init_pair(i, COLOR_WHITE, COLOR_BLACK); init_pair(i, COLOR_WHITE, COLOR_BLACK);
} }
/* font_setup();
* Setup mapping for vga to curses line graphics.
* FIXME: for better font, have to use ncursesw and setlocale()
*/
#if 0
/* FIXME: map from where? */
ACS_S1;
ACS_S3;
ACS_S7;
ACS_S9;
#endif
/* ACS_* is not constant. So, we can't initialize statically. */
vga_to_curses['\0'] = ' ';
vga_to_curses[0x04] = ACS_DIAMOND;
vga_to_curses[0x18] = ACS_UARROW;
vga_to_curses[0x19] = ACS_DARROW;
vga_to_curses[0x1a] = ACS_RARROW;
vga_to_curses[0x1b] = ACS_LARROW;
vga_to_curses[0x9c] = ACS_STERLING;
vga_to_curses[0xb0] = ACS_BOARD;
vga_to_curses[0xb1] = ACS_CKBOARD;
vga_to_curses[0xb3] = ACS_VLINE;
vga_to_curses[0xb4] = ACS_RTEE;
vga_to_curses[0xbf] = ACS_URCORNER;
vga_to_curses[0xc0] = ACS_LLCORNER;
vga_to_curses[0xc1] = ACS_BTEE;
vga_to_curses[0xc2] = ACS_TTEE;
vga_to_curses[0xc3] = ACS_LTEE;
vga_to_curses[0xc4] = ACS_HLINE;
vga_to_curses[0xc5] = ACS_PLUS;
vga_to_curses[0xce] = ACS_LANTERN;
vga_to_curses[0xd8] = ACS_NEQUAL;
vga_to_curses[0xd9] = ACS_LRCORNER;
vga_to_curses[0xda] = ACS_ULCORNER;
vga_to_curses[0xdb] = ACS_BLOCK;
vga_to_curses[0xe3] = ACS_PI;
vga_to_curses[0xf1] = ACS_PLMINUS;
vga_to_curses[0xf2] = ACS_GEQUAL;
vga_to_curses[0xf3] = ACS_LEQUAL;
vga_to_curses[0xf8] = ACS_DEGREE;
vga_to_curses[0xfe] = ACS_BULLET;
} }
static void curses_keyboard_setup(void) static void curses_keyboard_setup(void)
@ -493,6 +710,10 @@ static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
} }
#endif #endif
setlocale(LC_CTYPE, "");
if (opts->u.curses.charset) {
font_charset = opts->u.curses.charset;
}
curses_setup(); curses_setup();
curses_keyboard_setup(); curses_keyboard_setup();
atexit(curses_atexit); atexit(curses_atexit);

2
vl.c
View File

@ -3201,7 +3201,7 @@ int main(int argc, char **argv, char **envp)
#ifdef CONFIG_CURSES #ifdef CONFIG_CURSES
dpy.type = DISPLAY_TYPE_CURSES; dpy.type = DISPLAY_TYPE_CURSES;
#else #else
error_report("curses support is disabled"); error_report("curses or iconv support is disabled");
exit(1); exit(1);
#endif #endif
break; break;