summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2019-03-13 20:11:06 +0000
committerPeter Maydell <peter.maydell@linaro.org>2019-03-13 20:11:06 +0000
commit85ce84489a74d4c6046345c7baac94f0a3ffd88f (patch)
treedc00f1fb5439c488de78f9fcacd43cff6d9fec7f
parent3b5b6e9b513e239da0074ba7fcb5b92a4bf375d3 (diff)
parent2f8b7cd587558944532f587abb5203ce54badba9 (diff)
downloadqemu-85ce84489a74d4c6046345c7baac94f0a3ffd88f.zip
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>
-rwxr-xr-xconfigure60
-rw-r--r--qapi/ui.json14
-rw-r--r--qemu-options.hx5
-rw-r--r--ui/Makefile.objs4
-rw-r--r--ui/curses.c315
-rw-r--r--vl.c2
6 files changed, 348 insertions, 52 deletions
diff --git a/configure b/configure
index 45a3654c5b..abe453f8fd 100755
--- a/configure
+++ b/configure
@@ -1224,6 +1224,10 @@ for opt do
;;
--enable-curses) curses="yes"
;;
+ --disable-iconv) iconv="no"
+ ;;
+ --enable-iconv) iconv="yes"
+ ;;
--disable-curl) curl="no"
;;
--enable-curl) curl="yes"
@@ -1713,6 +1717,7 @@ disabled with --disable-FEATURE, default is enabled if available:
gtk gtk UI
vte vte support for the gtk UI
curses curses UI
+ iconv font glyph conversion support
vnc VNC UI support
vnc-sasl SASL encryption for VNC server
vnc-jpeg JPEG lossy compression for VNC server
@@ -3435,7 +3440,51 @@ EOF
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
+if test "$iconv" = "no" ; then
+ # curses will need iconv
+ curses=no
+fi
if test "$curses" != "no" ; then
if test "$mingw32" = "yes" ; then
curses_inc_list="$($pkg_config --cflags ncurses 2>/dev/null):"
@@ -3449,14 +3498,17 @@ if test "$curses" != "no" ; then
#include <locale.h>
#include <curses.h>
#include <wchar.h>
+#include <langinfo.h>
int main(void) {
+ const char *codeset;
wchar_t wch = L'w';
setlocale(LC_ALL, "");
resize_term(0, 0);
addwstr(L"wide chars\n");
addnwstr(&wch, 1);
add_wch(WACS_DEGREE);
- return 0;
+ codeset = nl_langinfo(CODESET);
+ return codeset != 0;
}
EOF
IFS=:
@@ -6251,6 +6303,7 @@ echo "libgcrypt $gcrypt"
echo "nettle $nettle $(echo_version $nettle $nettle_version)"
echo "libtasn1 $tasn1"
echo "PAM $auth_pam"
+echo "iconv support $iconv"
echo "curses support $curses"
echo "virgl support $virglrenderer $(echo_version $virglrenderer $virgl_version)"
echo "curl support $curl"
@@ -6586,6 +6639,11 @@ fi
if test "$cocoa" = "yes" ; then
echo "CONFIG_COCOA=y" >> $config_host_mak
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
echo "CONFIG_CURSES=m" >> $config_host_mak
echo "CURSES_CFLAGS=$curses_inc" >> $config_host_mak
diff --git a/qapi/ui.json b/qapi/ui.json
index c5d1d7f099..59e412139a 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1081,6 +1081,19 @@
'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:
#
# Display (user interface) type.
@@ -1142,6 +1155,7 @@
'*gl' : 'DisplayGLMode' },
'discriminator' : 'type',
'data' : { 'gtk' : 'DisplayGTK',
+ 'curses' : 'DisplayCurses',
'egl-headless' : 'DisplayEGLHeadless'} }
##
diff --git a/qemu-options.hx b/qemu-options.hx
index 8693f5fa3c..08749a3391 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1446,7 +1446,7 @@ DEF("display", HAS_ARG, QEMU_OPTION_display,
" [,window_close=on|off][,gl=on|core|es|off]\n"
"-display gtk[,grab_on_hover=on|off][,gl=on|off]|\n"
"-display vnc=<display>[,<optargs>]\n"
- "-display curses\n"
+ "-display curses[,charset=<encoding>]\n"
"-display none\n"
"-display egl-headless[,rendernode=<file>]"
" 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
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.
+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
Do not display video output. The guest will still see an emulated
graphics card, but its output will not be displayed to the QEMU
diff --git a/ui/Makefile.objs b/ui/Makefile.objs
index fe1a7aed97..cc2bf5b180 100644
--- a/ui/Makefile.objs
+++ b/ui/Makefile.objs
@@ -46,8 +46,8 @@ endif
common-obj-$(CONFIG_CURSES) += curses.mo
curses.mo-objs := curses.o
-curses.mo-cflags := $(CURSES_CFLAGS)
-curses.mo-libs := $(CURSES_LIBS)
+curses.mo-cflags := $(CURSES_CFLAGS) $(ICONV_CFLAGS)
+curses.mo-libs := $(CURSES_LIBS) $(ICONV_LIBS)
common-obj-$(call land,$(CONFIG_SPICE),$(CONFIG_GIO)) += spice-app.mo
spice-app.mo-objs := spice-app.o
diff --git a/ui/curses.c b/ui/curses.c
index 37954ce1b0..3a7e8649f3 100644
--- a/ui/curses.c
+++ b/ui/curses.c
@@ -27,6 +27,10 @@
#include <sys/ioctl.h>
#include <termios.h>
#endif
+#include <locale.h>
+#include <wchar.h>
+#include <langinfo.h>
+#include <iconv.h>
#include "qapi/error.h"
#include "qemu-common.h"
@@ -54,25 +58,30 @@ static WINDOW *screenpad = NULL;
static int width, height, gwidth, gheight, invalidate;
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,
int x, int y, int w, int h)
{
console_ch_t *line;
- chtype curses_line[width];
+ cchar_t curses_line[width];
line = screen + y * width;
for (h += y; y < h; y ++, line += width) {
for (x = 0; x < width; x++) {
chtype ch = line[x] & 0xff;
chtype at = line[x] & ~0xff;
- if (vga_to_curses[ch]) {
- ch = vga_to_curses[ch];
+ if (vga_to_curses[ch].chars[0]) {
+ 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);
@@ -391,6 +400,254 @@ static void curses_atexit(void)
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)
{
int i, colour_default[8] = {
@@ -420,47 +677,7 @@ static void curses_setup(void)
init_pair(i, COLOR_WHITE, COLOR_BLACK);
}
- /*
- * 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;
+ font_setup();
}
static void curses_keyboard_setup(void)
@@ -493,6 +710,10 @@ static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
}
#endif
+ setlocale(LC_CTYPE, "");
+ if (opts->u.curses.charset) {
+ font_charset = opts->u.curses.charset;
+ }
curses_setup();
curses_keyboard_setup();
atexit(curses_atexit);
diff --git a/vl.c b/vl.c
index 027b853d92..c1d5484e12 100644
--- a/vl.c
+++ b/vl.c
@@ -3201,7 +3201,7 @@ int main(int argc, char **argv, char **envp)
#ifdef CONFIG_CURSES
dpy.type = DISPLAY_TYPE_CURSES;
#else
- error_report("curses support is disabled");
+ error_report("curses or iconv support is disabled");
exit(1);
#endif
break;