Personal copy of mcabber
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

/*
* 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(&timestamp));
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(&timestamp));
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(&timestamp));
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,
</