因为项目需要,本人接到一个任务:开发一个linux控制台程序,C/S架构,客户端连接服务端后,用户在界面输入命令,发给服务端,并显示服务端的响应内容。这个任务的难点在客户端,类似xshell、securecrt的远程操作功能。在网上查找资料得知,这类程序一般使用ncurses库开发,ncurses库接口比较繁琐,不像MFC之类的Windows开发工具,提供简单易用的界面控件。
本文不介绍nucuses库和接口的具体用法,网上的资料也比较琐碎,如果感兴趣可以参考:https://download.youkuaiyun.com/download/willy16/20199446
这里将项目的界面部分代码贴出来,留作纪念,如果能帮助同行当然最好,这类程序有点偏门不好找。
这个程序把窗口分成两部分:底下部分提供用户输入,可以自动换行,支持上下左右键、BackSpace键、Tab键等;上半部分显示用户输入的内容(扩展一下就可以显示服务端发来的消息,还可以设置字体颜色),大概效果如下图:
下面是具体代码,可以直接编译,比如代码保存为test.c,编译命令:gcc test.c -o test -lncurses。
//控制台客户端
#include <signal.h>
#include <ncurses.h>
#include <sys/ioctl.h>
#define CMD_FLAG "<cmd> "
#define FLAG_LEN 6
#define MAX_Y (LINES - 1)
#define KEY_CTRL_C 0x03
#define KEY_BackSpace 0x08
#define KEY_TAB 0x09
#define KEY_Enter 0x0A
#define RECORD_SUM 100
#define CMD_MAX 1024
typedef struct console_win {
int resp_line;
WINDOW *resp_win;
int cmd_y;
int cmd_x;
int cmd_len;
int cmd_line;
char cmd[CMD_MAX+FLAG_LEN];
} console_win_t;
console_win_t s_win;
static int resp_win_lines()
{
return LINES - s_win.cmd_line;
}
static int cursor_len()
{
return s_win.cmd_x + (s_win.cmd_line-1-(MAX_Y-s_win.cmd_y))*COLS;
}
static char *cmd_str()
{
return &s_win.cmd[FLAG_LEN];
}
void show_resp_win(const char *data, const char *flag)
{
if (flag == NULL) {
int lines = strlen(data) / COLS + 1;
if (s_win.resp_line >= (resp_win_lines()-1)) {
mvwprintw(s_win.resp_win, resp_win_lines()-1, 0, "%s", data);
wrefresh(s_win.resp_win);
wscrl(s_win.resp_win, 1);
wrefresh(s_win.resp_win);
} else {
mvwprintw(s_win.resp_win, s_win.resp_line, 0, "%s", data);
wrefresh(s_win.resp_win);
s_win.resp_line += lines;
}
} else {
int lines = (strlen(data) + strlen(flag)) / COLS + 1;
if (s_win.resp_line >= (resp_win_lines()-1)) {
mvwprintw(s_win.resp_win, resp_win_lines()-1, 0, "%s%s", flag, data);
wrefresh(s_win.resp_win);
wscrl(s_win.resp_win, 1);
wrefresh(s_win.resp_win);
} else {
mvwprintw(s_win.resp_win, s_win.resp_line, 0, "%s%s", flag, data);
wrefresh(s_win.resp_win);
s_win.resp_line += lines;
}
}
wrefresh(s_win.resp_win);
move(s_win.cmd_y, s_win.cmd_x);
refresh();
}
static void resize_resp_win()
{
wresize(s_win.resp_win, resp_win_lines(), COLS);
wsetscrreg(s_win.resp_win, 0, resp_win_lines());
wrefresh(s_win.resp_win);
}
//显示用户正在输入的命令
static void show_cmd()
{
if (s_win.cmd_line == 1) {
mvprintw(s_win.cmd_y, 0, "%s", s_win.cmd);
refresh();
} else {
char tmp[CMD_MAX] = {0};
memcpy(tmp, s_win.cmd, COLS);
mvprintw(LINES - s_win.cmd_line, 0, "%s", tmp);
refresh();
char i = 1;
for (; i < s_win.cmd_line; i++) {
move(LINES-s_win.cmd_line+i, 0);
clrtoeol();
refresh();
mvprintw(LINES-s_win.cmd_line+i, 0, "%s", &s_win.cmd[COLS*i]);
refresh();
}
}
move(s_win.cmd_y, s_win.cmd_x);
refresh();
}
//处理终端大小变化
static void sig_winch(int signo)
{
int old_cols = COLS;
int old_lines = LINES;
//get the screen size
struct winsize w;
ioctl(0, TIOCGWINSZ, &w);
COLS = w.ws_col;
LINES = w.ws_row;
s_win.cmd_y = MAX_Y;
s_win.cmd_line = s_win.cmd_len / COLS + 1;
s_win.cmd_x = s_win.cmd_len % COLS;
resize_resp_win();
refresh();
show_cmd();
}
static int init_win()
{
signal(SIGWINCH, sig_winch);
initscr();
raw();
//noecho();
keypad(stdscr, TRUE);
refresh();
memset(&s_win, 0, sizeof(s_win));
s_win.resp_win = newwin(resp_win_lines(), COLS, 0, 0);
if(s_win.resp_win == NULL) {
return -1;
}
leaveok(s_win.resp_win, TRUE);
scrollok(s_win.resp_win, TRUE);
wsetscrreg(s_win.resp_win, 0, resp_win_lines());
return 0;
}
static void destroy_win()
{
if (s_win.resp_win != NULL) {
move(MAX_Y, 0);
clrtoeol();
refresh();
}
char tmp[128];
sprintf(tmp, "kill -9 %d", getpid());
system(tmp);
}
static void stop()
{
destroy_win();
}
static void on_key_left()
{
if (s_win.cmd_x > 0) {
if ((MAX_Y - s_win.cmd_y) < (s_win.cmd_line - 1)) {
move(s_win.cmd_y, --s_win.cmd_x);
} else if (s_win.cmd_x > FLAG_LEN) {
move(s_win.cmd_y, --s_win.cmd_x);
}
} else {
s_win.cmd_x = COLS - 1;
move(--s_win.cmd_y, s_win.cmd_x);
}
}
static void on_key_right()
{
if (s_win.cmd_y < MAX_Y) {
if (s_win.cmd_x < (COLS - 1)) {
move(s_win.cmd_y, ++s_win.cmd_x);
} else {
s_win.cmd_x = 0;
move(++s_win.cmd_y, s_win.cmd_x);
}
} else if (s_win.cmd_x < (strlen(s_win.cmd) % COLS)) {
move(s_win.cmd_y, ++s_win.cmd_x);
}
}
static void on_key_tab(const char *cmd)
{
//get command from the server
}
static void on_key_normal(int ch)
{
int tail_len = 0;
if (s_win.cmd_y == MAX_Y &&
s_win.cmd_x >= (s_win.cmd_len - COLS*(s_win.cmd_line-1))) {
//append character
s_win.cmd[s_win.cmd_len] = ch;
} else {
//insert character
char *pos = &s_win.cmd[s_win.cmd_x + (s_win.cmd_line-1-(MAX_Y-s_win.cmd_y))*COLS];
tail_len = strlen(pos);
memcpy(pos + 1, pos, tail_len);
*pos = ch;
}
s_win.cmd_len++;
s_win.cmd_x++;
if (s_win.cmd_x >= COLS) {
s_win.cmd_x = 0;
if (s_win.cmd_y < MAX_Y) {
s_win.cmd_y++;
} else if (tail_len == 0) {
s_win.cmd_line++;
}
}
if (tail_len > 0 && s_win.cmd_len >= s_win.cmd_line*COLS) {
s_win.cmd_line++;
s_win.cmd_y--;
}
resize_resp_win();
show_cmd();
}
static void on_key_backspace()
{
if ((MAX_Y-s_win.cmd_y) == (s_win.cmd_line-1) && s_win.cmd_x <= FLAG_LEN) {
move(s_win.cmd_y, s_win.cmd_x);
return;
}
char *pos = &s_win.cmd[s_win.cmd_x + (s_win.cmd_line-1-(MAX_Y-s_win.cmd_y))*COLS];
memcpy(pos - 1, pos, strlen(pos));
mvdelch(s_win.cmd_y, --s_win.cmd_x);
s_win.cmd[--s_win.cmd_len] = 0;
if (s_win.cmd_x == 0) {
s_win.cmd_x = COLS;
s_win.cmd_y--;
}
resize_resp_win();
show_cmd();
}
static void on_key_delete()
{
if (s_win.cmd_len == cursor_len()) {
return;
}
mvdelch(s_win.cmd_y, s_win.cmd_x);
char *pos = &s_win.cmd[cursor_len()];
memcpy(pos, pos + 1, strlen(pos + 1));
s_win.cmd[--s_win.cmd_len] = 0;
resize_resp_win();
show_cmd();
}
//接收用户输入字符
static int proc_user_cmd()
{
while(1) {
int cmd_count = 0;
s_win.cmd_line = 1;
s_win.cmd_y = MAX_Y;
s_win.cmd_x = FLAG_LEN;
s_win.cmd_len = FLAG_LEN;
memset(s_win.cmd, 0, sizeof(s_win.cmd));
strcpy(s_win.cmd, CMD_FLAG);
move(s_win.cmd_y, 0);
clrtoeol();
refresh();
mvprintw(s_win.cmd_y, 0, "%s", s_win.cmd);
refresh();
int ch;
while((ch=getch()) != KEY_Enter) {
switch (ch) {
case KEY_CTRL_C:
return 0;
case KEY_UP:
if (cmd_count < RECORD_SUM) {
//show_cmd_record(++cmd_count);
}
break;
case KEY_DOWN:
if (cmd_count > 0) {
//show_cmd_record(--cmd_count);
} else {
mvprintw(MAX_Y, 0, CMD_FLAG);
clrtoeol();
}
break;
case KEY_LEFT:
on_key_left();
break;
case KEY_RIGHT:
on_key_right();
break;
case KEY_TAB:
on_key_tab(cmd_str());
move(MAX_Y, s_win.cmd_x);
break;
case KEY_BackSpace:
on_key_backspace();
break;
case KEY_DC:
on_key_delete();
break;
default:
if (s_win.cmd_len < CMD_MAX) {
on_key_normal(ch);
}
break;
}
refresh();
}
show_resp_win(s_win.cmd, NULL);
if (s_win.cmd_len > FLAG_LEN) {
if (strcasecmp(cmd_str(), "exit") != 0) {
//parse_cmd(cmd_str());
} else {
return 0;
}
}
s_win.cmd_line = 1;
resize_resp_win();
usleep(1000);
}
return 0;
}
int main(int argc, char *argv[])
{
if (init_win() != 0) {
printf("init_win fail!\n");
destroy_win();
return -1;
}
proc_user_cmd();
stop();
return 0;
}