664 lines
19 KiB
C
664 lines
19 KiB
C
|
#include "config.h"
|
||
|
#include "packet.h"
|
||
|
#include "util.h"
|
||
|
#include "client/ui.h"
|
||
|
#include "client/db.h"
|
||
|
#include "client/user.h"
|
||
|
|
||
|
WINDOW *panel;
|
||
|
WINDOW *users_border;
|
||
|
WINDOW *chat_border;
|
||
|
WINDOW *users_content;
|
||
|
WINDOW *textbox;
|
||
|
WINDOW *chat_content;
|
||
|
|
||
|
ArrayList *users;
|
||
|
ArrayList *marked;
|
||
|
message_t messages[100];
|
||
|
int num_messages = 0;
|
||
|
long current_user = 0;
|
||
|
int current_window = 0;
|
||
|
int sockfd;
|
||
|
|
||
|
/* For tracking cursor position in content */
|
||
|
static int curs_pos = 0;
|
||
|
static char content[MAX_MESSAGE_LENGTH];
|
||
|
|
||
|
void send_message();
|
||
|
|
||
|
/*
|
||
|
* Free and close everything
|
||
|
*/
|
||
|
void deinit()
|
||
|
{
|
||
|
shutdown(sockfd, SHUT_WR);
|
||
|
close(sockfd);
|
||
|
arraylist_free(users);
|
||
|
arraylist_free(marked);
|
||
|
endwin();
|
||
|
}
|
||
|
|
||
|
void signal_handler(int signal)
|
||
|
{
|
||
|
switch (signal) {
|
||
|
case SIGPIPE:
|
||
|
error(0, "SIGPIPE received");
|
||
|
break;
|
||
|
case SIGABRT:
|
||
|
case SIGINT:
|
||
|
case SIGTERM:
|
||
|
deinit();
|
||
|
error(1, "Shutdown signal received");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Start ncurses
|
||
|
*/
|
||
|
void ncurses_init()
|
||
|
{
|
||
|
/* check if it is interactive shell */
|
||
|
if (!isatty(STDIN_FILENO)) {
|
||
|
error(1, "No tty detected. zen requires an interactive shell to run");
|
||
|
}
|
||
|
|
||
|
/* initialize screen, don't print special chars,
|
||
|
* make ctrl + c work, don't show cursor
|
||
|
* enable arrow keys */
|
||
|
initscr();
|
||
|
noecho();
|
||
|
cbreak();
|
||
|
keypad(stdscr, TRUE);
|
||
|
/* check terminal has colors */
|
||
|
if (!has_colors()) {
|
||
|
endwin();
|
||
|
error(1, "Color is not supported in your terminal");
|
||
|
} else {
|
||
|
use_default_colors();
|
||
|
start_color();
|
||
|
}
|
||
|
/* colors */
|
||
|
init_pair(1, COLOR_BLACK, -1); /* */
|
||
|
init_pair(2, COLOR_RED, -1); /* */
|
||
|
init_pair(3, COLOR_GREEN, -1); /* active window */
|
||
|
init_pair(4, COLOR_YELLOW, -1); /* */
|
||
|
init_pair(5, COLOR_BLUE, -1); /* inactive window */
|
||
|
init_pair(6, COLOR_MAGENTA, -1); /* */
|
||
|
init_pair(7, COLOR_CYAN, -1); /* Selected user */
|
||
|
init_pair(8, COLOR_WHITE, -1); /* */
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Draw windows
|
||
|
*/
|
||
|
void windows_init()
|
||
|
{
|
||
|
int users_width = MAX_NAME / 2;
|
||
|
int chat_width = COLS - (MAX_NAME / 2);
|
||
|
|
||
|
/*------------------------------+
|
||
|
|-----border----||---border----||
|
||
|
|| || ||
|
||
|
|| content || content ||
|
||
|
|| (users) || (chat) ||
|
||
|
|| ||-------------||
|
||
|
|---------------||-textbox-----||
|
||
|
+==========panel===============*/
|
||
|
|
||
|
/* lines, cols, y, x */
|
||
|
panel = newwin(PANEL_HEIGHT, COLS, LINES - PANEL_HEIGHT, 0 );
|
||
|
users_border = newwin(LINES - PANEL_HEIGHT, users_width + 2, 0, 0 );
|
||
|
chat_border = newwin(LINES - PANEL_HEIGHT - TEXTBOX_HEIGHT, chat_width - 2, 0, users_width + 2);
|
||
|
textbox = newwin(TEXTBOX_HEIGHT, chat_width - 2, LINES - PANEL_HEIGHT - TEXTBOX_HEIGHT, users_width + 3);
|
||
|
|
||
|
/* lines, cols, y, x */
|
||
|
users_content = subwin(users_border, LINES - PANEL_HEIGHT - 2, users_width, 1, 1 );
|
||
|
chat_content = subwin(chat_border, LINES - PANEL_HEIGHT - 2 - TEXTBOX_HEIGHT, chat_width - 4, 1, users_width + 3);
|
||
|
|
||
|
/* draw border around windows */
|
||
|
refresh();
|
||
|
draw_border(users_border, true);
|
||
|
draw_border(chat_border, false);
|
||
|
|
||
|
scrollok(textbox, true);
|
||
|
scrollok(users_content, true);
|
||
|
scrollok(chat_content, true);
|
||
|
refresh();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Draw the border of the window depending if it's active or not
|
||
|
*/
|
||
|
void draw_border(WINDOW *window, bool active)
|
||
|
{
|
||
|
/* turn on color depends on active */
|
||
|
if (active) {
|
||
|
wattron(window, COLOR_PAIR(3));
|
||
|
} else {
|
||
|
wattron(window, COLOR_PAIR(5));
|
||
|
}
|
||
|
|
||
|
box(window, 0, 0);
|
||
|
|
||
|
/* turn color off after turning it on */
|
||
|
if (active) {
|
||
|
wattroff(window, COLOR_PAIR(3));
|
||
|
} else {
|
||
|
wattroff(window, COLOR_PAIR(5));
|
||
|
}
|
||
|
wrefresh(window); /* Refresh the window to see the colored border and title */
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Print line to the panel
|
||
|
*/
|
||
|
void wpprintw(const char *fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
wclear(panel);
|
||
|
vw_printw(panel, fmt, args);
|
||
|
va_end(args);
|
||
|
wrefresh(panel);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Highlight current line by reversing the color
|
||
|
*/
|
||
|
void draw_users()
|
||
|
{
|
||
|
long overflow = 0;
|
||
|
/* Check if the current selected user is not shown in rendered text */
|
||
|
if (current_user > LINES - 3) {
|
||
|
/* overflown */
|
||
|
overflow = current_user - (LINES - 3);
|
||
|
}
|
||
|
|
||
|
/* Calculate number of users to show */
|
||
|
long range = users->length;
|
||
|
/* Stop drawing if there is no users */
|
||
|
if (range == 0) {
|
||
|
wprintw(chat_content, "No users. Start a converstation.");
|
||
|
wrefresh(chat_content);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (range > LINES - 3) {
|
||
|
/* if there are more users than lines available to display
|
||
|
* shrink range to avaiable lines to display with
|
||
|
* overflow to keep the number of iterations to be constant */
|
||
|
range = LINES - 3 + overflow;
|
||
|
}
|
||
|
|
||
|
/* Clears content before printing */
|
||
|
wclear(users_content);
|
||
|
|
||
|
/* To keep track the line to print after overflow */
|
||
|
long line_count = 0;
|
||
|
for (long i = overflow; i < range; i++) {
|
||
|
/* Check for currently selected user */
|
||
|
if ((overflow == 0 && i == current_user) || (overflow != 0 && i == current_user)) {
|
||
|
/* current selected user should have color reversed */
|
||
|
wattron(users_content, A_REVERSE);
|
||
|
|
||
|
/* check for marked users */
|
||
|
long num_marked = marked->length;
|
||
|
if (num_marked > 0) {
|
||
|
/* Determine length of formatted string */
|
||
|
int m_len = snprintf(NULL, 0, "[%ld] selected", num_marked);
|
||
|
char *selected = memalloc(m_len + 1);
|
||
|
|
||
|
snprintf(selected, m_len + 1, "[%ld] selected", num_marked);
|
||
|
wpprintw("(%ld/%ld) %s", current_user + 1, users->length, selected);
|
||
|
} else {
|
||
|
wpprintw("(%ld/%ld)", current_user + 1, users->length);
|
||
|
}
|
||
|
}
|
||
|
/* print the actual filename and stats */
|
||
|
user seluser = users->items[i];
|
||
|
size_t name_len = strlen(seluser.name);
|
||
|
|
||
|
/* If length of name is longer than half of allowed size in window,
|
||
|
* trim it to end with .. to show the it is too long to be displayed
|
||
|
*/
|
||
|
if (name_len >= (MAX_NAME / 2) - 2) {
|
||
|
name_len = (MAX_NAME / 2) - 2;
|
||
|
}
|
||
|
char *line = memalloc(name_len);
|
||
|
memcpy(line, seluser.name, name_len);
|
||
|
|
||
|
int color = users->items[i].color;
|
||
|
|
||
|
/* check is user marked for action */
|
||
|
bool is_marked = arraylist_search(marked, users->items[i].name) != -1;
|
||
|
if (is_marked) {
|
||
|
/* show user is selected */
|
||
|
wattron(users_content, COLOR_PAIR(7));
|
||
|
} else {
|
||
|
/* print username with default color */
|
||
|
wattron(users_content, COLOR_PAIR(color));
|
||
|
}
|
||
|
|
||
|
if (overflow > 0) {
|
||
|
mvwprintw(users_content, line_count, 0, "%s", line);
|
||
|
} else {
|
||
|
mvwprintw(users_content, i, 0, "%s", line);
|
||
|
}
|
||
|
|
||
|
/* turn off color after printing */
|
||
|
if (is_marked) {
|
||
|
wattroff(users_content, COLOR_PAIR(7));
|
||
|
} else {
|
||
|
wattroff(users_content, COLOR_PAIR(color));
|
||
|
}
|
||
|
|
||
|
wattroff(users_content, A_REVERSE);
|
||
|
free(line);
|
||
|
line_count++;
|
||
|
}
|
||
|
|
||
|
wrefresh(users_content);
|
||
|
wrefresh(panel);
|
||
|
/* show chat conversation every time cursor changes */
|
||
|
show_chat(users->items[current_user].name);
|
||
|
wrefresh(chat_content);
|
||
|
}
|
||
|
|
||
|
void add_message(uint8_t *author, uint8_t *recipient, uint8_t *content, uint32_t length, time_t creation)
|
||
|
{
|
||
|
message_t *msg = &messages[num_messages];
|
||
|
strcpy(msg->author, author);
|
||
|
strcpy(msg->recipient, recipient);
|
||
|
msg->content = memalloc(length);
|
||
|
strcpy(msg->content, content);
|
||
|
msg->creation = creation;
|
||
|
num_messages++;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Add message to chat window
|
||
|
* if flag is 1, print date as well
|
||
|
* user_color is the color defined above at ncurses_init
|
||
|
*/
|
||
|
void print_message(int flag, message_t *msg)
|
||
|
{
|
||
|
struct tm *timeinfo = localtime(&msg->creation);
|
||
|
char timestr[21];
|
||
|
if (flag) {
|
||
|
strftime(timestr, sizeof(timestr), "%b %d %Y %H:%M:%S", timeinfo);
|
||
|
} else {
|
||
|
strftime(timestr, sizeof(timestr), "%H:%M:%S", timeinfo);
|
||
|
}
|
||
|
wprintw(chat_content, "%s ", timestr);
|
||
|
|
||
|
wattron(chat_content, A_BOLD);
|
||
|
int user_color = get_user_color(users, msg->author);
|
||
|
wattron(chat_content, COLOR_PAIR(user_color));
|
||
|
wprintw(chat_content, "<%s> ", msg->author);
|
||
|
wattroff(chat_content, A_BOLD);
|
||
|
wattroff(chat_content, COLOR_PAIR(user_color));
|
||
|
|
||
|
int i = 0;
|
||
|
int n = strlen(msg->content);
|
||
|
int in_bold = 0, in_italic = 0, in_underline = 0, in_block = 0;
|
||
|
int last_active_color = -1;
|
||
|
|
||
|
while (i < n) {
|
||
|
/* Bold */
|
||
|
if (msg->content[i] == '*' && msg->content[i + 1] == '*') {
|
||
|
if (!in_bold) {
|
||
|
/* Look ahead for the matching closing delimiter */
|
||
|
int closing_pos = i + 2;
|
||
|
while (closing_pos < n && !(msg->content[closing_pos] == '*' && msg->content[closing_pos + 1] == '*')) {
|
||
|
closing_pos++;
|
||
|
}
|
||
|
if (closing_pos < n) {
|
||
|
wattron(chat_content, A_BOLD);
|
||
|
in_bold = 1;
|
||
|
} else {
|
||
|
/* Treat as regular text if closing delimiter */
|
||
|
waddch(chat_content, msg->content[i++]);
|
||
|
}
|
||
|
} else {
|
||
|
wattroff(chat_content, A_BOLD);
|
||
|
in_bold = 0;
|
||
|
}
|
||
|
/* Skip */
|
||
|
i += 2;
|
||
|
|
||
|
/* Italic */
|
||
|
} else if (msg->content[i] == '*') {
|
||
|
if (!in_italic) {
|
||
|
/* Look ahead for the matching closing delimiter */
|
||
|
int closing_pos = i + 1;
|
||
|
while (closing_pos < n && msg->content[closing_pos] != '*') {
|
||
|
closing_pos++;
|
||
|
}
|
||
|
if (closing_pos < n) {
|
||
|
wattron(chat_content, A_ITALIC);
|
||
|
in_italic = 1;
|
||
|
} else {
|
||
|
/* Treat as regular text if closing delimiter */
|
||
|
waddch(chat_content, msg->content[i++]);
|
||
|
}
|
||
|
} else {
|
||
|
wattroff(chat_content, A_ITALIC);
|
||
|
in_italic = 0;
|
||
|
}
|
||
|
/* Skip */
|
||
|
i += 1;
|
||
|
|
||
|
/* Underline */
|
||
|
} else if (msg->content[i] == '_') {
|
||
|
if (!in_underline) {
|
||
|
/* Look ahead for the matching closing delimiter */
|
||
|
int closing_pos = i + 1;
|
||
|
while (closing_pos < n && msg->content[closing_pos] != '_') {
|
||
|
closing_pos++;
|
||
|
}
|
||
|
if (closing_pos < n) {
|
||
|
wattron(chat_content, A_UNDERLINE);
|
||
|
in_underline = 1;
|
||
|
} else {
|
||
|
/* Treat as regular text if closing delimiter */
|
||
|
waddch(chat_content, msg->content[i++]);
|
||
|
}
|
||
|
} else {
|
||
|
wattroff(chat_content, A_UNDERLINE);
|
||
|
in_underline = 0;
|
||
|
}
|
||
|
/* Skip */
|
||
|
i += 1;
|
||
|
|
||
|
/* Block */
|
||
|
} else if (msg->content[i] == '`') {
|
||
|
if (!in_block) {
|
||
|
/* Look ahead for the matching closing delimiter */
|
||
|
int closing_pos = i + 1;
|
||
|
while (closing_pos < n && msg->content[closing_pos] != '`') {
|
||
|
closing_pos++;
|
||
|
}
|
||
|
if (closing_pos < n) {
|
||
|
wattron(chat_content, A_STANDOUT);
|
||
|
in_block = 1;
|
||
|
} else {
|
||
|
/* Treat as regular text if closing delimiter */
|
||
|
waddch(chat_content, msg->content[i++]);
|
||
|
}
|
||
|
} else {
|
||
|
wattroff(chat_content, A_STANDOUT);
|
||
|
in_block = 0;
|
||
|
}
|
||
|
/* Skip */
|
||
|
i += 1;
|
||
|
|
||
|
/* Allow escape sequence for genuine backslash */
|
||
|
} else if (msg->content[i] == '\\' && msg->content[i + 1] == '\\') {
|
||
|
/* Print a literal backslash */
|
||
|
waddch(chat_content, '\\');
|
||
|
/* Skip both backslashes */
|
||
|
i += 2;
|
||
|
|
||
|
/* Color, new line and tab */
|
||
|
} else if (msg->content[i] == '\\') {
|
||
|
/* Skip the backslash and check the next character */
|
||
|
i++;
|
||
|
/* Handle color codes \1 to \8 */
|
||
|
if (msg->content[i] >= '1' && msg->content[i] <= '8') {
|
||
|
/* Convert char to int */
|
||
|
int new_color = msg->content[i] - '0';
|
||
|
if (new_color == last_active_color) {
|
||
|
/* Turn off current color */
|
||
|
wattroff(chat_content, COLOR_PAIR(last_active_color));
|
||
|
/* Reset last active color */
|
||
|
last_active_color = -1;
|
||
|
} else {
|
||
|
if (last_active_color != -1) {
|
||
|
/* Turn off previous color */
|
||
|
wattroff(chat_content, COLOR_PAIR(last_active_color));
|
||
|
}
|
||
|
last_active_color = new_color;
|
||
|
/* Turn on new color */
|
||
|
wattron(chat_content, COLOR_PAIR(new_color));
|
||
|
}
|
||
|
i++;
|
||
|
/* Handle new line */
|
||
|
} else if (msg->content[i] == 'n') {
|
||
|
waddch(chat_content, '\n');
|
||
|
/* Skip the 'n' */
|
||
|
i++;
|
||
|
|
||
|
} else {
|
||
|
/* Invalid sequence, just print the backslash and character */
|
||
|
waddch(chat_content, '\\');
|
||
|
waddch(chat_content, msg->content[i]);
|
||
|
i++;
|
||
|
}
|
||
|
} else {
|
||
|
/* Print regular character */
|
||
|
waddch(chat_content, msg->content[i]);
|
||
|
i++;
|
||
|
}
|
||
|
}
|
||
|
/* Ensure attributes are turned off after printing */
|
||
|
wattroff(chat_content, A_BOLD);
|
||
|
wattroff(chat_content, A_ITALIC);
|
||
|
wattroff(chat_content, A_UNDERLINE);
|
||
|
wattroff(chat_content, A_STANDOUT);
|
||
|
for (int i = 1; i < 8; i++) {
|
||
|
wattroff(chat_content, COLOR_PAIR(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Get chat conversation into buffer and show it to chat window
|
||
|
*/
|
||
|
void show_chat(uint8_t *recipient)
|
||
|
{
|
||
|
wclear(chat_content);
|
||
|
for (int i = 0; i < 100; i++) {
|
||
|
message_t message = messages[i];
|
||
|
if (message.content == NULL) continue;
|
||
|
/* Find messages from recipient to client or vice versa */
|
||
|
/* outgoing = 1, incoming = 2 */
|
||
|
/* if message to print is older than previous message by a day,
|
||
|
* enable flag in print_message to include date */
|
||
|
int print_date = 0;
|
||
|
if (i > 0 && messages[i - 1].content != NULL && message.creation >= messages[i - 1].creation + 86400) {
|
||
|
print_date = 1;
|
||
|
}
|
||
|
if (strncmp(message.author, USERNAME, MAX_NAME) == 0 &&
|
||
|
strncmp(message.recipient, recipient, MAX_NAME) == 0) {
|
||
|
print_message(print_date, &message);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (strncmp(message.author, recipient, MAX_NAME) == 0 &&
|
||
|
strncmp(message.recipient, USERNAME, MAX_NAME) == 0) {
|
||
|
print_message(print_date, &message);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
wrefresh(chat_content);
|
||
|
/* after printing move cursor back to textbox */
|
||
|
wmove(textbox, 0, curs_pos + 2);
|
||
|
wrefresh(textbox);
|
||
|
}
|
||
|
/*
|
||
|
* Require heap allocated username
|
||
|
*/
|
||
|
void add_username(char *username)
|
||
|
{
|
||
|
int randomco = rand() % 8;
|
||
|
arraylist_add(users, username, randomco, false, false);
|
||
|
}
|
||
|
|
||
|
void get_chatbox_content(int ch)
|
||
|
{
|
||
|
if (ch == KEY_BACKSPACE || ch == 127) {
|
||
|
if (curs_pos > 0) {
|
||
|
curs_pos--;
|
||
|
content[curs_pos] = '\0';
|
||
|
}
|
||
|
}
|
||
|
/* Append it to the content if it is normal character */
|
||
|
else if (curs_pos < MAX_MESSAGE_LENGTH - 1) {
|
||
|
/* Filter readable ASCII */
|
||
|
if (ch > 31 && ch < 127) {
|
||
|
content[curs_pos++] = ch;
|
||
|
content[curs_pos] = '\0';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Display the current content */
|
||
|
mvwprintw(textbox, 0, 0, "> %s", content);
|
||
|
wrefresh(textbox);
|
||
|
}
|
||
|
|
||
|
void send_message()
|
||
|
{
|
||
|
uint8_t *recipient = users->items[current_user].name;
|
||
|
|
||
|
keypair_t *kp_from = get_keypair(USERNAME);
|
||
|
keypair_t *kp_to = get_keypair(recipient);
|
||
|
|
||
|
int status = ZSM_STA_SUCCESS;
|
||
|
|
||
|
uint8_t shared_key[SHARED_KEY_SIZE];
|
||
|
if (crypto_kx_client_session_keys(shared_key, NULL, kp_from->pk.raw,
|
||
|
kp_from->sk, kp_to->pk.raw) != 0) {
|
||
|
/* Recipient public key is suspicious */
|
||
|
write_log(LOG_ERROR, "Error performing key exchange with %s\n", recipient);
|
||
|
}
|
||
|
|
||
|
size_t content_len = strlen(content);
|
||
|
|
||
|
uint32_t cipher_len = content_len + ADDITIONAL_SIZE;
|
||
|
uint8_t nonce[NONCE_SIZE], encrypted[cipher_len];
|
||
|
|
||
|
/* Generate random nonce(number used once) */
|
||
|
randombytes_buf(nonce, sizeof(nonce));
|
||
|
|
||
|
/* Encrypt the content and store it to encrypted, should be cipher_len */
|
||
|
|
||
|
crypto_aead_xchacha20poly1305_ietf_encrypt(encrypted, NULL, content,
|
||
|
content_len, NULL, 0, NULL, nonce, shared_key);
|
||
|
|
||
|
size_t data_len = MAX_NAME * 2 + NONCE_SIZE + cipher_len;
|
||
|
uint8_t *data = memalloc(data_len);
|
||
|
|
||
|
/* Construct data */
|
||
|
memcpy(data, kp_from->pk.username, MAX_NAME);
|
||
|
memcpy(data + MAX_NAME, kp_to->pk.username, MAX_NAME);
|
||
|
memcpy(data + MAX_NAME * 2, nonce, NONCE_SIZE);
|
||
|
memcpy(data + MAX_NAME * 2 + NONCE_SIZE, encrypted, cipher_len);
|
||
|
|
||
|
uint8_t *signature = create_signature(data, data_len, kp_from->sk);
|
||
|
packet_t *pkt = create_packet(1, ZSM_TYP_MESSAGE, data_len, data, signature);
|
||
|
|
||
|
if (send_packet(pkt, sockfd) != ZSM_STA_SUCCESS) {
|
||
|
close(sockfd);
|
||
|
write_log(LOG_ERROR, "Failed to send message\n");
|
||
|
}
|
||
|
add_message(USERNAME, recipient, content, content_len, time(NULL));
|
||
|
free_packet(pkt);
|
||
|
show_chat(recipient);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Main loop of user interface
|
||
|
*/
|
||
|
void ui(int *fd)
|
||
|
{
|
||
|
signal(SIGPIPE, signal_handler);
|
||
|
signal(SIGABRT, signal_handler);
|
||
|
signal(SIGINT, signal_handler);
|
||
|
signal(SIGTERM, signal_handler);
|
||
|
ncurses_init();
|
||
|
windows_init();
|
||
|
sockfd = *fd;
|
||
|
users = arraylist_init(LINES);
|
||
|
marked = arraylist_init(100);
|
||
|
sqlite_init();
|
||
|
draw_users();
|
||
|
refresh();
|
||
|
while (1) {
|
||
|
if (current_window == CHAT_WINDOW) {
|
||
|
wclear(textbox);
|
||
|
mvwprintw(textbox, 0, 0, "> %s", content);
|
||
|
wrefresh(textbox);
|
||
|
wmove(textbox, 0, curs_pos + 2);
|
||
|
/* Set cursor to visible */
|
||
|
curs_set(2);
|
||
|
} else {
|
||
|
/* Set cursor to invisible */
|
||
|
curs_set(0);
|
||
|
}
|
||
|
int ch = getch();
|
||
|
switch (ch) {
|
||
|
/* go up by k or up arrow */
|
||
|
case UP:
|
||
|
if (current_window == USERS_WINDOW) {
|
||
|
if (current_user > 0)
|
||
|
current_user--;
|
||
|
|
||
|
draw_users();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
/* go down by j or down arrow */
|
||
|
case DOWN:
|
||
|
if (current_window == USERS_WINDOW) {
|
||
|
if (current_user < (users->length - 1))
|
||
|
current_user++;
|
||
|
|
||
|
draw_users();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
/* A is normally for left and E for right */
|
||
|
case CTRLA:
|
||
|
case CTRLE:
|
||
|
current_window ^= 1;
|
||
|
if (current_window == USERS_WINDOW) {
|
||
|
draw_border(users_border, true);
|
||
|
draw_border(chat_border, false);
|
||
|
} else {
|
||
|
draw_border(chat_border, true);
|
||
|
draw_border(users_border, false);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CLEAR_INPUT:
|
||
|
if (current_window == CHAT_WINDOW) {
|
||
|
curs_pos = 0;
|
||
|
content[0] = '\0';
|
||
|
}
|
||
|
|
||
|
case ENTER:
|
||
|
if (current_window == CHAT_WINDOW) {
|
||
|
content[curs_pos++] = ch;
|
||
|
content[curs_pos++] = '\0';
|
||
|
send_message();
|
||
|
/* Reset for new input */
|
||
|
curs_pos = 0;
|
||
|
|
||
|
/* Set content[0] for printing purposes */
|
||
|
content[0] = '\0';
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
default:
|
||
|
if (current_window == CHAT_WINDOW)
|
||
|
get_chatbox_content(ch);
|
||
|
|
||
|
}
|
||
|
}
|
||
|
deinit();
|
||
|
return;
|
||
|
}
|