You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4814 lines
131 KiB
4814 lines
131 KiB
/* |
|
* screen.c -- UI stuff |
|
* |
|
* Copyright (C) 2005-2014 Mikael Berthe <mikael@lilotux.net> |
|
* Parts of this file come from the Cabber project <cabber@ajmacias.com> |
|
* |
|
* This program is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; either version 2 of the License, or (at |
|
* your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, but |
|
* WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
* General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program; if not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <time.h> |
|
#include <ctype.h> |
|
|
|
#include <locale.h> |
|
#include <assert.h> |
|
#ifdef USE_SIGWINCH |
|
# include <sys/ioctl.h> |
|
# include <termios.h> |
|
# include <unistd.h> |
|
#endif |
|
|
|
#ifdef HAVE_LOCALCHARSET_H |
|
# include <localcharset.h> |
|
#else |
|
# include <langinfo.h> |
|
#endif |
|
|
|
#include "screen.h" |
|
#include "utf8.h" |
|
#include "hbuf.h" |
|
#include "commands.h" |
|
#include "compl.h" |
|
#include "roster.h" |
|
#include "histolog.h" |
|
#include "settings.h" |
|
#include "utils.h" |
|
#include "xmpp.h" |
|
#include "main.h" |
|
|
|
int COLOR_ATTRIB[COLOR_max]; |
|
|
|
#define get_color(col) (COLOR_PAIR(col)|COLOR_ATTRIB[col]) |
|
#define compose_color(col) (COLOR_PAIR(col->color_pair)|col->color_attrib) |
|
|
|
#define DEFAULT_LOG_WIN_HEIGHT (5) |
|
#define DEFAULT_ROSTER_WIDTH 24 |
|
#define CHAT_WIN_HEIGHT (maxY-2-1-Log_Win_Height) |
|
|
|
#define DEFAULT_ATTENTION_CHAR '!' |
|
|
|
const char *LocaleCharSet = "C"; |
|
|
|
static unsigned short int Log_Win_Height; |
|
static unsigned short int Roster_Width; |
|
static gboolean colors_stalled = FALSE; |
|
|
|
// Default attention sign trigger levels |
|
static guint ui_attn_sign_prio_level_muc = ROSTER_UI_PRIO_MUC_HL_MESSAGE; |
|
static guint ui_attn_sign_prio_level = ROSTER_UI_PRIO_ATTENTION_MESSAGE; |
|
|
|
static inline void check_offset(int); |
|
static void scr_cancel_current_completion(void); |
|
static void scr_end_current_completion(void); |
|
static void scr_insert_text(const char*); |
|
static void scr_handle_tab(gboolean fwd); |
|
|
|
static void scr_glog_print(const gchar *log_domain, GLogLevelFlags log_level, |
|
const gchar *message, gpointer user_data); |
|
|
|
#ifdef XEP0085 |
|
static gboolean scr_chatstates_timeout(); |
|
#endif |
|
|
|
static void open_chat_window(void); |
|
static void clear_inputline(void); |
|
|
|
static GHashTable *winbufhash; |
|
|
|
typedef struct { |
|
GList *hbuf; |
|
GList *top; // If top is NULL, we'll display the last lines |
|
char cleared; // For ex, user has issued a /clear command... |
|
char lock; |
|
char refcount; // refcount > 0 if there are other users of this struct |
|
// e.g. with symlinked history |
|
} buffdata_t; |
|
|
|
typedef struct { |
|
WINDOW *win; |
|
PANEL *panel; |
|
buffdata_t *bd; |
|
} winbuf_t; |
|
|
|
struct dimensions { |
|
int l; |
|
int c; |
|
}; |
|
|
|
static WINDOW *rosterWnd, *chatWnd, *activechatWnd, *inputWnd, *logWnd; |
|
static WINDOW *mainstatusWnd, *chatstatusWnd; |
|
static PANEL *rosterPanel, *chatPanel, *activechatPanel, *inputPanel; |
|
static PANEL *mainstatusPanel, *chatstatusPanel; |
|
static PANEL *logPanel; |
|
static int maxY, maxX; |
|
static int prev_chatwidth; |
|
static winbuf_t *statusWindow; |
|
static winbuf_t *currentWindow; |
|
static GList *statushbuf; |
|
|
|
static int roster_hidden; |
|
static int chatmode; |
|
static int multimode; |
|
static char *multiline, *multimode_subj; |
|
|
|
static bool Curses; |
|
static bool log_win_on_top; |
|
static bool roster_win_on_right; |
|
static guint autoaway_source = 0; |
|
|
|
static char inputLine[INPUTLINE_LENGTH+1]; |
|
static char *ptr_inputline; |
|
static short int inputline_offset; |
|
static int completion_started; |
|
static GList *cmdhisto; |
|
static GList *cmdhisto_cur; |
|
static guint cmdhisto_nblines; |
|
static char cmdhisto_backup[INPUTLINE_LENGTH+1]; |
|
|
|
static int chatstate; /* (0=active, 1=composing, 2=paused) */ |
|
static bool lock_chatstate; |
|
static time_t chatstate_timestamp; |
|
static guint chatstate_timeout_id = 0; |
|
|
|
int _update_roster; |
|
int utf8_mode; |
|
gboolean chatstates_disabled; |
|
gboolean Autoaway; |
|
|
|
gboolean vi_mode; |
|
|
|
#define MAX_KEYSEQ_LENGTH 8 |
|
|
|
typedef struct { |
|
char *seqstr; |
|
guint mkeycode; |
|
gint value; |
|
} keyseq_t; |
|
|
|
GSList *keyseqlist; |
|
static void add_keyseq(char *seqstr, guint mkeycode, gint value); |
|
|
|
static void scr_write_in_window(const char *winId, const char *text, |
|
time_t timestamp, unsigned int prefix_flags, |
|
int force_show, unsigned mucnicklen, |
|
gpointer xep184); |
|
|
|
static void scr_write_message(const char *bjid, const char *text, |
|
time_t timestamp, guint prefix_flags, |
|
unsigned mucnicklen, gpointer xep184); |
|
|
|
void scr_update_buddy_window(void); |
|
void scr_set_chatmode(int enable); |
|
|
|
typedef struct { |
|
int color_pair; |
|
int color_attrib; |
|
} ccolor_t; |
|
|
|
typedef struct { |
|
char *status, *wildcard; |
|
ccolor_t *color; |
|
GPatternSpec *compiled; |
|
} rostercolor_t; |
|
|
|
static GSList *rostercolrules = NULL; |
|
|
|
static GHashTable *muccolors = NULL, *nickcolors = NULL; |
|
|
|
typedef struct { |
|
bool manual; // Manually set? |
|
ccolor_t *color; |
|
} nickcolor_t; |
|
|
|
static int nickcolcount = 0; |
|
static ccolor_t ** nickcols = NULL; |
|
static muccol_t glob_muccol = MC_OFF; |
|
|
|
/* Functions */ |
|
|
|
static int find_color(const char *name) |
|
{ |
|
int result; |
|
|
|
if (!strcmp(name, "default")) |
|
return -1; |
|
if (!strcmp(name, "black")) |
|
return COLOR_BLACK; |
|
if (!strcmp(name, "red")) |
|
return COLOR_RED; |
|
if (!strcmp(name, "green")) |
|
return COLOR_GREEN; |
|
if (!strcmp(name, "yellow")) |
|
return COLOR_YELLOW; |
|
if (!strcmp(name, "blue")) |
|
return COLOR_BLUE; |
|
if (!strcmp(name, "magenta")) |
|
return COLOR_MAGENTA; |
|
if (!strcmp(name, "cyan")) |
|
return COLOR_CYAN; |
|
if (!strcmp(name, "white")) |
|
return COLOR_WHITE; |
|
|
|
// Directly support 256-color values |
|
result = atoi(name); |
|
if (result > 0 && (result < COLORS || !Curses)) |
|
return result; |
|
|
|
scr_LogPrint(LPRINT_LOGNORM, "ERROR: Wrong color: %s", name); |
|
return -1; |
|
} |
|
|
|
static ccolor_t *get_user_color(const char *color) |
|
{ |
|
bool isbright = FALSE; |
|
int cl; |
|
ccolor_t *ccol; |
|
if (!strncmp(color, "bright", 6)) { |
|
isbright = TRUE; |
|
color += 6; |
|
} |
|
cl = find_color(color); |
|
if (cl < 0) |
|
return NULL; |
|
ccol = g_new0(ccolor_t, 1); |
|
ccol->color_attrib = isbright ? A_BOLD : A_NORMAL; |
|
ccol->color_pair = cl + COLOR_max; // User colors come after the internal ones |
|
return ccol; |
|
} |
|
|
|
static void ensure_string_htable(GHashTable **table, |
|
GDestroyNotify value_destroy_func) |
|
{ |
|
if (*table) // Have it already |
|
return; |
|
*table = g_hash_table_new_full(g_str_hash, g_str_equal, |
|
g_free, value_destroy_func); |
|
} |
|
|
|
// Sets the coloring mode for given MUC |
|
// The MUC room does not need to be in the roster at that time |
|
// muc - the JID of room |
|
// type - the new type |
|
void scr_muc_color(const char *muc, muccol_t type) |
|
{ |
|
gchar *muclow = g_utf8_strdown(muc, -1); |
|
if (type == MC_REMOVE) { // Remove it |
|
if (strcmp(muc, "*")) { |
|
if (muccolors && g_hash_table_lookup(muccolors, muclow)) |
|
g_hash_table_remove(muccolors, muclow); |
|
} else { |
|
scr_LogPrint(LPRINT_NORMAL, "Can not remove global coloring mode"); |
|
} |
|
g_free(muclow); |
|
} else { // Add or overwrite |
|
if (strcmp(muc, "*")) { |
|
muccol_t *value = g_new(muccol_t, 1); |
|
*value = type; |
|
ensure_string_htable(&muccolors, g_free); |
|
g_hash_table_replace(muccolors, muclow, value); |
|
} else { |
|
glob_muccol = type; |
|
g_free(muclow); |
|
} |
|
} |
|
// Need to redraw? |
|
if (chatmode && |
|
((buddy_search_jid(muc) == current_buddy) || !strcmp(muc, "*"))) |
|
scr_update_buddy_window(); |
|
} |
|
|
|
// Sets the color for nick in MUC |
|
// If color is "-", the color is marked as automaticly assigned and is |
|
// not used if the room is in the "preset" mode |
|
void scr_muc_nick_color(const char *nick, const char *color) |
|
{ |
|
char *snick, *mnick; |
|
bool need_update = FALSE; |
|
snick = g_strdup_printf("<%s>", nick); |
|
mnick = g_strdup_printf("*%s ", nick); |
|
if (!strcmp(color, "-")) { // Remove the color |
|
if (nickcolors) { |
|
nickcolor_t *nc = g_hash_table_lookup(nickcolors, snick); |
|
if (nc) { // Have this nick already |
|
nc->manual = FALSE; |
|
nc = g_hash_table_lookup(nickcolors, mnick); |
|
assert(nc); // Must have both at the same time |
|
nc->manual = FALSE; |
|
}// Else -> no color saved, nothing to delete |
|
} |
|
g_free(snick); // They are not saved in the hash |
|
g_free(mnick); |
|
need_update = TRUE; |
|
} else { |
|
ccolor_t *cl = get_user_color(color); |
|
if (!cl) { |
|
scr_LogPrint(LPRINT_NORMAL, "No such color name"); |
|
g_free(snick); |
|
g_free(mnick); |
|
} else { |
|
nickcolor_t *nc = g_new(nickcolor_t, 1); |
|
ensure_string_htable(&nickcolors, NULL); |
|
nc->manual = TRUE; |
|
nc->color = cl; |
|
// Free the struct, if any there already |
|
g_free(g_hash_table_lookup(nickcolors, mnick)); |
|
// Save the new ones |
|
g_hash_table_replace(nickcolors, mnick, nc); |
|
g_hash_table_replace(nickcolors, snick, nc); |
|
need_update = TRUE; |
|
} |
|
} |
|
if (need_update && chatmode && |
|
(buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM)) |
|
scr_update_buddy_window(); |
|
} |
|
|
|
static void free_rostercolrule(rostercolor_t *col) |
|
{ |
|
g_free(col->status); |
|
g_free(col->wildcard); |
|
g_free(col->color); |
|
g_pattern_spec_free(col->compiled); |
|
g_free(col); |
|
} |
|
|
|
// Removes all roster coloring rules |
|
void scr_roster_clear_color(void) |
|
{ |
|
GSList *head; |
|
for (head = rostercolrules; head; head = g_slist_next(head)) { |
|
free_rostercolrule(head->data); |
|
} |
|
g_slist_free(rostercolrules); |
|
rostercolrules = NULL; |
|
scr_update_roster(); |
|
} |
|
|
|
// Adds, modifies or removes roster coloring rule |
|
// color set to "-" removes the rule, |
|
// otherwise it is modified (if exists) or added |
|
// |
|
// Returns weather it was successfull (therefore the roster should be |
|
// redrawed) or not. If it failed, for example because of invalid color |
|
// name, it also prints the error. |
|
bool scr_roster_color(const char *status, const char *wildcard, |
|
const char *color) |
|
{ |
|
GSList *head; |
|
GSList *found = NULL; |
|
for (head = rostercolrules; head; head = g_slist_next(head)) { |
|
rostercolor_t *rc = head->data; |
|
if ((!strcmp(status, rc->status)) && (!strcmp(wildcard, rc->wildcard))) { |
|
found = head; |
|
break; |
|
} |
|
} |
|
if (!strcmp(color,"-")) { // Delete the rule |
|
if (found) { |
|
free_rostercolrule(found->data); |
|
rostercolrules = g_slist_delete_link(rostercolrules, found); |
|
scr_update_roster(); |
|
return TRUE; |
|
} else { |
|
scr_LogPrint(LPRINT_NORMAL, "No such color rule, nothing removed"); |
|
return FALSE; |
|
} |
|
} else { |
|
ccolor_t *cl = get_user_color(color); |
|
if (!cl) { |
|
scr_LogPrint(LPRINT_NORMAL, "No such color name"); |
|
return FALSE; |
|
} |
|
if (found) { |
|
rostercolor_t *rc = found->data; |
|
g_free(rc->color); |
|
rc->color = cl; |
|
} else { |
|
rostercolor_t *rc = g_new(rostercolor_t, 1); |
|
rc->status = g_strdup(status); |
|
rc->wildcard = g_strdup(wildcard); |
|
rc->compiled = g_pattern_spec_new(wildcard); |
|
rc->color = cl; |
|
rostercolrules = g_slist_prepend(rostercolrules, rc); |
|
} |
|
scr_update_roster(); |
|
return TRUE; |
|
} |
|
} |
|
|
|
static void parse_colors(void) |
|
{ |
|
const char *colors[] = { |
|
"", "", |
|
"general", |
|
"msgout", |
|
"msghl", |
|
"status", |
|
"log", |
|
"roster", |
|
"rostersel", |
|
"rosterselmsg", |
|
"rosternewmsg", |
|
"info", |
|
"msgin", |
|
"readmark", |
|
"timestamp", |
|
NULL |
|
}; |
|
|
|
const char *color; |
|
const char *background = settings_opt_get("color_background"); |
|
const char *backselected = settings_opt_get("color_bgrostersel"); |
|
const char *backstatus = settings_opt_get("color_bgstatus"); |
|
char *tmp; |
|
int i; |
|
|
|
// Initialize color attributes |
|
memset(COLOR_ATTRIB, 0, sizeof(COLOR_ATTRIB)); |
|
|
|
// Default values |
|
if (!background) background = "black"; |
|
if (!backselected) backselected = "cyan"; |
|
if (!backstatus) backstatus = "blue"; |
|
|
|
for (i=0; colors[i]; i++) { |
|
tmp = g_strdup_printf("color_%s", colors[i]); |
|
color = settings_opt_get(tmp); |
|
g_free(tmp); |
|
|
|
if (color) { |
|
if (!strncmp(color, "bright", 6)) { |
|
COLOR_ATTRIB[i+1] = A_BOLD; |
|
color += 6; |
|
} |
|
} |
|
|
|
switch (i + 1) { |
|
case 1: |
|
init_pair(1, COLOR_BLACK, COLOR_WHITE); |
|
break; |
|
case 2: |
|
init_pair(2, COLOR_WHITE, COLOR_BLACK); |
|
break; |
|
case COLOR_GENERAL: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE), |
|
find_color(background)); |
|
break; |
|
case COLOR_MSGOUT: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_CYAN), |
|
find_color(background)); |
|
break; |
|
case COLOR_MSGHL: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_YELLOW), |
|
find_color(background)); |
|
break; |
|
case COLOR_STATUS: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE), |
|
find_color(backstatus)); |
|
break; |
|
case COLOR_LOG: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE), |
|
find_color(background)); |
|
break; |
|
case COLOR_ROSTER: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_GREEN), |
|
find_color(background)); |
|
break; |
|
case COLOR_ROSTERSEL: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_BLUE), |
|
find_color(backselected)); |
|
break; |
|
case COLOR_ROSTERSELNMSG: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_RED), |
|
find_color(backselected)); |
|
break; |
|
case COLOR_ROSTERNMSG: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_RED), |
|
find_color(background)); |
|
break; |
|
case COLOR_INFO: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE), |
|
find_color(background)); |
|
break; |
|
case COLOR_MSGIN: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE), |
|
find_color(background)); |
|
break; |
|
case COLOR_READMARK: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_RED), |
|
find_color(background)); |
|
break; |
|
case COLOR_TIMESTAMP: |
|
init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE), |
|
find_color(background)); |
|
break; |
|
} |
|
} |
|
for (i = COLOR_max; i < (COLOR_max + COLORS); i++) |
|
init_pair(i, i-COLOR_max, find_color(background)); |
|
|
|
if (!nickcols) { |
|
char *ncolors = g_strdup(settings_opt_get("nick_colors")); |
|
if (ncolors) { |
|
char *ncolor_start, *ncolor_end; |
|
ncolor_start = ncolor_end = ncolors; |
|
|
|
while (*ncolor_end) |
|
ncolor_end++; |
|
|
|
while (ncolors < ncolor_end && *ncolors) { |
|
if ((*ncolors == ' ') || (*ncolors == '\t')) { |
|
ncolors++; |
|
} else { |
|
char *end = ncolors; |
|
ccolor_t *cl; |
|
while (*end && (*end != ' ') && (*end != '\t')) |
|
end++; |
|
*end = '\0'; |
|
cl = get_user_color(ncolors); |
|
if (!cl) { |
|
scr_LogPrint(LPRINT_NORMAL, "Unknown color %s", ncolors); |
|
} else { |
|
nickcols = g_realloc(nickcols, (++nickcolcount) * sizeof *nickcols); |
|
nickcols[nickcolcount-1] = cl; |
|
} |
|
ncolors = end+1; |
|
} |
|
} |
|
g_free(ncolor_start); |
|
} |
|
if (!nickcols) { // Fallback to have something |
|
nickcolcount = 1; |
|
nickcols = g_new(ccolor_t*, 1); |
|
*nickcols = g_new(ccolor_t, 1); |
|
(*nickcols)->color_pair = COLOR_GENERAL; |
|
(*nickcols)->color_attrib = A_NORMAL; |
|
} |
|
} |
|
|
|
colors_stalled = FALSE; |
|
} |
|
|
|
static void init_keycodes(void) |
|
{ |
|
add_keyseq("O5A", MKEY_EQUIV, 521); // Ctrl-Up |
|
add_keyseq("O5B", MKEY_EQUIV, 514); // Ctrl-Down |
|
add_keyseq("O5C", MKEY_EQUIV, 518); // Ctrl-Right |
|
add_keyseq("O5D", MKEY_EQUIV, 516); // Ctrl-Left |
|
add_keyseq("O6A", MKEY_EQUIV, 520); // Shift-Up |
|
add_keyseq("O6B", MKEY_EQUIV, 513); // Shift-Down |
|
add_keyseq("O6C", MKEY_EQUIV, 402); // Shift-Right |
|
add_keyseq("O6D", MKEY_EQUIV, 393); // Shift-Left |
|
add_keyseq("O2A", MKEY_EQUIV, 520); // Shift-Up |
|
add_keyseq("O2B", MKEY_EQUIV, 513); // Shift-Down |
|
add_keyseq("O2C", MKEY_EQUIV, 402); // Shift-Right |
|
add_keyseq("O2D", MKEY_EQUIV, 393); // Shift-Left |
|
add_keyseq("[5^", MKEY_CTRL_PGUP, 0); // Ctrl-PageUp |
|
add_keyseq("[6^", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown |
|
add_keyseq("[5@", MKEY_CTRL_SHIFT_PGUP, 0); // Ctrl-Shift-PageUp |
|
add_keyseq("[6@", MKEY_CTRL_SHIFT_PGDOWN, 0); // Ctrl-Shift-PageDown |
|
add_keyseq("[7@", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home |
|
add_keyseq("[8@", MKEY_CTRL_SHIFT_END, 0); // Ctrl-Shift-End |
|
add_keyseq("[8^", MKEY_CTRL_END, 0); // Ctrl-End |
|
add_keyseq("[7^", MKEY_CTRL_HOME, 0); // Ctrl-Home |
|
add_keyseq("[2^", MKEY_CTRL_INS, 0); // Ctrl-Insert |
|
add_keyseq("[3^", MKEY_CTRL_DEL, 0); // Ctrl-Delete |
|
|
|
// Xterm |
|
add_keyseq("[1;5A", MKEY_EQUIV, 521); // Ctrl-Up |
|
add_keyseq("[1;5B", MKEY_EQUIV, 514); // Ctrl-Down |
|
add_keyseq("[1;5C", MKEY_EQUIV, 518); // Ctrl-Right |
|
add_keyseq("[1;5D", MKEY_EQUIV, 516); // Ctrl-Left |
|
add_keyseq("[1;6A", MKEY_EQUIV, 520); // Ctrl-Shift-Up |
|
add_keyseq("[1;6B", MKEY_EQUIV, 513); // Ctrl-Shift-Down |
|
add_keyseq("[1;6C", MKEY_EQUIV, 402); // Ctrl-Shift-Right |
|
add_keyseq("[1;6D", MKEY_EQUIV, 393); // Ctrl-Shift-Left |
|
add_keyseq("[1;6H", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home |
|
add_keyseq("[1;6F", MKEY_CTRL_SHIFT_END, 0); // Ctrl-Shift-End |
|
add_keyseq("[1;2A", MKEY_EQUIV, 521); // Shift-Up |
|
add_keyseq("[1;2B", MKEY_EQUIV, 514); // Shift-Down |
|
add_keyseq("[5;5~", MKEY_CTRL_PGUP, 0); // Ctrl-PageUp |
|
add_keyseq("[6;5~", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown |
|
add_keyseq("[1;5F", MKEY_CTRL_END, 0); // Ctrl-End |
|
add_keyseq("[1;5H", MKEY_CTRL_HOME, 0); // Ctrl-Home |
|
add_keyseq("[2;5~", MKEY_CTRL_INS, 0); // Ctrl-Insert |
|
add_keyseq("[3;5~", MKEY_CTRL_DEL, 0); // Ctrl-Delete |
|
|
|
// PuTTY |
|
add_keyseq("[A", MKEY_EQUIV, 521); // Ctrl-Up |
|
add_keyseq("[B", MKEY_EQUIV, 514); // Ctrl-Down |
|
add_keyseq("[C", MKEY_EQUIV, 518); // Ctrl-Right |
|
add_keyseq("[D", MKEY_EQUIV, 516); // Ctrl-Left |
|
|
|
// screen |
|
add_keyseq("Oa", MKEY_EQUIV, 521); // Ctrl-Up |
|
add_keyseq("Ob", MKEY_EQUIV, 514); // Ctrl-Down |
|
add_keyseq("Oc", MKEY_EQUIV, 518); // Ctrl-Right |
|
add_keyseq("Od", MKEY_EQUIV, 516); // Ctrl-Left |
|
add_keyseq("[a", MKEY_EQUIV, 520); // Shift-Up |
|
add_keyseq("[b", MKEY_EQUIV, 513); // Shift-Down |
|
add_keyseq("[c", MKEY_EQUIV, 402); // Shift-Right |
|
add_keyseq("[d", MKEY_EQUIV, 393); // Shift-Left |
|
add_keyseq("[5$", MKEY_SHIFT_PGUP, 0); // Shift-PageUp |
|
add_keyseq("[6$", MKEY_SHIFT_PGDOWN, 0); // Shift-PageDown |
|
|
|
// VT100 |
|
add_keyseq("[H", MKEY_EQUIV, KEY_HOME); // Home |
|
add_keyseq("[F", MKEY_EQUIV, KEY_END); // End |
|
|
|
// Konsole Linux |
|
add_keyseq("[1~", MKEY_EQUIV, KEY_HOME); // Home |
|
add_keyseq("[4~", MKEY_EQUIV, KEY_END); // End |
|
} |
|
|
|
// scr_init_bindings() |
|
// Create default key bindings |
|
// Return 0 if error and 1 if none |
|
void scr_init_bindings(void) |
|
{ |
|
GString *sbuf = g_string_new(""); |
|
|
|
// Common backspace key codes: 8, 127 |
|
settings_set(SETTINGS_TYPE_BINDING, "8", "iline char_bdel"); // Ctrl-h |
|
settings_set(SETTINGS_TYPE_BINDING, "127", "iline char_bdel"); |
|
g_string_printf(sbuf, "%d", KEY_BACKSPACE); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_bdel"); |
|
g_string_printf(sbuf, "%d", KEY_DC); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_fdel"); |
|
g_string_printf(sbuf, "%d", KEY_LEFT); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline bchar"); |
|
g_string_printf(sbuf, "%d", KEY_RIGHT); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline fchar"); |
|
settings_set(SETTINGS_TYPE_BINDING, "7", "iline compl_cancel"); // Ctrl-g |
|
g_string_printf(sbuf, "%d", KEY_UP); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, |
|
"iline hist_beginning_search_bwd"); |
|
g_string_printf(sbuf, "%d", KEY_DOWN); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, |
|
"iline hist_beginning_search_fwd"); |
|
g_string_printf(sbuf, "%d", KEY_PPAGE); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster up"); |
|
g_string_printf(sbuf, "%d", KEY_NPAGE); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster down"); |
|
g_string_printf(sbuf, "%d", KEY_HOME); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_start"); |
|
settings_set(SETTINGS_TYPE_BINDING, "1", "iline iline_start"); // Ctrl-a |
|
g_string_printf(sbuf, "%d", KEY_END); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_end"); |
|
settings_set(SETTINGS_TYPE_BINDING, "5", "iline iline_end"); // Ctrl-e |
|
// Ctrl-o (accept-line-and-down-history): |
|
settings_set(SETTINGS_TYPE_BINDING, "15", "iline iline_accept_down_hist"); |
|
settings_set(SETTINGS_TYPE_BINDING, "21", "iline iline_bdel"); // Ctrl-u |
|
g_string_printf(sbuf, "%d", KEY_EOL); |
|
settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_fdel"); |
|
settings_set(SETTINGS_TYPE_BINDING, "11", "iline iline_fdel"); // Ctrl-k |
|
settings_set(SETTINGS_TYPE_BINDING, "16", "buffer up"); // Ctrl-p |
|
settings_set(SETTINGS_TYPE_BINDING, "14", "buffer down"); // Ctrl-n |
|
settings_set(SETTINGS_TYPE_BINDING, "20", "iline char_swap"); // Ctrl-t |
|
settings_set(SETTINGS_TYPE_BINDING, "23", "iline word_bdel"); // Ctrl-w |
|
settings_set(SETTINGS_TYPE_BINDING, "M98", "iline bword"); // Meta-b |
|
settings_set(SETTINGS_TYPE_BINDING, "M102", "iline fword"); // Meta-f |
|
settings_set(SETTINGS_TYPE_BINDING, "M100", "iline word_fdel"); // Meta-d |
|
// Ctrl-Left (2 codes): |
|
settings_set(SETTINGS_TYPE_BINDING, "515", "iline bword"); |
|
settings_set(SETTINGS_TYPE_BINDING, "516", "iline bword"); |
|
// Ctrl-Right (2 codes): |
|
settings_set(SETTINGS_TYPE_BINDING, "517", "iline fword"); |
|
settings_set(SETTINGS_TYPE_BINDING, "518", "iline fword"); |
|
settings_set(SETTINGS_TYPE_BINDING, "12", "screen_refresh"); // Ctrl-l |
|
settings_set(SETTINGS_TYPE_BINDING, "27", "chat_disable --show-roster");// Esc |
|
settings_set(SETTINGS_TYPE_BINDING, "M27", "chat_disable"); // Esc-Esc |
|
settings_set(SETTINGS_TYPE_BINDING, "4", "iline send_multiline"); // Ctrl-d |
|
settings_set(SETTINGS_TYPE_BINDING, "M117", "iline word_upcase"); // Meta-u |
|
settings_set(SETTINGS_TYPE_BINDING, "M108", "iline word_downcase"); // Meta-l |
|
settings_set(SETTINGS_TYPE_BINDING, "M99", "iline word_capit"); // Meta-c |
|
|
|
settings_set(SETTINGS_TYPE_BINDING, "265", "help"); // Bind F1 to help... |
|
|
|
g_string_free(sbuf, TRUE); |
|
} |
|
|
|
// is_speckey(key) |
|
// Return TRUE if key is a special code, i.e. no char should be displayed on |
|
// the screen. It's not very nice, it's a workaround for the systems where |
|
// isprint(KEY_PPAGE) returns TRUE... |
|
static int is_speckey(int key) |
|
{ |
|
switch (key) { |
|
case 127: |
|
case 393: |
|
case 402: |
|
case KEY_BACKSPACE: |
|
case KEY_DC: |
|
case KEY_LEFT: |
|
case KEY_RIGHT: |
|
case KEY_UP: |
|
case KEY_DOWN: |
|
case KEY_PPAGE: |
|
case KEY_NPAGE: |
|
case KEY_HOME: |
|
case KEY_END: |
|
case KEY_EOL: |
|
return TRUE; |
|
} |
|
|
|
// Fn keys |
|
if (key >= 265 && key < 265+12) |
|
return TRUE; |
|
|
|
// Special key combinations |
|
if (key >= 513 && key <= 521) |
|
return TRUE; |
|
|
|
return FALSE; |
|
} |
|
|
|
void scr_init_locale_charset(void) |
|
{ |
|
setlocale(LC_ALL, ""); |
|
#ifdef HAVE_LOCALCHARSET_H |
|
LocaleCharSet = locale_charset(); |
|
#else |
|
LocaleCharSet = nl_langinfo(CODESET); |
|
#endif |
|
utf8_mode = (strcmp(LocaleCharSet, "UTF-8") == 0); |
|
} |
|
|
|
gboolean scr_curses_status(void) |
|
{ |
|
return Curses; |
|
} |
|
|
|
static gchar *scr_vi_mode_guard(const gchar *key, const gchar *new_value) |
|
{ |
|
int new_mode = 0; |
|
if (new_value) |
|
new_mode = atoi(new_value); |
|
if (new_mode == 0 || new_mode == 1) |
|
vi_mode = new_mode; |
|
return g_strdup(new_value); |
|
} |
|
|
|
static gchar *scr_color_guard(const gchar *key, const gchar *new_value) |
|
{ |
|
if (g_strcmp0(settings_opt_get(key), new_value)) |
|
colors_stalled = TRUE; |
|
return g_strdup(new_value); |
|
} |
|
|
|
void scr_init_curses(void) |
|
{ |
|
/* Key sequences initialization */ |
|
init_keycodes(); |
|
|
|
initscr(); |
|
raw(); |
|
noecho(); |
|
nonl(); |
|
intrflush(stdscr, FALSE); |
|
start_color(); |
|
use_default_colors(); |
|
#ifdef NCURSES_MOUSE_VERSION |
|
if (settings_opt_get_int("use_mouse")) |
|
mousemask(ALL_MOUSE_EVENTS, NULL); |
|
#endif |
|
|
|
if (settings_opt_get("escdelay")) { |
|
#ifdef HAVE_ESCDELAY |
|
ESCDELAY = (unsigned) settings_opt_get_int("escdelay"); |
|
#else |
|
scr_LogPrint(LPRINT_LOGNORM, "ERROR: no ESCDELAY support."); |
|
#endif |
|
} |
|
|
|
// Set up vi_mode guard |
|
settings_set_guard("vi_mode", scr_vi_mode_guard); |
|
if (settings_opt_get_int("vi_mode") == 1) |
|
vi_mode = true; |
|
|
|
parse_colors(); |
|
|
|
settings_set_guard("color_background", scr_color_guard); |
|
settings_set_guard("color_general", scr_color_guard); |
|
settings_set_guard("color_info", scr_color_guard); |
|
settings_set_guard("color_msgin", scr_color_guard); |
|
settings_set_guard("color_msgout", scr_color_guard); |
|
settings_set_guard("color_msghl", scr_color_guard); |
|
settings_set_guard("color_bgstatus", scr_color_guard); |
|
settings_set_guard("color_status", scr_color_guard); |
|
settings_set_guard("color_log", scr_color_guard); |
|
settings_set_guard("color_roster", scr_color_guard); |
|
settings_set_guard("color_bgrostersel", scr_color_guard); |
|
settings_set_guard("color_rostersel", scr_color_guard); |
|
settings_set_guard("color_rosterselmsg", scr_color_guard); |
|
settings_set_guard("color_rosternewmsg", scr_color_guard); |
|
settings_set_guard("color_timestamp", scr_color_guard); |
|
|
|
getmaxyx(stdscr, maxY, maxX); |
|
Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT; |
|
// Note scr_draw_main_window() should be called early after scr_init_curses() |
|
// to update Log_Win_Height and set max{X,Y} |
|
|
|
inputLine[0] = 0; |
|
ptr_inputline = inputLine; |
|
|
|
Curses = TRUE; |
|
|
|
g_log_set_handler("GLib", G_LOG_LEVEL_MASK, scr_glog_print, NULL); |
|
return; |
|
} |
|
|
|
void scr_terminate_curses(void) |
|
{ |
|
if (!Curses) return; |
|
clear(); |
|
refresh(); |
|
endwin(); |
|
Curses = FALSE; |
|
return; |
|
} |
|
|
|
void scr_beep(void) |
|
{ |
|
beep(); |
|
} |
|
|
|
// This and following belongs to dynamic setting of time prefix |
|
static const char *timeprefixes[] = { |
|
"%m-%d %H:%M ", |
|
"%H:%M ", |
|
" " |
|
}; |
|
|
|
static const char *spectimeprefixes[] = { |
|
"%m-%d %H:%M:%S ", |
|
"%H:%M:%S ", |
|
" " |
|
}; |
|
|
|
static int timepreflengths[] = { |
|
// (length of the corresponding timeprefix + 5) |
|
17, |
|
11, |
|
6 |
|
}; |
|
|
|
static const char *gettprefix(void) |
|
{ |
|
guint n = settings_opt_get_int("time_prefix"); |
|
return timeprefixes[(n < 3 ? n : 0)]; |
|
} |
|
|
|
static const char *getspectprefix(void) |
|
{ |
|
guint n = settings_opt_get_int("time_prefix"); |
|
return spectimeprefixes[(n < 3 ? n : 0)]; |
|
} |
|
|
|
guint scr_getprefixwidth(void) |
|
{ |
|
guint n = settings_opt_get_int("time_prefix"); |
|
return timepreflengths[(n < 3 ? n : 0)]; |
|
} |
|
|
|
guint scr_gettextwidth(void) |
|
{ |
|
int used_width = Roster_Width + scr_getprefixwidth(); |
|
return maxX > used_width ? maxX - used_width : 0; |
|
} |
|
|
|
guint scr_gettextheight(void) |
|
{ |
|
// log window, two status bars and one input line |
|
return maxY - Log_Win_Height - 3; |
|
} |
|
|
|
guint scr_getlogwinheight(void) |
|
{ |
|
return Log_Win_Height; |
|
} |
|
|
|
// scr_print_logwindow(string) |
|
// Display the string in the log window. |
|
// Note: The string must be in the user's locale! |
|
void scr_print_logwindow(const char *string) |
|
{ |
|
time_t timestamp; |
|
char strtimestamp[64]; |
|
|
|
timestamp = time(NULL); |
|
strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(×tamp)); |
|
if (Curses) { |
|
wprintw(logWnd, "\n%s %s", strtimestamp, string); |
|
update_panels(); |
|
} else { |
|
printf("%s %s\n", strtimestamp, string); |
|
} |
|
} |
|
|
|
// scr_log_print(...) |
|
// Display a message in the log window and in the status buffer. |
|
// Add the message to the tracelog file if the log flag is set. |
|
// This function will convert from UTF-8 unless the LPRINT_NOTUTF8 flag is set. |
|
void scr_log_print(unsigned int flag, const char *fmt, ...) |
|
{ |
|
time_t timestamp; |
|
char strtimestamp[64]; |
|
char *buffer, *btext; |
|
char *convbuf1 = NULL, *convbuf2 = NULL; |
|
va_list ap; |
|
|
|
if (!(flag & ~LPRINT_NOTUTF8)) return; // Shouldn't happen |
|
|
|
timestamp = time(NULL); |
|
strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(×tamp)); |
|
va_start(ap, fmt); |
|
btext = g_strdup_vprintf(fmt, ap); |
|
va_end(ap); |
|
|
|
if (flag & LPRINT_NORMAL) { |
|
char *buffer_locale; |
|
char *buf_specialwindow; |
|
|
|
buffer = g_strdup_printf("%s %s", strtimestamp, btext); |
|
|
|
// Convert buffer to current locale for wprintw() |
|
if (!(flag & LPRINT_NOTUTF8)) |
|
buffer_locale = convbuf1 = from_utf8(buffer); |
|
else |
|
buffer_locale = buffer; |
|
|
|
if (!buffer_locale) { |
|
wprintw(logWnd, |
|
"\n%s*Error: cannot convert string to locale.", strtimestamp); |
|
update_panels(); |
|
g_free(buffer); |
|
g_free(btext); |
|
return; |
|
} |
|
|
|
// For the special status buffer, we need utf-8, but without the timestamp |
|
if (flag & LPRINT_NOTUTF8) |
|
buf_specialwindow = convbuf2 = to_utf8(btext); |
|
else |
|
buf_specialwindow = btext; |
|
|
|
if (Curses) { |
|
wprintw(logWnd, "\n%s", buffer_locale); |
|
update_panels(); |
|
scr_write_in_window(NULL, buf_specialwindow, timestamp, |
|
HBB_PREFIX_SPECIAL, FALSE, 0, NULL); |
|
} else { |
|
printf("%s\n", buffer_locale); |
|
// ncurses are not initialized yet, so we call directly hbuf routine |
|
hbuf_add_line(&statushbuf, buf_specialwindow, timestamp, |
|
HBB_PREFIX_SPECIAL, 0, 0, 0, NULL); |
|
} |
|
|
|
g_free(convbuf1); |
|
g_free(convbuf2); |
|
g_free(buffer); |
|
} |
|
|
|
if (flag & (LPRINT_LOG|LPRINT_DEBUG)) { |
|
strftime(strtimestamp, 23, "[%Y-%m-%d %H:%M:%S]", localtime(×tamp)); |
|
buffer = g_strdup_printf("%s %s\n", strtimestamp, btext); |
|
ut_write_log(flag, buffer); |
|
g_free(buffer); |
|
} |
|
g_free(btext); |
|
} |
|
|
|
// This is a GLogFunc for Glib log messages |
|
static void scr_glog_print(const gchar *log_domain, GLogLevelFlags log_level, |
|
const gchar *message, gpointer user_data) |
|
{ |
|
scr_log_print(LPRINT_NORMAL, "[%s] %s", log_domain, message); |
|
} |
|
|
|
static winbuf_t *scr_search_window(const char *winId, int special) |
|
{ |
|
char *id; |
|
winbuf_t *wbp; |
|
|
|
if (special) |
|
return statusWindow; // Only one special window atm. |
|
|
|
if (!winId) |
|
return NULL; |
|
|
|
id = g_strdup(winId); |
|
mc_strtolower(id); |
|
wbp = g_hash_table_lookup(winbufhash, id); |
|
g_free(id); |
|
return wbp; |
|
} |
|
|
|
int scr_buddy_buffer_exists(const char *bjid) |
|
{ |
|
return (scr_search_window(bjid, FALSE) != NULL); |
|
} |
|
|
|
// scr_new_buddy(title, dontshow) |
|
// Note: title (aka winId/jid) can be NULL for special buffers |
|
static winbuf_t *scr_new_buddy(const char *title, int dont_show) |
|
{ |
|
winbuf_t *tmp; |
|
char *id; |
|
|
|
tmp = g_new0(winbuf_t, 1); |
|
|
|
tmp->win = activechatWnd; |
|
tmp->panel = activechatPanel; |
|
|
|
if (!dont_show) { |
|
currentWindow = tmp; |
|
} else { |
|
if (currentWindow) |
|
top_panel(currentWindow->panel); |
|
else |
|
top_panel(chatPanel); |
|
} |
|
update_panels(); |
|
|
|
// If title is NULL, this is a special buffer |
|
if (!title) { |
|
tmp->bd = g_new0(buffdata_t, 1); |
|
return tmp; |
|
} |
|
|
|
id = hlog_get_log_jid(title); |
|
if (id) { |
|
// This is a symlinked history log file. |
|
// Let's check if the target JID buffer has already been created. |
|
winbuf_t *wb = scr_search_window(id, FALSE); |
|
if (!wb) |
|
wb = scr_new_buddy(id, TRUE); |
|
tmp->bd = wb->bd; |
|
tmp->bd->refcount++; |
|
g_free(id); |
|
} else { // Load buddy history from file (if enabled) |
|
tmp->bd = g_new0(buffdata_t, 1); |
|
hlog_read_history(title, &tmp->bd->hbuf, scr_gettextwidth()); |
|
|
|
// Set a readmark to separate new content |
|
hbuf_set_readmark(tmp->bd->hbuf, TRUE); |
|
} |
|
|
|
id = g_strdup(title); |
|
mc_strtolower(id); |
|
g_hash_table_insert(winbufhash, id, tmp); |
|
|
|
return tmp; |
|
} |
|
|
|
// scr_line_prefix(line, pref, preflen) |
|
// Use data from the hbb_line structure and write the prefix |
|
// to pref (not exceeding preflen, trailing null byte included). |
|
size_t scr_line_prefix(hbb_line *line, char *pref, guint preflen) |
|
{ |
|
char date[64]; |
|
size_t timepreflen = 0; |
|
|
|
if (line->timestamp && |
|
!(line->flags & (HBB_PREFIX_SPECIAL|HBB_PREFIX_CONT))) { |
|
timepreflen = strftime(date, 30, gettprefix(), localtime(&line->timestamp)); |
|
} else |
|
strcpy(date, " "); |
|
|
|
if (!(line->flags & HBB_PREFIX_CONT)) { |
|
if (line->flags & HBB_PREFIX_INFO) { |
|
char dir = '*'; |
|
if (line->flags & HBB_PREFIX_IN) |
|
dir = '<'; |
|
else if (line->flags & HBB_PREFIX_OUT) |
|
dir = '>'; |
|
g_snprintf(pref, preflen, "%s*%c* ", date, dir); |
|
} else if (line->flags & HBB_PREFIX_ERR) { |
|
char dir = '#'; |
|
if (line->flags & HBB_PREFIX_IN) |
|
dir = '<'; |
|
else if (line->flags & HBB_PREFIX_OUT) |
|
dir = '>'; |
|
g_snprintf(pref, preflen, "%s#%c# ", date, dir); |
|
} else if (line->flags & HBB_PREFIX_IN) { |
|
char cryptflag; |
|
if (line->flags & HBB_PREFIX_PGPCRYPT) |
|
cryptflag = '~'; |
|
else if (line->flags & HBB_PREFIX_OTRCRYPT) |
|
cryptflag = 'O'; |
|
else |
|
cryptflag = '='; |
|
g_snprintf(pref, preflen, "%s<%c= ", date, cryptflag); |
|
} else if (line->flags & HBB_PREFIX_OUT) { |
|
char cryptflag, receiptflag; |
|
if (line->flags & HBB_PREFIX_PGPCRYPT) |
|
cryptflag = '~'; |
|
else if (line->flags & HBB_PREFIX_OTRCRYPT) |
|
cryptflag = 'O'; |
|
else |
|
cryptflag = '-'; |
|
if (line->flags & HBB_PREFIX_RECEIPT) |
|
receiptflag = 'r'; |
|
else |
|
receiptflag = '-'; |
|
g_snprintf(pref, preflen, "%s%c%c> ", date, receiptflag, cryptflag); |
|
} else if (line->flags & HBB_PREFIX_SPECIAL) { |
|
timepreflen = strftime(date, 30, getspectprefix(), localtime(&line->timestamp)); |
|
g_snprintf(pref, preflen, "%s ", date); |
|
} else { |
|
g_snprintf(pref, preflen, "%s ", date); |
|
} |
|
} else { |
|
g_snprintf(pref, preflen, " "); |
|
} |
|
return timepreflen; |
|
} |
|
|
|
// scr_update_window() |
|
// (Re-)Display the given chat window. |
|
static void scr_update_window(winbuf_t *win_entry) |
|
{ |
|
int n, mark_offset = 0; |
|
guint prefixwidth; |
|
char pref[96]; |
|
hbb_line **lines, *line; |
|
GList *hbuf_head; |
|
int color = COLOR_GENERAL; |
|
bool readmark = FALSE; |
|
bool skipline = FALSE; |
|
int autolock; |
|
|
|
autolock = settings_opt_get_int("buffer_smart_scrolling"); |
|
|
|
prefixwidth = scr_getprefixwidth(); |
|
prefixwidth = MIN(prefixwidth, sizeof pref); |
|
|
|
// Should the window be empty? |
|
if (win_entry->bd->cleared) { |
|
werase(win_entry->win); |
|
if (autolock && win_entry->bd->lock) |
|
scr_buffer_scroll_lock(0); |
|
return; |
|
} |
|
|
|
// win_entry->bd->top is the top message of the screen. If it set to NULL, |
|
// we are displaying the last messages. |
|
|
|
// We will show the last CHAT_WIN_HEIGHT lines. |
|
// Let's find out where it begins. |
|
if (!win_entry->bd->top || (g_list_position(g_list_first(win_entry->bd->hbuf), |
|
win_entry->bd->top) == -1)) { |
|
// Move up CHAT_WIN_HEIGHT lines |
|
win_entry->bd->hbuf = g_list_last(win_entry->bd->hbuf); |
|
hbuf_head = win_entry->bd->hbuf; |
|
win_entry->bd->top = NULL; // (Just to make sure) |
|
n = 0; |
|
while (hbuf_head && (n < CHAT_WIN_HEIGHT-1) && g_list_previous(hbuf_head)) { |
|
hbuf_head = g_list_previous(hbuf_head); |
|
n++; |
|
} |
|
// If the buffer is locked, remember current "top" line for the next time. |
|
if (win_entry->bd->lock) |
|
win_entry->bd->top = hbuf_head; |
|
} else |
|
hbuf_head = win_entry->bd->top; |
|
|
|
// Get the last CHAT_WIN_HEIGHT lines, and one more to detect scroll. |
|
lines = hbuf_get_lines(hbuf_head, CHAT_WIN_HEIGHT+1); |
|
|
|
if (CHAT_WIN_HEIGHT > 1) { |
|
// Do we have a read mark? |
|
for (n = 0; n < CHAT_WIN_HEIGHT; n++) { |
|
line = *(lines+n); |
|
if (line) { |
|
if (line->flags & HBB_PREFIX_READMARK) { |
|
// If this is not the last line, we'll display a mark |
|
if (n+1 < CHAT_WIN_HEIGHT && *(lines+n+1)) { |
|
readmark = TRUE; |
|
skipline = TRUE; |
|
mark_offset = -1; |
|
} |
|
} |
|
} else if (readmark) { |
|
// There will be empty lines, so we don't need to skip the first line |
|
skipline = FALSE; |
|
mark_offset = 0; |
|
} |
|
} |
|
} |
|
|
|
// Display the lines |
|
for (n = 0 ; n < CHAT_WIN_HEIGHT; n++) { |
|
int timelen; |
|
int winy = n + mark_offset; |
|
wmove(win_entry->win, winy, 0); |
|
line = *(lines+n); |
|
if (line) { |
|
if (skipline) |
|
goto scr_update_window_skipline; |
|
|
|
if (line->flags & HBB_PREFIX_HLIGHT_OUT) |
|
color = COLOR_MSGOUT; |
|
else if (line->flags & HBB_PREFIX_HLIGHT) |
|
color = COLOR_MSGHL; |
|
else if (line->flags & HBB_PREFIX_INFO) |
|
color = COLOR_INFO; |
|
else if (line->flags & HBB_PREFIX_IN) |
|
color = COLOR_MSGIN; |
|
else |
|
color = COLOR_GENERAL; |
|
|
|
if (color != COLOR_GENERAL) |
|
wbkgdset(win_entry->win, get_color(color)); |
|
|
|
// Generate the prefix area and display it |
|
|
|
timelen = scr_line_prefix(line, pref, prefixwidth); |
|
if (timelen && line->flags & HBB_PREFIX_DELAYED) { |
|
char tmp; |
|
|
|
tmp = pref[timelen]; |
|
pref[timelen] = '\0'; |
|
wbkgdset(win_entry->win, get_color(COLOR_TIMESTAMP)); |
|
wprintw(win_entry->win, "%s", pref); |
|
pref[timelen] = tmp; |
|
wbkgdset(win_entry->win, get_color(color)); |
|
wprintw(win_entry->win, "%s", pref+timelen); |
|
} else |
|
wprintw(win_entry->win, "%s", pref); |
|
|
|
// Make sure we are at the right position |
|
wmove(win_entry->win, winy, prefixwidth-1); |
|
|
|
// The MUC nick - overwrite with proper color |
|
if (line->mucnicklen) { |
|
char *mucjid; |
|
char tmp; |
|
nickcolor_t *actual = NULL; |
|
muccol_t type, *typetmp; |
|
|
|
// Store the char after the nick |
|
tmp = line->text[line->mucnicklen]; |
|
type = glob_muccol; |
|
// Terminate the string after the nick |
|
line->text[line->mucnicklen] = '\0'; |
|
mucjid = g_utf8_strdown(CURRENT_JID, -1); |
|
if (muccolors) { |
|
typetmp = g_hash_table_lookup(muccolors, mucjid); |
|
if (typetmp) |
|
type = *typetmp; |
|
} |
|
g_free(mucjid); |
|
// Need to generate a color for the specified nick? |
|
if ((type == MC_ALL) && (!nickcolors || |
|
!g_hash_table_lookup(nickcolors, line->text))) { |
|
char *snick, *mnick; |
|
nickcolor_t *nc; |
|
const char *p = line->text; |
|
unsigned int nicksum = 0; |
|
snick = g_strdup(line->text); |
|
mnick = g_strdup(line->text); |
|
nc = g_new(nickcolor_t, 1); |
|
ensure_string_htable(&nickcolors, NULL); |
|
while (*p) |
|
nicksum += *p++; |
|
nc->color = nickcols[nicksum % nickcolcount]; |
|
nc->manual = FALSE; |
|
*snick = '<'; |
|
snick[strlen(snick)-1] = '>'; |
|
*mnick = '*'; |
|
mnick[strlen(mnick)-1] = ' '; |
|
// Insert them |
|
g_hash_table_insert(nickcolors, snick, nc); |
|
g_hash_table_insert(nickcolors, mnick, nc); |
|
} |
|
if (nickcolors) |
|
actual = g_hash_table_lookup(nickcolors, line->text); |
|
if (actual && ((type == MC_ALL) || (actual->manual)) |
|
&& (line->flags & HBB_PREFIX_IN) && |
|
(!(line->flags & HBB_PREFIX_HLIGHT_OUT))) |
|
wbkgdset(win_entry->win, compose_color(actual->color)); |
|
wprintw(win_entry->win, "%s", line->text); |
|
// Return the char |
|
line->text[line->mucnicklen] = tmp; |
|
// Return the color back |
|
wbkgdset(win_entry->win, get_color(color)); |
|
} |
|
|
|
// Display text line |
|
wprintw(win_entry->win, "%s", line->text+line->mucnicklen); |
|
wclrtoeol(win_entry->win); |
|
|
|
// Restore default ("general") color |
|
if (color != COLOR_GENERAL) |
|
wbkgdset(win_entry->win, get_color(COLOR_GENERAL)); |
|
|
|
scr_update_window_skipline: |
|
skipline = FALSE; |
|
if (readmark && line->flags & HBB_PREFIX_READMARK) { |
|
int i, w; |
|
mark_offset++; |
|
|
|
// Display the mark |
|
winy = n + mark_offset; |
|
wmove(win_entry->win, winy, 0); |
|
wbkgdset(win_entry->win, get_color(COLOR_READMARK)); |
|
g_snprintf(pref, prefixwidth, " == "); |
|
wprintw(win_entry->win, "%s", pref); |
|
w = scr_gettextwidth() / 3; |
|
for (i=0; i<w; i++) |
|
wprintw(win_entry->win, "== "); |
|
wclrtoeol(win_entry->win); |
|
wbkgdset(win_entry->win, get_color(COLOR_GENERAL)); |
|
} |
|
g_free(line->text); |
|
g_free(line); |
|
} else { |
|
wclrtobot(win_entry->win); |
|
break; |
|
} |
|
} |
|
line = *(lines+CHAT_WIN_HEIGHT); //line is scrolled out and never written |
|
if (line) { |
|
if (autolock && !win_entry->bd->lock) { |
|
if (!hbuf_jump_readmark(hbuf_head)) |
|
scr_buffer_readmark(TRUE); |
|
scr_buffer_scroll_lock(1); |
|
} |
|
g_free(line->text); |
|
g_free(line); |
|
} else if (autolock && win_entry->bd->lock) { |
|
scr_buffer_scroll_lock(0); |
|
} |
|
|
|
g_free(lines); |
|
} |
|
|
|
static winbuf_t *scr_create_window(const char *winId, int special, int dont_show) |
|
{ |
|
if (special) { |
|
if (!statusWindow) { |
|
statusWindow = scr_new_buddy(NULL, dont_show); |
|
statusWindow->bd->hbuf = statushbuf; |
|
} |
|
return statusWindow; |
|
} else { |
|
return scr_new_buddy(winId, dont_show); |
|
} |
|
} |
|
|
|
// scr_show_window() |
|
// Display the chat window with the given identifier. |
|
// "special" must be true if this is a special buffer window. |
|
static void scr_show_window(const char *winId, int special) |
|
{ |
|
winbuf_t *win_entry; |
|
|
|
win_entry = scr_search_window(winId, special); |
|
|
|
if (!win_entry) { |
|
win_entry = scr_create_window(winId, special, FALSE); |
|
} |
|
|
|
top_panel(win_entry->panel); |
|
currentWindow = win_entry; |
|
chatmode = TRUE; |
|
if (!win_entry->bd->lock) |
|
roster_msg_setflag(winId, special, FALSE); |
|
if (!special) |
|
roster_setflags(winId, ROSTER_FLAG_LOCK, TRUE); |
|
scr_update_roster(); |
|
|
|
// Refresh the window |
|
scr_update_window(win_entry); |
|
|
|
// Finished :) |
|
update_panels(); |
|
|
|
top_panel(inputPanel); |
|
} |
|
|
|
// scr_show_buddy_window() |
|
// Display the chat window buffer for the current buddy. |
|
void scr_show_buddy_window(void) |
|
{ |
|
const gchar *bjid; |
|
|
|
buddylist_build(); |
|
if (!current_buddy) { |
|
bjid = NULL; |
|
} else { |
|
bjid = CURRENT_JID; |
|
if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL) { |
|
scr_show_window(buddy_getname(BUDDATA(current_buddy)), TRUE); |
|
return; |
|
} |
|
} |
|
|
|
if (!bjid) { |
|
top_panel(chatPanel); |
|
top_panel(inputPanel); |
|
currentWindow = NULL; |
|
return; |
|
} |
|
|
|
roster_msg_update_unread(bjid, FALSE); |
|
scr_show_window(bjid, FALSE); |
|
} |
|
|
|
// scr_update_buddy_window() |
|
// (Re)Display the current window. |
|
// If chatmode is enabled, call scr_show_buddy_window(), |
|
// else display the chat window. |
|
void scr_update_buddy_window(void) |
|
{ |
|
if (chatmode) { |
|
scr_show_buddy_window(); |
|
return; |
|
} |
|
|
|
top_panel(chatPanel); |
|
top_panel(inputPanel); |
|
} |
|
|
|
// scr_write_in_window() |
|
// Write some text in the winId window (this usually is a jid). |
|
// Use winId == NULL for the special status buffer. |
|
// Lines are splitted when they are too long to fit in the chat window. |
|
// If this window doesn't exist, it is created. |
|
static void scr_write_in_window(const char *winId, const char *text, |
|
time_t timestamp, unsigned int prefix_flags, |
|
int force_show, unsigned mucnicklen, |
|
gpointer xep184) |
|
{ |
|
winbuf_t *win_entry; |
|
char *text_locale; |
|
int dont_show = FALSE; |
|
int special; |
|
guint num_history_blocks; |
|
bool setmsgflg = FALSE; |
|
bool clearmsgflg = FALSE; |
|
char *nicktmp, *nicklocaltmp; |
|
|
|
// Look for the window entry. |
|
special = (winId == NULL); |
|
win_entry = scr_search_window(winId, special); |
|
|
|
// Do we have to really show the window? |
|
if (!chatmode) |
|
dont_show = TRUE; |
|
else if ((!force_show) && ((!currentWindow || (currentWindow != win_entry)))) |
|
dont_show = TRUE; |
|
|
|
// If the window entry doesn't exist yet, let's create it. |
|
if (!win_entry) { |
|
win_entry = scr_create_window(winId, special, dont_show); |
|
} |
|
|
|
// The message must be displayed -> update top pointer |
|
if (win_entry->bd->cleared) |
|
win_entry->bd->top = g_list_last(win_entry->bd->hbuf); |
|
|
|
// Make sure we do not free the buffer while it's locked or when |
|
// top is set. |
|
if (win_entry->bd->lock || win_entry->bd->top) |
|
num_history_blocks = 0U; |
|
else |
|
num_history_blocks = get_max_history_blocks(); |
|
|
|
text_locale = from_utf8(text); |
|
// Convert the nick alone and compute its length |
|
if (mucnicklen) { |
|
nicktmp = g_strndup(text, mucnicklen); |
|
nicklocaltmp = from_utf8(nicktmp); |
|
if (nicklocaltmp) |
|
mucnicklen = strlen(nicklocaltmp); |
|
g_free(nicklocaltmp); |
|
g_free(nicktmp); |
|
} |
|
hbuf_add_line(&win_entry->bd->hbuf, text_locale, timestamp, prefix_flags, |
|
scr_gettextwidth(), num_history_blocks, mucnicklen, xep184); |
|
g_free(text_locale); |
|
|
|
if (win_entry->bd->cleared) { |
|
win_entry->bd->cleared = FALSE; |
|
if (g_list_next(win_entry->bd->top)) |
|
win_entry->bd->top = g_list_next(win_entry->bd->top); |
|
} |
|
|
|
// Make sure the last line appears in the window; update top if necessary |
|
if (!win_entry->bd->lock && win_entry->bd->top) { |
|
int dist; |
|
GList *first = g_list_first(win_entry->bd->hbuf); |
|
dist = g_list_position(first, g_list_last(win_entry->bd->hbuf)) - |
|
g_list_position(first, win_entry->bd->top); |
|
if (dist >= CHAT_WIN_HEIGHT) |
|
win_entry->bd->top = NULL; |
|
} |
|
|
|
if (!dont_show) { |
|
if (win_entry->bd->lock) |
|
setmsgflg = TRUE; |
|
else |
|
// If this is an outgoing message, remove the readmark |
|
if (!special && (prefix_flags & (HBB_PREFIX_OUT|HBB_PREFIX_HLIGHT_OUT))) |
|
hbuf_set_readmark(win_entry->bd->hbuf, FALSE); |
|
// Show and refresh the window |
|
top_panel(win_entry->panel); |
|
scr_update_window(win_entry); |
|
top_panel(inputPanel); |
|
update_panels(); |
|
} else if (settings_opt_get_int("clear_unread_on_carbon") && |
|
prefix_flags & HBB_PREFIX_OUT && |
|
prefix_flags & HBB_PREFIX_CARBON) { |
|
clearmsgflg = TRUE; |
|
} else if (!(prefix_flags & HBB_PREFIX_NOFLAG)) { |
|
setmsgflg = TRUE; |
|
} |
|
if (!special) { |
|
if (clearmsgflg) { |
|
roster_msg_update_unread(winId, FALSE); |
|
roster_msg_setflag(winId, FALSE, FALSE); |
|
scr_update_roster(); |
|
} else if (setmsgflg) { |
|
roster_msg_update_unread(winId, TRUE); |
|
roster_msg_setflag(winId, FALSE, TRUE); |
|
scr_update_roster(); |
|
} |
|
} |
|
} |
|
|
|
static char *attention_sign_guard(const gchar *key, const gchar *new_value) |
|
{ |
|
scr_update_roster(); |
|
if (g_strcmp0(settings_opt_get(key), new_value)) { |
|
guint sign; |
|
char *c; |
|
if (!new_value || !*new_value) |
|
return NULL; |
|
sign = get_char(new_value); |
|
c = next_char((char*)new_value); |
|
if (get_char_width(new_value) != 1 || !iswprint(sign) || *c) { |
|
scr_log_print(LPRINT_NORMAL, "attention_char value is invalid."); |
|
return NULL; |
|
} |
|
// The new value looks good (1-char wide and printable) |
|
return g_strdup(new_value); |
|
} |
|
return g_strdup(new_value); |
|
} |
|
|
|
// scr_init_settings() |
|
// Create guards for UI settings |
|
void scr_init_settings(void) |
|
{ |
|
settings_set_guard("attention_char", attention_sign_guard); |
|
} |
|
|
|
static unsigned int attention_sign(void) |
|
{ |
|
const char *as = settings_opt_get("attention_char"); |
|
if (!as) |
|
return DEFAULT_ATTENTION_CHAR; |
|
return get_char(as); |
|
} |
|
|
|
// scr_update_main_status(forceupdate) |
|
// Redraw the main (bottom) status line. |
|
// You can set forceupdate to FALSE in order to optimize screen refresh |
|
// if you call top_panel()/update_panels() later. |
|
void scr_update_main_status(int forceupdate) |
|
{ |
|
char *sm = from_utf8(xmpp_getstatusmsg()); |
|
const char *info = settings_opt_get("info"); |
|
guint prio = 0; |
|
gpointer unread_ptr; |
|
guint unreadchar; |
|
|
|
unread_ptr = unread_msg(NULL); |
|
if (unread_ptr) { |
|
prio = buddy_getuiprio(unread_ptr); |
|
// If there's an unerad buffer but no priority set, let's consider the |
|
// priority is 1. |
|
if (!prio && buddy_getflags(unread_ptr) & ROSTER_FLAG_MSG) |
|
prio = 1; |
|
} |
|
|
|
// Status bar unread message flag |
|
if (prio >= ROSTER_UI_PRIO_MUC_HL_MESSAGE) |
|
unreadchar = attention_sign(); |
|
else if (prio > 0) |
|
unreadchar = '#'; |
|
else |
|
unreadchar = ' '; |
|
|
|
werase(mainstatusWnd); |
|
if (info) { |
|
char *info_locale = from_utf8(info); |
|
mvwprintw(mainstatusWnd, 0, 0, "%lc[%c] %s %s", unreadchar, |
|
imstatus2char[xmpp_getstatus()], |
|
info_locale, (sm ? sm : "")); |
|
g_free(info_locale); |
|
} else |
|
mvwprintw(mainstatusWnd, 0, 0, "%lc[%c] %s", unreadchar, |
|
imstatus2char[xmpp_getstatus()], (sm ? sm : "")); |
|
if (forceupdate) { |
|
top_panel(inputPanel); |
|
update_panels(); |
|
} |
|
g_free(sm); |
|
} |
|
|
|
// scr_draw_main_window() |
|
// Set fullinit to TRUE to also create panels. Set it to FALSE for a resize. |
|
// |
|
// I think it could be improved a _lot_ but I'm really not an ncurses |
|
// expert... :-\ Mikael. |
|
// |
|
void scr_draw_main_window(unsigned int fullinit) |
|
{ |
|
int requested_size; |
|
gchar *ver, *message; |
|
int chat_y_pos, chatstatus_y_pos, log_y_pos; |
|
int roster_x_pos, chat_x_pos; |
|
|
|
if (maxY < 4) |
|
maxY = 4; |
|
|
|
if (NULL == settings_opt_get("log_win_height")) |
|
requested_size = DEFAULT_LOG_WIN_HEIGHT; |
|
else |
|
requested_size = settings_opt_get_int("log_win_height"); |
|
if (requested_size <= 0) { |
|
Log_Win_Height = 0; |
|
} else { |
|
if (maxY >= requested_size + 4) |
|
Log_Win_Height = requested_size; |
|
else { |
|
Log_Win_Height = maxY - 4; |
|
} |
|
} |
|
|
|
if (roster_hidden) { |
|
Roster_Width = 0; |
|
} else { |
|
requested_size = settings_opt_get_int("roster_width"); |
|
if (requested_size > 1) |
|
Roster_Width = requested_size; |
|
else if (requested_size == 1) |
|
Roster_Width = 2; |
|
else |
|
Roster_Width = DEFAULT_ROSTER_WIDTH; |
|
} |
|
|
|
log_win_on_top = (settings_opt_get_int("log_win_on_top") == 1); |
|
roster_win_on_right = (settings_opt_get_int("roster_win_on_right") == 1); |
|
|
|
if (log_win_on_top) { |
|
log_y_pos = 0; |
|
chatstatus_y_pos = Log_Win_Height; |
|
chat_y_pos = Log_Win_Height + 1; |
|
} else { |
|
chat_y_pos = 0; |
|
chatstatus_y_pos = CHAT_WIN_HEIGHT; |
|
log_y_pos = CHAT_WIN_HEIGHT + 1; |
|
} |
|
|
|
if (roster_win_on_right) { |
|
roster_x_pos = maxX - Roster_Width; |
|
chat_x_pos = 0; |
|
} else { |
|
roster_x_pos = 0; |
|
chat_x_pos = Roster_Width; |
|
} |
|
|
|
if (fullinit) { |
|
if (!winbufhash) |
|
winbufhash = g_hash_table_new_full(g_str_hash, g_str_equal, |
|
g_free, g_free); |
|
/* Create windows */ |
|
rosterWnd = newwin(CHAT_WIN_HEIGHT, Roster_Width, chat_y_pos, roster_x_pos); |
|
chatWnd = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos, |
|
chat_x_pos); |
|
activechatWnd = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos, |
|
chat_x_pos); |
|
logWnd = newwin(Log_Win_Height, maxX, log_y_pos, 0); |
|
chatstatusWnd = newwin(1, maxX, chatstatus_y_pos, 0); |
|
mainstatusWnd = newwin(1, maxX, maxY-2, 0); |
|
inputWnd = newwin(1, maxX, maxY-1, 0); |
|
if (!rosterWnd || !chatWnd || !logWnd || !inputWnd) { |
|
scr_terminate_curses(); |
|
fprintf(stderr, "Cannot create windows!\n"); |
|
exit(EXIT_FAILURE); |
|
} |
|
wbkgd(rosterWnd, get_color(COLOR_GENERAL)); |
|
wbkgd(chatWnd, get_color(COLOR_GENERAL)); |
|
wbkgd(activechatWnd, get_color(COLOR_GENERAL)); |
|
wbkgd(logWnd, get_color(COLOR_LOG)); |
|
wbkgd(chatstatusWnd, get_color(COLOR_STATUS)); |
|
wbkgd(mainstatusWnd, get_color(COLOR_STATUS)); |
|
} else { |
|
/* Resize/move windows */ |
|
wresize(rosterWnd, CHAT_WIN_HEIGHT, Roster_Width); |
|
wresize(chatWnd, CHAT_WIN_HEIGHT, maxX - Roster_Width); |
|
wresize(logWnd, Log_Win_Height, maxX); |
|
|
|
mvwin(chatWnd, chat_y_pos, chat_x_pos); |
|
mvwin(rosterWnd, chat_y_pos, roster_x_pos); |
|
mvwin(logWnd, log_y_pos, 0); |
|
|
|
// Resize & move chat status window |
|
wresize(chatstatusWnd, 1, maxX); |
|
mvwin(chatstatusWnd, chatstatus_y_pos, 0); |
|
// Resize & move main status window |
|
wresize(mainstatusWnd, 1, maxX); |
|
mvwin(mainstatusWnd, maxY-2, 0); |
|
// Resize & move input line window |
|
wresize(inputWnd, 1, maxX); |
|
mvwin(inputWnd, maxY-1, 0); |
|
|
|
werase(chatWnd); |
|
} |
|
|
|
/* Draw/init windows */ |
|
|
|
ver = mcabber_version(); |
|
message = g_strdup_printf("MCabber version %s.\n", ver); |
|
mvwprintw(chatWnd, 0, 0, "%s", message); |
|
mvwprintw(chatWnd, 1, 0, "https://mcabber.com/"); |
|
g_free(ver); |
|
g_free(message); |
|
|
|
// Auto-scrolling in log window |
|
scrollok(logWnd, TRUE); |
|
|
|
|
|
if (fullinit) { |
|
// Enable keypad (+ special keys) |
|
keypad(inputWnd, TRUE); |
|
#ifdef __MirBSD__ |
|
wtimeout(inputWnd, 50 /* ms */); |
|
#else |
|
nodelay(inputWnd, TRUE); |
|
#endif |
|
|
|
// Create panels |
|
rosterPanel = new_panel(rosterWnd); |
|
chatPanel = new_panel(chatWnd); |
|
activechatPanel = new_panel(activechatWnd); |
|
logPanel = new_panel(logWnd); |
|
chatstatusPanel = new_panel(chatstatusWnd); |
|
mainstatusPanel = new_panel(mainstatusWnd); |
|
inputPanel = new_panel(inputWnd); |
|
|
|
// Build the buddylist at least once, to make sure the special buffer |
|
// is added |
|
buddylist_defer_build(); |
|
|
|
// Init prev_chatwidth; this variable will be used to prevent us |
|
// from rewrapping buffers when the width doesn't change. |
|
prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth(); |
|
// Wrap existing status buffer lines |
|
hbuf_rebuild(&statushbuf, prev_chatwidth); |
|
|
|
#ifndef UNICODE |
|
if (utf8_mode) |
|
scr_LogPrint(LPRINT_NORMAL, |
|
"WARNING: Compiled without full UTF-8 support!"); |
|
#endif |
|
} else { |
|
// Update panels |
|
replace_panel(rosterPanel, rosterWnd); |
|
replace_panel(chatPanel, chatWnd); |
|
replace_panel(logPanel, logWnd); |
|
replace_panel(chatstatusPanel, chatstatusWnd); |
|
replace_panel(mainstatusPanel, mainstatusWnd); |
|
replace_panel(inputPanel, inputWnd); |
|
} |
|
|
|
if (0 == Log_Win_Height) { |
|
hide_panel(logPanel); |
|
} else { |
|
show_panel(logPanel); |
|
} |
|
|
|
// We'll need to redraw the roster |
|
scr_update_roster(); |
|
return; |
|
} |
|
|
|
static void resize_win_buffer(gpointer key, gpointer value, gpointer data) |
|
{ |
|
winbuf_t *wbp = value; |
|
struct dimensions *dim = data; |
|
int chat_x_pos, chat_y_pos; |
|
int new_chatwidth; |
|
|
|
if (!(wbp && wbp->win)) |
|
return; |
|
|
|
if (log_win_on_top) |
|
chat_y_pos = Log_Win_Height + 1; |
|
else |
|
chat_y_pos = 0; |
|
|
|
if (roster_win_on_right) |
|
chat_x_pos = 0; |
|
else |
|
chat_x_pos = Roster_Width; |
|
|
|
// Resize/move buddy window |
|
wresize(wbp->win, dim->l, dim->c); |
|
mvwin(wbp->win, chat_y_pos, chat_x_pos); |
|
werase(wbp->win); |
|
// If a panel exists, replace the old window with the new |
|
if (wbp->panel) |
|
replace_panel(wbp->panel, wbp->win); |
|
// Redo line wrapping |
|
wbp->bd->top = hbuf_previous_persistent(wbp->bd->top); |
|
|
|
new_chatwidth = maxX - Roster_Width - scr_getprefixwidth(); |
|
if (new_chatwidth != prev_chatwidth) |
|
hbuf_rebuild(&wbp->bd->hbuf, new_chatwidth); |
|
} |
|
|
|
// scr_resize() |
|
// Function called when the window is resized. |
|
// - Resize windows |
|
// - Rewrap lines in each buddy buffer |
|
void scr_resize(void) |
|
{ |
|
struct dimensions dim; |
|
|
|
// First, update the global variables |
|
getmaxyx(stdscr, maxY, maxX); |
|
// scr_draw_main_window() will take care of maxY and Log_Win_Height |
|
|
|
// Make sure the cursor stays inside the window |
|
check_offset(0); |
|
|
|
// Resize windows and update panels |
|
scr_draw_main_window(FALSE); |
|
|
|
// Resize all buddy windows |
|
dim.l = CHAT_WIN_HEIGHT; |
|
dim.c = maxX - Roster_Width; |
|
if (dim.c < 1) |
|
dim.c = 1; |
|
|
|
// Resize all buffers |
|
g_hash_table_foreach(winbufhash, resize_win_buffer, &dim); |
|
|
|
// Resize/move special status buffer |
|
if (statusWindow) |
|
resize_win_buffer(NULL, statusWindow, &dim); |
|
|
|
// Update prev_chatwidth, now that all buffers have been resized |
|
prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth(); |
|
|
|
// Refresh current buddy window |
|
if (chatmode) |
|
scr_show_buddy_window(); |
|
} |
|
|
|
#ifdef USE_SIGWINCH |
|
void sigwinch_resize(void) |
|
{ |
|
struct winsize size; |
|
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) != -1) |
|
resizeterm(size.ws_row, size.ws_col); |
|
scr_resize(); |
|
} |
|
#endif |
|
|
|
// scr_update_chat_status(forceupdate) |
|
// Redraw the buddy status bar. |
|
// Set forceupdate to TRUE if update_panels() must be called. |
|
void scr_update_chat_status(int forceupdate) |
|
{ |
|
unsigned short btype, isgrp, ismuc, isspe; |
|
const char *btypetext = "Unknown"; |
|
const char *fullname; |
|
char *fullnameres = NULL; |
|
const char *activeres; |
|
const char *msg = NULL; |
|
char status; |
|
char *buf, *buf_locale; |
|
|
|
// Usually we need to update the bottom status line too, |
|
// at least to refresh the pending message flag. |
|
scr_update_main_status(FALSE); |
|
|
|
// Clear the line |
|
werase(chatstatusWnd); |
|
|
|
if (!current_buddy) { |
|
if (forceupdate) { |
|
update_panels(); |
|
} |
|
return; |
|
} |
|
|
|
fullname = buddy_getname(BUDDATA(current_buddy)); |
|
btype = buddy_gettype(BUDDATA(current_buddy)); |
|
|
|
isgrp = ismuc = isspe = 0; |
|
if (btype & ROSTER_TYPE_USER) { |
|
btypetext = "Buddy"; |
|
} else if (btype & ROSTER_TYPE_GROUP) { |
|
btypetext = "Group"; |
|
isgrp = 1; |
|
} else if (btype & ROSTER_TYPE_AGENT) { |
|
btypetext = "Agent"; |
|
} else if (btype & ROSTER_TYPE_ROOM) { |
|
btypetext = "Room"; |
|
ismuc = 1; |
|
} else if (btype & ROSTER_TYPE_SPECIAL) { |
|
btypetext = "Special buffer"; |
|
isspe = 1; |
|
} |
|
|
|
if (chatmode) { |
|
wprintw(chatstatusWnd, "~"); |
|
} else { |
|
unsigned short bflags = buddy_getflags(BUDDATA(current_buddy)); |
|
if (bflags & ROSTER_FLAG_MSG) { |
|
// There is an unread message from the current buddy |
|
wprintw(chatstatusWnd, "#"); |
|
} |
|
} |
|
|
|
if (chatmode && !isgrp) { |
|
winbuf_t *win_entry; |
|
win_entry = scr_search_window(buddy_getjid(BUDDATA(current_buddy)), isspe); |
|
if (win_entry && win_entry->bd->lock) |
|
mvwprintw(chatstatusWnd, 0, 0, "*"); |
|
} |
|
|
|
if (isgrp || isspe) { |
|
buf_locale = from_utf8(fullname); |
|
mvwprintw(chatstatusWnd, 0, 5, "%s: %s", btypetext, buf_locale); |
|
g_free(buf_locale); |
|
if (forceupdate) { |
|
update_panels(); |
|
} |
|
return; |
|
} |
|
|
|
status = '?'; |
|
|
|
activeres = buddy_getactiveresource(BUDDATA(current_buddy)); |
|
|
|
if (ismuc) { |
|
if (buddy_getinsideroom(BUDDATA(current_buddy))) |
|
status = 'C'; |
|
else |
|
status = 'x'; |
|
} else if (xmpp_getstatus() != offline) { |
|
enum imstatus budstate; |
|
budstate = buddy_getstatus(BUDDATA(current_buddy), activeres); |
|
if (budstate < imstatus_size) |
|
status = imstatus2char[budstate]; |
|
} |
|
|
|
// No status message for MUC rooms |
|
if (!ismuc) { |
|
if (activeres) { |
|
fullnameres = g_strdup_printf("%s/%s", fullname, activeres); |
|
fullname = fullnameres; |
|
msg = buddy_getstatusmsg(BUDDATA(current_buddy), activeres); |
|
} else { |
|
GSList *resources, *p_res, *p_next_res; |
|
resources = buddy_getresources(BUDDATA(current_buddy)); |
|
|
|
for (p_res = resources ; p_res ; p_res = p_next_res) { |
|
p_next_res = g_slist_next(p_res); |
|
// Store the status message of the latest resource (highest priority) |
|
if (!p_next_res) |
|
msg = buddy_getstatusmsg(BUDDATA(current_buddy), p_res->data); |
|
g_free(p_res->data); |
|
} |
|
g_slist_free(resources); |
|
} |
|
} else { |
|
msg = buddy_gettopic(BUDDATA(current_buddy)); |
|
} |
|
|
|
if (msg) |
|
buf = g_strdup_printf("[%c] %s: %s -- %s", status, btypetext, fullname, msg); |
|
else |
|
buf = g_strdup_printf("[%c] %s: %s", status, btypetext, fullname); |
|
replace_nl_with_dots(buf); |
|
buf_locale = from_utf8(buf); |
|
mvwprintw(chatstatusWnd, 0, 1, "%s", buf_locale); |
|
g_free(fullnameres); |
|
g_free(buf_locale); |
|
g_free(buf); |
|
|
|
// Display chatstates of the contact, if available. |
|
if (btype & ROSTER_TYPE_USER) { |
|
char eventchar = 0; |
|
guint event; |
|
|
|
// We specify active resource here, so when there is none then the resource |
|
// with the highest priority will be used. |
|
event = buddy_resource_getevents(BUDDATA(current_buddy), activeres); |
|
|
|
if (event == ROSTER_EVENT_ACTIVE) |
|
eventchar = 'A'; |
|
else if (event == ROSTER_EVENT_COMPOSING) |
|
eventchar = 'C'; |
|
else if (event == ROSTER_EVENT_PAUSED) |
|
eventchar = 'P'; |
|
else if (event == ROSTER_EVENT_INACTIVE) |
|
eventchar = 'I'; |
|
else if (event == ROSTER_EVENT_GONE) |
|
eventchar = 'G'; |
|
|
|
if (eventchar) |
|
mvwprintw(chatstatusWnd, 0, maxX-3, "[%c]", eventchar); |
|
} |
|
|
|
|
|
if (forceupdate) { |
|
update_panels(); |
|
} |
|
} |
|
|
|
void increment_if_buddy_not_filtered(gpointer rosterdata, void *param) |
|
{ |
|
int *p = param; |
|
if (buddylist_is_status_filtered(buddy_getstatus(rosterdata, NULL))) |
|
*p=*p+1; |
|
} |
|
|
|
// scr_draw_roster() |
|
// Display the buddylist (not really the roster) on the screen |
|
void scr_draw_roster(void) |
|
{ |
|
static int offset = 0; |
|
char *name, *rline, *unread; |
|
int maxx, maxy; |
|
GList *buddy; |
|
int i, n; |
|
int rOffset; |
|
int cursor_backup; |
|
guint status, pending; |
|
enum imstatus currentstatus = xmpp_getstatus(); |
|
int x_pos; |
|
int prefix_length; |
|
char space[2] = " "; |
|
|
|
// We can reset update_roster |
|
if (_update_roster == FALSE) |
|
return; |
|
_update_roster = FALSE; |
|
|
|
buddylist_build(); |
|
|
|
getmaxyx(rosterWnd, maxy, maxx); |
|
maxx--; // Last char is for vertical border |
|
|
|
cursor_backup = curs_set(0); |
|
|
|
if (!buddylist) |
|
offset = 0; |
|
else |
|
scr_update_chat_status(FALSE); |
|
|
|
// Cleanup of roster window |
|
wbkgdset(rosterWnd, get_color(COLOR_GENERAL)); // clear background color |
|
werase(rosterWnd); |
|
|
|
if (Roster_Width) { |
|
int line_x_pos = roster_win_on_right ? 0 : Roster_Width-1; |
|
// Redraw the vertical line (not very good...) |
|
for (i=0 ; i < CHAT_WIN_HEIGHT ; i++) |
|
mvwaddch(rosterWnd, i, line_x_pos, ACS_VLINE); |
|
} |
|
|
|
// Leave now if buddylist is empty or the roster is hidden |
|
if (!buddylist || !Roster_Width) { |
|
update_panels(); |
|
curs_set(cursor_backup); |
|
return; |
|
} |
|
|
|
// Update offset if necessary |
|
// a) Try to show as many buddylist items as possible |
|
i = g_list_length(buddylist) - maxy; |
|
if (i < 0) |
|
i = 0; |
|
if (i < offset) |
|
offset = i; |
|
// b) Make sure the current_buddy is visible |
|
i = g_list_position(buddylist, current_buddy); |
|
if (i == -1) { // This is bad |
|
scr_LogPrint(LPRINT_NORMAL, "Doh! Can't find current selected buddy!!"); |
|
curs_set(cursor_backup); |
|
return; |
|
} else if (i < offset) { |
|
offset = i; |
|
} else if (i+1 > offset + maxy) { |
|
offset = i + 1 - maxy; |
|
} |
|
|
|
if (roster_win_on_right) |
|
x_pos = 1; // 1 char offset (vertical line) |
|
else |
|
x_pos = 0; |
|
|
|
if (settings_opt_get_int("roster_no_leading_space") == 1) { |
|
space[0] = '\0'; |
|
prefix_length = 6; |
|
} else { |
|
prefix_length = 7; |
|
} |
|
|
|
name = g_new0(char, 4*Roster_Width); |
|
unread = g_new0(char, Roster_Width+1);; |
|
rline = g_new0(char, 4*Roster_Width+1); |
|
|
|
buddy = buddylist; |
|
rOffset = offset; |
|
|
|
for (i=0; i<maxy && buddy; buddy = g_list_next(buddy)) { |
|
unsigned short bflags, btype; |
|
unsigned short ismsg, isgrp, ismuc, ishid, isspe; |
|
guint isurg; |
|
gchar *rline_locale; |
|
|
|
bflags = buddy_getflags(BUDDATA(buddy)); |
|
btype = buddy_gettype(BUDDATA(buddy)); |
|
|
|
ismsg = bflags & ROSTER_FLAG_MSG; |
|
ishid = bflags & ROSTER_FLAG_HIDE; |
|
isgrp = btype & ROSTER_TYPE_GROUP; |
|
ismuc = btype & ROSTER_TYPE_ROOM; |
|
isspe = btype & ROSTER_TYPE_SPECIAL; |
|
isurg = buddy_getuiprio(BUDDATA(buddy)); |
|
|
|
if (rOffset > 0) { |
|
rOffset--; |
|
continue; |
|
} |
|
|
|
status = '?'; |
|
pending = ' '; |
|
|
|
if (!ismuc) { |
|
// There is currently no chat state support for MUC |
|
GSList *resources = buddy_getresources(BUDDATA(buddy)); |
|
GSList *p_res; |
|
|
|
for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) { |
|
guint events = buddy_resource_getevents(BUDDATA(buddy), |
|
p_res ? p_res->data : ""); |
|
if ((events & ROSTER_EVENT_PAUSED) && pending != '+') |
|
pending = '.'; |
|
if (events & ROSTER_EVENT_COMPOSING) |
|
pending = '+'; |
|
g_free(p_res->data); |
|
} |
|
g_slist_free(resources); |
|
} |
|
|
|
// Display message notice if there is a message flag, but not |
|
// for unfolded groups. |
|
if (ismsg && (!isgrp || ishid)) { |
|
pending = '#'; |
|
} |
|
|
|
if (ismuc) { |
|
if (buddy_getinsideroom(BUDDATA(buddy))) |
|
status = 'C'; |
|
else |
|
status = 'x'; |
|
} else if (currentstatus != offline) { |
|
enum imstatus budstate; |
|
budstate = buddy_getstatus(BUDDATA(buddy), NULL); |
|
if (budstate < imstatus_size) |
|
status = imstatus2char[budstate]; |
|
} |
|
if (buddy == current_buddy) { |
|
if (pending == '#') |
|
wbkgdset(rosterWnd, get_color(COLOR_ROSTERSELNMSG)); |
|
else |
|
wbkgdset(rosterWnd, get_color(COLOR_ROSTERSEL)); |
|
// The 3 following lines aim at coloring the whole line |
|
wmove(rosterWnd, i, x_pos); |
|
for (n = 0; n < maxx; n++) |
|
waddch(rosterWnd, ' '); |
|
} else { |
|
if (pending == '#') |
|
wbkgdset(rosterWnd, get_color(COLOR_ROSTERNMSG)); |
|
else { |
|
int color = get_color(COLOR_ROSTER); |
|
if ((!isspe) && (!isgrp)) { // Look for color rules |
|
GSList *head; |
|
const char *bjid = buddy_getjid(BUDDATA(buddy)); |
|
for (head = rostercolrules; head; head = g_slist_next(head)) { |
|
rostercolor_t *rc = head->data; |
|
if (g_pattern_match_string(rc->compiled, bjid) && |
|
(!strcmp("*", rc->status) || strchr(rc->status, status))) { |
|
color = compose_color(rc->color); |
|
break; |
|
} |
|
} |
|
} |
|
wbkgdset(rosterWnd, color); |
|
} |
|
} |
|
|
|
name[0] = 0; |
|
unread[0] = 0; |
|
if (Roster_Width > prefix_length) { |
|
g_utf8_strncpy(name, buddy_getname(BUDDATA(buddy)), Roster_Width-prefix_length); |
|
if (settings_opt_get_int("roster_show_unread_count")) { |
|
guint unread_count = buddy_getunread(BUDDATA(buddy)); |
|
glong name_length = g_utf8_strlen(name, 4*Roster_Width); |
|
if (unread_count > 0 && Roster_Width > prefix_length + name_length) { |
|
snprintf(unread, Roster_Width-(prefix_length+name_length)+1, " (%u)", unread_count); |
|
} |
|
} |
|
} |
|
|
|
if (pending == '#') { |
|
// Attention sign? |
|
if ((ismuc && isurg >= ui_attn_sign_prio_level_muc) || |
|
(!ismuc && isurg >= ui_attn_sign_prio_level)) |
|
pending = attention_sign(); |
|
} |
|
|
|
if (isgrp) { |
|
if (ishid) { |
|
int group_count = 0; |
|
foreach_group_member(BUDDATA(buddy), increment_if_buddy_not_filtered, |
|
&group_count); |
|
snprintf(rline, 4*Roster_Width, "%s%lc+++ %s (%i)", space, pending, |
|
name, group_count); |
|
/* Do not display the item count if there isn't enough space */ |
|
if (g_utf8_strlen(rline, 4*Roster_Width) >= Roster_Width) |
|
snprintf(rline, 4*Roster_Width, "%s%lc+++ %s", space, pending, name); |
|
} |
|
else |
|
snprintf(rline, 4*Roster_Width, "%s%lc--- %s", space, pending, name); |
|
} else if (isspe) { |
|
snprintf(rline, 4*Roster_Width, "%s%lc%s", space, pending, name); |
|
} else { |
|
char sepleft = '['; |
|
char sepright = ']'; |
|
if (btype & ROSTER_TYPE_USER) { |
|
guint subtype = buddy_getsubscription(BUDDATA(buddy)); |
|
if (status == '_' && !(subtype & sub_to)) |
|
status = '?'; |
|
if (!(subtype & sub_from)) { |
|
sepleft = '{'; |
|
sepright = '}'; |
|
} |
|
} |
|
snprintf(rline, 4*Roster_Width, "%s%lc%c%c%c %s%s", |
|
space, pending, sepleft, status, sepright, name, unread); |
|
} |
|
|
|
rline_locale = from_utf8(rline); |
|
mvwprintw(rosterWnd, i, x_pos, "%s", rline_locale); |
|
g_free(rline_locale); |
|
i++; |
|
} |
|
|
|
g_free(rline); |
|
g_free(unread); |
|
g_free(name); |
|
top_panel(inputPanel); |
|
update_panels(); |
|
curs_set(cursor_backup); |
|
} |
|
|
|
void scr_update_roster(void) |
|
{ |
|
_update_roster = TRUE; |
|
} |
|
|
|
|
|
// scr_roster_visibility(status) |
|
// Set the roster visibility: |
|
// status=1 Show roster |
|
// status=0 Hide roster |
|
// status=-1 Toggle roster status |
|
void scr_roster_visibility(int status) |
|
{ |
|
int old_roster_status = roster_hidden; |
|
|
|
if (status > 0) |
|
roster_hidden = FALSE; |
|
else if (status == 0) |
|
roster_hidden = TRUE; |
|
else |
|
roster_hidden = !roster_hidden; |
|
|
|
if (roster_hidden != old_roster_status) { |
|
// Recalculate windows size and redraw |
|
scr_resize(); |
|
redrawwin(stdscr); |
|
} |
|
} |
|
|
|
static void scr_write_message(const char *bjid, const char *text, |
|
time_t timestamp, guint prefix_flags, |
|
unsigned mucnicklen, gpointer xep184) |
|
{ |
|
char *xtext; |
|
|
|
if (!timestamp) |
|
timestamp = time(NULL); |
|
else |
|
prefix_flags |= HBB_PREFIX_DELAYED; |
|
|
|
xtext = ut_expand_tabs(text); // Expand tabs and filter out some chars |
|
|
|
scr_write_in_window(bjid, xtext, timestamp, prefix_flags, FALSE, mucnicklen, |
|
xep184); |
|
|
|
if (xtext != (char*)text) |
|
g_free(xtext); |
|
} |
|
|
|
// If prefix is NULL, HBB_PREFIX_IN is supposed. |
|
void scr_write_incoming_message(const char *jidfrom, const char *text, |
|
time_t timestamp, |
|
guint prefix, unsigned mucnicklen) |
|
{ |
|
if (!(prefix & |
|
~HBB_PREFIX_NOFLAG & ~HBB_PREFIX_HLIGHT & ~HBB_PREFIX_HLIGHT_OUT & |
|
~HBB_PREFIX_PGPCRYPT & ~HBB_PREFIX_OTRCRYPT & ~HBB_PREFIX_CARBON)) |
|
prefix |= HBB_PREFIX_IN; |
|
|
|
scr_write_message(jidfrom, text, timestamp, prefix, mucnicklen, NULL); |
|
} |
|
|
|
void scr_write_outgoing_message(const char *jidto, const char *text, |
|
guint prefix, gpointer xep184) |
|
{ |
|
GSList *roster_elt; |
|
roster_elt = roster_find(jidto, jidsearch, |
|
|