Code:
#define NCURSES_WIDECHAR 1
#include <locale.h>
#include <string.h>
#include <wchar.h>
#include <time.h>
#include <ncursesw/curses.h>
size_t unicode_to_utf8(char *const utf8, const size_t max,
const int *const unicode, const size_t unicode_len)
{
const int *src = unicode;
const int *const end = unicode + unicode_len;
unsigned char *const dst = (unsigned char *const)utf8;
size_t len = 0;
while (src < end)
if (*src > 0 && *src < 0x80) {
if (len < max) dst[len] = *src;
len += 1;
src++;
} else
if (*src >= 0x80 && *src < 0x800) {
if (len + 0 < max) dst[len + 0] = 0xC0 | ((*src >> 6) & 0x1F);
if (len + 1 < max) dst[len + 1] = 0x80 | ((*src) & 0x3F);
len += 2;
src++;
} else
if (*src >= 0x800 && *src < 0x10000) {
if (len + 0 < max) dst[len + 0] = 0xE0 | ((*src >> 12) & 0x0F);
if (len + 1 < max) dst[len + 1] = 0x80 | ((*src >> 6) & 0x3F);
if (len + 2 < max) dst[len + 2] = 0x80 | ((*src) & 0x3F);
len += 3;
src++;
} else
if (*src >= 0x10000 && *src < 0x200000) {
if (len + 0 < max) dst[len + 0] = 0xF0 | ((*src >> 18) & 0x07);
if (len + 1 < max) dst[len + 1] = 0x80 | ((*src >> 12) & 0x3F);
if (len + 2 < max) dst[len + 2] = 0x80 | ((*src >> 6) & 0x3F);
if (len + 3 < max) dst[len + 3] = 0x80 | ((*src) & 0x3F);
len += 4;
src++;
} else {
/* Skip invalid code points. */
src++;
}
/* Append EOS. */
if (len < max)
dst[len] = '\0';
else
if (max > 0)
dst[max - 1] = '\0';
return len;
}
int main()
{
WINDOW *chat_window;
WINDOW *input_window;
time_t now, then;
wint_t c;
int t, in_key, in_char;
int line_buf[1024], line_len, line_pos;
char line_str[4 * sizeof line_buf / sizeof line_buf[0]];
size_t line_strlen;
/* Enable wide character support. */
setlocale(LC_ALL, "");
/* Start curses mode. */
initscr();
/* No line buffering. */
raw();
/* No echoing keypresses to the screen. */
noecho();
/* Reserve five lines for the input window. */
chat_window = newwin(LINES - 5, COLS, 0, 0);
input_window = newwin(5, COLS, LINES - 5, 0);
/* The main loop will quit with F4. */
wclear(chat_window);
scrollok(chat_window, 1);
wprintw(chat_window, "Press F4 or CTRL+Q or CTRL+W to quit.");
wrefresh(chat_window);
/* Show the input prompt. */
wclear(input_window);
wprintw(input_window, "> ");
/* wget_wch() returns ERR if there is no key pressed. */
nodelay(input_window, TRUE);
/* We want F1 et cetera. */
keypad(input_window, TRUE);
/* No input line yet. */
line_len = 0;
line_pos = 0;
now = time(NULL);
while (1) {
int changes = 0;
int cx, cy;
/* Check for pending chat messages. */
then = now;
now = time(NULL);
if (then != now) {
wprintw(chat_window, "\n\rTick tock.");
changes = 1;
}
/* Check for user input. */
t = wget_wch(input_window, &c);
if (t == KEY_CODE_YES) {
in_key = c;
in_char = 0;
} else
if (t == OK) {
in_key = 0;
in_char = c;
} else {
in_key = 0;
in_char = 0;
}
/* F4, Ctrl-Q, and Ctrl-C quit. */
if (in_key == KEY_F(4) || in_char == 3 || in_char == 17)
break;
/* Terminal window resized? */
if (in_key == KEY_RESIZE) {
wresize(chat_window, LINES - 5, COLS);
wresize(input_window, 5, COLS);
mvwin(input_window, LINES - 5, 0);
changes = 1;
}
/* Cursor movement left and right. */
if (in_key == KEY_LEFT && line_pos > 0) {
line_pos--;
changes = 1;
} else
if (in_key == KEY_RIGHT && line_pos < line_len) {
line_pos++;
changes = 1;
}
/* Up/Down == Home/End */
if (in_key == KEY_UP || in_key == KEY_HOME) {
line_pos = 0;
changes = 1;
} else
if (in_key == KEY_DOWN || in_key == KEY_END) {
line_pos = line_len;
changes = 1;
}
/* Backspace deletes the previous character. */
if (in_key == KEY_BACKSPACE || in_char == '\b') {
if (line_pos > 0) {
if (line_len > line_pos)
memmove(line_buf + line_pos - 1, line_buf + line_pos, (line_len - line_pos) * sizeof line_buf[0]);
line_pos--;
line_len--;
}
changes = 1;
}
/* Delete deletes the next character. */
if (in_key == KEY_DC || in_char == 127) {
if (line_pos < line_len) {
if (line_pos + 1 < line_len)
memmove(line_buf + line_pos, line_buf + line_pos + 1, (line_len - line_pos - 1) * sizeof line_buf[0]);
line_len--;
}
changes = 1;
}
/* Enter submits the string. */
if (in_key == KEY_ENTER || in_char == '\n') {
line_strlen = unicode_to_utf8(line_str, sizeof line_str, line_buf, line_len);
if (line_strlen > 0) {
/*
* TODO: Send line_str (contains line_strlen bytes).
*/
wprintw(chat_window, "\n\rMe: %s", line_str);
}
line_len = 0;
line_pos = 0;
changes = 1;
}
/* New character. */
if (in_char >= 32 && line_len < (int)(sizeof line_buf / sizeof line_buf[0])) {
if (line_pos < line_len)
memmove(line_buf + line_pos + 1, line_buf + line_pos, (line_len - line_pos) * sizeof line_buf[0]);
line_buf[line_pos] = in_char;
line_pos++;
line_len++;
changes = 1;
}
/* If any changes, refresh output. */
if (changes) {
wrefresh(chat_window);
wclear(input_window);
wprintw(input_window, "> ");
if (line_pos > 0) {
line_strlen = unicode_to_utf8(line_str, sizeof line_str, line_buf, line_pos);
wprintw(input_window, "%s", line_str);
}
if (line_len > line_pos) {
getyx(input_window, cy, cx); /* Macro, not a function. Modifies cx and cy! */
line_strlen = unicode_to_utf8(line_str, sizeof line_str, line_buf + line_pos, line_len - line_pos);
wprintw(input_window, "%s", line_str);
wmove(input_window, cy, cx);
}
wrefresh(input_window);
}
}
/* End curses mode. */
endwin();
return 0;
}
If you save the above as