前言
同事遇到在ncurses库编程时,中文乱码的问题。
他实验时,要退到ncurses-5.8版本,才能正常显示中文,但是panel库还是中文乱码。
今天手头的事整完了,我也看看这个问题。
其实中文乱码这事,一想就不可能,这库发展这么多年,还有人用这库做控制台版本的游戏。
那utf8支持是必然的,连和用户交互的信息提示都不支持国际化,那还玩啥啊。
公司里的同事都没玩过ncurses, 都是从头找资料来学。
网上有一篇NCURSES编程入门.pdf, 刚开始学时,是要先看下这个文档,知道api的大致用法。
但是这个文档只讲api的用法,没有讲库编译,代码页,国际化的问题。
可能的中文乱码原因:
* 使用了不支持utf8的库版本引起的问题。
* 库连接错了
* 也有可能连接的库和头文件不对应.
总之,有问题时,就要看官方文档+官方例子+自己实验,自己去找到底问题出哪了。在出现问题的情况下,再抱着旧的或第三方做的公开资料看,对排错就没啥效果了。
当前最新的版本时ncurses-6.1
遇到问题时,还是要看官方文档(README, INSTALL), 然后做些实验试一试。
具体api的用法,可能由于版本的不同,也要去看随源码发布的例子工程.
实验
解开源码包
root@localhost:/home/test# pwd
/home/test
root@localhost:/home/test# tar -xzvf ./ncurses-6.1.tar.gz
root@localhost:/home/test# ls
ncurses-6.1 ncurses-6.1.tar.gz
root@localhost:/home/test# cd ncurses-6.1/
编译源码工程
先看README,知道要去看INSTALL
在README中,作者特意提到:
- 要有配置选项 --enable-widec 才支持宽字符文本
- 为了防止库连接库,影响到自己的程序, 或覆盖已有的不提供版本的库,影响到其他人的程序(如果系统中已经安装了其他版本的ncurses库), 可以指定配置 --enable-reentrant 生成不同的库名称
在INSTALL中看到: - 为了使用宽字符文本,要包含的头文件也不同
the normal ncurses
header would be included using
#include <ncurses/curses.h>
#include <ncurses/term.h>
while the ncursesw headers would be found this way:
#include <ncursesw/curses.h>
#include <ncursesw/term.h>
看了官方文档,就大概知道中文乱码的原因了.
确定配置选项
看INSTALL中, configure的配置选项很多, --enable-widec --enable-reentrant 肯定要选, 其他的看INSTALL中的说明,适当选一下。
查看可用的配置选项
./configure --help
...
--enable-widec compile with wide-char/UTF-8 code
--enable-reentrant compile with reentrant code
./configure --prefix=/home/ncurses-6.1-build --enable-widec --enable-reentrant
...
** Configuration summary for NCURSES 6.1 20180127:
extended funcs: yes
xterm terminfo: xterm-new
bin directory: /home/ncurses-6.1-build/bin
lib directory: /home/ncurses-6.1-build/lib
include directory: /home/ncurses-6.1-build/include/ncursestw
man directory: /home/ncurses-6.1-build/share/man
terminfo directory: /home/ncurses-6.1-build/share/terminfo
** Include-directory is not in a standard location
编译工程
make
...
compiling demo (obj_s)
/usr/bin/g++ -o demo ../objects/demo.o -L../lib -lncurses++tw -L../lib -lformtw -lmenutw -lpaneltw -lncursestw -lutil -DHAVE_CONFIG_H -I../c++ -I. -I../include -D_GNU_SOURCE -D_DEFAULT_SOURCE -DNDEBUG -O2
make[1]: Leaving directory '/home/test/ncurses-6.1/c++'
root@localhost:/home/test/ncurses-6.1#
可以看到编译出来的库为命名为xtw, 也能看到要编译一个宽字符版的ncurses工程,要连接的库为 -lncurses++tw -L…/lib -lformtw -lmenutw -lpaneltw -lncursestw -lutil
按照说明,运行测试程序./test/x, 看看能否运行
看到报错信息:
root@localhost:/home/test/ncurses-6.1/test# ./background
Error opening terminal: xterm.
去查INSTALL中关于xterm的配置选项, 需要加入–without-xterm-new
重新配置,编译,测试
./configure --prefix=/home/ncurses-6.1-build --enable-widec --enable-reentrant --without-xterm-new
...
** Configuration summary for NCURSES 6.1 20180127:
extended funcs: yes
xterm terminfo: xterm-old
bin directory: /home/ncurses-6.1-build/bin
lib directory: /home/ncurses-6.1-build/lib
include directory: /home/ncurses-6.1-build/include/ncursestw
man directory: /home/ncurses-6.1-build/share/man
terminfo directory: /home/ncurses-6.1-build/share/terminfo
** Include-directory is not in a standard location
可以看到现在使用旧版xterm
make
...
compiling demo (obj_s)
/usr/bin/g++ -o demo ../objects/demo.o -L../lib -lncurses++tw -L../lib -lformtw -lmenutw -lpaneltw -lncursestw -lutil -DHAVE_CONFIG_H -I../c++ -I. -I../include -D_GNU_SOURCE -D_DEFAULT_SOURCE -DNDEBUG -O2
make[1]: Leaving directory '/home/test/ncurses-6.1/c++'
测试
cd ./test
root@localhost:/home/test/ncurses-6.1/test# ./background
Error opening terminal: xterm.
root@localhost:/home/test/ncurses-6.1/test# ./lrtest
Error opening terminal: xterm.
root@localhost:/home/test/ncurses-6.1/test# aptitude install xterm
看来不是xterm版本问题,而是我实验的系统中没有装xterm。
root@localhost:/home/test/ncurses-6.1/test# aptitude install xterm
root@localhost:/home/test/ncurses-6.1/test# apt-get install xterm
Reading package lists... Done
Building dependency tree
Reading state information... Done
xterm is already the newest version.
root@localhost:/home/test/ncurses-6.1/test# xterm
Warning: This program is an suid-root program or is being run by the root user.
The full text of the error or warning message cannot be safely formatted
in this environment. You may get a more descriptive message by running the
program as a non-root user or by removing the suid bit on the executable.
xterm: Xt error: Can't open display: %s
xterm: DISPLAY is not set
Is X11 forwarding enabled in your sshd config?
grep -i x11 /etc/ssh/sshd_config
You should have:
X11Forwarding yes
on debian
/etc/init.d/ssh restart
用putty选上x11选项,再去连接,也打不开xtrm.
最后做了一个实验,在xwindow桌面上右击打开控制台,在这个控制台上运行库自带的编译好的程序,是正常运行的。
然后,按ALT+CTRL+F1, 从xwindow桌面切到本地控制台, 这时运行库自带的编译好的程序,也是打不带xterm.
我又用xterm直接做了测试。
按下ALT+CTRL+F7, 从本地控制台切回xwindow, 在xwindow中的shell窗口运行xterm,是正常的。
再按下ALT+CTRL+F1,从xwindow桌面切到本地控制台, 这时,运行xterm是不行的,报错信息一样。
这说明xterm只能在xwindow下用,这个加入xterm支持的版本真坑人。
为了能使用此库来显示utf8文本信息,需要从2018年1月的这个最新版本往前找一个稳定的发布版,在那个版本上编译utf-8支持和实验.
用配置 ./configure --prefix=/home/my-ncurses-build --enable-widec --enable-reentrant
退到5.8还不行(还报xterm打不开),醉了。
突然想到,先make install 然后再实验测试程序的效果,这下可以了。INSTALL写的不靠谱啊.
人家6.1版本就是可以的 ?
先用库自带的测试程序看看中文(宽字符)是否能显示输入正常, 结果是可以。
测试程序目录为 ./test/, 测试宽字符的程序如下
./test/inch_wide
./test/ins_wide
./form_driver_w
想写个测试程序,看看panel是否显示中文正常.这是本次实验的目的.
我想一定也是正常的吧? 要不就不科学了:)
测试程序
测试过了,在panel中也能正常显示中文
测试的效果
测试工程下载
src_test_ncurses_panel.7z
实验环境:debian8.8 + ncurses-6.1
实验目的:验证ncurses-6.1的编译选项支持宽字符后,工程中可以使用utf8版的中文字符串.
测试代码
// @file main.cpp
// @note test chinise text ncurses panel, see if display ok?
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include "ncursestw/panel.h"
#define NLINES 10
#define NCOLS 40
void init_wins(WINDOW **wins, int n);
void win_show(WINDOW *win, char *label, int label_color);
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color);
int main()
{ WINDOW *my_wins[3];
PANEL *my_panels[3];
PANEL *top;
int ch;
// 系统的代码页描述在 /etc/default/locale
// 查看系统的代码页
// root@localhost:/home/dev/test_ncurses_panel# cat /etc/default/locale
// # File generated by update-locale
// LANG="en_US.UTF-8"
// setlocale(LC_ALL, ""); // 为了显示中文, 必须有这句设置代码页, 没填写就按/etc/default/locale走
setlocale(LC_ALL, "en_US.UTF-8"); // 也可以自己填写代码页, 如果填的不对, 会显示乱码
// setlocale(LC_ALL, "chs"); // 这个就乱码, 因为本实现的编码是utf8
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize all the colors */
init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_GREEN, COLOR_BLACK);
init_pair(3, COLOR_BLUE, COLOR_BLACK);
init_pair(4, COLOR_CYAN, COLOR_BLACK);
init_wins(my_wins, 3);
/* Attach a panel to each window */ /* Order is bottom up */
my_panels[0] = new_panel(my_wins[0]); /* Push 0, order: stdscr-0 */
my_panels[1] = new_panel(my_wins[1]); /* Push 1, order: stdscr-0-1 */
my_panels[2] = new_panel(my_wins[2]); /* Push 2, order: stdscr-0-1-2 */
/* Set up the user pointers to the next panel */
set_panel_userptr(my_panels[0], my_panels[1]);
set_panel_userptr(my_panels[1], my_panels[2]);
set_panel_userptr(my_panels[2], my_panels[0]);
/* Update the stacking order. 2nd panel will be on top */
update_panels();
/* Show it on the screen */
attron(COLOR_PAIR(4));
// LINES 代表console的底部行数, 库自带的宏
mvprintw(LINES - 1, 0, "使用TAB键切换面板, F1键退出程序");
attroff(COLOR_PAIR(4));
doupdate();
top = my_panels[2];
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case 9:
top = (PANEL *)panel_userptr(top);
top_panel(top);
break;
}
update_panels();
doupdate();
}
endwin();
return 0;
}
/* Put all the windows */
void init_wins(WINDOW **wins, int n)
{ int x, y, i;
char label[80];
y = 2;
x = 10;
for(i = 0; i < n; ++i)
{ wins[i] = newwin(NLINES, NCOLS, y, x);
sprintf(label, "窗口号码 【%d】", i + 1);
win_show(wins[i], label, i + 1);
y += 3;
x += 7;
}
}
/* Show the window with a border and a label */
void win_show(WINDOW *win, char *label, int label_color)
{
int startx = 0;
int starty = 0;
int height = 0;
int width = 0;
getbegyx(win, starty, startx);
getmaxyx(win, height, width);
box(win, 0, 0);
mvwaddch(win, 2, 0, ACS_LTEE);
mvwhline(win, 2, 1, ACS_HLINE, width - 2);
mvwaddch(win, 2, width - 1, ACS_RTEE);
print_in_middle(win, 1, 0, width, label, COLOR_PAIR(label_color));
}
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color)
{ int length, x, y;
float temp;
if(win == NULL)
win = stdscr;
getyx(win, y, x);
if(startx != 0)
x = startx;
if(starty != 0)
y = starty;
if(width == 0)
width = 80;
length = strlen(string);
temp = (width - length)/ 2;
x = startx + (int)temp;
wattron(win, color);
mvwprintw(win, y, x, "%s", string);
wattroff(win, color);
refresh();
}
# ==============================================================================
# makefile
# command list:
# make rebuild
# ==============================================================================
CC = g++
# -Werror
CFLAGS = --std=c++11 \
-Wall \
-g
BIN = test_ncurses_panel
# ./configure --prefix=/home/ncurses-6.1-build --enable-widec --enable-reentrant
INC_PATH = -I./ \
-I/home/ncurses-6.1-build/include/ \
-I/home/ncurses-6.1-build/include/ncursestw/
LIBPATH = -L/home/ncurses-6.1-build/lib/
LIBS = -lstdc++ \
-pthread \
-lncurses++tw -lformtw -lmenutw -lpaneltw -lncursestw \
DEPEND_CODE_DIR = ./empty_dir
DEPEND_CODE_SRC = $(shell find $(DEPEND_CODE_DIR) -name '*.cpp')
DEPEND_CODE_OBJ = $(DEPEND_CODE_SRC:.cpp=.o)
ROOT_CODE_SRC = $(wildcard *.cpp)
ROOT_CODE_OBJ = $(ROOT_CODE_SRC:.cpp=.o)
SUB_CODE_DIR = ./empty_dir/
SUB_CODE_SRC = $(shell find $(SUB_CODE_DIR) -name '*.cpp')
SUB_CODE_OBJ = $(SUB_CODE_SRC:.cpp=.o)
all:$(BIN)
@echo Makefile all...
@echo **==============================================================================
if [ -f $(BIN) ] ; \
then \
cp $(BIN) ../bin/ ; \
echo "build ok :)" ; \
else \
echo "build failed :(" ; \
fi;
@echo **==============================================================================
$(BIN): $(ROOT_CODE_OBJ) $(DEPEND_CODE_OBJ) $(SUB_CODE_OBJ)
${CC} ${CFLAGS} ${INC_PATH} \
$(ROOT_CODE_OBJ) $(DEPEND_CODE_OBJ) $(SUB_CODE_OBJ) \
${LIBS} ${LIBPATH} \
-o ${BIN} \
.cpp.o:
@echo $<
@echo build $^ ...
${CC} ${CFLAGS} ${INC_PATH} -c $^ -o $@
help:
clear
@echo make help
@echo make rebuild
clean:
clear
@echo ================================================================================
rm -f $(BIN)
@echo ================================================================================
rm -f $(ROOT_CODE_OBJ)
@echo ================================================================================
rm -f $(DEPEND_CODE_OBJ)
@echo ================================================================================
rm -f $(SUB_CODE_OBJ)
@echo ================================================================================
rebuild:
make clean
make all
rebuild_and_run:
make rebuild
./$(BIN)
2018-10-27
编译好程序后,换一台计算机运行,会有报错如下:
root@localhost:/usr/local/bin# ./test_ncurses
Error opening terminal: xterm.
这时,应该在xshell登陆后,输入如下语句,设置环境变量后,test_ncurses运行正常.
export TERM=vt100
export TERMINFO=/usr/share/terminfo