1 /*
2  * screen.c     -- UI stuff
3  *
4  * Copyright (C) 2005-2014 Mikael Berthe <mikael@lilotux.net>
5  * Parts of this file come from the Cabber project <cabber@ajmacias.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or (at
10  * your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <time.h>
25 #include <ctype.h>
26 
27 #include <config.h>
28 #include <locale.h>
29 #include <assert.h>
30 #ifdef USE_SIGWINCH
31 # include <sys/ioctl.h>
32 # include <termios.h>
33 # include <unistd.h>
34 #endif
35 
36 #ifdef HAVE_LOCALCHARSET_H
37 # include <localcharset.h>
38 #else
39 # include <langinfo.h>
40 #endif
41 
42 #ifdef WITH_ENCHANT
43 # include <enchant.h>
44 #else
45 # ifdef WITH_ASPELL
46 #  include <aspell.h>
47 # endif
48 #endif
49 
50 #include "screen.h"
51 #include "utf8.h"
52 #include "hbuf.h"
53 #include "commands.h"
54 #include "compl.h"
55 #include "roster.h"
56 #include "histolog.h"
57 #include "settings.h"
58 #include "utils.h"
59 #include "xmpp.h"
60 #include "main.h"
61 
62 int COLOR_ATTRIB[COLOR_max];
63 
64 #define get_color(col)      (COLOR_PAIR(col)|COLOR_ATTRIB[col])
65 #define compose_color(col)  (COLOR_PAIR(col->color_pair)|col->color_attrib)
66 
67 #define DEFAULT_LOG_WIN_HEIGHT (5)
68 #define DEFAULT_ROSTER_WIDTH    24
69 #define CHAT_WIN_HEIGHT (maxY-2-1-Log_Win_Height)
70 
71 #define DEFAULT_ATTENTION_CHAR '!'
72 
73 const char *LocaleCharSet = "C";
74 
75 static unsigned short int Log_Win_Height;
76 static unsigned short int Roster_Width;
77 static gboolean colors_stalled = FALSE;
78 
79 // Default attention sign trigger levels
80 static guint ui_attn_sign_prio_level_muc = ROSTER_UI_PRIO_MUC_HL_MESSAGE;
81 static guint ui_attn_sign_prio_level     = ROSTER_UI_PRIO_ATTENTION_MESSAGE;
82 
83 static inline void check_offset(int);
84 static void scr_cancel_current_completion(void);
85 static void scr_end_current_completion(void);
86 static void scr_insert_text(const char*);
87 static void scr_handle_tab(gboolean fwd);
88 
89 static void scr_glog_print(const gchar *log_domain, GLogLevelFlags log_level,
90                            const gchar *message, gpointer user_data);
91 
92 #ifdef XEP0085
93 static gboolean scr_chatstates_timeout();
94 #endif
95 
96 #if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
97 static void spellcheck(char *, char *);
98 #endif
99 
100 static void open_chat_window(void);
101 static void clear_inputline(void);
102 
103 static GHashTable *winbufhash;
104 
105 typedef struct {
106   GList    *hbuf;
107   GList    *top;      // If top is NULL, we'll display the last lines
108   char      cleared;  // For ex, user has issued a /clear command...
109   char      lock;
110   char      refcount; // refcount > 0 if there are other users of this struct
111                       // e.g. with symlinked history
112 } buffdata_t;
113 
114 typedef struct {
115   WINDOW *win;
116   PANEL  *panel;
117   buffdata_t *bd;
118 } winbuf_t;
119 
120 struct dimensions {
121   int l;
122   int c;
123 };
124 
125 static WINDOW *rosterWnd, *chatWnd, *activechatWnd, *inputWnd, *logWnd;
126 static WINDOW *mainstatusWnd, *chatstatusWnd;
127 static PANEL *rosterPanel, *chatPanel, *activechatPanel, *inputPanel;
128 static PANEL *mainstatusPanel, *chatstatusPanel;
129 static PANEL *logPanel;
130 static int maxY, maxX;
131 static int prev_chatwidth;
132 static winbuf_t *statusWindow;
133 static winbuf_t *currentWindow;
134 static GList  *statushbuf;
135 
136 static int roster_hidden;
137 static int chatmode;
138 static int multimode;
139 static char *multiline, *multimode_subj;
140 
141 static bool Curses;
142 static bool log_win_on_top;
143 static bool roster_win_on_right;
144 static guint autoaway_source = 0;
145 
146 static char       inputLine[INPUTLINE_LENGTH+1];
147 #if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
148 static char       maskLine[INPUTLINE_LENGTH+1];
149 #endif
150 static char      *ptr_inputline;
151 static short int  inputline_offset;
152 static int    completion_started;
153 static GList *cmdhisto;
154 static GList *cmdhisto_cur;
155 static guint  cmdhisto_nblines;
156 static char   cmdhisto_backup[INPUTLINE_LENGTH+1];
157 
158 static int    chatstate; /* (0=active, 1=composing, 2=paused) */
159 static bool   lock_chatstate;
160 static time_t chatstate_timestamp;
161 static guint  chatstate_timeout_id = 0;
162 
163 int _update_roster;
164 int utf8_mode;
165 gboolean chatstates_disabled;
166 gboolean Autoaway;
167 
168 gboolean vi_mode;
169 
170 #define MAX_KEYSEQ_LENGTH 8
171 
172 typedef struct {
173   char *seqstr;
174   guint mkeycode;
175   gint  value;
176 } keyseq_t;
177 
178 GSList *keyseqlist;
179 static void add_keyseq(char *seqstr, guint mkeycode, gint value);
180 
181 static void scr_write_in_window(const char *winId, const char *text,
182                                 time_t timestamp, unsigned int prefix_flags,
183                                 int force_show, unsigned mucnicklen,
184                                 gpointer xep184);
185 
186 static void scr_write_message(const char *bjid, const char *text,
187                               time_t timestamp, guint prefix_flags,
188                               unsigned mucnicklen, gpointer xep184);
189 
190 void scr_update_buddy_window(void);
191 void scr_set_chatmode(int enable);
192 
193 #define SPELLBADCHAR 5
194 
195 #if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
196 typedef struct {
197 #ifdef WITH_ENCHANT
198   EnchantBroker *broker;
199   EnchantDict *checker;
200 #endif
201 #ifdef WITH_ASPELL
202   AspellConfig *config;
203   AspellSpeller *checker;
204 #endif
205 } spell_checker_t;
206 
207 GSList* spell_checkers = NULL;
208 #endif
209 
210 typedef struct {
211   int color_pair;
212   int color_attrib;
213 } ccolor_t;
214 
215 typedef struct {
216   char *status, *wildcard;
217   ccolor_t *color;
218   GPatternSpec *compiled;
219 } rostercolor_t;
220 
221 static GSList *rostercolrules = NULL;
222 
223 static GHashTable *muccolors = NULL, *nickcolors = NULL;
224 
225 typedef struct {
226   bool manual; // Manually set?
227   ccolor_t *color;
228 } nickcolor_t;
229 
230 static int nickcolcount = 0;
231 static ccolor_t ** nickcols = NULL;
232 static muccol_t glob_muccol = MC_OFF;
233 
234 /* Functions */
235 
find_color(const char * name)236 static int find_color(const char *name)
237 {
238   int result;
239 
240   if (!strcmp(name, "default"))
241     return -1;
242   if (!strcmp(name, "black"))
243     return COLOR_BLACK;
244   if (!strcmp(name, "red"))
245     return COLOR_RED;
246   if (!strcmp(name, "green"))
247     return COLOR_GREEN;
248   if (!strcmp(name, "yellow"))
249     return COLOR_YELLOW;
250   if (!strcmp(name, "blue"))
251     return COLOR_BLUE;
252   if (!strcmp(name, "magenta"))
253     return COLOR_MAGENTA;
254   if (!strcmp(name, "cyan"))
255     return COLOR_CYAN;
256   if (!strcmp(name, "white"))
257     return COLOR_WHITE;
258 
259   // Directly support 256-color values
260   result = atoi(name);
261   if (result > 0 && (result < COLORS || !Curses))
262     return result;
263 
264   scr_LogPrint(LPRINT_LOGNORM, "ERROR: Wrong color: %s", name);
265   return -1;
266 }
267 
get_user_color(const char * color)268 static ccolor_t *get_user_color(const char *color)
269 {
270   bool isbright = FALSE;
271   int cl;
272   ccolor_t *ccol;
273   if (!strncmp(color, "bright", 6)) {
274     isbright = TRUE;
275     color += 6;
276   }
277   cl = find_color(color);
278   if (cl < 0)
279     return NULL;
280   ccol = g_new0(ccolor_t, 1);
281   ccol->color_attrib = isbright ? A_BOLD : A_NORMAL;
282   ccol->color_pair = cl + COLOR_max; // User colors come after the internal ones
283   return ccol;
284 }
285 
ensure_string_htable(GHashTable ** table,GDestroyNotify value_destroy_func)286 static void ensure_string_htable(GHashTable **table,
287                                  GDestroyNotify value_destroy_func)
288 {
289   if (*table) // Have it already
290     return;
291   *table = g_hash_table_new_full(g_str_hash, g_str_equal,
292       g_free, value_destroy_func);
293 }
294 
295 // Sets the coloring mode for given MUC
296 // The MUC room does not need to be in the roster at that time
297 // muc - the JID of room
298 // type - the new type
scr_muc_color(const char * muc,muccol_t type)299 void scr_muc_color(const char *muc, muccol_t type)
300 {
301   gchar *muclow = g_utf8_strdown(muc, -1);
302   if (type == MC_REMOVE) { // Remove it
303     if (strcmp(muc, "*")) {
304       if (muccolors && g_hash_table_lookup(muccolors, muclow))
305         g_hash_table_remove(muccolors, muclow);
306     } else {
307       scr_LogPrint(LPRINT_NORMAL, "Can not remove global coloring mode");
308     }
309     g_free(muclow);
310   } else { // Add or overwrite
311     if (strcmp(muc, "*")) {
312       muccol_t *value = g_new(muccol_t, 1);
313       *value = type;
314       ensure_string_htable(&muccolors, g_free);
315       g_hash_table_replace(muccolors, muclow, value);
316     } else {
317       glob_muccol = type;
318       g_free(muclow);
319     }
320   }
321   // Need to redraw?
322   if (chatmode &&
323       ((buddy_search_jid(muc) == current_buddy) || !strcmp(muc, "*")))
324     scr_update_buddy_window();
325 }
326 
327 // Sets the color for nick in MUC
328 // If color is "-", the color is marked as automaticly assigned and is
329 // not used if the room is in the "preset" mode
scr_muc_nick_color(const char * nick,const char * color)330 void scr_muc_nick_color(const char *nick, const char *color)
331 {
332   char *snick, *mnick;
333   bool need_update = FALSE;
334   snick = g_strdup_printf("<%s>", nick);
335   mnick = g_strdup_printf("*%s ", nick);
336   if (!strcmp(color, "-")) { // Remove the color
337     if (nickcolors) {
338       nickcolor_t *nc = g_hash_table_lookup(nickcolors, snick);
339       if (nc) { // Have this nick already
340         nc->manual = FALSE;
341         nc = g_hash_table_lookup(nickcolors, mnick);
342         assert(nc); // Must have both at the same time
343         nc->manual = FALSE;
344       }// Else -> no color saved, nothing to delete
345     }
346     g_free(snick); // They are not saved in the hash
347     g_free(mnick);
348     need_update = TRUE;
349   } else {
350     ccolor_t *cl = get_user_color(color);
351     if (!cl) {
352       scr_LogPrint(LPRINT_NORMAL, "No such color name");
353       g_free(snick);
354       g_free(mnick);
355     } else {
356       nickcolor_t *nc = g_new(nickcolor_t, 1);
357       ensure_string_htable(&nickcolors, NULL);
358       nc->manual = TRUE;
359       nc->color = cl;
360       // Free the struct, if any there already
361       g_free(g_hash_table_lookup(nickcolors, mnick));
362       // Save the new ones
363       g_hash_table_replace(nickcolors, mnick, nc);
364       g_hash_table_replace(nickcolors, snick, nc);
365       need_update = TRUE;
366     }
367   }
368   if (need_update && chatmode &&
369       (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM))
370     scr_update_buddy_window();
371 }
372 
free_rostercolrule(rostercolor_t * col)373 static void free_rostercolrule(rostercolor_t *col)
374 {
375   g_free(col->status);
376   g_free(col->wildcard);
377   g_free(col->color);
378   g_pattern_spec_free(col->compiled);
379   g_free(col);
380 }
381 
382 // Removes all roster coloring rules
scr_roster_clear_color(void)383 void scr_roster_clear_color(void)
384 {
385   GSList *head;
386   for (head = rostercolrules; head; head = g_slist_next(head)) {
387     free_rostercolrule(head->data);
388   }
389   g_slist_free(rostercolrules);
390   rostercolrules = NULL;
391   scr_update_roster();
392 }
393 
394 // Adds, modifies or removes roster coloring rule
395 // color set to "-" removes the rule,
396 // otherwise it is modified (if exists) or added
397 //
398 // Returns weather it was successfull (therefore the roster should be
399 // redrawed) or not. If it failed, for example because of invalid color
400 // name, it also prints the error.
scr_roster_color(const char * status,const char * wildcard,const char * color)401 bool scr_roster_color(const char *status, const char *wildcard,
402                       const char *color)
403 {
404   GSList *head;
405   GSList *found = NULL;
406   for (head = rostercolrules; head; head = g_slist_next(head)) {
407     rostercolor_t *rc = head->data;
408     if ((!strcmp(status, rc->status)) && (!strcmp(wildcard, rc->wildcard))) {
409       found = head;
410       break;
411     }
412   }
413   if (!strcmp(color,"-")) { // Delete the rule
414     if (found) {
415       free_rostercolrule(found->data);
416       rostercolrules = g_slist_delete_link(rostercolrules, found);
417       scr_update_roster();
418       return TRUE;
419     } else {
420       scr_LogPrint(LPRINT_NORMAL, "No such color rule, nothing removed");
421       return FALSE;
422     }
423   } else {
424     ccolor_t *cl = get_user_color(color);
425     if (!cl) {
426       scr_LogPrint(LPRINT_NORMAL, "No such color name");
427       return FALSE;
428     }
429     if (found) {
430       rostercolor_t *rc = found->data;
431       g_free(rc->color);
432       rc->color = cl;
433     } else {
434       rostercolor_t *rc = g_new(rostercolor_t, 1);
435       rc->status = g_strdup(status);
436       rc->wildcard = g_strdup(wildcard);
437       rc->compiled = g_pattern_spec_new(wildcard);
438       rc->color = cl;
439       rostercolrules = g_slist_prepend(rostercolrules, rc);
440     }
441     scr_update_roster();
442     return TRUE;
443   }
444 }
445 
parse_colors(void)446 static void parse_colors(void)
447 {
448   const char *colors[] = {
449     "", "",
450     "general",
451     "msgout",
452     "msghl",
453     "status",
454     "log",
455     "roster",
456     "rostersel",
457     "rosterselmsg",
458     "rosternewmsg",
459     "info",
460     "msgin",
461     "readmark",
462     "timestamp",
463     NULL
464   };
465 
466   const char *color;
467   const char *background   = settings_opt_get("color_background");
468   const char *backselected = settings_opt_get("color_bgrostersel");
469   const char *backstatus   = settings_opt_get("color_bgstatus");
470   char *tmp;
471   int i;
472 
473   // Initialize color attributes
474   memset(COLOR_ATTRIB, 0, sizeof(COLOR_ATTRIB));
475 
476   // Default values
477   if (!background)   background   = "black";
478   if (!backselected) backselected = "cyan";
479   if (!backstatus)   backstatus   = "blue";
480 
481   for (i=0; colors[i]; i++) {
482     tmp = g_strdup_printf("color_%s", colors[i]);
483     color = settings_opt_get(tmp);
484     g_free(tmp);
485 
486     if (color) {
487       if (!strncmp(color, "bright", 6)) {
488         COLOR_ATTRIB[i+1] = A_BOLD;
489         color += 6;
490       }
491     }
492 
493     switch (i + 1) {
494       case 1:
495           init_pair(1, COLOR_BLACK, COLOR_WHITE);
496           break;
497       case 2:
498           init_pair(2, COLOR_WHITE, COLOR_BLACK);
499           break;
500       case COLOR_GENERAL:
501           init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
502                     find_color(background));
503           break;
504       case COLOR_MSGOUT:
505           init_pair(i+1, ((color) ? find_color(color) : COLOR_CYAN),
506                     find_color(background));
507           break;
508       case COLOR_MSGHL:
509           init_pair(i+1, ((color) ? find_color(color) : COLOR_YELLOW),
510                     find_color(background));
511           break;
512       case COLOR_STATUS:
513           init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
514                     find_color(backstatus));
515           break;
516       case COLOR_LOG:
517           init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
518                     find_color(background));
519           break;
520       case COLOR_ROSTER:
521           init_pair(i+1, ((color) ? find_color(color) : COLOR_GREEN),
522                     find_color(background));
523           break;
524       case COLOR_ROSTERSEL:
525           init_pair(i+1, ((color) ? find_color(color) : COLOR_BLUE),
526                     find_color(backselected));
527           break;
528       case COLOR_ROSTERSELNMSG:
529           init_pair(i+1, ((color) ? find_color(color) : COLOR_RED),
530                     find_color(backselected));
531           break;
532       case COLOR_ROSTERNMSG:
533           init_pair(i+1, ((color) ? find_color(color) : COLOR_RED),
534                     find_color(background));
535           break;
536       case COLOR_INFO:
537           init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
538                     find_color(background));
539           break;
540       case COLOR_MSGIN:
541           init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
542                     find_color(background));
543           break;
544       case COLOR_READMARK:
545           init_pair(i+1, ((color) ? find_color(color) : COLOR_RED),
546                     find_color(background));
547           break;
548       case COLOR_TIMESTAMP:
549           init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
550                     find_color(background));
551           break;
552     }
553   }
554   for (i = COLOR_max; i < (COLOR_max + COLORS); i++)
555     init_pair(i, i-COLOR_max, find_color(background));
556 
557   if (!nickcols) {
558     char *ncolors = g_strdup(settings_opt_get("nick_colors"));
559     if (ncolors) {
560       char *ncolor_start, *ncolor_end;
561       ncolor_start = ncolor_end = ncolors;
562 
563       while (*ncolor_end)
564         ncolor_end++;
565 
566       while (ncolors < ncolor_end && *ncolors) {
567         if ((*ncolors == ' ') || (*ncolors == '\t')) {
568           ncolors++;
569         } else {
570           char *end = ncolors;
571           ccolor_t *cl;
572           while (*end && (*end != ' ') && (*end != '\t'))
573             end++;
574           *end = '\0';
575           cl = get_user_color(ncolors);
576           if (!cl) {
577             scr_LogPrint(LPRINT_NORMAL, "Unknown color %s", ncolors);
578           } else {
579             nickcols = g_realloc(nickcols, (++nickcolcount) * sizeof *nickcols);
580             nickcols[nickcolcount-1] = cl;
581           }
582           ncolors = end+1;
583         }
584       }
585       g_free(ncolor_start);
586     }
587     if (!nickcols) { // Fallback to have something
588       nickcolcount = 1;
589       nickcols = g_new(ccolor_t*, 1);
590       *nickcols = g_new(ccolor_t, 1);
591       (*nickcols)->color_pair = COLOR_GENERAL;
592       (*nickcols)->color_attrib = A_NORMAL;
593     }
594   }
595 
596   colors_stalled = FALSE;
597 }
598 
init_keycodes(void)599 static void init_keycodes(void)
600 {
601   add_keyseq("O5A", MKEY_EQUIV, 521); // Ctrl-Up
602   add_keyseq("O5B", MKEY_EQUIV, 514); // Ctrl-Down
603   add_keyseq("O5C", MKEY_EQUIV, 518); // Ctrl-Right
604   add_keyseq("O5D", MKEY_EQUIV, 516); // Ctrl-Left
605   add_keyseq("O6A", MKEY_EQUIV, 520); // Shift-Up
606   add_keyseq("O6B", MKEY_EQUIV, 513); // Shift-Down
607   add_keyseq("O6C", MKEY_EQUIV, 402); // Shift-Right
608   add_keyseq("O6D", MKEY_EQUIV, 393); // Shift-Left
609   add_keyseq("O2A", MKEY_EQUIV, 520); // Shift-Up
610   add_keyseq("O2B", MKEY_EQUIV, 513); // Shift-Down
611   add_keyseq("O2C", MKEY_EQUIV, 402); // Shift-Right
612   add_keyseq("O2D", MKEY_EQUIV, 393); // Shift-Left
613   add_keyseq("[5^", MKEY_CTRL_PGUP, 0);   // Ctrl-PageUp
614   add_keyseq("[6^", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown
615   add_keyseq("[5@", MKEY_CTRL_SHIFT_PGUP, 0);   // Ctrl-Shift-PageUp
616   add_keyseq("[6@", MKEY_CTRL_SHIFT_PGDOWN, 0); // Ctrl-Shift-PageDown
617   add_keyseq("[7@", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home
618   add_keyseq("[8@", MKEY_CTRL_SHIFT_END, 0);  // Ctrl-Shift-End
619   add_keyseq("[8^", MKEY_CTRL_END, 0);  // Ctrl-End
620   add_keyseq("[7^", MKEY_CTRL_HOME, 0); // Ctrl-Home
621   add_keyseq("[2^", MKEY_CTRL_INS, 0);  // Ctrl-Insert
622   add_keyseq("[3^", MKEY_CTRL_DEL, 0);  // Ctrl-Delete
623 
624   // Xterm
625   add_keyseq("[1;5A", MKEY_EQUIV, 521); // Ctrl-Up
626   add_keyseq("[1;5B", MKEY_EQUIV, 514); // Ctrl-Down
627   add_keyseq("[1;5C", MKEY_EQUIV, 518); // Ctrl-Right
628   add_keyseq("[1;5D", MKEY_EQUIV, 516); // Ctrl-Left
629   add_keyseq("[1;6A", MKEY_EQUIV, 520); // Ctrl-Shift-Up
630   add_keyseq("[1;6B", MKEY_EQUIV, 513); // Ctrl-Shift-Down
631   add_keyseq("[1;6C", MKEY_EQUIV, 402); // Ctrl-Shift-Right
632   add_keyseq("[1;6D", MKEY_EQUIV, 393); // Ctrl-Shift-Left
633   add_keyseq("[1;6H", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home
634   add_keyseq("[1;6F", MKEY_CTRL_SHIFT_END, 0);  // Ctrl-Shift-End
635   add_keyseq("[1;2A", MKEY_EQUIV, 521); // Shift-Up
636   add_keyseq("[1;2B", MKEY_EQUIV, 514); // Shift-Down
637   add_keyseq("[5;5~", MKEY_CTRL_PGUP, 0);   // Ctrl-PageUp
638   add_keyseq("[6;5~", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown
639   add_keyseq("[1;5F", MKEY_CTRL_END, 0);  // Ctrl-End
640   add_keyseq("[1;5H", MKEY_CTRL_HOME, 0); // Ctrl-Home
641   add_keyseq("[2;5~", MKEY_CTRL_INS, 0);  // Ctrl-Insert
642   add_keyseq("[3;5~", MKEY_CTRL_DEL, 0);  // Ctrl-Delete
643 
644   // PuTTY
645   add_keyseq("[A", MKEY_EQUIV, 521); // Ctrl-Up
646   add_keyseq("[B", MKEY_EQUIV, 514); // Ctrl-Down
647   add_keyseq("[C", MKEY_EQUIV, 518); // Ctrl-Right
648   add_keyseq("[D", MKEY_EQUIV, 516); // Ctrl-Left
649 
650   // screen
651   add_keyseq("Oa", MKEY_EQUIV, 521); // Ctrl-Up
652   add_keyseq("Ob", MKEY_EQUIV, 514); // Ctrl-Down
653   add_keyseq("Oc", MKEY_EQUIV, 518); // Ctrl-Right
654   add_keyseq("Od", MKEY_EQUIV, 516); // Ctrl-Left
655   add_keyseq("[a", MKEY_EQUIV, 520); // Shift-Up
656   add_keyseq("[b", MKEY_EQUIV, 513); // Shift-Down
657   add_keyseq("[c", MKEY_EQUIV, 402); // Shift-Right
658   add_keyseq("[d", MKEY_EQUIV, 393); // Shift-Left
659   add_keyseq("[5$", MKEY_SHIFT_PGUP, 0);   // Shift-PageUp
660   add_keyseq("[6$", MKEY_SHIFT_PGDOWN, 0); // Shift-PageDown
661 
662   // VT100
663   add_keyseq("[H", MKEY_EQUIV, KEY_HOME); // Home
664   add_keyseq("[F", MKEY_EQUIV, KEY_END);  // End
665 
666   // Konsole Linux
667   add_keyseq("[1~", MKEY_EQUIV, KEY_HOME); // Home
668   add_keyseq("[4~", MKEY_EQUIV, KEY_END);  // End
669 }
670 
671 //  scr_init_bindings()
672 // Create default key bindings
673 // Return 0 if error and 1 if none
scr_init_bindings(void)674 void scr_init_bindings(void)
675 {
676   GString *sbuf = g_string_new("");
677 
678   // Common backspace key codes: 8, 127
679   settings_set(SETTINGS_TYPE_BINDING, "8", "iline char_bdel");    // Ctrl-h
680   settings_set(SETTINGS_TYPE_BINDING, "127", "iline char_bdel");
681   g_string_printf(sbuf, "%d", KEY_BACKSPACE);
682   settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_bdel");
683   g_string_printf(sbuf, "%d", KEY_DC);
684   settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_fdel");
685   g_string_printf(sbuf, "%d", KEY_LEFT);
686   settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline bchar");
687   g_string_printf(sbuf, "%d", KEY_RIGHT);
688   settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline fchar");
689   settings_set(SETTINGS_TYPE_BINDING, "7", "iline compl_cancel"); // Ctrl-g
690   g_string_printf(sbuf, "%d", KEY_UP);
691   settings_set(SETTINGS_TYPE_BINDING, sbuf->str,
692                "iline hist_beginning_search_bwd");
693   g_string_printf(sbuf, "%d", KEY_DOWN);
694   settings_set(SETTINGS_TYPE_BINDING, sbuf->str,
695                "iline hist_beginning_search_fwd");
696   g_string_printf(sbuf, "%d", KEY_PPAGE);
697   settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster up");
698   g_string_printf(sbuf, "%d", KEY_NPAGE);
699   settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster down");
700   g_string_printf(sbuf, "%d", KEY_HOME);
701   settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_start");
702   settings_set(SETTINGS_TYPE_BINDING, "1", "iline iline_start");  // Ctrl-a
703   g_string_printf(sbuf, "%d", KEY_END);
704   settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_end");
705   settings_set(SETTINGS_TYPE_BINDING, "5", "iline iline_end");    // Ctrl-e
706   // Ctrl-o (accept-line-and-down-history):
707   settings_set(SETTINGS_TYPE_BINDING, "15", "iline iline_accept_down_hist");
708   settings_set(SETTINGS_TYPE_BINDING, "21", "iline iline_bdel");  // Ctrl-u
709   g_string_printf(sbuf, "%d", KEY_EOL);
710   settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_fdel");
711   settings_set(SETTINGS_TYPE_BINDING, "11", "iline iline_fdel");  // Ctrl-k
712   settings_set(SETTINGS_TYPE_BINDING, "16", "buffer up");         // Ctrl-p
713   settings_set(SETTINGS_TYPE_BINDING, "14", "buffer down");       // Ctrl-n
714   settings_set(SETTINGS_TYPE_BINDING, "20", "iline char_swap");   // Ctrl-t
715   settings_set(SETTINGS_TYPE_BINDING, "23", "iline word_bdel");   // Ctrl-w
716   settings_set(SETTINGS_TYPE_BINDING, "M98", "iline bword");      // Meta-b
717   settings_set(SETTINGS_TYPE_BINDING, "M102", "iline fword");     // Meta-f
718   settings_set(SETTINGS_TYPE_BINDING, "M100", "iline word_fdel"); // Meta-d
719   // Ctrl-Left  (2 codes):
720   settings_set(SETTINGS_TYPE_BINDING, "515", "iline bword");
721   settings_set(SETTINGS_TYPE_BINDING, "516", "iline bword");
722   // Ctrl-Right (2 codes):
723   settings_set(SETTINGS_TYPE_BINDING, "517", "iline fword");
724   settings_set(SETTINGS_TYPE_BINDING, "518", "iline fword");
725   settings_set(SETTINGS_TYPE_BINDING, "12", "screen_refresh");    // Ctrl-l
726   settings_set(SETTINGS_TYPE_BINDING, "27", "chat_disable --show-roster");// Esc
727   settings_set(SETTINGS_TYPE_BINDING, "M27", "chat_disable");     // Esc-Esc
728   settings_set(SETTINGS_TYPE_BINDING, "4", "iline send_multiline"); // Ctrl-d
729   settings_set(SETTINGS_TYPE_BINDING, "M117", "iline word_upcase"); // Meta-u
730   settings_set(SETTINGS_TYPE_BINDING, "M108", "iline word_downcase"); // Meta-l
731   settings_set(SETTINGS_TYPE_BINDING, "M99", "iline word_capit"); // Meta-c
732 
733   settings_set(SETTINGS_TYPE_BINDING, "265", "help"); // Bind F1 to help...
734 
735   g_string_free(sbuf, TRUE);
736 }
737 
738 //  is_speckey(key)
739 // Return TRUE if key is a special code, i.e. no char should be displayed on
740 // the screen.  It's not very nice, it's a workaround for the systems where
741 // isprint(KEY_PPAGE) returns TRUE...
is_speckey(int key)742 static int is_speckey(int key)
743 {
744   switch (key) {
745     case 127:
746     case 393:
747     case 402:
748     case KEY_BACKSPACE:
749     case KEY_DC:
750     case KEY_LEFT:
751     case KEY_RIGHT:
752     case KEY_UP:
753     case KEY_DOWN:
754     case KEY_PPAGE:
755     case KEY_NPAGE:
756     case KEY_HOME:
757     case KEY_END:
758     case KEY_EOL:
759         return TRUE;
760   }
761 
762   // Fn keys
763   if (key >= 265 && key < 265+12)
764     return TRUE;
765 
766   // Special key combinations
767   if (key >= 513 && key <= 521)
768     return TRUE;
769 
770   return FALSE;
771 }
772 
scr_init_locale_charset(void)773 void scr_init_locale_charset(void)
774 {
775   setlocale(LC_ALL, "");
776 #ifdef HAVE_LOCALCHARSET_H
777   LocaleCharSet = locale_charset();
778 #else
779   LocaleCharSet = nl_langinfo(CODESET);
780 #endif
781   utf8_mode = (strcmp(LocaleCharSet, "UTF-8") == 0);
782 }
783 
scr_curses_status(void)784 gboolean scr_curses_status(void)
785 {
786   return Curses;
787 }
788 
scr_vi_mode_guard(const gchar * key,const gchar * new_value)789 static gchar *scr_vi_mode_guard(const gchar *key, const gchar *new_value)
790 {
791   int new_mode = 0;
792   if (new_value)
793     new_mode = atoi(new_value);
794   if (new_mode == 0 || new_mode == 1)
795     vi_mode = new_mode;
796   return g_strdup(new_value);
797 }
798 
scr_color_guard(const gchar * key,const gchar * new_value)799 static gchar *scr_color_guard(const gchar *key, const gchar *new_value)
800 {
801   if (g_strcmp0(settings_opt_get(key), new_value))
802     colors_stalled = TRUE;
803   return g_strdup(new_value);
804 }
805 
scr_init_curses(void)806 void scr_init_curses(void)
807 {
808   /* Key sequences initialization */
809   init_keycodes();
810 
811   initscr();
812   raw();
813   noecho();
814   nonl();
815   intrflush(stdscr, FALSE);
816   start_color();
817   use_default_colors();
818 #ifdef NCURSES_MOUSE_VERSION
819   if (settings_opt_get_int("use_mouse"))
820     mousemask(ALL_MOUSE_EVENTS, NULL);
821 #endif
822 
823   if (settings_opt_get("escdelay")) {
824 #ifdef HAVE_ESCDELAY
825     ESCDELAY = (unsigned) settings_opt_get_int("escdelay");
826 #else
827     scr_LogPrint(LPRINT_LOGNORM, "ERROR: no ESCDELAY support.");
828 #endif
829   }
830 
831   // Set up vi_mode guard
832   settings_set_guard("vi_mode", scr_vi_mode_guard);
833   if (settings_opt_get_int("vi_mode") == 1)
834     vi_mode = true;
835 
836   parse_colors();
837 
838   settings_set_guard("color_background", scr_color_guard);
839   settings_set_guard("color_general", scr_color_guard);
840   settings_set_guard("color_info", scr_color_guard);
841   settings_set_guard("color_msgin", scr_color_guard);
842   settings_set_guard("color_msgout", scr_color_guard);
843   settings_set_guard("color_msghl", scr_color_guard);
844   settings_set_guard("color_bgstatus", scr_color_guard);
845   settings_set_guard("color_status", scr_color_guard);
846   settings_set_guard("color_log", scr_color_guard);
847   settings_set_guard("color_roster", scr_color_guard);
848   settings_set_guard("color_bgrostersel", scr_color_guard);
849   settings_set_guard("color_rostersel", scr_color_guard);
850   settings_set_guard("color_rosterselmsg", scr_color_guard);
851   settings_set_guard("color_rosternewmsg", scr_color_guard);
852   settings_set_guard("color_timestamp", scr_color_guard);
853 
854   getmaxyx(stdscr, maxY, maxX);
855   Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT;
856   // Note scr_draw_main_window() should be called early after scr_init_curses()
857   // to update Log_Win_Height and set max{X,Y}
858 
859   inputLine[0] = 0;
860   ptr_inputline = inputLine;
861 
862   Curses = TRUE;
863 
864   g_log_set_handler("GLib", G_LOG_LEVEL_MASK, scr_glog_print, NULL);
865   return;
866 }
867 
scr_terminate_curses(void)868 void scr_terminate_curses(void)
869 {
870   if (!Curses) return;
871   clear();
872   refresh();
873   endwin();
874   Curses = FALSE;
875   return;
876 }
877 
scr_beep(void)878 void scr_beep(void)
879 {
880   beep();
881 }
882 
883 // This and following belongs to dynamic setting of time prefix
884 static const char *timeprefixes[] = {
885   "%m-%d %H:%M ",
886   "%H:%M ",
887   " "
888 };
889 
890 static const char *spectimeprefixes[] = {
891   "%m-%d %H:%M:%S   ",
892   "%H:%M:%S   ",
893   "   "
894 };
895 
896 static int timepreflengths[] = {
897   // (length of the corresponding timeprefix + 5)
898   17,
899   11,
900   6
901 };
902 
gettprefix(void)903 static const char *gettprefix(void)
904 {
905   guint n = settings_opt_get_int("time_prefix");
906   return timeprefixes[(n < 3 ? n : 0)];
907 }
908 
getspectprefix(void)909 static const char *getspectprefix(void)
910 {
911   guint n = settings_opt_get_int("time_prefix");
912   return spectimeprefixes[(n < 3 ? n : 0)];
913 }
914 
scr_getprefixwidth(void)915 guint scr_getprefixwidth(void)
916 {
917   guint n = settings_opt_get_int("time_prefix");
918   return timepreflengths[(n < 3 ? n : 0)];
919 }
920 
scr_gettextwidth(void)921 guint scr_gettextwidth(void)
922 {
923   int used_width = Roster_Width + scr_getprefixwidth();
924   return maxX > used_width ? maxX - used_width : 0;
925 }
926 
scr_gettextheight(void)927 guint scr_gettextheight(void)
928 {
929   // log window, two status bars and one input line
930   return maxY - Log_Win_Height - 3;
931 }
932 
scr_getlogwinheight(void)933 guint scr_getlogwinheight(void)
934 {
935   return Log_Win_Height;
936 }
937 
938 //  scr_print_logwindow(string)
939 // Display the string in the log window.
940 // Note: The string must be in the user's locale!
scr_print_logwindow(const char * string)941 void scr_print_logwindow(const char *string)
942 {
943   time_t timestamp;
944   char strtimestamp[64];
945 
946   timestamp = time(NULL);
947   strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(&timestamp));
948   if (Curses) {
949     wprintw(logWnd, "\n%s %s", strtimestamp, string);
950     update_panels();
951   } else {
952     printf("%s %s\n", strtimestamp, string);
953   }
954 }
955 
956 //  scr_log_print(...)
957 // Display a message in the log window and in the status buffer.
958 // Add the message to the tracelog file if the log flag is set.
959 // This function will convert from UTF-8 unless the LPRINT_NOTUTF8 flag is set.
scr_log_print(unsigned int flag,const char * fmt,...)960 void scr_log_print(unsigned int flag, const char *fmt, ...)
961 {
962   time_t timestamp;
963   char strtimestamp[64];
964   char *buffer, *btext;
965   char *convbuf1 = NULL, *convbuf2 = NULL;
966   va_list ap;
967 
968   if (!(flag & ~LPRINT_NOTUTF8)) return; // Shouldn't happen
969 
970   timestamp = time(NULL);
971   strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(&timestamp));
972   va_start(ap, fmt);
973   btext = g_strdup_vprintf(fmt, ap);
974   va_end(ap);
975 
976   if (flag & LPRINT_NORMAL) {
977     char *buffer_locale;
978     char *buf_specialwindow;
979 
980     buffer = g_strdup_printf("%s %s", strtimestamp, btext);
981 
982     // Convert buffer to current locale for wprintw()
983     if (!(flag & LPRINT_NOTUTF8))
984       buffer_locale = convbuf1 = from_utf8(buffer);
985     else
986       buffer_locale = buffer;
987 
988     if (!buffer_locale) {
989       wprintw(logWnd,
990               "\n%s*Error: cannot convert string to locale.", strtimestamp);
991       update_panels();
992       g_free(buffer);
993       g_free(btext);
994       return;
995     }
996 
997     // For the special status buffer, we need utf-8, but without the timestamp
998     if (flag & LPRINT_NOTUTF8)
999       buf_specialwindow = convbuf2 = to_utf8(btext);
1000     else
1001       buf_specialwindow = btext;
1002 
1003     if (Curses) {
1004       wprintw(logWnd, "\n%s", buffer_locale);
1005       update_panels();
1006       scr_write_in_window(NULL, buf_specialwindow, timestamp,
1007                           HBB_PREFIX_SPECIAL, FALSE, 0, NULL);
1008     } else {
1009       printf("%s\n", buffer_locale);
1010       // ncurses are not initialized yet, so we call directly hbuf routine
1011       hbuf_add_line(&statushbuf, buf_specialwindow, timestamp,
1012         HBB_PREFIX_SPECIAL, 0, 0, 0, NULL);
1013     }
1014 
1015     g_free(convbuf1);
1016     g_free(convbuf2);
1017     g_free(buffer);
1018   }
1019 
1020   if (flag & (LPRINT_LOG|LPRINT_DEBUG)) {
1021     strftime(strtimestamp, 23, "[%Y-%m-%d %H:%M:%S]", localtime(&timestamp));
1022     buffer = g_strdup_printf("%s %s\n", strtimestamp, btext);
1023     ut_write_log(flag, buffer);
1024     g_free(buffer);
1025   }
1026   g_free(btext);
1027 }
1028 
1029 // This is a GLogFunc for Glib log messages
scr_glog_print(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer user_data)1030 static void scr_glog_print(const gchar *log_domain, GLogLevelFlags log_level,
1031                            const gchar *message, gpointer user_data)
1032 {
1033   scr_log_print(LPRINT_NORMAL, "[%s] %s", log_domain, message);
1034 }
1035 
scr_search_window(const char * winId,int special)1036 static winbuf_t *scr_search_window(const char *winId, int special)
1037 {
1038   char *id;
1039   winbuf_t *wbp;
1040 
1041   if (special)
1042     return statusWindow; // Only one special window atm.
1043 
1044   if (!winId)
1045     return NULL;
1046 
1047   id = g_strdup(winId);
1048   mc_strtolower(id);
1049   wbp = g_hash_table_lookup(winbufhash, id);
1050   g_free(id);
1051   return wbp;
1052 }
1053 
scr_buddy_buffer_exists(const char * bjid)1054 int scr_buddy_buffer_exists(const char *bjid)
1055 {
1056   return (scr_search_window(bjid, FALSE) != NULL);
1057 }
1058 
1059 //  scr_new_buddy(title, dontshow)
1060 // Note: title (aka winId/jid) can be NULL for special buffers
scr_new_buddy(const char * title,int dont_show)1061 static winbuf_t *scr_new_buddy(const char *title, int dont_show)
1062 {
1063   winbuf_t *tmp;
1064   char *id;
1065 
1066   tmp = g_new0(winbuf_t, 1);
1067 
1068   tmp->win = activechatWnd;
1069   tmp->panel = activechatPanel;
1070 
1071   if (!dont_show) {
1072     currentWindow = tmp;
1073   } else {
1074     if (currentWindow)
1075       top_panel(currentWindow->panel);
1076     else
1077       top_panel(chatPanel);
1078   }
1079   update_panels();
1080 
1081   // If title is NULL, this is a special buffer
1082   if (!title) {
1083     tmp->bd = g_new0(buffdata_t, 1);
1084     return tmp;
1085   }
1086 
1087   id = hlog_get_log_jid(title);
1088   if (id) {
1089     // This is a symlinked history log file.
1090     // Let's check if the target JID buffer has already been created.
1091     winbuf_t *wb = scr_search_window(id, FALSE);
1092     if (!wb)
1093       wb = scr_new_buddy(id, TRUE);
1094     tmp->bd = wb->bd;
1095     tmp->bd->refcount++;
1096     g_free(id);
1097   } else {  // Load buddy history from file (if enabled)
1098     tmp->bd = g_new0(buffdata_t, 1);
1099     hlog_read_history(title, &tmp->bd->hbuf, scr_gettextwidth());
1100 
1101     // Set a readmark to separate new content
1102     hbuf_set_readmark(tmp->bd->hbuf, TRUE);
1103   }
1104 
1105   id = g_strdup(title);
1106   mc_strtolower(id);
1107   g_hash_table_insert(winbufhash, id, tmp);
1108 
1109   return tmp;
1110 }
1111 
1112 //  scr_line_prefix(line, pref, preflen)
1113 // Use data from the hbb_line structure and write the prefix
1114 // to pref (not exceeding preflen, trailing null byte included).
scr_line_prefix(hbb_line * line,char * pref,guint preflen)1115 size_t scr_line_prefix(hbb_line *line, char *pref, guint preflen)
1116 {
1117   char date[64];
1118   size_t timepreflen = 0;
1119 
1120   if (line->timestamp &&
1121       !(line->flags & (HBB_PREFIX_SPECIAL|HBB_PREFIX_CONT))) {
1122     timepreflen = strftime(date, 30, gettprefix(), localtime(&line->timestamp));
1123   } else
1124     strcpy(date, "           ");
1125 
1126   if (!(line->flags & HBB_PREFIX_CONT)) {
1127     if (line->flags & HBB_PREFIX_INFO) {
1128       char dir = '*';
1129       if (line->flags & HBB_PREFIX_IN)
1130         dir = '<';
1131       else if (line->flags & HBB_PREFIX_OUT)
1132         dir = '>';
1133       g_snprintf(pref, preflen, "%s*%c* ", date, dir);
1134     } else if (line->flags & HBB_PREFIX_ERR) {
1135       char dir = '#';
1136       if (line->flags & HBB_PREFIX_IN)
1137         dir = '<';
1138       else if (line->flags & HBB_PREFIX_OUT)
1139         dir = '>';
1140       g_snprintf(pref, preflen, "%s#%c# ", date, dir);
1141     } else if (line->flags & HBB_PREFIX_IN) {
1142       char cryptflag;
1143       if (line->flags & HBB_PREFIX_PGPCRYPT)
1144         cryptflag = '~';
1145       else if (line->flags & HBB_PREFIX_OTRCRYPT)
1146         cryptflag = 'O';
1147       else
1148         cryptflag = '=';
1149       g_snprintf(pref, preflen, "%s<%c= ", date, cryptflag);
1150     } else if (line->flags & HBB_PREFIX_OUT) {
1151       char cryptflag, receiptflag;
1152       if (line->flags & HBB_PREFIX_PGPCRYPT)
1153         cryptflag = '~';
1154       else if (line->flags & HBB_PREFIX_OTRCRYPT)
1155         cryptflag = 'O';
1156       else
1157         cryptflag = '-';
1158       if (line->flags & HBB_PREFIX_RECEIPT)
1159         receiptflag = 'r';
1160       else
1161         receiptflag = '-';
1162       g_snprintf(pref, preflen, "%s%c%c> ", date, receiptflag, cryptflag);
1163     } else if (line->flags & HBB_PREFIX_SPECIAL) {
1164       timepreflen = strftime(date, 30, getspectprefix(), localtime(&line->timestamp));
1165       g_snprintf(pref, preflen, "%s   ", date);
1166     } else {
1167       g_snprintf(pref, preflen, "%s    ", date);
1168     }
1169   } else {
1170     g_snprintf(pref, preflen, "                ");
1171   }
1172   return timepreflen;
1173 }
1174 
1175 //  scr_update_window()
1176 // (Re-)Display the given chat window.
scr_update_window(winbuf_t * win_entry)1177 static void scr_update_window(winbuf_t *win_entry)
1178 {
1179   int n, mark_offset = 0;
1180   guint prefixwidth;
1181   char pref[96];
1182   hbb_line **lines, *line;
1183   GList *hbuf_head;
1184   int color = COLOR_GENERAL;
1185   bool readmark = FALSE;
1186   bool skipline = FALSE;
1187   int autolock;
1188 
1189   autolock = settings_opt_get_int("buffer_smart_scrolling");
1190 
1191   prefixwidth = scr_getprefixwidth();
1192   prefixwidth = MIN(prefixwidth, sizeof pref);
1193 
1194   // Should the window be empty?
1195   if (win_entry->bd->cleared) {
1196     werase(win_entry->win);
1197     if (autolock && win_entry->bd->lock)
1198       scr_buffer_scroll_lock(0);
1199     return;
1200   }
1201 
1202   // win_entry->bd->top is the top message of the screen.  If it set to NULL,
1203   // we are displaying the last messages.
1204 
1205   // We will show the last CHAT_WIN_HEIGHT lines.
1206   // Let's find out where it begins.
1207   if (!win_entry->bd->top || (g_list_position(g_list_first(win_entry->bd->hbuf),
1208                                               win_entry->bd->top) == -1)) {
1209     // Move up CHAT_WIN_HEIGHT lines
1210     win_entry->bd->hbuf = g_list_last(win_entry->bd->hbuf);
1211     hbuf_head = win_entry->bd->hbuf;
1212     win_entry->bd->top = NULL; // (Just to make sure)
1213     n = 0;
1214     while (hbuf_head && (n < CHAT_WIN_HEIGHT-1) && g_list_previous(hbuf_head)) {
1215       hbuf_head = g_list_previous(hbuf_head);
1216       n++;
1217     }
1218     // If the buffer is locked, remember current "top" line for the next time.
1219     if (win_entry->bd->lock)
1220       win_entry->bd->top = hbuf_head;
1221   } else
1222     hbuf_head = win_entry->bd->top;
1223 
1224   // Get the last CHAT_WIN_HEIGHT lines, and one more to detect scroll.
1225   lines = hbuf_get_lines(hbuf_head, CHAT_WIN_HEIGHT+1);
1226 
1227   if (CHAT_WIN_HEIGHT > 1) {
1228     // Do we have a read mark?
1229     for (n = 0; n < CHAT_WIN_HEIGHT; n++) {
1230       line = *(lines+n);
1231       if (line) {
1232         if (line->flags & HBB_PREFIX_READMARK) {
1233           // If this is not the last line, we'll display a mark
1234           if (n+1 < CHAT_WIN_HEIGHT && *(lines+n+1)) {
1235             readmark = TRUE;
1236             skipline = TRUE;
1237             mark_offset = -1;
1238           }
1239         }
1240       } else if (readmark) {
1241         // There will be empty lines, so we don't need to skip the first line
1242         skipline = FALSE;
1243         mark_offset = 0;
1244       }
1245     }
1246   }
1247 
1248   // Display the lines
1249   for (n = 0 ; n < CHAT_WIN_HEIGHT; n++) {
1250     int timelen;
1251     int winy = n + mark_offset;
1252     wmove(win_entry->win, winy, 0);
1253     line = *(lines+n);
1254     if (line) {
1255       if (skipline)
1256         goto scr_update_window_skipline;
1257 
1258       if (line->flags & HBB_PREFIX_HLIGHT_OUT)
1259         color = COLOR_MSGOUT;
1260       else if (line->flags & HBB_PREFIX_HLIGHT)
1261         color = COLOR_MSGHL;
1262       else if (line->flags & HBB_PREFIX_INFO)
1263         color = COLOR_INFO;
1264       else if (line->flags & HBB_PREFIX_IN)
1265         color = COLOR_MSGIN;
1266       else
1267         color = COLOR_GENERAL;
1268 
1269       if (color != COLOR_GENERAL)
1270         wbkgdset(win_entry->win, get_color(color));
1271 
1272       // Generate the prefix area and display it
1273 
1274       timelen = scr_line_prefix(line, pref, prefixwidth);
1275       if (timelen && line->flags & HBB_PREFIX_DELAYED) {
1276         char tmp;
1277 
1278         tmp = pref[timelen];
1279         pref[timelen] = '\0';
1280         wbkgdset(win_entry->win, get_color(COLOR_TIMESTAMP));
1281         wprintw(win_entry->win, pref);
1282         pref[timelen] = tmp;
1283         wbkgdset(win_entry->win, get_color(color));
1284         wprintw(win_entry->win, pref+timelen);
1285       } else
1286         wprintw(win_entry->win, pref);
1287 
1288       // Make sure we are at the right position
1289       wmove(win_entry->win, winy, prefixwidth-1);
1290 
1291       // The MUC nick - overwrite with proper color
1292       if (line->mucnicklen) {
1293         char *mucjid;
1294         char tmp;
1295         nickcolor_t *actual = NULL;
1296         muccol_t type, *typetmp;
1297 
1298         // Store the char after the nick
1299         tmp = line->text[line->mucnicklen];
1300         type = glob_muccol;
1301         // Terminate the string after the nick
1302         line->text[line->mucnicklen] = '\0';
1303         mucjid = g_utf8_strdown(CURRENT_JID, -1);
1304         if (muccolors) {
1305           typetmp = g_hash_table_lookup(muccolors, mucjid);
1306           if (typetmp)
1307             type = *typetmp;
1308         }
1309         g_free(mucjid);
1310         // Need to generate a color for the specified nick?
1311         if ((type == MC_ALL) && (!nickcolors ||
1312             !g_hash_table_lookup(nickcolors, line->text))) {
1313           char *snick, *mnick;
1314           nickcolor_t *nc;
1315           const char *p = line->text;
1316           unsigned int nicksum = 0;
1317           snick = g_strdup(line->text);
1318           mnick = g_strdup(line->text);
1319           nc = g_new(nickcolor_t, 1);
1320           ensure_string_htable(&nickcolors, NULL);
1321           while (*p)
1322             nicksum += *p++;
1323           nc->color = nickcols[nicksum % nickcolcount];
1324           nc->manual = FALSE;
1325           *snick = '<';
1326           snick[strlen(snick)-1] = '>';
1327           *mnick = '*';
1328           mnick[strlen(mnick)-1] = ' ';
1329           // Insert them
1330           g_hash_table_insert(nickcolors, snick, nc);
1331           g_hash_table_insert(nickcolors, mnick, nc);
1332         }
1333         if (nickcolors)
1334           actual = g_hash_table_lookup(nickcolors, line->text);
1335         if (actual && ((type == MC_ALL) || (actual->manual))
1336             && (line->flags & HBB_PREFIX_IN) &&
1337            (!(line->flags & HBB_PREFIX_HLIGHT_OUT)))
1338           wbkgdset(win_entry->win, compose_color(actual->color));
1339         wprintw(win_entry->win, "%s", line->text);
1340         // Return the char
1341         line->text[line->mucnicklen] = tmp;
1342         // Return the color back
1343         wbkgdset(win_entry->win, get_color(color));
1344       }
1345 
1346       // Display text line
1347       wprintw(win_entry->win, "%s", line->text+line->mucnicklen);
1348       wclrtoeol(win_entry->win);
1349 
1350       // Restore default ("general") color
1351       if (color != COLOR_GENERAL)
1352         wbkgdset(win_entry->win, get_color(COLOR_GENERAL));
1353 
1354 scr_update_window_skipline:
1355       skipline = FALSE;
1356       if (readmark && line->flags & HBB_PREFIX_READMARK) {
1357         int i, w;
1358         mark_offset++;
1359 
1360         // Display the mark
1361         winy = n + mark_offset;
1362         wmove(win_entry->win, winy, 0);
1363         wbkgdset(win_entry->win, get_color(COLOR_READMARK));
1364         g_snprintf(pref, prefixwidth, "             == ");
1365         wprintw(win_entry->win, pref);
1366         w = scr_gettextwidth() / 3;
1367         for (i=0; i<w; i++)
1368           wprintw(win_entry->win, "== ");
1369         wclrtoeol(win_entry->win);
1370         wbkgdset(win_entry->win, get_color(COLOR_GENERAL));
1371       }
1372       g_free(line->text);
1373       g_free(line);
1374     } else {
1375       wclrtobot(win_entry->win);
1376       break;
1377     }
1378   }
1379   line = *(lines+CHAT_WIN_HEIGHT); //line is scrolled out and never written
1380   if (line) {
1381     if (autolock && !win_entry->bd->lock) {
1382       if (!hbuf_jump_readmark(hbuf_head))
1383         scr_buffer_readmark(TRUE);
1384       scr_buffer_scroll_lock(1);
1385     }
1386     g_free(line->text);
1387     g_free(line);
1388   } else if (autolock && win_entry->bd->lock) {
1389     scr_buffer_scroll_lock(0);
1390   }
1391 
1392   g_free(lines);
1393 }
1394 
scr_create_window(const char * winId,int special,int dont_show)1395 static winbuf_t *scr_create_window(const char *winId, int special, int dont_show)
1396 {
1397   if (special) {
1398     if (!statusWindow) {
1399       statusWindow = scr_new_buddy(NULL, dont_show);
1400       statusWindow->bd->hbuf = statushbuf;
1401     }
1402     return statusWindow;
1403   } else {
1404     return scr_new_buddy(winId, dont_show);
1405   }
1406 }
1407 
1408 //  scr_show_window()
1409 // Display the chat window with the given identifier.
1410 // "special" must be true if this is a special buffer window.
scr_show_window(const char * winId,int special)1411 static void scr_show_window(const char *winId, int special)
1412 {
1413   winbuf_t *win_entry;
1414 
1415   win_entry = scr_search_window(winId, special);
1416 
1417   if (!win_entry) {
1418     win_entry = scr_create_window(winId, special, FALSE);
1419   }
1420 
1421   top_panel(win_entry->panel);
1422   currentWindow = win_entry;
1423   chatmode = TRUE;
1424   if (!win_entry->bd->lock)
1425     roster_msg_setflag(winId, special, FALSE);
1426   if (!special)
1427     roster_setflags(winId, ROSTER_FLAG_LOCK, TRUE);
1428   scr_update_roster();
1429 
1430   // Refresh the window
1431   scr_update_window(win_entry);
1432 
1433   // Finished :)
1434   update_panels();
1435 
1436   top_panel(inputPanel);
1437 }
1438 
1439 //  scr_show_buddy_window()
1440 // Display the chat window buffer for the current buddy.
scr_show_buddy_window(void)1441 void scr_show_buddy_window(void)
1442 {
1443   const gchar *bjid;
1444 
1445   buddylist_build();
1446   if (!current_buddy) {
1447     bjid = NULL;
1448   } else {
1449     bjid = CURRENT_JID;
1450     if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL) {
1451       scr_show_window(buddy_getname(BUDDATA(current_buddy)), TRUE);
1452       return;
1453     }
1454   }
1455 
1456   if (!bjid) {
1457     top_panel(chatPanel);
1458     top_panel(inputPanel);
1459     currentWindow = NULL;
1460     return;
1461   }
1462 
1463   roster_msg_update_unread(bjid, FALSE);
1464   scr_show_window(bjid, FALSE);
1465 }
1466 
1467 //  scr_update_buddy_window()
1468 // (Re)Display the current window.
1469 // If chatmode is enabled, call scr_show_buddy_window(),
1470 // else display the chat window.
scr_update_buddy_window(void)1471 void scr_update_buddy_window(void)
1472 {
1473   if (chatmode) {
1474     scr_show_buddy_window();
1475     return;
1476   }
1477 
1478   top_panel(chatPanel);
1479   top_panel(inputPanel);
1480 }
1481 
1482 //  scr_write_in_window()
1483 // Write some text in the winId window (this usually is a jid).
1484 // Use winId == NULL for the special status buffer.
1485 // Lines are splitted when they are too long to fit in the chat window.
1486 // If this window doesn't exist, it is created.
scr_write_in_window(const char * winId,const char * text,time_t timestamp,unsigned int prefix_flags,int force_show,unsigned mucnicklen,gpointer xep184)1487 static void scr_write_in_window(const char *winId, const char *text,
1488                                 time_t timestamp, unsigned int prefix_flags,
1489                                 int force_show, unsigned mucnicklen,
1490                                 gpointer xep184)
1491 {
1492   winbuf_t *win_entry;
1493   char *text_locale;
1494   int dont_show = FALSE;
1495   int special;
1496   guint num_history_blocks;
1497   bool setmsgflg = FALSE;
1498   bool clearmsgflg = FALSE;
1499   char *nicktmp, *nicklocaltmp;
1500 
1501   // Look for the window entry.
1502   special = (winId == NULL);
1503   win_entry = scr_search_window(winId, special);
1504 
1505   // Do we have to really show the window?
1506   if (!chatmode)
1507     dont_show = TRUE;
1508   else if ((!force_show) && ((!currentWindow || (currentWindow != win_entry))))
1509     dont_show = TRUE;
1510 
1511   // If the window entry doesn't exist yet, let's create it.
1512   if (!win_entry) {
1513     win_entry = scr_create_window(winId, special, dont_show);
1514   }
1515 
1516   // The message must be displayed -> update top pointer
1517   if (win_entry->bd->cleared)
1518     win_entry->bd->top = g_list_last(win_entry->bd->hbuf);
1519 
1520   // Make sure we do not free the buffer while it's locked or when
1521   // top is set.
1522   if (win_entry->bd->lock || win_entry->bd->top)
1523     num_history_blocks = 0U;
1524   else
1525     num_history_blocks = get_max_history_blocks();
1526 
1527   text_locale = from_utf8(text);
1528   // Convert the nick alone and compute its length
1529   if (mucnicklen) {
1530     nicktmp = g_strndup(text, mucnicklen);
1531     nicklocaltmp = from_utf8(nicktmp);
1532     if (nicklocaltmp)
1533       mucnicklen = strlen(nicklocaltmp);
1534     g_free(nicklocaltmp);
1535     g_free(nicktmp);
1536   }
1537   hbuf_add_line(&win_entry->bd->hbuf, text_locale, timestamp, prefix_flags,
1538                 scr_gettextwidth(), num_history_blocks, mucnicklen, xep184);
1539   g_free(text_locale);
1540 
1541   if (win_entry->bd->cleared) {
1542     win_entry->bd->cleared = FALSE;
1543     if (g_list_next(win_entry->bd->top))
1544       win_entry->bd->top = g_list_next(win_entry->bd->top);
1545   }
1546 
1547   // Make sure the last line appears in the window; update top if necessary
1548   if (!win_entry->bd->lock && win_entry->bd->top) {
1549     int dist;
1550     GList *first = g_list_first(win_entry->bd->hbuf);
1551     dist = g_list_position(first, g_list_last(win_entry->bd->hbuf)) -
1552            g_list_position(first, win_entry->bd->top);
1553     if (dist >= CHAT_WIN_HEIGHT)
1554       win_entry->bd->top = NULL;
1555   }
1556 
1557   if (!dont_show) {
1558     if (win_entry->bd->lock)
1559       setmsgflg = TRUE;
1560     else
1561       // If this is an outgoing message, remove the readmark
1562       if (!special && (prefix_flags & (HBB_PREFIX_OUT|HBB_PREFIX_HLIGHT_OUT)))
1563         hbuf_set_readmark(win_entry->bd->hbuf, FALSE);
1564     // Show and refresh the window
1565     top_panel(win_entry->panel);
1566     scr_update_window(win_entry);
1567     top_panel(inputPanel);
1568     update_panels();
1569   } else if (settings_opt_get_int("clear_unread_on_carbon") &&
1570              prefix_flags & HBB_PREFIX_OUT &&
1571              prefix_flags & HBB_PREFIX_CARBON) {
1572     clearmsgflg = TRUE;
1573   } else if (!(prefix_flags & HBB_PREFIX_NOFLAG)) {
1574     setmsgflg = TRUE;
1575   }
1576   if (!special) {
1577     if (clearmsgflg) {
1578       roster_msg_update_unread(winId, FALSE);
1579       roster_msg_setflag(winId, FALSE, FALSE);
1580       scr_update_roster();
1581     } else if (setmsgflg) {
1582       roster_msg_update_unread(winId, TRUE);
1583       roster_msg_setflag(winId, FALSE, TRUE);
1584       scr_update_roster();
1585     }
1586   }
1587 }
1588 
attention_sign_guard(const gchar * key,const gchar * new_value)1589 static char *attention_sign_guard(const gchar *key, const gchar *new_value)
1590 {
1591   scr_update_roster();
1592   if (g_strcmp0(settings_opt_get(key), new_value)) {
1593     guint sign;
1594     char *c;
1595     if (!new_value || !*new_value)
1596       return NULL;
1597     sign = get_char(new_value);
1598     c = next_char((char*)new_value);
1599     if (get_char_width(new_value) != 1 || !iswprint(sign) || *c) {
1600       scr_log_print(LPRINT_NORMAL, "attention_char value is invalid.");
1601       return NULL;
1602     }
1603     // The new value looks good (1-char  wide and printable)
1604     return g_strdup(new_value);
1605   }
1606   return g_strdup(new_value);
1607 }
1608 
1609 //  scr_init_settings()
1610 // Create guards for UI settings
scr_init_settings(void)1611 void scr_init_settings(void)
1612 {
1613   settings_set_guard("attention_char", attention_sign_guard);
1614 }
1615 
attention_sign(void)1616 static unsigned int attention_sign(void)
1617 {
1618   const char *as = settings_opt_get("attention_char");
1619   if (!as)
1620       return DEFAULT_ATTENTION_CHAR;
1621   return get_char(as);
1622 }
1623 
1624 //  scr_update_main_status(forceupdate)
1625 // Redraw the main (bottom) status line.
1626 // You can set forceupdate to FALSE in order to optimize screen refresh
1627 // if you call top_panel()/update_panels() later.
scr_update_main_status(int forceupdate)1628 void scr_update_main_status(int forceupdate)
1629 {
1630   char *sm = from_utf8(xmpp_getstatusmsg());
1631   const char *info = settings_opt_get("info");
1632   guint prio = 0;
1633   gpointer unread_ptr;
1634   guint unreadchar;
1635 
1636   unread_ptr = unread_msg(NULL);
1637   if (unread_ptr) {
1638     prio = buddy_getuiprio(unread_ptr);
1639     // If there's an unerad buffer but no priority set, let's consider the
1640     // priority is 1.
1641     if (!prio && buddy_getflags(unread_ptr) & ROSTER_FLAG_MSG)
1642       prio = 1;
1643   }
1644 
1645   // Status bar unread message flag
1646   if (prio >= ROSTER_UI_PRIO_MUC_HL_MESSAGE)
1647     unreadchar = attention_sign();
1648   else if (prio > 0)
1649     unreadchar = '#';
1650   else
1651     unreadchar = ' ';
1652 
1653   werase(mainstatusWnd);
1654   if (info) {
1655     char *info_locale = from_utf8(info);
1656     mvwprintw(mainstatusWnd, 0, 0, "%lc[%c] %s %s", unreadchar,
1657               imstatus2char[xmpp_getstatus()],
1658               info_locale, (sm ? sm : ""));
1659     g_free(info_locale);
1660   } else
1661     mvwprintw(mainstatusWnd, 0, 0, "%lc[%c] %s", unreadchar,
1662               imstatus2char[xmpp_getstatus()], (sm ? sm : ""));
1663   if (forceupdate) {
1664     top_panel(inputPanel);
1665     update_panels();
1666   }
1667   g_free(sm);
1668 }
1669 
1670 //  scr_draw_main_window()
1671 // Set fullinit to TRUE to also create panels.  Set it to FALSE for a resize.
1672 //
1673 // I think it could be improved a _lot_ but I'm really not an ncurses
1674 // expert... :-\   Mikael.
1675 //
scr_draw_main_window(unsigned int fullinit)1676 void scr_draw_main_window(unsigned int fullinit)
1677 {
1678   int requested_size;
1679   gchar *ver, *message;
1680   int chat_y_pos, chatstatus_y_pos, log_y_pos;
1681   int roster_x_pos, chat_x_pos;
1682 
1683   if (maxY < 4)
1684     maxY = 4;
1685 
1686   if (NULL == settings_opt_get("log_win_height"))
1687     requested_size = DEFAULT_LOG_WIN_HEIGHT;
1688   else
1689     requested_size = settings_opt_get_int("log_win_height");
1690   if (requested_size <= 0) {
1691     Log_Win_Height = 0;
1692   } else {
1693     if (maxY >= requested_size + 4)
1694       Log_Win_Height = requested_size;
1695     else {
1696       Log_Win_Height = maxY - 4;
1697     }
1698   }
1699 
1700   if (roster_hidden) {
1701     Roster_Width = 0;
1702   } else {
1703     requested_size = settings_opt_get_int("roster_width");
1704     if (requested_size > 1)
1705       Roster_Width = requested_size;
1706     else if (requested_size == 1)
1707       Roster_Width = 2;
1708     else
1709       Roster_Width = DEFAULT_ROSTER_WIDTH;
1710   }
1711 
1712   log_win_on_top = (settings_opt_get_int("log_win_on_top") == 1);
1713   roster_win_on_right = (settings_opt_get_int("roster_win_on_right") == 1);
1714 
1715   if (log_win_on_top) {
1716     log_y_pos = 0;
1717     chatstatus_y_pos = Log_Win_Height;
1718     chat_y_pos = Log_Win_Height + 1;
1719   } else {
1720     chat_y_pos = 0;
1721     chatstatus_y_pos = CHAT_WIN_HEIGHT;
1722     log_y_pos = CHAT_WIN_HEIGHT + 1;
1723   }
1724 
1725   if (roster_win_on_right) {
1726     roster_x_pos = maxX - Roster_Width;
1727     chat_x_pos = 0;
1728   } else {
1729     roster_x_pos = 0;
1730     chat_x_pos = Roster_Width;
1731   }
1732 
1733   if (fullinit) {
1734     if (!winbufhash)
1735       winbufhash = g_hash_table_new_full(g_str_hash, g_str_equal,
1736                                          g_free, g_free);
1737     /* Create windows */
1738     rosterWnd = newwin(CHAT_WIN_HEIGHT, Roster_Width, chat_y_pos, roster_x_pos);
1739     chatWnd   = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos,
1740                        chat_x_pos);
1741     activechatWnd = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos,
1742                            chat_x_pos);
1743     logWnd    = newwin(Log_Win_Height, maxX, log_y_pos, 0);
1744     chatstatusWnd = newwin(1, maxX, chatstatus_y_pos, 0);
1745     mainstatusWnd = newwin(1, maxX, maxY-2, 0);
1746     inputWnd  = newwin(1, maxX, maxY-1, 0);
1747     if (!rosterWnd || !chatWnd || !logWnd || !inputWnd) {
1748       scr_terminate_curses();
1749       fprintf(stderr, "Cannot create windows!\n");
1750       exit(EXIT_FAILURE);
1751     }
1752     wbkgd(rosterWnd,      get_color(COLOR_GENERAL));
1753     wbkgd(chatWnd,        get_color(COLOR_GENERAL));
1754     wbkgd(activechatWnd,  get_color(COLOR_GENERAL));
1755     wbkgd(logWnd,         get_color(COLOR_LOG));
1756     wbkgd(chatstatusWnd,  get_color(COLOR_STATUS));
1757     wbkgd(mainstatusWnd,  get_color(COLOR_STATUS));
1758   } else {
1759     /* Resize/move windows */
1760     wresize(rosterWnd, CHAT_WIN_HEIGHT, Roster_Width);
1761     wresize(chatWnd, CHAT_WIN_HEIGHT, maxX - Roster_Width);
1762     wresize(logWnd, Log_Win_Height, maxX);
1763 
1764     mvwin(chatWnd, chat_y_pos, chat_x_pos);
1765     mvwin(rosterWnd, chat_y_pos, roster_x_pos);
1766     mvwin(logWnd, log_y_pos, 0);
1767 
1768     // Resize & move chat status window
1769     wresize(chatstatusWnd, 1, maxX);
1770     mvwin(chatstatusWnd, chatstatus_y_pos, 0);
1771     // Resize & move main status window
1772     wresize(mainstatusWnd, 1, maxX);
1773     mvwin(mainstatusWnd, maxY-2, 0);
1774     // Resize & move input line window
1775     wresize(inputWnd, 1, maxX);
1776     mvwin(inputWnd, maxY-1, 0);
1777 
1778     werase(chatWnd);
1779   }
1780 
1781   /* Draw/init windows */
1782 
1783   ver = mcabber_version();
1784   message = g_strdup_printf("MCabber version %s.\n", ver);
1785   mvwprintw(chatWnd, 0, 0, message);
1786   mvwprintw(chatWnd, 1, 0, "http://mcabber.com/");
1787   g_free(ver);
1788   g_free(message);
1789 
1790   // Auto-scrolling in log window
1791   scrollok(logWnd, TRUE);
1792 
1793 
1794   if (fullinit) {
1795     // Enable keypad (+ special keys)
1796     keypad(inputWnd, TRUE);
1797 #ifdef __MirBSD__
1798     wtimeout(inputWnd, 50 /* ms */);
1799 #else
1800     nodelay(inputWnd, TRUE);
1801 #endif
1802 
1803     // Create panels
1804     rosterPanel = new_panel(rosterWnd);
1805     chatPanel   = new_panel(chatWnd);
1806     activechatPanel = new_panel(activechatWnd);
1807     logPanel    = new_panel(logWnd);
1808     chatstatusPanel = new_panel(chatstatusWnd);
1809     mainstatusPanel = new_panel(mainstatusWnd);
1810     inputPanel  = new_panel(inputWnd);
1811 
1812     // Build the buddylist at least once, to make sure the special buffer
1813     // is added
1814     buddylist_defer_build();
1815 
1816     // Init prev_chatwidth; this variable will be used to prevent us
1817     // from rewrapping buffers when the width doesn't change.
1818     prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
1819     // Wrap existing status buffer lines
1820     hbuf_rebuild(&statushbuf, prev_chatwidth);
1821 
1822 #ifndef UNICODE
1823     if (utf8_mode)
1824       scr_LogPrint(LPRINT_NORMAL,
1825                    "WARNING: Compiled without full UTF-8 support!");
1826 #endif
1827   } else {
1828     // Update panels
1829     replace_panel(rosterPanel, rosterWnd);
1830     replace_panel(chatPanel, chatWnd);
1831     replace_panel(logPanel, logWnd);
1832     replace_panel(chatstatusPanel, chatstatusWnd);
1833     replace_panel(mainstatusPanel, mainstatusWnd);
1834     replace_panel(inputPanel, inputWnd);
1835   }
1836 
1837   if (0 == Log_Win_Height) {
1838     hide_panel(logPanel);
1839   } else {
1840     show_panel(logPanel);
1841   }
1842 
1843   // We'll need to redraw the roster
1844   scr_update_roster();
1845   return;
1846 }
1847 
resize_win_buffer(gpointer key,gpointer value,gpointer data)1848 static void resize_win_buffer(gpointer key, gpointer value, gpointer data)
1849 {
1850   winbuf_t *wbp = value;
1851   struct dimensions *dim = data;
1852   int chat_x_pos, chat_y_pos;
1853   int new_chatwidth;
1854 
1855   if (!(wbp && wbp->win))
1856     return;
1857 
1858   if (log_win_on_top)
1859     chat_y_pos = Log_Win_Height + 1;
1860   else
1861     chat_y_pos = 0;
1862 
1863   if (roster_win_on_right)
1864     chat_x_pos = 0;
1865   else
1866     chat_x_pos = Roster_Width;
1867 
1868   // Resize/move buddy window
1869   wresize(wbp->win, dim->l, dim->c);
1870   mvwin(wbp->win, chat_y_pos, chat_x_pos);
1871   werase(wbp->win);
1872   // If a panel exists, replace the old window with the new
1873   if (wbp->panel)
1874     replace_panel(wbp->panel, wbp->win);
1875   // Redo line wrapping
1876   wbp->bd->top = hbuf_previous_persistent(wbp->bd->top);
1877 
1878   new_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
1879   if (new_chatwidth != prev_chatwidth)
1880     hbuf_rebuild(&wbp->bd->hbuf, new_chatwidth);
1881 }
1882 
1883 //  scr_resize()
1884 // Function called when the window is resized.
1885 // - Resize windows
1886 // - Rewrap lines in each buddy buffer
scr_resize(void)1887 void scr_resize(void)
1888 {
1889   struct dimensions dim;
1890 
1891   // First, update the global variables
1892   getmaxyx(stdscr, maxY, maxX);
1893   // scr_draw_main_window() will take care of maxY and Log_Win_Height
1894 
1895   // Make sure the cursor stays inside the window
1896   check_offset(0);
1897 
1898   // Resize windows and update panels
1899   scr_draw_main_window(FALSE);
1900 
1901   // Resize all buddy windows
1902   dim.l = CHAT_WIN_HEIGHT;
1903   dim.c = maxX - Roster_Width;
1904   if (dim.c < 1)
1905     dim.c = 1;
1906 
1907   // Resize all buffers
1908   g_hash_table_foreach(winbufhash, resize_win_buffer, &dim);
1909 
1910   // Resize/move special status buffer
1911   if (statusWindow)
1912     resize_win_buffer(NULL, statusWindow, &dim);
1913 
1914   // Update prev_chatwidth, now that all buffers have been resized
1915   prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
1916 
1917   // Refresh current buddy window
1918   if (chatmode)
1919     scr_show_buddy_window();
1920 }
1921 
1922 #ifdef USE_SIGWINCH
sigwinch_resize(void)1923 void sigwinch_resize(void)
1924 {
1925   struct winsize size;
1926   if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) != -1)
1927     resizeterm(size.ws_row, size.ws_col);
1928   scr_resize();
1929 }
1930 #endif
1931 
1932 //  scr_update_chat_status(forceupdate)
1933 // Redraw the buddy status bar.
1934 // Set forceupdate to TRUE if update_panels() must be called.
scr_update_chat_status(int forceupdate)1935 void scr_update_chat_status(int forceupdate)
1936 {
1937   unsigned short btype, isgrp, ismuc, isspe;
1938   const char *btypetext = "Unknown";
1939   const char *fullname;
1940   char *fullnameres = NULL;
1941   const char *activeres;
1942   const char *msg = NULL;
1943   char status;
1944   char *buf, *buf_locale;
1945 
1946   // Usually we need to update the bottom status line too,
1947   // at least to refresh the pending message flag.
1948   scr_update_main_status(FALSE);
1949 
1950   // Clear the line
1951   werase(chatstatusWnd);
1952 
1953   if (!current_buddy) {
1954     if (forceupdate) {
1955       update_panels();
1956     }
1957     return;
1958   }
1959 
1960   fullname = buddy_getname(BUDDATA(current_buddy));
1961   btype = buddy_gettype(BUDDATA(current_buddy));
1962 
1963   isgrp = ismuc = isspe = 0;
1964   if (btype & ROSTER_TYPE_USER) {
1965     btypetext = "Buddy";
1966   } else if (btype & ROSTER_TYPE_GROUP) {
1967     btypetext = "Group";
1968     isgrp = 1;
1969   } else if (btype & ROSTER_TYPE_AGENT) {
1970     btypetext = "Agent";
1971   } else if (btype & ROSTER_TYPE_ROOM) {
1972     btypetext = "Room";
1973     ismuc = 1;
1974   } else if (btype & ROSTER_TYPE_SPECIAL) {
1975     btypetext = "Special buffer";
1976     isspe = 1;
1977   }
1978 
1979   if (chatmode) {
1980     wprintw(chatstatusWnd, "~");
1981   } else {
1982     unsigned short bflags = buddy_getflags(BUDDATA(current_buddy));
1983     if (bflags & ROSTER_FLAG_MSG) {
1984       // There is an unread message from the current buddy
1985       wprintw(chatstatusWnd, "#");
1986     }
1987   }
1988 
1989   if (chatmode && !isgrp) {
1990     winbuf_t *win_entry;
1991     win_entry = scr_search_window(buddy_getjid(BUDDATA(current_buddy)), isspe);
1992     if (win_entry && win_entry->bd->lock)
1993       mvwprintw(chatstatusWnd, 0, 0, "*");
1994   }
1995 
1996   if (isgrp || isspe) {
1997     buf_locale = from_utf8(fullname);
1998     mvwprintw(chatstatusWnd, 0, 5, "%s: %s", btypetext, buf_locale);
1999     g_free(buf_locale);
2000     if (forceupdate) {
2001       update_panels();
2002     }
2003     return;
2004   }
2005 
2006   status = '?';
2007 
2008   activeres = buddy_getactiveresource(BUDDATA(current_buddy));
2009 
2010   if (ismuc) {
2011     if (buddy_getinsideroom(BUDDATA(current_buddy)))
2012       status = 'C';
2013     else
2014       status = 'x';
2015   } else if (xmpp_getstatus() != offline) {
2016     enum imstatus budstate;
2017     budstate = buddy_getstatus(BUDDATA(current_buddy), activeres);
2018     if (budstate < imstatus_size)
2019       status = imstatus2char[budstate];
2020   }
2021 
2022   // No status message for MUC rooms
2023   if (!ismuc) {
2024     if (activeres) {
2025       fullnameres = g_strdup_printf("%s/%s", fullname, activeres);
2026       fullname = fullnameres;
2027       msg = buddy_getstatusmsg(BUDDATA(current_buddy), activeres);
2028     } else {
2029       GSList *resources, *p_res, *p_next_res;
2030       resources = buddy_getresources(BUDDATA(current_buddy));
2031 
2032       for (p_res = resources ; p_res ; p_res = p_next_res) {
2033         p_next_res = g_slist_next(p_res);
2034         // Store the status message of the latest resource (highest priority)
2035         if (!p_next_res)
2036           msg = buddy_getstatusmsg(BUDDATA(current_buddy), p_res->data);
2037         g_free(p_res->data);
2038       }
2039       g_slist_free(resources);
2040     }
2041   } else {
2042     msg = buddy_gettopic(BUDDATA(current_buddy));
2043   }
2044 
2045   if (msg)
2046     buf = g_strdup_printf("[%c] %s: %s -- %s", status, btypetext, fullname, msg);
2047   else
2048     buf = g_strdup_printf("[%c] %s: %s", status, btypetext, fullname);
2049   replace_nl_with_dots(buf);
2050   buf_locale = from_utf8(buf);
2051   mvwprintw(chatstatusWnd, 0, 1, "%s", buf_locale);
2052   g_free(fullnameres);
2053   g_free(buf_locale);
2054   g_free(buf);
2055 
2056   // Display chatstates of the contact, if available.
2057   if (btype & ROSTER_TYPE_USER) {
2058     char eventchar = 0;
2059     guint event;
2060 
2061     // We specify active resource here, so when there is none then the resource
2062     // with the highest priority will be used.
2063     event = buddy_resource_getevents(BUDDATA(current_buddy), activeres);
2064 
2065     if (event == ROSTER_EVENT_ACTIVE)
2066       eventchar = 'A';
2067     else if (event == ROSTER_EVENT_COMPOSING)
2068       eventchar = 'C';
2069     else if (event == ROSTER_EVENT_PAUSED)
2070       eventchar = 'P';
2071     else if (event == ROSTER_EVENT_INACTIVE)
2072       eventchar = 'I';
2073     else if (event == ROSTER_EVENT_GONE)
2074       eventchar = 'G';
2075 
2076     if (eventchar)
2077       mvwprintw(chatstatusWnd, 0, maxX-3, "[%c]", eventchar);
2078   }
2079 
2080 
2081   if (forceupdate) {
2082     update_panels();
2083   }
2084 }
2085 
increment_if_buddy_not_filtered(gpointer rosterdata,void * param)2086 void increment_if_buddy_not_filtered(gpointer rosterdata, void *param)
2087 {
2088   int *p = param;
2089   if (buddylist_is_status_filtered(buddy_getstatus(rosterdata, NULL)))
2090     *p=*p+1;
2091 }
2092 
2093 //  scr_draw_roster()
2094 // Display the buddylist (not really the roster) on the screen
scr_draw_roster(void)2095 void scr_draw_roster(void)
2096 {
2097   static int offset = 0;
2098   char *name, *rline, *unread;
2099   int maxx, maxy;
2100   GList *buddy;
2101   int i, n;
2102   int rOffset;
2103   int cursor_backup;
2104   guint status, pending;
2105   enum imstatus currentstatus = xmpp_getstatus();
2106   int x_pos;
2107   int prefix_length;
2108   char space[2] = " ";
2109 
2110   // We can reset update_roster
2111   if (_update_roster == FALSE)
2112     return;
2113   _update_roster = FALSE;
2114 
2115   buddylist_build();
2116 
2117   getmaxyx(rosterWnd, maxy, maxx);
2118   maxx--;  // Last char is for vertical border
2119 
2120   cursor_backup = curs_set(0);
2121 
2122   if (!buddylist)
2123     offset = 0;
2124   else
2125     scr_update_chat_status(FALSE);
2126 
2127   // Cleanup of roster window
2128   wbkgdset(rosterWnd, get_color(COLOR_GENERAL)); // clear background color
2129   werase(rosterWnd);
2130 
2131   if (Roster_Width) {
2132     int line_x_pos = roster_win_on_right ? 0 : Roster_Width-1;
2133     // Redraw the vertical line (not very good...)
2134     for (i=0 ; i < CHAT_WIN_HEIGHT ; i++)
2135       mvwaddch(rosterWnd, i, line_x_pos, ACS_VLINE);
2136   }
2137 
2138   // Leave now if buddylist is empty or the roster is hidden
2139   if (!buddylist || !Roster_Width) {
2140     update_panels();
2141     curs_set(cursor_backup);
2142     return;
2143   }
2144 
2145   // Update offset if necessary
2146   // a) Try to show as many buddylist items as possible
2147   i = g_list_length(buddylist) - maxy;
2148   if (i < 0)
2149     i = 0;
2150   if (i < offset)
2151     offset = i;
2152   // b) Make sure the current_buddy is visible
2153   i = g_list_position(buddylist, current_buddy);
2154   if (i == -1) { // This is bad
2155     scr_LogPrint(LPRINT_NORMAL, "Doh! Can't find current selected buddy!!");
2156     curs_set(cursor_backup);
2157     return;
2158   } else if (i < offset) {
2159     offset = i;
2160   } else if (i+1 > offset + maxy) {
2161     offset = i + 1 - maxy;
2162   }
2163 
2164   if (roster_win_on_right)
2165     x_pos = 1; // 1 char offset (vertical line)
2166   else
2167     x_pos = 0;
2168 
2169   if (settings_opt_get_int("roster_no_leading_space") == 1) {
2170     space[0] = '\0';
2171     prefix_length = 6;
2172   } else {
2173     prefix_length = 7;
2174   }
2175 
2176   name = g_new0(char, 4*Roster_Width);
2177   unread = g_new0(char, Roster_Width+1);;
2178   rline = g_new0(char, 4*Roster_Width+1);
2179 
2180   buddy = buddylist;
2181   rOffset = offset;
2182 
2183   for (i=0; i<maxy && buddy; buddy = g_list_next(buddy)) {
2184     unsigned short bflags, btype;
2185     unsigned short ismsg, isgrp, ismuc, ishid, isspe;
2186     guint isurg;
2187     gchar *rline_locale;
2188 
2189     bflags = buddy_getflags(BUDDATA(buddy));
2190     btype = buddy_gettype(BUDDATA(buddy));
2191 
2192     ismsg = bflags & ROSTER_FLAG_MSG;
2193     ishid = bflags & ROSTER_FLAG_HIDE;
2194     isgrp = btype  & ROSTER_TYPE_GROUP;
2195     ismuc = btype  & ROSTER_TYPE_ROOM;
2196     isspe = btype  & ROSTER_TYPE_SPECIAL;
2197     isurg = buddy_getuiprio(BUDDATA(buddy));
2198 
2199     if (rOffset > 0) {
2200       rOffset--;
2201       continue;
2202     }
2203 
2204     status = '?';
2205     pending = ' ';
2206 
2207     if (!ismuc) {
2208       // There is currently no chat state support for MUC
2209       GSList *resources = buddy_getresources(BUDDATA(buddy));
2210       GSList *p_res;
2211 
2212       for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
2213         guint events = buddy_resource_getevents(BUDDATA(buddy),
2214                                                 p_res ? p_res->data : "");
2215         if ((events & ROSTER_EVENT_PAUSED) && pending != '+')
2216           pending = '.';
2217         if (events & ROSTER_EVENT_COMPOSING)
2218           pending = '+';
2219         g_free(p_res->data);
2220       }
2221       g_slist_free(resources);
2222     }
2223 
2224     // Display message notice if there is a message flag, but not
2225     // for unfolded groups.
2226     if (ismsg && (!isgrp || ishid)) {
2227       pending = '#';
2228     }
2229 
2230     if (ismuc) {
2231       if (buddy_getinsideroom(BUDDATA(buddy)))
2232         status = 'C';
2233       else
2234         status = 'x';
2235     } else if (currentstatus != offline) {
2236       enum imstatus budstate;
2237       budstate = buddy_getstatus(BUDDATA(buddy), NULL);
2238       if (budstate < imstatus_size)
2239         status = imstatus2char[budstate];
2240     }
2241     if (buddy == current_buddy) {
2242       if (pending == '#')
2243         wbkgdset(rosterWnd, get_color(COLOR_ROSTERSELNMSG));
2244       else
2245         wbkgdset(rosterWnd, get_color(COLOR_ROSTERSEL));
2246       // The 3 following lines aim at coloring the whole line
2247       wmove(rosterWnd, i, x_pos);
2248       for (n = 0; n < maxx; n++)
2249         waddch(rosterWnd, ' ');
2250     } else {
2251       if (pending == '#')
2252         wbkgdset(rosterWnd, get_color(COLOR_ROSTERNMSG));
2253       else {
2254         int color = get_color(COLOR_ROSTER);
2255         if ((!isspe) && (!isgrp)) { // Look for color rules
2256           GSList *head;
2257           const char *bjid = buddy_getjid(BUDDATA(buddy));
2258           for (head = rostercolrules; head; head = g_slist_next(head)) {
2259             rostercolor_t *rc = head->data;
2260             if (g_pattern_match_string(rc->compiled, bjid) &&
2261                 (!strcmp("*", rc->status) || strchr(rc->status, status))) {
2262               color = compose_color(rc->color);
2263               break;
2264             }
2265           }
2266         }
2267         wbkgdset(rosterWnd, color);
2268       }
2269     }
2270 
2271     name[0] = 0;
2272     unread[0] = 0;
2273     if (Roster_Width > prefix_length) {
2274       g_utf8_strncpy(name, buddy_getname(BUDDATA(buddy)), Roster_Width-prefix_length);
2275       if (settings_opt_get_int("roster_show_unread_count")) {
2276         guint unread_count = buddy_getunread(BUDDATA(buddy));
2277         glong name_length = g_utf8_strlen(name, 4*Roster_Width);
2278         if (unread_count > 0 && Roster_Width > prefix_length + name_length) {
2279           snprintf(unread, Roster_Width-(prefix_length+name_length)+1, " (%u)", unread_count);
2280         }
2281       }
2282     }
2283 
2284     if (pending == '#') {
2285       // Attention sign?
2286       if ((ismuc && isurg >= ui_attn_sign_prio_level_muc) ||
2287           (!ismuc && isurg >= ui_attn_sign_prio_level))
2288         pending = attention_sign();
2289     }
2290 
2291     if (isgrp) {
2292       if (ishid) {
2293         int group_count = 0;
2294         foreach_group_member(BUDDATA(buddy), increment_if_buddy_not_filtered,
2295                              &group_count);
2296         snprintf(rline, 4*Roster_Width, "%s%lc+++ %s (%i)", space, pending,
2297                  name, group_count);
2298         /* Do not display the item count if there isn't enough space */
2299         if (g_utf8_strlen(rline, 4*Roster_Width) >= Roster_Width)
2300           snprintf(rline, 4*Roster_Width, "%s%lc+++ %s", space, pending, name);
2301       }
2302       else
2303         snprintf(rline, 4*Roster_Width, "%s%lc--- %s", space, pending, name);
2304     } else if (isspe) {
2305       snprintf(rline, 4*Roster_Width, "%s%lc%s", space, pending, name);
2306     } else {
2307       char sepleft  = '[';
2308       char sepright = ']';
2309       if (btype & ROSTER_TYPE_USER) {
2310         guint subtype = buddy_getsubscription(BUDDATA(buddy));
2311         if (status == '_' && !(subtype & sub_to))
2312           status = '?';
2313         if (!(subtype & sub_from)) {
2314           sepleft  = '{';
2315           sepright = '}';
2316         }
2317       }
2318       snprintf(rline, 4*Roster_Width, "%s%lc%c%c%c %s%s",
2319                space, pending, sepleft, status, sepright, name, unread);
2320     }
2321 
2322     rline_locale = from_utf8(rline);
2323     mvwprintw(rosterWnd, i, x_pos, "%s", rline_locale);
2324     g_free(rline_locale);
2325     i++;
2326   }
2327 
2328   g_free(rline);
2329   g_free(unread);
2330   g_free(name);
2331   top_panel(inputPanel);
2332   update_panels();
2333   curs_set(cursor_backup);
2334 }
2335 
scr_update_roster(void)2336 void scr_update_roster(void)
2337 {
2338   _update_roster = TRUE;
2339 }
2340 
2341 
2342 //  scr_roster_visibility(status)
2343 // Set the roster visibility:
2344 // status=1   Show roster
2345 // status=0   Hide roster
2346 // status=-1  Toggle roster status
scr_roster_visibility(int status)2347 void scr_roster_visibility(int status)
2348 {
2349   int old_roster_status = roster_hidden;
2350 
2351   if (status > 0)
2352     roster_hidden = FALSE;
2353   else if (status == 0)
2354     roster_hidden = TRUE;
2355   else
2356     roster_hidden = !roster_hidden;
2357 
2358   if (roster_hidden != old_roster_status) {
2359     // Recalculate windows size and redraw
2360     scr_resize();
2361     redrawwin(stdscr);
2362   }
2363 }
2364 
scr_write_message(const char * bjid,const char * text,time_t timestamp,guint prefix_flags,unsigned mucnicklen,gpointer xep184)2365 static void scr_write_message(const char *bjid, const char *text,
2366                               time_t timestamp, guint prefix_flags,
2367                               unsigned mucnicklen, gpointer xep184)
2368 {
2369   char *xtext;
2370 
2371   if (!timestamp)
2372     timestamp = time(NULL);
2373   else
2374     prefix_flags |= HBB_PREFIX_DELAYED;
2375 
2376   xtext = ut_expand_tabs(text); // Expand tabs and filter out some chars
2377 
2378   scr_write_in_window(bjid, xtext, timestamp, prefix_flags, FALSE, mucnicklen,
2379                       xep184);
2380 
2381   if (xtext != (char*)text)
2382     g_free(xtext);
2383 }
2384 
2385 // If prefix is NULL, HBB_PREFIX_IN is supposed.
scr_write_incoming_message(const char * jidfrom,const char * text,time_t timestamp,guint prefix,unsigned mucnicklen)2386 void scr_write_incoming_message(const char *jidfrom, const char *text,
2387                                 time_t timestamp,
2388                                 guint prefix, unsigned mucnicklen)
2389 {
2390   if (!(prefix &
2391         ~HBB_PREFIX_NOFLAG & ~HBB_PREFIX_HLIGHT & ~HBB_PREFIX_HLIGHT_OUT &
2392         ~HBB_PREFIX_PGPCRYPT & ~HBB_PREFIX_OTRCRYPT & ~HBB_PREFIX_CARBON))
2393     prefix |= HBB_PREFIX_IN;
2394 
2395   scr_write_message(jidfrom, text, timestamp, prefix, mucnicklen, NULL);
2396 }
2397 
scr_write_outgoing_message(const char * jidto,const char * text,guint prefix,gpointer xep184)2398 void scr_write_outgoing_message(const char *jidto, const char *text,
2399                                 guint prefix, gpointer xep184)
2400 {
2401   GSList *roster_elt;
2402   roster_elt = roster_find(jidto, jidsearch,
2403                            ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
2404 
2405   scr_write_message(jidto, text,
2406                     0, prefix|HBB_PREFIX_OUT|HBB_PREFIX_HLIGHT_OUT, 0, xep184);
2407 
2408   // Show jidto's buffer unless the buddy is not in the buddylist
2409   if (roster_elt && g_list_position(buddylist, roster_elt->data) != -1)
2410     scr_show_window(jidto, FALSE);
2411 }
2412 
scr_remove_receipt_flag(const char * bjid,gconstpointer xep184)2413 void scr_remove_receipt_flag(const char *bjid, gconstpointer xep184)
2414 {
2415   winbuf_t *win_entry = scr_search_window(bjid, FALSE);
2416   if (win_entry && xep184) {
2417     hbuf_remove_receipt(win_entry->bd->hbuf, xep184);
2418     if (chatmode && (buddy_search_jid(bjid) == current_buddy))
2419       scr_update_buddy_window();
2420   }
2421 }
2422 
set_autoaway(bool setaway)2423 static inline void set_autoaway(bool setaway)
2424 {
2425   static enum imstatus oldstatus;
2426   static char *oldmsg;
2427   Autoaway = setaway;
2428 
2429   if (setaway) {
2430     const char *msg, *prevmsg;
2431     oldstatus = xmpp_getstatus();
2432     if (oldmsg) {
2433       g_free(oldmsg);
2434       oldmsg = NULL;
2435     }
2436     prevmsg = xmpp_getstatusmsg();
2437     msg = settings_opt_get("message_autoaway");
2438     if (!msg)
2439       msg = prevmsg;
2440     if (prevmsg)
2441       oldmsg = g_strdup(prevmsg);
2442     xmpp_setstatus(away, NULL, msg, FALSE);
2443   } else {
2444     // Back
2445     xmpp_setstatus(oldstatus, NULL, (oldmsg ? oldmsg : ""), FALSE);
2446     if (oldmsg) {
2447       g_free(oldmsg);
2448       oldmsg = NULL;
2449     }
2450   }
2451 }
2452 
2453 //  set_chatstate(state)
2454 // Set the current chat state (0=active, 1=composing, 2=paused)
2455 // If the chat state has changed, call xmpp_send_chatstate()
set_chatstate(int state)2456 static void set_chatstate(int state)
2457 {
2458 #ifdef XEP0085
2459   if (chatstates_disabled)
2460     return;
2461   if (!chatmode)
2462     state = 0;
2463   if (state != chatstate) {
2464     chatstate = state;
2465     if (current_buddy &&
2466         buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_USER) {
2467       guint jep_state;
2468       if (chatstate == 1) {
2469         if (chatstate_timeout_id == 0)
2470           chatstate_timeout_id = g_timeout_add_seconds(1,
2471                                                        scr_chatstates_timeout,
2472                                                        NULL);
2473         jep_state = ROSTER_EVENT_COMPOSING;
2474       }
2475       else if (chatstate == 2)
2476         jep_state = ROSTER_EVENT_PAUSED;
2477       else
2478         jep_state = ROSTER_EVENT_ACTIVE;
2479       xmpp_send_chatstate(BUDDATA(current_buddy), jep_state);
2480     }
2481     if (!chatstate)
2482       chatstate_timestamp = 0;
2483   }
2484 #endif
2485 }
2486 
2487 #ifdef XEP0085
scr_chatstates_timeout(void)2488 static gboolean scr_chatstates_timeout(void)
2489 {
2490   time_t now;
2491   time(&now);
2492   // Check if we're currently composing...
2493   if (chatstate != 1 || !chatstate_timestamp) {
2494     chatstate_timeout_id = 0;
2495     return FALSE;
2496   }
2497 
2498   // If the timeout is reached, let's change the state right now.
2499   if (now >= chatstate_timestamp + COMPOSING_TIMEOUT) {
2500     chatstate_timestamp = now;
2501     set_chatstate(2);
2502     chatstate_timeout_id = 0;
2503     return FALSE;
2504   }
2505   return TRUE;
2506 }
2507 #endif
2508 
scr_autoaway_timeout_callback(gpointer data)2509 static gboolean scr_autoaway_timeout_callback(gpointer data)
2510 {
2511   enum imstatus cur_st = xmpp_getstatus();
2512   if (cur_st != available && cur_st != freeforchat)
2513     // Some non-user-originated status changes, let's wait more.
2514     // Maybe the proper fix for that will be set global variable
2515     // "autoaway_delayed" and check that variable in postconnect
2516     // hook (afaik, only source for such status changes are
2517     // error disconnects).
2518     return TRUE;
2519   set_autoaway(TRUE);
2520   // source will be destroyed after return
2521   autoaway_source = 0;
2522   return FALSE;
2523 }
2524 
scr_reinstall_autoaway_timeout(void)2525 static void scr_reinstall_autoaway_timeout(void)
2526 {
2527   unsigned int autoaway_timeout = settings_opt_get_int("autoaway");
2528   enum imstatus cur_st = xmpp_getstatus();
2529   if (autoaway_source) {
2530     g_source_remove(autoaway_source);
2531     autoaway_source = 0;
2532   }
2533   if (autoaway_timeout && (cur_st == available || cur_st == freeforchat))
2534     autoaway_source = g_timeout_add_seconds(autoaway_timeout,
2535                                             scr_autoaway_timeout_callback,
2536                                             NULL);
2537 }
2538 
2539 // Check if we should reset autoaway timeout source
scr_check_auto_away(int activity)2540 void scr_check_auto_away(int activity)
2541 {
2542   if (Autoaway && activity) {
2543     scr_reinstall_autoaway_timeout();
2544     set_autoaway(FALSE);
2545   } else if (activity || !autoaway_source)
2546     scr_reinstall_autoaway_timeout();
2547 }
2548 
2549 //  set_current_buddy(newbuddy)
2550 // Set the current_buddy to newbuddy (if not NULL)
2551 // Lock the newbuddy, and unlock the previous current_buddy
set_current_buddy(GList * newbuddy)2552 static void set_current_buddy(GList *newbuddy)
2553 {
2554   enum imstatus prev_st = imstatus_size;
2555   /* prev_st initialized to imstatus_size, which is used as "undef" value.
2556    * We are sure prev_st will get a different status value after the
2557    * buddy_getstatus() call.
2558    */
2559 
2560   if (!current_buddy || !newbuddy)  return;
2561   if (newbuddy == current_buddy)    return;
2562 
2563   // We're moving to another buddy.  We're thus inactive wrt current_buddy.
2564   set_chatstate(0);
2565   // We don't want the chatstate to be changed again right now.
2566   lock_chatstate = TRUE;
2567 
2568   prev_st = buddy_getstatus(BUDDATA(current_buddy), NULL);
2569   buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
2570   if (chatmode) {
2571     scr_buffer_readmark(TRUE);
2572     alternate_buddy = current_buddy;
2573   }
2574   current_buddy = newbuddy;
2575   // Lock the buddy in the buddylist if we're in chat mode
2576   if (chatmode) {
2577     buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, TRUE);
2578     // Remove the readmark if it is at the end of the buffer
2579     scr_buffer_readmark(-1);
2580   }
2581   // We should rebuild the buddylist when the last selected buddy isn't
2582   // displayed anymore
2583   if (!(buddylist_get_filter() & 1<<prev_st))
2584     buddylist_defer_build();
2585   scr_update_roster();
2586 }
2587 
2588 //  scr_roster_top()
2589 // Go to the first buddy in the buddylist
scr_roster_top(void)2590 void scr_roster_top(void)
2591 {
2592   set_current_buddy(buddylist);
2593   if (chatmode) {
2594     last_activity_buddy = current_buddy;
2595     scr_show_buddy_window();
2596   }
2597 }
2598 
2599 //  scr_roster_bottom()
2600 // Go to the last buddy in the buddylist
scr_roster_bottom(void)2601 void scr_roster_bottom(void)
2602 {
2603   set_current_buddy(g_list_last(buddylist));
2604   if (chatmode) {
2605     last_activity_buddy = current_buddy;
2606     scr_show_buddy_window();
2607   }
2608 }
2609 
2610 //  scr_roster_up_down(updown, n)
2611 // Go to the nth next buddy in the buddylist
2612 // (up if updown == -1, down if updown == 1)
scr_roster_up_down(int updown,unsigned int n)2613 void scr_roster_up_down(int updown, unsigned int n)
2614 {
2615   unsigned int i;
2616   GList *new_buddy = current_buddy;
2617   GList *tmp_buddy;
2618 
2619   if (!current_buddy)
2620     return;
2621 
2622   for (i = 0; i < n; i++) {
2623     if (updown < 0)
2624       tmp_buddy = g_list_previous(new_buddy);
2625     else
2626       tmp_buddy = g_list_next(new_buddy);
2627     if (tmp_buddy)
2628       new_buddy = tmp_buddy;
2629   }
2630   if (new_buddy == current_buddy)
2631     return;
2632 
2633   set_current_buddy(new_buddy);
2634   if (chatmode) {
2635     last_activity_buddy = current_buddy;
2636     scr_show_buddy_window();
2637   }
2638 }
2639 
2640 //  scr_roster_prev_group()
2641 // Go to the previous group in the buddylist
scr_roster_prev_group(void)2642 void scr_roster_prev_group(void)
2643 {
2644   GList *bud;
2645 
2646   for (bud = current_buddy ; bud ; ) {
2647     bud = g_list_previous(bud);
2648     if (!bud)
2649       break;
2650     if (buddy_gettype(BUDDATA(bud)) & ROSTER_TYPE_GROUP) {
2651       set_current_buddy(bud);
2652       if (chatmode) {
2653         last_activity_buddy = current_buddy;
2654         scr_show_buddy_window();
2655       }
2656       break;
2657     }
2658   }
2659 }
2660 
2661 //  scr_roster_next_group()
2662 // Go to the next group in the buddylist
scr_roster_next_group(void)2663 void scr_roster_next_group(void)
2664 {
2665   GList *bud;
2666 
2667   for (bud = current_buddy ; bud ; ) {
2668     bud = g_list_next(bud);
2669     if (!bud)
2670       break;
2671     if (buddy_gettype(BUDDATA(bud)) & ROSTER_TYPE_GROUP) {
2672       set_current_buddy(bud);
2673       if (chatmode) {
2674         last_activity_buddy = current_buddy;
2675         scr_show_buddy_window();
2676       }
2677       break;
2678     }
2679   }
2680 }
2681 
2682 //  scr_roster_search(str)
2683 // Look forward for a buddy with jid/name containing str.
scr_roster_search(char * str)2684 void scr_roster_search(char *str)
2685 {
2686   set_current_buddy(buddy_search(str));
2687   if (chatmode) {
2688     last_activity_buddy = current_buddy;
2689     scr_show_buddy_window();
2690   }
2691 }
2692 
2693 //  scr_roster_jump_jid(bjid)
2694 // Jump to buddy bjid.
2695 // NOTE: With this function, the buddy is added to the roster if doesn't exist.
scr_roster_jump_jid(char * barejid)2696 void scr_roster_jump_jid(char *barejid)
2697 {
2698   GSList *roster_elt;
2699   // Look for an existing buddy
2700   roster_elt = roster_find(barejid, jidsearch,
2701                  ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
2702   // Create it if necessary
2703   if (!roster_elt)
2704     roster_elt = roster_add_user(barejid, NULL, NULL, ROSTER_TYPE_USER,
2705                                  sub_none, -1);
2706   // Set a lock to see it in the buddylist
2707   buddy_setflags(BUDDATA(roster_elt), ROSTER_FLAG_LOCK, TRUE);
2708   buddylist_defer_build();
2709   // Jump to the buddy
2710   set_current_buddy(buddy_search_jid(barejid));
2711   if (chatmode) {
2712     last_activity_buddy = current_buddy;
2713     scr_show_buddy_window();
2714   }
2715 }
2716 
2717 //  scr_roster_unread_message(next)
2718 // Go to a new message.  If next is not null, try to go to the next new
2719 // message.  If it is not possible or if next is NULL, go to the first new
2720 // message from unread_list.
scr_roster_unread_message(int next)2721 void scr_roster_unread_message(int next)
2722 {
2723   gpointer unread_ptr;
2724   gpointer refbuddata;
2725   GList *nbuddy;
2726 
2727   if (!current_buddy) return;
2728 
2729   if (next) refbuddata = BUDDATA(current_buddy);
2730   else      refbuddata = NULL;
2731 
2732   unread_ptr = unread_msg(refbuddata);
2733   if (!unread_ptr) {
2734     if (!last_activity_buddy || g_list_position(buddylist, last_activity_buddy) == -1)
2735       return;
2736     unread_ptr = BUDDATA(last_activity_buddy);
2737   }
2738 
2739   if (!(buddy_gettype(unread_ptr) & ROSTER_TYPE_SPECIAL)) {
2740     gpointer ngroup;
2741     // If buddy is in a folded group, we need to expand it
2742     ngroup = buddy_getgroup(unread_ptr);
2743     if (buddy_getflags(ngroup) & ROSTER_FLAG_HIDE) {
2744       buddy_setflags(ngroup, ROSTER_FLAG_HIDE, FALSE);
2745       buddylist_defer_build();
2746     }
2747   }
2748 
2749   buddylist_build();
2750   nbuddy = g_list_find(buddylist, unread_ptr);
2751   if (nbuddy) {
2752     set_current_buddy(nbuddy);
2753     if (chatmode) scr_show_buddy_window();
2754   } else
2755     scr_LogPrint(LPRINT_LOGNORM, "Error: nbuddy == NULL"); // should not happen
2756 }
2757 
2758 //  scr_roster_next_open_buffer()
2759 // Jump to the next open buffer (experimental XXX)
2760 // This implementation ignores the hidden entries (folded groups).
scr_roster_next_open_buffer(void)2761 void scr_roster_next_open_buffer(void)
2762 {
2763   GList *bud = current_buddy;
2764 
2765   if (!current_buddy) return;
2766 
2767   for (;;) {
2768     guint budtype;
2769     bud = g_list_next(bud);
2770     // End of list: jump to the first entry
2771     if (!bud)
2772       bud = buddylist;
2773     // Check if we're back to the initial position
2774     if (bud == current_buddy)
2775       break;
2776     // Ignore the special buffer(s), groups
2777     budtype = buddy_gettype(BUDDATA(bud));
2778     if (budtype & (ROSTER_TYPE_GROUP | ROSTER_TYPE_SPECIAL))
2779       continue;
2780 
2781     // Check if a buffer/window exists
2782     if (scr_search_window(buddy_getjid(BUDDATA(bud)), 0)) {
2783       set_current_buddy(bud);
2784       if (chatmode) {
2785         last_activity_buddy = current_buddy;
2786         scr_show_buddy_window();
2787       }
2788       break;
2789     }
2790   }
2791 }
2792 
2793 //  scr_roster_jump_alternate()
2794 // Try to jump to alternate (== previous) buddy
scr_roster_jump_alternate(void)2795 void scr_roster_jump_alternate(void)
2796 {
2797   if (!alternate_buddy || g_list_position(buddylist, alternate_buddy) == -1)
2798     return;
2799   set_current_buddy(alternate_buddy);
2800   if (chatmode) {
2801     last_activity_buddy = current_buddy;
2802     scr_show_buddy_window();
2803   }
2804 }
2805 
2806 //  scr_roster_display(filter)
2807 // Set the roster filter mask.  If filter is null/empty, the current
2808 // mask is displayed.
scr_roster_display(const char * filter)2809 void scr_roster_display(const char *filter)
2810 {
2811   guchar status;
2812   enum imstatus budstate;
2813   char strfilter[imstatus_size+1];
2814   char *psfilter;
2815 
2816   if (filter && *filter) {
2817     int show_all = (*filter == '*');
2818     status = 0;
2819     for (budstate = 0; budstate < imstatus_size-1; budstate++)
2820       if (strchr(filter, imstatus2char[budstate]) || show_all)
2821         status |= 1<<budstate;
2822     buddylist_set_filter(status);
2823     buddylist_defer_build();
2824     scr_update_roster();
2825     return;
2826   }
2827 
2828   // Display current filter
2829   psfilter = strfilter;
2830   status = buddylist_get_filter();
2831   for (budstate = 0; budstate < imstatus_size-1; budstate++)
2832     if (status & 1<<budstate)
2833       *psfilter++ = imstatus2char[budstate];
2834   *psfilter = '\0';
2835   scr_LogPrint(LPRINT_NORMAL, "Roster status filter: %s", strfilter);
2836 }
2837 
2838 //  scr_buffer_scroll_up_down()
2839 // Scroll up/down the current buddy window,
2840 // - half a screen if nblines is 0,
2841 // - up if updown == -1, down if updown == 1
scr_buffer_scroll_up_down(int updown,unsigned int nblines)2842 void scr_buffer_scroll_up_down(int updown, unsigned int nblines)
2843 {
2844   winbuf_t *win_entry;
2845   int n, nbl;
2846   GList *hbuf_top;
2847   guint isspe;
2848 
2849   // Get win_entry
2850   if (!current_buddy) return;
2851 
2852   isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
2853   win_entry  = scr_search_window(CURRENT_JID, isspe);
2854   if (!win_entry) return;
2855 
2856   if (!nblines) {
2857     // Scroll half a screen (or less)
2858     nbl = CHAT_WIN_HEIGHT/2;
2859   } else {
2860     nbl = nblines;
2861   }
2862   hbuf_top = win_entry->bd->top;
2863 
2864   if (updown == -1) {   // UP
2865     n = 0;
2866     if (!hbuf_top) {
2867       hbuf_top = g_list_last(win_entry->bd->hbuf);
2868       if (!win_entry->bd->cleared) {
2869         if (!nblines) nbl = nbl*3 - 1;
2870         else nbl += CHAT_WIN_HEIGHT - 1;
2871       } else {
2872         win_entry->bd->cleared = FALSE;
2873         n++; // We'll scroll one line less
2874       }
2875     }
2876     for ( ; hbuf_top && n < nbl && g_list_previous(hbuf_top) ; n++)
2877       hbuf_top = g_list_previous(hbuf_top);
2878     win_entry->bd->top = hbuf_top;
2879   } else {              // DOWN
2880     for (n=0 ; hbuf_top && n < nbl ; n++)
2881       hbuf_top = g_list_next(hbuf_top);
2882     win_entry->bd->top = hbuf_top;
2883     // Check if we are at the bottom
2884     for (n=0 ; hbuf_top && n < CHAT_WIN_HEIGHT-1 ; n++)
2885       hbuf_top = g_list_next(hbuf_top);
2886     if (!hbuf_top)
2887       win_entry->bd->top = NULL; // End reached
2888   }
2889 
2890   // Refresh the window
2891   scr_update_window(win_entry);
2892 
2893   // Finished :)
2894   update_panels();
2895 }
2896 
2897 //  scr_buffer_clear()
2898 // Clear the current buddy window (used for the /clear command)
scr_buffer_clear(void)2899 void scr_buffer_clear(void)
2900 {
2901   winbuf_t *win_entry;
2902   guint isspe;
2903 
2904   // Get win_entry
2905   if (!current_buddy) return;
2906   isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
2907   win_entry = scr_search_window(CURRENT_JID, isspe);
2908   if (!win_entry) return;
2909 
2910   win_entry->bd->cleared = TRUE;
2911   win_entry->bd->top = NULL;
2912 
2913   // Refresh the window
2914   scr_update_window(win_entry);
2915 
2916   // Finished :)
2917   update_panels();
2918 }
2919 
2920 //  buffer_purge()
2921 // key: winId/jid
2922 // value: winbuf_t structure
2923 // data: int, set to 1 if the buffer should be closed.
2924 // NOTE: does not work for special buffers.
2925 // Returns TRUE IFF the win_entry can be closed and freed.
buffer_purge(gpointer key,gpointer value,gpointer data)2926 static gboolean buffer_purge(gpointer key, gpointer value, gpointer data)
2927 {
2928   int *p_closebuf = data;
2929   winbuf_t *win_entry = value;
2930   gboolean retval = FALSE;
2931 
2932   // Delete the current hbuf
2933   // unless we close the buffer *and* this is a shared bd structure
2934   if (!(*p_closebuf && win_entry->bd->refcount))
2935     hbuf_free(&win_entry->bd->hbuf);
2936 
2937   if (*p_closebuf) {
2938     GSList *roster_elt;
2939     retval = TRUE;
2940     roster_elt = roster_find(key, jidsearch,
2941         ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
2942     if (roster_elt)
2943       buddy_setactiveresource(roster_elt->data, NULL);
2944     if (win_entry->bd->refcount) {
2945       win_entry->bd->refcount--;
2946     } else {
2947       g_free(win_entry->bd);
2948       win_entry->bd = NULL;
2949     }
2950   } else {
2951     win_entry->bd->cleared = FALSE;
2952     win_entry->bd->top = NULL;
2953   }
2954   return retval;
2955 }
2956 
2957 //  scr_buffer_purge(closebuf, jid)
2958 // Purge/Drop the current buddy buffer or jid's buffer if jid != NULL.
2959 // If closebuf is 1, close the buffer.
scr_buffer_purge(int closebuf,const char * jid)2960 void scr_buffer_purge(int closebuf, const char *jid)
2961 {
2962   winbuf_t *win_entry;
2963   guint isspe;
2964   const char *cjid;
2965   char *ljid = NULL;
2966   guint hold_chatmode = FALSE;
2967 
2968   if (jid) {
2969     isspe = FALSE;
2970     ljid = g_strdup(jid);
2971     mc_strtolower(ljid);
2972     cjid = ljid;
2973     // If closebuf is TRUE, it's probably better not to leave chat mode
2974     // if the change isn't related to the current buffer.
2975     if (closebuf && current_buddy) {
2976       if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL ||
2977           strcasecmp(jid, CURRENT_JID))
2978         hold_chatmode = TRUE;
2979     }
2980   } else {
2981     // Get win_entry
2982     if (!current_buddy) return;
2983     cjid = CURRENT_JID;
2984     isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
2985   }
2986   win_entry = scr_search_window(cjid, isspe);
2987   if (!win_entry) {
2988     g_free(ljid);
2989     return;
2990   }
2991 
2992   if (!isspe) {
2993     if (buffer_purge((gpointer)cjid, win_entry, &closebuf))
2994       g_hash_table_remove(winbufhash, cjid);
2995     roster_msg_setflag(cjid, FALSE, FALSE);
2996     if (closebuf && !hold_chatmode) {
2997       scr_set_chatmode(FALSE);
2998       currentWindow = NULL;
2999     }
3000   } else {
3001     // (Special buffer)
3002     // Reset the current hbuf
3003     hbuf_free(&win_entry->bd->hbuf);
3004     // Currently it can only be the status buffer
3005     statushbuf = NULL;
3006     roster_msg_setflag(SPECIAL_BUFFER_STATUS_ID, TRUE, FALSE);
3007 
3008     win_entry->bd->cleared = FALSE;
3009     win_entry->bd->top = NULL;
3010   }
3011 
3012   scr_update_roster();
3013 
3014   // Refresh the window
3015   scr_update_buddy_window();
3016 
3017   // Finished :)
3018   update_panels();
3019 
3020   g_free(ljid);
3021 }
3022 
3023 //  scr_buffer_purge_all(closebuf)
3024 // Purge all existing buffers.
3025 // If closebuf is 1, the buffers are closed.
scr_buffer_purge_all(int closebuf)3026 void scr_buffer_purge_all(int closebuf)
3027 {
3028   g_hash_table_foreach_remove(winbufhash, buffer_purge, &closebuf);
3029 
3030   if (closebuf) {
3031     scr_set_chatmode(FALSE);
3032     currentWindow = NULL;
3033   }
3034 
3035   // Refresh the window
3036   scr_update_buddy_window();
3037 
3038   // Finished :)
3039   update_panels();
3040 }
3041 
3042 //  scr_buffer_scroll_lock(lock)
3043 // Lock/unlock the current buddy buffer
3044 // lock = 1 : lock
3045 // lock = 0 : unlock
3046 // lock = -1: toggle lock status
scr_buffer_scroll_lock(int lock)3047 void scr_buffer_scroll_lock(int lock)
3048 {
3049   winbuf_t *win_entry;
3050   guint isspe;
3051 
3052   // Get win_entry
3053   if (!current_buddy) return;
3054   isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
3055   win_entry = scr_search_window(CURRENT_JID, isspe);
3056   if (!win_entry) return;
3057 
3058   if (lock == -1)
3059     lock = !win_entry->bd->lock;
3060 
3061   if (lock) {
3062     win_entry->bd->lock = TRUE;
3063   } else {
3064     win_entry->bd->lock = FALSE;
3065     if (isspe || (buddy_getflags(BUDDATA(current_buddy)) & ROSTER_FLAG_MSG))
3066       win_entry->bd->top = NULL;
3067   }
3068 
3069   // If chatmode is disabled and we're at the bottom of the buffer,
3070   // we need to set the "top" line, so we need to call scr_show_buddy_window()
3071   // at least once.  (Maybe it will cause a double refresh...)
3072   if (!chatmode && !win_entry->bd->top) {
3073     chatmode = TRUE;
3074     scr_show_buddy_window();
3075     chatmode = FALSE;
3076   }
3077 
3078   // Refresh the window
3079   scr_update_buddy_window();
3080 
3081   // Finished :)
3082   update_panels();
3083 }
3084 
3085 //  scr_buffer_readmark(action)
3086 // Update the readmark flag for the current buffer
3087 // If action = 1, set the readmark flag on the last message
3088 // If action = 0, reset the readmark flag
3089 // If action = -1, remove the readmark flag iff it is on the last line
scr_buffer_readmark(gchar action)3090 void scr_buffer_readmark(gchar action)
3091 {
3092   winbuf_t *win_entry;
3093   guint isspe;
3094   int autolock;
3095 
3096   // Get win_entry
3097   if (!current_buddy) return;
3098   isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
3099   if (isspe) return; // Maybe not necessary
3100   win_entry = scr_search_window(CURRENT_JID, isspe);
3101   if (!win_entry) return;
3102 
3103   autolock = settings_opt_get_int("buffer_smart_scrolling");
3104   if (!win_entry->bd->lock || autolock) {
3105     if (action >= 0)
3106       hbuf_set_readmark(win_entry->bd->hbuf, action);
3107     else
3108       hbuf_remove_trailing_readmark(win_entry->bd->hbuf);
3109   }
3110 }
3111 
3112 
3113 //  scr_buffer_top_bottom()
3114 // Jump to the head/tail of the current buddy window
3115 // (top if topbottom == -1, bottom topbottom == 1)
scr_buffer_top_bottom(int topbottom)3116 void scr_buffer_top_bottom(int topbottom)
3117 {
3118   winbuf_t *win_entry;
3119   guint isspe;
3120 
3121   // Get win_entry
3122   if (!current_buddy) return;
3123   isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
3124   win_entry = scr_search_window(CURRENT_JID, isspe);
3125   if (!win_entry) return;
3126 
3127   win_entry->bd->cleared = FALSE;
3128   if (topbottom == 1)
3129     win_entry->bd->top = NULL;
3130   else
3131     win_entry->bd->top = g_list_first(win_entry->bd->hbuf);
3132 
3133   // Refresh the window
3134   scr_update_window(win_entry);
3135 
3136   // Finished :)
3137   update_panels();
3138 }
3139 
3140 //  scr_buffer_search(direction, text)
3141 // Jump to the next line containing text
3142 // (backward search if direction == -1, forward if topbottom == 1)
scr_buffer_search(int direction,const char * text)3143 void scr_buffer_search(int direction, const char *text)
3144 {
3145   winbuf_t *win_entry;
3146   GList *current_line, *search_res;
3147   guint isspe;
3148 
3149   // Get win_entry
3150   if (!current_buddy) return;
3151   isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
3152   win_entry = scr_search_window(CURRENT_JID, isspe);
3153   if (!win_entry) return;
3154 
3155   if (win_entry->bd->top)
3156     current_line = win_entry->bd->top;
3157   else
3158     current_line = g_list_last(win_entry->bd->hbuf);
3159 
3160   search_res = hbuf_search(current_line, direction, text);
3161 
3162   if (search_res) {
3163     win_entry->bd->cleared = FALSE;
3164     win_entry->bd->top = search_res;
3165 
3166     // Refresh the window
3167     scr_update_window(win_entry);
3168 
3169     // Finished :)
3170     update_panels();
3171   } else
3172     scr_LogPrint(LPRINT_NORMAL, "Search string not found.");
3173 }
3174 
3175 //  scr_buffer_percent(n)
3176 // Jump to the specified position in the buffer, in %
scr_buffer_percent(int pc)3177 void scr_buffer_percent(int pc)
3178 {
3179   winbuf_t *win_entry;
3180   GList *search_res;
3181   guint isspe;
3182 
3183   // Get win_entry
3184   if (!current_buddy) return;
3185   isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
3186   win_entry = scr_search_window(CURRENT_JID, isspe);
3187   if (!win_entry) return;
3188 
3189   if (pc < 0 || pc > 100) {
3190     scr_LogPrint(LPRINT_NORMAL, "Bad %% value");
3191     return;
3192   }
3193 
3194   search_res = hbuf_jump_percent(win_entry->bd->hbuf, pc);
3195 
3196   win_entry->bd->cleared = FALSE;
3197   win_entry->bd->top = search_res;
3198 
3199   // Refresh the window
3200   scr_update_window(win_entry);
3201 
3202   // Finished :)
3203   update_panels();
3204 }
3205 
3206 //  scr_buffer_date(t)
3207 // Jump to the first line after date t in the buffer
3208 // t is a date in seconds since `00:00:00 1970-01-01 UTC'
scr_buffer_date(time_t t)3209 void scr_buffer_date(time_t t)
3210 {
3211   winbuf_t *win_entry;
3212   GList *search_res;
3213   guint isspe;
3214 
3215   // Get win_entry
3216   if (!current_buddy) return;
3217   isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
3218   win_entry = scr_search_window(CURRENT_JID, isspe);
3219   if (!win_entry) return;
3220 
3221   search_res = hbuf_jump_date(win_entry->bd->hbuf, t);
3222 
3223   win_entry->bd->cleared = FALSE;
3224   win_entry->bd->top = search_res;
3225 
3226   if (!search_res)
3227     scr_log_print(LPRINT_NORMAL, "Date not found.");
3228 
3229   // Refresh the window
3230   scr_update_window(win_entry);
3231 
3232   // Finished :)
3233   update_panels();
3234 }
3235 
3236 //  scr_buffer_jump_readmark()
3237 // Jump to the buffer readmark, if there's one
scr_buffer_jump_readmark(void)3238 void scr_buffer_jump_readmark(void)
3239 {
3240   winbuf_t *win_entry;
3241   GList *search_res;
3242   guint isspe;
3243 
3244   // Get win_entry
3245   if (!current_buddy) return;
3246   isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
3247   if (isspe) return;
3248   win_entry = scr_search_window(CURRENT_JID, isspe);
3249   if (!win_entry) return;
3250 
3251   search_res = hbuf_jump_readmark(win_entry->bd->hbuf);
3252 
3253   if (!search_res) {
3254     scr_log_print(LPRINT_NORMAL, "Readmark not found.");
3255     return;
3256   }
3257 
3258   win_entry->bd->cleared = FALSE;
3259   win_entry->bd->top = search_res;
3260 
3261   // Refresh the window
3262   scr_update_window(win_entry);
3263 
3264   // Finished :)
3265   update_panels();
3266 }
3267 
3268 //  scr_buffer_dump(filename)
3269 // Dump the current buffer content to the specified file.
scr_buffer_dump(const char * file)3270 void scr_buffer_dump(const char *file)
3271 {
3272   char *extfname;
3273 
3274   if (!currentWindow) {
3275     scr_LogPrint(LPRINT_NORMAL, "No current buffer!");
3276     return;
3277   }
3278 
3279   if (!file || !*file) {
3280     scr_LogPrint(LPRINT_NORMAL, "Missing parameter (file name)!");
3281     return;
3282   }
3283 
3284   extfname = expand_filename(file);
3285   hbuf_dump_to_file(currentWindow->bd->hbuf, extfname);
3286   g_free(extfname);
3287 }
3288 
3289 //  buffer_list()
3290 // key: winId/jid
3291 // value: winbuf_t structure
3292 // data: none.
buffer_list(gpointer key,gpointer value,gpointer data)3293 static void buffer_list(gpointer key, gpointer value, gpointer data)
3294 {
3295   GList *head;
3296   winbuf_t *win_entry = value;
3297 
3298   head = g_list_first(win_entry->bd->hbuf);
3299 
3300   scr_LogPrint(LPRINT_NORMAL, " %s  (%u/%u)", (const char *) key,
3301                g_list_length(head), hbuf_get_blocks_number(head));
3302 }
3303 
scr_buffer_list(void)3304 void scr_buffer_list(void)
3305 {
3306   scr_LogPrint(LPRINT_NORMAL, "Buffer list:");
3307   buffer_list("[status]", statusWindow, NULL);
3308   g_hash_table_foreach(winbufhash, buffer_list, NULL);
3309   scr_LogPrint(LPRINT_NORMAL, "End of buffer list.");
3310   scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
3311   scr_setattentionflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE,
3312                                  ROSTER_UI_PRIO_STATUS_WIN_MESSAGE, prio_max);
3313 }
3314 
3315 //  scr_set_chatmode()
3316 // Public function to (un)set chatmode...
scr_set_chatmode(int enable)3317 void scr_set_chatmode(int enable)
3318 {
3319   gboolean enter_chatmode = enable && chatmode == FALSE;
3320   chatmode = enable;
3321   scr_update_chat_status(TRUE);
3322   if (enter_chatmode)
3323     scr_buffer_readmark(-1);
3324 }
3325 
3326 //  scr_get_chatmode()
3327 // Public function to get chatmode state.
scr_get_chatmode(void)3328 int scr_get_chatmode(void)
3329 {
3330   return chatmode;
3331 }
3332 
3333 //  scr_get_multimode()
3334 // Public function to get multimode status...
scr_get_multimode(void)3335 int scr_get_multimode(void)
3336 {
3337   return multimode;
3338 }
3339 
3340 //  scr_setmsgflag_if_needed(jid)
3341 // Set the message flag unless we're already in the jid buffer window
scr_setmsgflag_if_needed(const char * bjid,int special)3342 void scr_setmsgflag_if_needed(const char *bjid, int special)
3343 {
3344   const char *current_id;
3345   bool iscurrentlocked = FALSE;
3346 
3347   if (!bjid)
3348     return;
3349 
3350   if (current_buddy) {
3351     if (special)
3352       current_id = buddy_getname(BUDDATA(current_buddy));
3353     else
3354       current_id = buddy_getjid(BUDDATA(current_buddy));
3355     if (current_id) {
3356       winbuf_t *win_entry = scr_search_window(current_id, special);
3357       if (!win_entry) return;
3358       iscurrentlocked = win_entry->bd->lock;
3359     }
3360   } else {
3361     current_id = NULL;
3362   }
3363   if (!chatmode || !current_id || strcmp(bjid, current_id) || iscurrentlocked) {
3364     roster_msg_setflag(bjid, special, TRUE);
3365     scr_update_roster();
3366   }
3367 }
3368 
3369 //  scr_setattentionflag_if_needed(bare_jid, special, value, action)
3370 // Set the attention flag unless we're already in the jid buffer window
3371 // TODO: avoid code duplication with scr_setmsgflag_if_needed()
scr_setattentionflag_if_needed(const char * bjid,int special,guint value,enum setuiprio_ops action)3372 void scr_setattentionflag_if_needed(const char *bjid, int special,
3373                                     guint value, enum setuiprio_ops action)
3374 {
3375   const char *current_id;
3376   winbuf_t *wb;
3377   bool iscurrentlocked = FALSE;
3378 
3379   if (!bjid)
3380     return;
3381 
3382   wb = scr_search_window(bjid, special);
3383   if (!wb)
3384     return;
3385 
3386   if (current_buddy) {
3387     if (special)
3388       current_id = buddy_getname(BUDDATA(current_buddy));
3389     else
3390       current_id = buddy_getjid(BUDDATA(current_buddy));
3391     if (current_id) {
3392       winbuf_t *win_entry = scr_search_window(current_id, special);
3393       if (!win_entry) return;
3394       iscurrentlocked = win_entry->bd->lock;
3395     }
3396   } else {
3397     current_id = NULL;
3398   }
3399 
3400   if (!chatmode || !current_id || strcmp(bjid, current_id) || iscurrentlocked) {
3401     roster_setuiprio(bjid, special, value, action);
3402     scr_update_roster();
3403   }
3404 }
3405 
3406 //  scr_set_multimode()
3407 // Public function to (un)set multimode...
3408 // Convention:
3409 //  0 = disabled / 1 = multimode / 2 = multimode verbatim (commands disabled)
scr_set_multimode(int enable,char * subject)3410 void scr_set_multimode(int enable, char *subject)
3411 {
3412   g_free(multiline);
3413   multiline = NULL;
3414 
3415   g_free(multimode_subj);
3416   if (enable && subject)
3417     multimode_subj = g_strdup(subject);
3418   else
3419     multimode_subj = NULL;
3420 
3421   multimode = enable;
3422 }
3423 
3424 //  scr_get_multiline()
3425 // Public function to get the current multi-line.
scr_get_multiline(void)3426 const char *scr_get_multiline(void)
3427 {
3428   if (multimode && multiline)
3429     return multiline;
3430   return NULL;
3431 }
3432 
3433 //  scr_get_multimode_subj()
3434 // Public function to get the multi-line subject, if any.
scr_get_multimode_subj(void)3435 const char *scr_get_multimode_subj(void)
3436 {
3437   if (multimode)
3438     return multimode_subj;
3439   return NULL;
3440 }
3441 
3442 //  scr_append_multiline(line)
3443 // Public function to append a line to the current multi-line message.
3444 // Skip empty leading lines.
scr_append_multiline(const char * line)3445 void scr_append_multiline(const char *line)
3446 {
3447   static int num;
3448 
3449   if (!multimode) {
3450     scr_LogPrint(LPRINT_NORMAL, "Error: Not in multi-line message mode!");
3451     return;
3452   }
3453   if (multiline) {
3454     int len = strlen(multiline)+strlen(line)+2;
3455     if (len >= HBB_BLOCKSIZE - 1) {
3456       // We don't handle single messages with size > HBB_BLOCKSIZE
3457       // (see hbuf)
3458       scr_LogPrint(LPRINT_NORMAL, "Your multi-line message is too big, "
3459                    "this line has not been added.");
3460       scr_LogPrint(LPRINT_NORMAL, "Please send this part now...");
3461       return;
3462     }
3463     if (num >= MULTILINE_MAX_LINE_NUMBER) {
3464       // We don't allow too many lines; however the maximum is arbitrary
3465       // (It should be < 1000 yet)
3466       scr_LogPrint(LPRINT_NORMAL, "Your message has too many lines, "
3467                    "this one has not been added.");
3468       scr_LogPrint(LPRINT_NORMAL, "Please send this part now...");
3469       return;
3470     }
3471     multiline = g_renew(char, multiline, len);
3472     strcat(multiline, "\n");
3473     strcat(multiline, line);
3474     num++;
3475   } else {
3476     // First message line (we skip leading empty lines)
3477     num = 0;
3478     if (line[0]) {
3479       multiline = g_strdup(line);
3480       num++;
3481     } else
3482       return;
3483   }
3484   scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
3485                "Multi-line mode: line #%d added  [%.25s...", num, line);
3486 }
3487 
3488 //  scr_cmdhisto_addline()
3489 // Add a line to the inputLine history
scr_cmdhisto_addline(char * line)3490 static void scr_cmdhisto_addline(char *line)
3491 {
3492   int max_histo_lines;
3493 
3494   if (!line || !*line)
3495     return;
3496 
3497   max_histo_lines = settings_opt_get_int("cmdhistory_lines");
3498 
3499   if (max_histo_lines < 0)
3500     max_histo_lines = 1;
3501 
3502   if (max_histo_lines)
3503     while (cmdhisto_nblines >= (guint)max_histo_lines) {
3504       if (cmdhisto_cur && cmdhisto_cur == cmdhisto)
3505         break;
3506       g_free(cmdhisto->data);
3507       cmdhisto = g_list_delete_link(cmdhisto, cmdhisto);
3508       cmdhisto_nblines--;
3509     }
3510 
3511   cmdhisto = g_list_append(cmdhisto, g_strdup(line));
3512   cmdhisto_nblines++;
3513 }
3514 
3515 //  scr_cmdhisto_reset()
3516 // Reset the inputLine history
scr_cmdhisto_reset(void)3517 static void scr_cmdhisto_reset(void)
3518 {
3519   while (cmdhisto_nblines) {
3520     g_free(cmdhisto->data);
3521     cmdhisto = g_list_delete_link(cmdhisto, cmdhisto);
3522     cmdhisto_nblines--;
3523   }
3524   cmdhisto_backup[0] = 0;
3525   cmdhisto_cur = NULL;
3526 }
3527 
3528 //  scr_cmdhisto_prev()
3529 // Look for previous line beginning w/ the given mask in the inputLine history
3530 // Returns NULL if none found
scr_cmdhisto_prev(char * mask,guint len)3531 static const char *scr_cmdhisto_prev(char *mask, guint len)
3532 {
3533   GList *hl;
3534   if (!cmdhisto_cur) {
3535     hl = g_list_last(cmdhisto);
3536     if (hl) { // backup current line
3537       strncpy(cmdhisto_backup, mask, INPUTLINE_LENGTH);
3538     }
3539   } else {
3540     hl = g_list_previous(cmdhisto_cur);
3541   }
3542   while (hl) {
3543     if (!strncmp((char*)hl->data, mask, len)) {
3544       // Found a match
3545       cmdhisto_cur = hl;
3546       return (const char*)hl->data;
3547     }
3548     hl = g_list_previous(hl);
3549   }
3550   return NULL;
3551 }
3552 
3553 //  scr_cmdhisto_next()
3554 // Look for next line beginning w/ the given mask in the inputLine history
3555 // Returns NULL if none found
scr_cmdhisto_next(char * mask,guint len)3556 static const char *scr_cmdhisto_next(char *mask, guint len)
3557 {
3558   GList *hl;
3559   if (!cmdhisto_cur) return NULL;
3560   hl = cmdhisto_cur;
3561   while ((hl = g_list_next(hl)) != NULL)
3562     if (!strncmp((char*)hl->data, mask, len)) {
3563       // Found a match
3564       cmdhisto_cur = hl;
3565       return (const char*)hl->data;
3566     }
3567   // If the "backuped" line matches, we'll use it
3568   if (strncmp(cmdhisto_backup, mask, len)) return NULL; // No match
3569   cmdhisto_cur = NULL;
3570   return cmdhisto_backup;
3571 }
3572 
_strmove(char * dst,const char * src)3573 static char *_strmove(char *dst, const char *src)
3574 {
3575   char *dest = dst;
3576   while ((*dest++ = *src++) != '\0')
3577     ;
3578   return dest;
3579 }
3580 
3581 //  readline_transpose_chars()
3582 // Drag  the  character  before point forward over the character at
3583 // point, moving point forward as well.  If point is at the end  of
3584 // the  line, then this transposes the two characters before point.
readline_transpose_chars(void)3585 void readline_transpose_chars(void)
3586 {
3587   char *c1, *c2;
3588   unsigned a, b;
3589 
3590   if (ptr_inputline == inputLine) return;
3591 
3592   if (!*ptr_inputline) { // We're at EOL
3593     // If line is only 1 char long, nothing to do...
3594     if (ptr_inputline == prev_char(ptr_inputline, inputLine)) return;
3595     // Transpose the two previous characters
3596     c2 = prev_char(ptr_inputline, inputLine);
3597     c1 = prev_char(c2, inputLine);
3598     a = get_char(c1);
3599     b = get_char(c2);
3600     put_char(put_char(c1, b), a);
3601   } else {
3602     // Swap the two characters before the cursor and move right.
3603     c2 = ptr_inputline;
3604     c1 = prev_char(c2, inputLine);
3605     a = get_char(c1);
3606     b = get_char(c2);
3607     put_char(put_char(c1, b), a);
3608     check_offset(1);
3609   }
3610 }
3611 
readline_forward_kill_word(void)3612 void readline_forward_kill_word(void)
3613 {
3614   char *c, *old = ptr_inputline;
3615   int spaceallowed = 1;
3616 
3617   if (! *ptr_inputline) return;
3618 
3619   for (c = ptr_inputline ; *c ; c = next_char(c)) {
3620     if (!iswalnum(get_char(c))) {
3621       if (iswblank(get_char(c))) {
3622         if (!spaceallowed) break;
3623       } else spaceallowed = 0;
3624     } else spaceallowed = 0;
3625   }
3626 
3627   // Modify the line
3628   for (;;) {
3629     *old = *c++;
3630     if (!*old++) break;
3631   }
3632 }
3633 
3634 //  readline_backward_kill_word()
3635 // Kill the word before the cursor, in input line
readline_backward_kill_word(void)3636 void readline_backward_kill_word(void)
3637 {
3638   char *c, *old = ptr_inputline;
3639   int spaceallowed = 1;
3640 
3641   if (ptr_inputline == inputLine) return;
3642 
3643   c = prev_char(ptr_inputline, inputLine);
3644   for ( ; c > inputLine ; c = prev_char(c, inputLine)) {
3645     if (!iswalnum(get_char(c))) {
3646       if (iswblank(get_char(c))) {
3647         if (!spaceallowed) break;
3648       } else spaceallowed = 0;
3649     } else spaceallowed = 0;
3650   }
3651 
3652   if (c == inputLine && *c == COMMAND_CHAR && old != c+1) {
3653       c = next_char(c);
3654   } else if (c != inputLine || (iswblank(get_char(c)) && !spaceallowed)) {
3655     if ((c < prev_char(ptr_inputline, inputLine)) && (!iswalnum(get_char(c))))
3656       c = next_char(c);
3657   }
3658 
3659   // Modify the line
3660   ptr_inputline = c;
3661   _strmove(ptr_inputline, old);
3662   check_offset(-1);
3663 }
3664 
3665 //  readline_backward_word()
3666 // Move back to the start of the current or previous word
readline_backward_word(void)3667 void readline_backward_word(void)
3668 {
3669   int i = 0;
3670 
3671   if (ptr_inputline == inputLine) return;
3672 
3673   if (iswalnum(get_char(ptr_inputline)) &&
3674       !iswalnum(get_char(prev_char(ptr_inputline, inputLine))))
3675     i--;
3676 
3677   for ( ;
3678        ptr_inputline > inputLine;
3679        ptr_inputline = prev_char(ptr_inputline, inputLine)) {
3680     if (!iswalnum(get_char(ptr_inputline))) {
3681       if (i) {
3682         ptr_inputline = next_char(ptr_inputline);
3683         break;
3684       }
3685     } else i++;
3686   }
3687 
3688   check_offset(-1);
3689 }
3690 
3691 //  readline_forward_word()
3692 // Move forward to the end of the next word
readline_forward_word(void)3693 void readline_forward_word(void)
3694 {
3695   int stopsymbol_allowed = 1;
3696 
3697   while (*ptr_inputline) {
3698     if (!iswalnum(get_char(ptr_inputline))) {
3699       if (!stopsymbol_allowed) break;
3700     } else stopsymbol_allowed = 0;
3701     ptr_inputline = next_char(ptr_inputline);
3702   }
3703 
3704   check_offset(1);
3705 }
3706 
readline_updowncase_word(int upcase)3707 void readline_updowncase_word(int upcase)
3708 {
3709   int stopsymbol_allowed = 1;
3710 
3711   while (*ptr_inputline) {
3712     if (!iswalnum(get_char(ptr_inputline))) {
3713       if (!stopsymbol_allowed) break;
3714     } else {
3715       stopsymbol_allowed = 0;
3716       if (upcase)
3717         put_char(ptr_inputline, towupper(get_char(ptr_inputline)));
3718       else
3719         put_char(ptr_inputline, towlower(get_char(ptr_inputline)));
3720     }
3721     ptr_inputline = next_char(ptr_inputline);
3722   }
3723 
3724   check_offset(1);
3725 }
3726 
readline_capitalize_word(void)3727 void readline_capitalize_word(void)
3728 {
3729   int stopsymbol_allowed = 1;
3730   int upcased = 0;
3731 
3732   while (*ptr_inputline) {
3733     if (!iswalnum(get_char(ptr_inputline))) {
3734       if (!stopsymbol_allowed) break;
3735     } else {
3736       stopsymbol_allowed = 0;
3737       if (!upcased) {
3738         put_char(ptr_inputline, towupper(get_char(ptr_inputline)));
3739         upcased = 1;
3740       } else
3741         put_char(ptr_inputline, towlower(get_char(ptr_inputline)));
3742     }
3743     ptr_inputline = next_char(ptr_inputline);
3744   }
3745 
3746   check_offset(1);
3747 }
3748 
readline_backward_char(void)3749 void readline_backward_char(void)
3750 {
3751   if (ptr_inputline == (char*)&inputLine) return;
3752 
3753   ptr_inputline = prev_char(ptr_inputline, inputLine);
3754   check_offset(-1);
3755 }
3756 
readline_forward_char(void)3757 void readline_forward_char(void)
3758 {
3759   if (!*ptr_inputline) return;
3760 
3761   ptr_inputline = next_char(ptr_inputline);
3762   check_offset(1);
3763 }
3764 
3765 //  readline_accept_line(down_history)
3766 // Validate current command line.
3767 // If down_history is true, load the next history line.
readline_accept_line(int down_history)3768 void readline_accept_line(int down_history)
3769 {
3770   scr_check_auto_away(TRUE);
3771   last_activity_buddy = current_buddy;
3772   process_line(inputLine);
3773   // Add line to history
3774   scr_cmdhisto_addline(inputLine);
3775   // Reset the line
3776   ptr_inputline = inputLine;
3777   *ptr_inputline = 0;
3778   inputline_offset = 0;
3779 
3780   if (down_history) {
3781     // Use next history line instead of a blank line
3782     const char *l = scr_cmdhisto_next("", 0);
3783     if (l) strcpy(inputLine, l);
3784     // Reset backup history line
3785     cmdhisto_backup[0] = 0;
3786   } else {
3787     // Reset history line pointer
3788     cmdhisto_cur = NULL;
3789   }
3790 }
3791 
3792 //  readline_clear_history()
3793 // Clear command line history.
readline_clear_history(void)3794 void readline_clear_history(void)
3795 {
3796   scr_cmdhisto_reset();
3797 }
3798 
readline_cancel_completion(void)3799 void readline_cancel_completion(void)
3800 {
3801   scr_cancel_current_completion();
3802   scr_end_current_completion();
3803   check_offset(-1);
3804 }
3805 
readline_do_completion(gboolean fwd)3806 void readline_do_completion(gboolean fwd)
3807 {
3808   int i, n;
3809 
3810   if (multimode != 2) {
3811     // Not in verbatim multi-line mode
3812     scr_handle_tab(fwd);
3813   } else {
3814     // Verbatim multi-line mode: expand tab
3815     char tabstr[9];
3816     n = 8 - (ptr_inputline - inputLine) % 8;
3817     for (i = 0; i < n; i++)
3818       tabstr[i] = ' ';
3819     tabstr[i] = '\0';
3820     scr_insert_text(tabstr);
3821   }
3822   check_offset(0);
3823 }
3824 
readline_refresh_screen(void)3825 void readline_refresh_screen(void)
3826 {
3827   scr_check_auto_away(TRUE);
3828   keypad(inputWnd, TRUE);
3829   parse_colors();
3830   scr_resize();
3831   redrawwin(stdscr);
3832 }
3833 
readline_disable_chat_mode(guint show_roster)3834 void readline_disable_chat_mode(guint show_roster)
3835 {
3836   scr_check_auto_away(TRUE);
3837   if (chatmode) {
3838     scr_buffer_readmark(TRUE);
3839     if (vi_mode)
3840       clear_inputline();
3841   }
3842   currentWindow = NULL;
3843   chatmode = FALSE;
3844   if (current_buddy)
3845     buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
3846   if (show_roster)
3847     scr_roster_visibility(1);
3848   scr_update_chat_status(FALSE);
3849   top_panel(chatPanel);
3850   top_panel(inputPanel);
3851   update_panels();
3852 }
3853 
readline_hist_beginning_search_bwd(void)3854 void readline_hist_beginning_search_bwd(void)
3855 {
3856   const char *l = scr_cmdhisto_prev(inputLine, ptr_inputline-inputLine);
3857   if (l) strcpy(inputLine, l);
3858 }
3859 
readline_hist_beginning_search_fwd(void)3860 void readline_hist_beginning_search_fwd(void)
3861 {
3862   const char *l = scr_cmdhisto_next(inputLine, ptr_inputline-inputLine);
3863   if (l) strcpy(inputLine, l);
3864 }
3865 
readline_hist_prev(void)3866 void readline_hist_prev(void)
3867 {
3868   const char *l = scr_cmdhisto_prev(inputLine, 0);
3869   if (l) {
3870     strcpy(inputLine, l);
3871     // Set the pointer at the EOL.
3872     // We have to move it to BOL first, because we could be too far already.
3873     readline_iline_start();
3874     readline_iline_end();
3875   }
3876 }
3877 
readline_hist_next(void)3878 void readline_hist_next(void)
3879 {
3880   const char *l = scr_cmdhisto_next(inputLine, 0);
3881   if (l) {
3882     strcpy(inputLine, l);
3883     // Set the pointer at the EOL.
3884     // We have to move it to BOL first, because we could be too far already.
3885     readline_iline_start();
3886     readline_iline_end();
3887   }
3888 }
3889 
readline_backward_kill_char(void)3890 void readline_backward_kill_char(void)
3891 {
3892   char *src, *c;
3893 
3894   if (ptr_inputline == (char*)&inputLine)
3895     return;
3896 
3897   src = ptr_inputline;
3898   c = prev_char(ptr_inputline, inputLine);
3899   ptr_inputline = c;
3900   _strmove(ptr_inputline, src);
3901   check_offset(-1);
3902 }
3903 
readline_forward_kill_char(void)3904 void readline_forward_kill_char(void)
3905 {
3906   if (!*ptr_inputline)
3907     return;
3908 
3909   _strmove(ptr_inputline, next_char(ptr_inputline));
3910 }
3911 
readline_iline_start(void)3912 void readline_iline_start(void)
3913 {
3914   ptr_inputline = inputLine;
3915   inputline_offset = 0;
3916 }
3917 
readline_iline_end(void)3918 void readline_iline_end(void)
3919 {
3920   for (; *ptr_inputline; ptr_inputline++) ;
3921   check_offset(1);
3922 }
3923 
readline_backward_kill_iline(void)3924 void readline_backward_kill_iline(void)
3925 {
3926   char *dest = inputLine;
3927 
3928   if (ptr_inputline == inputLine) return;
3929 
3930   if (*dest == COMMAND_CHAR && ptr_inputline != dest+1)
3931     dest = next_char(dest);
3932 
3933   _strmove(dest, ptr_inputline);
3934   ptr_inputline = dest;
3935   inputline_offset = 0;
3936 }
3937 
readline_forward_kill_iline(void)3938 void readline_forward_kill_iline(void)
3939 {
3940   *ptr_inputline = 0;
3941 }
3942 
readline_send_multiline(void)3943 void readline_send_multiline(void)
3944 {
3945   // Validate current multi-line
3946   if (multimode)
3947     process_command(mkcmdstr("msay send"), TRUE);
3948 }
3949 
readline_insert(const char * toinsert)3950 void readline_insert(const char *toinsert)
3951 {
3952   if (!toinsert || !*toinsert) return;
3953 
3954   scr_insert_text(toinsert);
3955   check_offset(0);
3956 }
3957 
3958 //  which_row()
3959 // Tells which row our cursor is in, in the command line.
3960 // -2 -> normal text
3961 // -1 -> room: nickname completion
3962 //  0 -> command
3963 //  1 -> parameter 1 (etc.)
3964 //  If > 0, then *p_row is set to the beginning of the row
which_row(const char ** p_row)3965 static int which_row(const char **p_row)
3966 {
3967   int row = -1;
3968   char *p;
3969   int quote = FALSE;
3970 
3971   // Not a command?
3972   if ((ptr_inputline == inputLine) || (inputLine[0] != COMMAND_CHAR)) {
3973     if (!current_buddy) return -2;
3974     if (buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_ROOM) {
3975       *p_row = inputLine;
3976       return -1;
3977     }
3978     return -2;
3979   }
3980 
3981   // This is a command
3982   row = 0;
3983   for (p = inputLine ; p < ptr_inputline ; p = next_char(p)) {
3984     if (quote) {
3985       if (*p == '"' && *(p-1) != '\\')
3986         quote = FALSE;
3987       continue;
3988     }
3989     if (*p == '"' && *(p-1) != '\\') {
3990       quote = TRUE;
3991     } else if (*p == ' ') {
3992       if (*(p-1) != ' ')
3993         row++;
3994       *p_row = p+1;
3995     }
3996   }
3997   return row;
3998 }
3999 
4000 //  scr_insert_text()
4001 // Insert the given text at the current cursor position.
4002 // The cursor is moved.  We don't check if the cursor still is in the screen
4003 // after, the caller should do that.
scr_insert_text(const char * text)4004 static void scr_insert_text(const char *text)
4005 {
4006   char tmpLine[INPUTLINE_LENGTH+1];
4007   int len = strlen(text);
4008   // Check the line isn't too long
4009   if (strlen(inputLine) + len >= INPUTLINE_LENGTH) {
4010     scr_LogPrint(LPRINT_LOGNORM, "Cannot insert text, line too long.");
4011     return;
4012   }
4013 
4014   strcpy(tmpLine, ptr_inputline);
4015   strcpy(ptr_inputline, text);
4016   ptr_inputline += len;
4017   strcpy(ptr_inputline, tmpLine);
4018 }
4019 
4020 static void scr_cancel_current_completion(void);
4021 
4022 //  scr_handle_tab()
4023 // Function called when tab is pressed.
4024 // Initiate or continue a completion...
4025 // If fwd is false, a backward-completion is requested.
scr_handle_tab(gboolean fwd)4026 static void scr_handle_tab(gboolean fwd)
4027 {
4028   int nrow;
4029   const char *row;
4030   const char *cchar;
4031   guint compl_categ;
4032 
4033   row = inputLine; // (Kills a GCC warning)
4034   nrow = which_row(&row);
4035 
4036   // a) No completion if no leading slash ('cause not a command),
4037   //    unless this is a room (then, it is a nickname completion)
4038   // b) We can't have more than 2 parameters (we use 2 flags)
4039   if ((nrow == -2) || (nrow == 3 && !completion_started) || nrow > 3)
4040     return;
4041 
4042   if (nrow == 0) {          // Command completion
4043     row = next_char(inputLine);
4044     compl_categ = COMPL_CMD;
4045   } else if (nrow == -1) {  // Nickname completion
4046     compl_categ = COMPL_RESOURCE;
4047   } else {                  // Other completion, depending on the command
4048     int alias = FALSE;
4049     cmd *com;
4050     char *xpline = expandalias(inputLine);
4051     com = cmd_get(xpline);
4052     if (xpline != inputLine) {
4053       // This is an alias, so we can't complete rows > 0
4054       alias = TRUE;
4055       g_free(xpline);
4056     }
4057     if ((!com && (!alias || !completion_started)) || !row) {
4058       scr_LogPrint(LPRINT_NORMAL, "I cannot complete that...");
4059       return;
4060     }
4061     if (!alias)
4062       compl_categ = com->completion_flags[nrow-1];
4063     else
4064       compl_categ = 0;
4065   }
4066 
4067   if (!completion_started) {
4068     guint dynlist;
4069     GSList *list;
4070 
4071     if (!compl_categ)
4072       return; // Nothing to complete
4073 
4074     list = compl_get_category_list(compl_categ, &dynlist);
4075     if (list) {
4076       guint n;
4077       char *prefix = g_strndup(row, ptr_inputline-row);
4078       // Init completion
4079       n = new_completion(prefix, list,
4080                          (compl_categ == COMPL_RESOURCE ?
4081                           settings_opt_get("muc_completion_suffix") : NULL));
4082       g_free(prefix);
4083       if (n == 0 && nrow == -1) {
4084         // This is a MUC room and we can't complete from the beginning of the
4085         // line.  Let's try a bit harder and complete the current word.
4086         row = prev_char(ptr_inputline, inputLine);
4087         while (row >= inputLine) {
4088           if (iswspace(get_char(row)) || get_char(row) == '(') {
4089               row = next_char((char*)row);
4090               break;
4091           }
4092           if (row == inputLine)
4093             break;
4094           row = prev_char((char*)row, inputLine);
4095         }
4096         // There's no need to try again if row == inputLine
4097         if (row > inputLine) {
4098           prefix = g_strndup(row, ptr_inputline-row);
4099           new_completion(prefix, list, NULL);
4100           g_free(prefix);
4101         }
4102       }
4103       // Free the list if it's a dynamic one
4104       if (dynlist) {
4105         GSList *slp;
4106         for (slp = list; slp; slp = g_slist_next(slp))
4107           g_free(slp->data);
4108         g_slist_free(list);
4109       }
4110       // Now complete
4111       cchar = complete(fwd);
4112       if (cchar)
4113         scr_insert_text(cchar);
4114       completion_started = TRUE;
4115     }
4116   } else {      // Completion already initialized
4117     scr_cancel_current_completion();
4118     // Now complete again
4119     cchar = complete(fwd);
4120     if (cchar)
4121       scr_insert_text(cchar);
4122   }
4123 }
4124 
scr_cancel_current_completion(void)4125 static void scr_cancel_current_completion(void)
4126 {
4127   char *c;
4128   char *src = ptr_inputline;
4129   guint back = cancel_completion();
4130   guint i;
4131   // Remove $back chars
4132   for (i = 0; i < back; i++)
4133     ptr_inputline = prev_char(ptr_inputline, inputLine);
4134   c = ptr_inputline;
4135   for ( ; *src ; )
4136     *c++ = *src++;
4137   *c = 0;
4138 }
4139 
scr_end_current_completion(void)4140 static void scr_end_current_completion(void)
4141 {
4142   done_completion();
4143   completion_started = FALSE;
4144 }
4145 
4146 //  check_offset(int direction)
4147 // Check inputline_offset value, and make sure the cursor is inside the
4148 // screen.
check_offset(int direction)4149 static inline void check_offset(int direction)
4150 {
4151   int i;
4152   char *c = &inputLine[inputline_offset];
4153   // Left side
4154   if (inputline_offset && direction <= 0) {
4155     while (ptr_inputline <= c) {
4156       for (i = 0; i < 5; i++)
4157         c = prev_char(c, inputLine);
4158       if (c == inputLine)
4159         break;
4160     }
4161   }
4162   // Right side
4163   if (direction >= 0) {
4164     int delta = get_char_width(c);
4165     while (ptr_inputline > c) {
4166       c = next_char(c);
4167       delta += get_char_width(c);
4168     }
4169     c = &inputLine[inputline_offset];
4170     while (delta >= maxX) {
4171       for (i = 0; i < 5; i++) {
4172         delta -= get_char_width(c);
4173         c = next_char(c);
4174       }
4175     }
4176   }
4177   inputline_offset = c - inputLine;
4178 }
4179 
4180 #if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
4181 // prints inputLine with underlined words when misspelled
print_checked_line(void)4182 static inline void print_checked_line(void)
4183 {
4184   char *wprint_char_fmt = "%c";
4185   int point;
4186   int nrchar = maxX;
4187   char *ptrCur = inputLine + inputline_offset;
4188 
4189 #ifdef UNICODE
4190   // We need this to display a single UTF-8 char... Any better solution?
4191   if (utf8_mode)
4192     wprint_char_fmt = "%lc";
4193 #endif
4194 
4195   wmove(inputWnd, 0, 0); // problem with backspace
4196 
4197   while (*ptrCur && nrchar-- > 0) {
4198     point = ptrCur - inputLine;
4199     if (maskLine[point])
4200       wattrset(inputWnd, A_UNDERLINE);
4201     wprintw(inputWnd, wprint_char_fmt, get_char(ptrCur));
4202     wattrset(inputWnd, A_NORMAL);
4203     ptrCur = next_char(ptrCur);
4204   }
4205 }
4206 #endif
4207 
refresh_inputline(void)4208 static inline void refresh_inputline(void)
4209 {
4210 #if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
4211   if (settings_opt_get_int("spell_enable") && (chatmode || !vi_mode)) {
4212     memset(maskLine, 0, INPUTLINE_LENGTH+1);
4213     spellcheck(inputLine, maskLine);
4214   }
4215   print_checked_line();
4216   wclrtoeol(inputWnd);
4217   if (*ptr_inputline) {
4218     // hack to set cursor pos. Characters can have different width,
4219     // so I know of no better way.
4220     char c = *ptr_inputline;
4221     *ptr_inputline = 0;
4222     print_checked_line();
4223     *ptr_inputline = c;
4224   }
4225 #else
4226   mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset);
4227   wclrtoeol(inputWnd);
4228   if (*ptr_inputline) {
4229     // hack to set cursor pos. Characters can have different width,
4230     // so I know of no better way.
4231     char c = *ptr_inputline;
4232     *ptr_inputline = 0;
4233     mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset);
4234     *ptr_inputline = c;
4235   }
4236 #endif
4237 }
4238 
scr_handle_CtrlC(void)4239 void scr_handle_CtrlC(void)
4240 {
4241   if (!Curses) return;
4242   // Leave multi-line mode
4243   process_command(mkcmdstr("msay abort"), TRUE);
4244   // Same as Ctrl-g, now
4245   scr_cancel_current_completion();
4246   scr_end_current_completion();
4247   check_offset(-1);
4248   refresh_inputline();
4249 }
4250 
add_keyseq(char * seqstr,guint mkeycode,gint value)4251 static void add_keyseq(char *seqstr, guint mkeycode, gint value)
4252 {
4253   keyseq_t *ks;
4254 
4255   // Let's make sure the length is correct
4256   if (strlen(seqstr) > MAX_KEYSEQ_LENGTH) {
4257     scr_LogPrint(LPRINT_LOGNORM, "add_keyseq(): key sequence is too long!");
4258     return;
4259   }
4260 
4261   ks = g_new0(keyseq_t, 1);
4262   ks->seqstr = g_strdup(seqstr);
4263   ks->mkeycode = mkeycode;
4264   ks->value = value;
4265   keyseqlist = g_slist_append(keyseqlist, ks);
4266 }
4267 
4268 //  match_keyseq(iseq, &ret)
4269 // Check if "iseq" is a known key escape sequence.
4270 // Return value:
4271 // -1  if "seq" matches no known sequence
4272 //  0  if "seq" could match 1 or more known sequences
4273 // >0  if "seq" matches a key sequence; the mkey code is returned
4274 //     and *ret is set to the matching keyseq structure.
match_keyseq(int * iseq,keyseq_t ** ret)4275 static inline gint match_keyseq(int *iseq, keyseq_t **ret)
4276 {
4277   GSList *ksl;
4278   keyseq_t *ksp;
4279   char *p, c;
4280   int *i;
4281   int needmore = FALSE;
4282 
4283   for (ksl = keyseqlist; ksl; ksl = g_slist_next(ksl)) {
4284     ksp = ksl->data;
4285     p = ksp->seqstr;
4286     i = iseq;
4287     while (1) {
4288       c = (unsigned char)*i;
4289       if (!*p && !c) { // Match
4290         (*ret) = ksp;
4291         return ksp->mkeycode;
4292       }
4293       if (!c) {
4294         // iseq is too short
4295         needmore = TRUE;
4296         break;
4297       } else if (!*p || c != *p) {
4298         // This isn't a match
4299         break;
4300       }
4301       p++; i++;
4302     }
4303   }
4304 
4305   if (needmore)
4306     return 0;
4307   return -1;
4308 }
4309 
match_utf8_keyseq(int * iseq)4310 static inline int match_utf8_keyseq(int *iseq)
4311 {
4312   int *strp = iseq;
4313   unsigned c = *strp++;
4314   unsigned mask = 0x80;
4315   int len = -1;
4316   while (c & mask) {
4317     mask >>= 1;
4318     len++;
4319   }
4320   if (len <= 0 || len > 4)
4321     return -1;
4322   c &= mask - 1;
4323   while ((*strp & 0xc0) == 0x80) {
4324     if (len-- <= 0) // can't happen
4325       return -1;
4326     c = (c << 6) | (*strp++ & 0x3f);
4327   }
4328   if (len)
4329     return 0;
4330   return c;
4331 }
4332 
scr_getch(keycode_t * kcode)4333 void scr_getch(keycode_t *kcode)
4334 {
4335   keyseq_t *mks = NULL;
4336   int  ks[MAX_KEYSEQ_LENGTH+1];
4337   int i;
4338 
4339   memset(kcode, 0, sizeof(keycode_t));
4340   memset(ks,  0, sizeof(ks));
4341 
4342   kcode->value = wgetch(inputWnd);
4343   if (utf8_mode) {
4344     bool ismeta = (kcode->value == 27);
4345 #ifdef NCURSES_MOUSE_VERSION
4346     bool ismouse = (kcode->value == KEY_MOUSE);
4347 
4348     if (ismouse) {
4349       MEVENT mouse;
4350       getmouse(&mouse);
4351       kcode->value = mouse.bstate;
4352       kcode->mcode = MKEY_MOUSE;
4353       return;
4354     } else if (ismeta)
4355 #else
4356     if (ismeta)
4357 #endif
4358       ks[0] = wgetch(inputWnd);
4359     else
4360       ks[0] = kcode->value;
4361 
4362     for (i = 0; i < MAX_KEYSEQ_LENGTH - 1; i++) {
4363       int match = match_utf8_keyseq(ks);
4364       if (match == -1)
4365         break;
4366       if (match > 0) {
4367         kcode->value = match;
4368         kcode->utf8 = 1;
4369         if (ismeta)
4370           kcode->mcode = MKEY_META;
4371         return;
4372       }
4373       ks[i + 1] = wgetch(inputWnd);
4374       if (ks[i + 1] == ERR)
4375         break;
4376     }
4377     while (i > 0)
4378       ungetch(ks[i--]);
4379     if (ismeta)
4380       ungetch(ks[0]);
4381     memset(ks,  0, sizeof(ks));
4382   }
4383   if (kcode->value != 27)
4384     return;
4385 
4386   // Check for escape key sequence
4387   for (i=0; i < MAX_KEYSEQ_LENGTH; i++) {
4388     int match;
4389     ks[i] = wgetch(inputWnd);
4390     if (ks[i] == ERR) break;
4391     match = match_keyseq(ks, &mks);
4392     if (match == -1) {
4393       // No such key sequence.  Let's increment i as it is a valid key.
4394       i++;
4395       break;
4396     }
4397     if (match > 0) {
4398       // We have a matching sequence
4399       kcode->mcode = mks->mkeycode;
4400       kcode->value = mks->value;
4401       return;
4402     }
4403   }
4404 
4405   // No match.  Let's return a meta-key.
4406   if (i > 0) {
4407     kcode->mcode = MKEY_META;
4408     kcode->value = ks[0];
4409   }
4410   if (i > 1) {
4411     // We need to push some keys back to the keyboard buffer
4412     while (i-- > 1)
4413       ungetch(ks[i]);
4414   }
4415   return;
4416 }
4417 
scr_do_update(void)4418 void scr_do_update(void)
4419 {
4420   if (colors_stalled)
4421     parse_colors();
4422   doupdate();
4423 }
4424 
bindcommand(keycode_t kcode)4425 static void bindcommand(keycode_t kcode)
4426 {
4427   gchar asciikey[16], asciicode[16];
4428   const gchar *boundcmd;
4429 
4430   if (kcode.utf8)
4431     g_snprintf(asciicode, 15, "U%d", kcode.value);
4432   else
4433     g_snprintf(asciicode, 15, "%d", kcode.value);
4434 
4435   if (!kcode.mcode || kcode.mcode == MKEY_EQUIV)
4436     g_snprintf(asciikey, 15, "%s", asciicode);
4437   else if (kcode.mcode == MKEY_META)
4438     g_snprintf(asciikey, 15, "M%s", asciicode);
4439   else if (kcode.mcode == MKEY_MOUSE)
4440     g_snprintf(asciikey, 15, "p%s", asciicode);
4441   else
4442     g_snprintf(asciikey, 15, "MK%d", kcode.mcode);
4443 
4444   boundcmd = settings_get(SETTINGS_TYPE_BINDING, asciikey);
4445 
4446   if (boundcmd) {
4447     gchar *cmdline = from_utf8(boundcmd);
4448     scr_check_auto_away(TRUE);
4449     process_command(cmdline, TRUE);
4450     g_free(cmdline);
4451     return;
4452   }
4453 
4454   scr_LogPrint(LPRINT_NORMAL, "Unknown key=%s", asciikey);
4455 #ifndef UNICODE
4456   if (utf8_mode)
4457     scr_LogPrint(LPRINT_NORMAL,
4458                  "WARNING: Compiled without full UTF-8 support!");
4459 #endif
4460 }
4461 
scr_process_vi_arrow_key(int key)4462 static void scr_process_vi_arrow_key(int key)
4463 {
4464   const char *l;
4465   char mask[INPUTLINE_LENGTH+1];
4466   size_t cmd_len = strlen(mask);
4467   size_t str_len = strlen(inputLine) - 1;
4468 
4469   strncpy(mask, mkcmdstr("roster search "), INPUTLINE_LENGTH);
4470 
4471   if  (inputLine[0] == COMMAND_CHAR) {
4472     if (!chatmode) { // Command mode
4473       if (key == KEY_UP)
4474         l = scr_cmdhisto_prev(inputLine, ptr_inputline - inputLine);
4475       else
4476         l = scr_cmdhisto_next(inputLine, ptr_inputline - inputLine);
4477       if (l)
4478         strcpy(inputLine, l);
4479 
4480       return;
4481     }
4482 
4483     // Chat mode
4484 
4485     if (cmd_len + str_len > INPUTLINE_LENGTH)
4486       return;
4487 
4488     memcpy(mask + cmd_len, inputLine + 1, str_len + 1);
4489     if (key == KEY_UP)
4490       l = scr_cmdhisto_prev(mask, ptr_inputline - inputLine + cmd_len - 1);
4491     else
4492       l = scr_cmdhisto_next(mask, ptr_inputline - inputLine + cmd_len - 1);
4493     if (l)
4494       strcpy(inputLine + 1, l + cmd_len);
4495 
4496     return;
4497   }
4498 
4499   if  (inputLine[0] == VI_SEARCH_COMMAND_CHAR)
4500     return;
4501 
4502   if (key == KEY_UP)
4503     process_command(mkcmdstr("roster up"), TRUE);
4504   else if (key == KEY_DOWN)
4505     process_command(mkcmdstr("roster down"), TRUE);
4506 }
4507 
4508 //  scr_process_key(key)
4509 // Handle the pressed key, in the command line (bottom).
scr_process_key(keycode_t kcode)4510 void scr_process_key(keycode_t kcode)
4511 {
4512   int key = kcode.value;
4513   int display_char = FALSE;
4514   int vi_search = FALSE;
4515   static int ex_or_search_mode = FALSE;
4516 
4517   lock_chatstate = FALSE;
4518 
4519   switch (kcode.mcode) {
4520     case 0:
4521         // key = kcode.value;
4522         break;
4523     case MKEY_EQUIV:
4524         // key = kcode.value;
4525         break;
4526     case MKEY_META:
4527     default:
4528         bindcommand(kcode);
4529         key = ERR; // Do not process any further
4530   }
4531 
4532   if (vi_mode && !chatmode) {
4533     int got_cmd_prefix = FALSE;
4534     int unrecognized = FALSE;
4535     static char search_cmd[INPUTLINE_LENGTH+1] = "";
4536 
4537     if (key == KEY_UP || key == KEY_DOWN) {
4538       scr_process_vi_arrow_key(key);
4539       key = ERR;    // Do not process any further
4540     } else if (ex_or_search_mode) {
4541       switch (key) {
4542         case 27:    // Escape
4543             clear_inputline();
4544             ex_or_search_mode = FALSE;
4545             break;
4546         case 9:     // Tab
4547         case 353:   // Shift-Tab
4548             if (inputLine[0] == VI_SEARCH_COMMAND_CHAR)
4549               vi_search = TRUE;
4550             break;
4551         case 13:    // Enter
4552         case 343:   // Enter on Maemo
4553             if (inputLine[0] == COMMAND_CHAR) {
4554               {
4555                 char *p = strchr(inputLine, 0);
4556 
4557                 while (*--p == ' ' && p > inputLine)
4558                   *p = 0;
4559               }
4560               if (!strcmp(inputLine, mkcmdstr("x")) ||
4561                   !strcmp(inputLine, mkcmdstr("q")) ||
4562                   !strcmp(inputLine, mkcmdstr("wq")))
4563                 strcpy(inputLine, mkcmdstr("quit"));
4564               if (isdigit((int)(unsigned char)inputLine[1]) &&
4565                   strlen(inputLine) <= 9) {
4566                 process_command(mkcmdstr("roster top"), TRUE);
4567                 memcpy(inputLine + 13, inputLine + 1, 10);
4568                 memcpy(inputLine + 1, "roster down ", 12);
4569               }
4570               inputLine[0] = COMMAND_CHAR;
4571               process_command(inputLine, TRUE);
4572               scr_cmdhisto_addline(inputLine);
4573             } else if (inputLine[0] == VI_SEARCH_COMMAND_CHAR) {
4574               size_t cmd_len;
4575               size_t str_len = strlen(inputLine) - 1;
4576 
4577               strncpy(search_cmd, mkcmdstr("roster search "), INPUTLINE_LENGTH);
4578               cmd_len = strlen(search_cmd);
4579 
4580               if (cmd_len + str_len > INPUTLINE_LENGTH)
4581                 return;
4582 
4583               memcpy(search_cmd + cmd_len, inputLine + 1, str_len + 1);
4584 
4585               process_command(search_cmd, TRUE);
4586               scr_cmdhisto_addline(search_cmd);
4587             } else if (inputLine[0] == 0) {
4588               if (buddy_gettype(BUDDATA(current_buddy)) ==
4589                   ROSTER_TYPE_GROUP)
4590                 process_command(mkcmdstr("group toggle"), TRUE);
4591               else
4592                 open_chat_window();
4593             }
4594             ex_or_search_mode = FALSE;
4595       }
4596     } else if (key >= '0' && key <= '9') {
4597       got_cmd_prefix = TRUE;
4598     } else if (key == COMMAND_CHAR || key == VI_SEARCH_COMMAND_CHAR) {
4599       ex_or_search_mode = TRUE;
4600       cmdhisto_cur = NULL;
4601     } else {
4602       switch (key) {
4603         case ' ':
4604             process_command(mkcmdstr("group toggle"), TRUE);
4605             break;
4606         case '!':
4607             {
4608               const char *bjid = buddy_getjid(BUDDATA(current_buddy));
4609 
4610               if (bjid) {
4611                 guint type = buddy_gettype(BUDDATA(current_buddy));
4612                 guint prio = buddy_getuiprio(BUDDATA(current_buddy));
4613 
4614                 if (type & ROSTER_TYPE_ROOM &&
4615                     prio < ROSTER_UI_PRIO_MUC_HL_MESSAGE) {
4616                   roster_setuiprio(bjid, FALSE,
4617                                    ROSTER_UI_PRIO_MUC_HL_MESSAGE, prio_set);
4618                   roster_msg_setflag(bjid, FALSE, TRUE);
4619                 } else if (type & ROSTER_TYPE_USER &&
4620                            prio < ROSTER_UI_PRIO_ATTENTION_MESSAGE) {
4621                   roster_setuiprio(bjid, FALSE,
4622                                    ROSTER_UI_PRIO_ATTENTION_MESSAGE, prio_set);
4623                   roster_msg_setflag(bjid, FALSE, TRUE);
4624                 } else {
4625                   roster_msg_setflag(bjid, FALSE, FALSE);
4626                 }
4627                 scr_update_roster();
4628               }
4629             }
4630             break;
4631         case '#':
4632             {
4633               const char *bjid = buddy_getjid(BUDDATA(current_buddy));
4634 
4635               if (bjid) {
4636                 unsigned short bflags = buddy_getflags(BUDDATA(current_buddy));
4637 
4638                 if (bflags & ROSTER_FLAG_MSG)
4639                   roster_msg_setflag(bjid, FALSE, FALSE);
4640                 else
4641                   roster_msg_setflag(bjid, FALSE, TRUE);
4642 
4643                 scr_update_roster();
4644               }
4645             }
4646             break;
4647         case '\'':
4648             if (inputLine[0] == '\'')
4649               process_command(mkcmdstr("roster alternate"), TRUE);
4650             else
4651               got_cmd_prefix = TRUE;
4652             break;
4653         case 'A':
4654             process_command(mkcmdstr("roster unread_first"), TRUE);
4655             break;
4656         case 'a':
4657             process_command(mkcmdstr("roster unread_next"), TRUE);
4658             break;
4659         case 'F':
4660             process_command(mkcmdstr("roster group_prev"), TRUE);
4661             break;
4662         case 'f':
4663             process_command(mkcmdstr("roster group_next"), TRUE);
4664             break;
4665         case 'G':
4666             process_command(mkcmdstr("roster bottom"), TRUE);
4667             break;
4668         case 'g':
4669             if (inputLine[0] == 'g')
4670               process_command(mkcmdstr("roster top"), TRUE);
4671             else {
4672               clear_inputline();
4673               got_cmd_prefix = TRUE;
4674             }
4675             break;
4676         case 'i':
4677             open_chat_window();
4678             break;
4679         case 'j':
4680             if (isdigit((int)(unsigned char)inputLine[0]) &&
4681                 strlen(inputLine) <= 9) {
4682               char down_cmd[24];
4683               snprintf (down_cmd, sizeof(down_cmd), "%.13s%.9s",
4684                         mkcmdstr("roster down "), inputLine);
4685               process_command(down_cmd, TRUE);
4686             } else
4687               process_command(mkcmdstr("roster down"), TRUE);
4688             break;
4689         case 'k':
4690             if (isdigit((int)(unsigned char)inputLine[0]) &&
4691                 strlen(inputLine) <= 9) {
4692               char up_cmd[24];
4693               snprintf (up_cmd, sizeof(up_cmd), "%.11s%.9s",
4694                         mkcmdstr("roster up "), inputLine);
4695               process_command(up_cmd, TRUE);
4696             } else
4697               process_command(mkcmdstr("roster up "), TRUE);
4698             break;
4699         case 'M':
4700             if (inputLine[0] == 'z') {
4701               GSList *groups = compl_list(ROSTER_TYPE_GROUP);
4702               GSList *g;
4703 
4704               for (g = groups; g; g = g_slist_next(g)) {
4705                 char fold_cmd[256];
4706                 size_t cmd_len, grp_len;
4707 
4708                 strncpy(fold_cmd, mkcmdstr("group fold "), 32);
4709                 cmd_len = strlen(fold_cmd);
4710                 grp_len = strlen(g->data);
4711 
4712                 if (cmd_len + grp_len + 1 > sizeof(fold_cmd))
4713                   continue;
4714                 memcpy(fold_cmd + cmd_len, g->data, grp_len + 1);
4715                 process_command(fold_cmd, TRUE);
4716                 g_free(g->data);
4717               }
4718               g_slist_free(groups);
4719             } else
4720               unrecognized = TRUE;
4721             break;
4722         case 'n':
4723             process_command(search_cmd, TRUE);
4724             break;
4725         case 'O':
4726             process_command(mkcmdstr("roster unread_first"), TRUE);
4727             open_chat_window();
4728             break;
4729         case 'o':
4730             process_command(mkcmdstr("roster unread_next"), TRUE);
4731             open_chat_window();
4732             break;
4733         case 'R':
4734             if (inputLine[0] == 'z') {
4735               GSList *groups = compl_list(ROSTER_TYPE_GROUP);
4736               GSList *g;
4737 
4738               for (g = groups; g; g = g_slist_next(g)) {
4739                 char fold_cmd[256];
4740                 size_t cmd_len, grp_len;
4741 
4742                 strncpy(fold_cmd, mkcmdstr("group unfold "), 32);
4743                 cmd_len = strlen(fold_cmd);
4744                 grp_len = strlen(g->data);
4745 
4746                 if (cmd_len + grp_len + 1 > sizeof(fold_cmd))
4747                   continue;
4748                 memcpy(fold_cmd + cmd_len, g->data, grp_len + 1);
4749                 process_command(fold_cmd, TRUE);
4750                 g_free(g->data);
4751               }
4752               g_slist_free(groups);
4753             } else
4754               unrecognized = TRUE;
4755             break;
4756         case 'Z':
4757             if (inputLine[0] == 'Z')
4758               process_command(mkcmdstr("quit"), TRUE);
4759             else {
4760               clear_inputline();
4761               got_cmd_prefix = TRUE;
4762             }
4763             break;
4764         case 'z':
4765             clear_inputline();
4766             got_cmd_prefix = TRUE;
4767             break;
4768         case 13:    // Enter
4769         case 343:   // Enter on Maemo
4770             if (inputLine[0] == 0) {
4771               if (buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_GROUP)
4772                 process_command(mkcmdstr("group toggle"), TRUE);
4773               else
4774                 open_chat_window();
4775             }
4776             break;
4777         default:
4778             unrecognized = TRUE;
4779             break;
4780       }
4781       cmdhisto_cur = NULL;
4782     }
4783     if (!ex_or_search_mode && !got_cmd_prefix) {
4784       clear_inputline();
4785       if (!unrecognized)
4786         key = ERR;  // Do not process any further
4787     }
4788     lock_chatstate = TRUE;
4789   }
4790 
4791   if (kcode.utf8) {
4792     if (key != ERR && !kcode.mcode)
4793       display_char = TRUE;
4794     goto display;
4795   }
4796 
4797   switch (key) {
4798     case 0:
4799     case ERR:
4800         break;
4801     case 9:     // Tab
4802         if (!vi_search)
4803           readline_do_completion(TRUE);   // Forward-completion
4804         break;
4805     case 353:   // Shift-Tab
4806         if (!vi_search)
4807           readline_do_completion(FALSE);  // Backward-completion
4808         break;
4809     case 13:    // Enter
4810     case 343:   // Enter on Maemo
4811         readline_accept_line(FALSE);
4812         break;
4813     case 3:     // Ctrl-C
4814         scr_handle_CtrlC();
4815         break;
4816     case KEY_RESIZE:
4817         scr_resize();
4818         break;
4819     default:
4820         display_char = TRUE;
4821   } // switch
4822 
4823 display:
4824   if (display_char) {
4825     guint printable;
4826 
4827     if (kcode.utf8) {
4828       printable = iswprint(key);
4829     } else {
4830 #ifdef __CYGWIN__
4831       printable = (isprint(key) || (key >= 161 && key <= 255))
4832                   && !is_speckey(key);
4833 #else
4834       printable = isprint(key) && !is_speckey(key);
4835 #endif
4836     }
4837     if (printable) {
4838       char tmpLine[INPUTLINE_LENGTH+1];
4839 
4840       // Check the line isn't too long
4841       if (strlen(inputLine) + 4 > INPUTLINE_LENGTH)
4842         return;
4843 
4844       // Insert char
4845       strcpy(tmpLine, ptr_inputline);
4846       ptr_inputline = put_char(ptr_inputline, key);
4847       strcpy(ptr_inputline, tmpLine);
4848       check_offset(1);
4849     } else {
4850       // Look for a key binding.
4851       if (!kcode.utf8)
4852         bindcommand(kcode);
4853     }
4854   }
4855 
4856   if (completion_started && key != 9 && key != 353 && key != KEY_RESIZE)
4857     scr_end_current_completion();
4858   refresh_inputline();
4859 
4860   if (ex_or_search_mode &&
4861       inputLine[0] != COMMAND_CHAR && inputLine[0] != VI_SEARCH_COMMAND_CHAR)
4862     ex_or_search_mode = FALSE;
4863 
4864   if (!lock_chatstate) {
4865     // Set chat state to composing (1) if the user is currently composing,
4866     // i.e. not an empty line and not a command line.
4867     if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR)
4868       set_chatstate(0);
4869     else
4870       set_chatstate(1);
4871     if (chatstate)
4872       time(&chatstate_timestamp);
4873   }
4874   return;
4875 }
4876 
4877 #if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
spell_checker_free(gpointer data)4878 static void spell_checker_free(gpointer data)
4879 {
4880   spell_checker_t *sc = data;
4881 #ifdef WITH_ENCHANT
4882   enchant_broker_free_dict(sc->broker, sc->checker);
4883   enchant_broker_free(sc->broker);
4884 #endif
4885 #ifdef WITH_ASPELL
4886   delete_aspell_speller(sc->checker);
4887   delete_aspell_config(sc->config);
4888 #endif
4889   g_free(sc);
4890 }
4891 
new_spell_checker(const char * spell_lang)4892 static spell_checker_t* new_spell_checker(const char* spell_lang)
4893 {
4894   spell_checker_t *sc = g_new(spell_checker_t, 1);
4895 #ifdef WITH_ASPELL
4896   const char *spell_encoding = settings_opt_get("spell_encoding");
4897   AspellCanHaveError *possible_err;
4898   sc->config = new_aspell_config();
4899   if (spell_encoding)
4900     aspell_config_replace(sc->config, "encoding", spell_encoding);
4901   aspell_config_replace(sc->config, "lang", spell_lang);
4902   possible_err = new_aspell_speller(sc->config);
4903 
4904   if (aspell_error_number(possible_err) != 0) {
4905     delete_aspell_config(sc->config);
4906     g_free(sc);
4907     sc = NULL;
4908   } else {
4909     sc->checker = to_aspell_speller(possible_err);
4910   }
4911 #endif
4912 #ifdef WITH_ENCHANT
4913   sc->broker = enchant_broker_init();
4914   sc->checker = enchant_broker_request_dict(sc->broker, spell_lang);
4915   if (!sc->checker) {
4916     enchant_broker_free(sc->broker);
4917     g_free(sc);
4918     sc = NULL;
4919   }
4920 #endif
4921   return sc;
4922 }
4923 
4924 // initialization
spellcheck_init(void)4925 void spellcheck_init(void)
4926 {
4927   int spell_enable            = settings_opt_get_int("spell_enable");
4928   const char *spell_lang     = settings_opt_get("spell_lang");
4929   gchar **langs;
4930   gchar **lang_iter;
4931   spell_checker_t *sc;
4932 
4933   if (!spell_enable)
4934     return;
4935 
4936   spellcheck_deinit();
4937 
4938   if (!spell_lang) { // Cannot initialize: language not specified
4939     scr_LogPrint(LPRINT_LOGNORM, "Error: Cannot initialize spell checker, language not specified.");
4940     scr_LogPrint(LPRINT_LOGNORM, "Please set the 'spell_lang' variable.");
4941     return;
4942   }
4943 
4944   langs = g_strsplit(spell_lang, " ", -1);
4945   for (lang_iter = langs; *lang_iter; ++lang_iter) {
4946     if (**lang_iter) { // Skip empty strings
4947       sc = new_spell_checker(*lang_iter);
4948       if (sc) {
4949         spell_checkers = g_slist_append(spell_checkers, sc);
4950       } else {
4951         scr_LogPrint(LPRINT_LOGNORM,
4952                      "Warning: Could not load spell checker language '%s'.",
4953                      *lang_iter);
4954       }
4955     }
4956   }
4957   g_strfreev(langs);
4958 }
4959 
4960 // Deinitialization of spellchecker
spellcheck_deinit(void)4961 void spellcheck_deinit(void)
4962 {
4963   g_slist_foreach (spell_checkers, (GFunc) spell_checker_free, NULL);
4964   g_slist_free (spell_checkers);
4965   spell_checkers = NULL;
4966 }
4967 
4968 typedef struct {
4969   const char* str;
4970   int len;
4971 } spell_substring_t;
4972 
spellcheckword(gconstpointer sc_ptr,gconstpointer substr_ptr)4973 static int spellcheckword(gconstpointer sc_ptr, gconstpointer substr_ptr)
4974 {
4975   spell_checker_t *sc = (spell_checker_t*) sc_ptr;
4976   spell_substring_t *substr = (spell_substring_t*) substr_ptr;
4977 #ifdef WITH_ENCHANT
4978   // enchant_dict_check will return 0 on good word
4979   return enchant_dict_check(sc->checker, substr->str, substr->len);
4980 #endif
4981 #ifdef WITH_ASPELL
4982   // aspell_speller_check will return 1 on good word, so we need to make it 0
4983   return aspell_speller_check(sc->checker, substr->str, substr->len) - 1;
4984 #endif
4985   return 0; // Keep compiler happy
4986 }
4987 
4988 #define spell_isalpha(c) (utf8_mode ? iswalpha(get_char(c)) : isalpha(*c))
4989 
4990 // Spell checking function
spellcheck(char * line,char * checked)4991 static void spellcheck(char *line, char *checked)
4992 {
4993   const char *start, *line_start;
4994   spell_substring_t substr;
4995 
4996   if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR)
4997     return;
4998 
4999   // Give up early if not languages are loaded
5000   if (!spell_checkers)
5001     return;
5002 
5003   line_start = line;
5004 
5005   while (*line) {
5006 
5007     if (!spell_isalpha(line)) {
5008       line = next_char(line);
5009       continue;
5010     }
5011 
5012     if (!strncmp(line, "http://", 7)) {
5013       line += 7; // : and / characters are 1 byte long in utf8, right?
5014 
5015       while (!strchr(" \t\r\n", *line))
5016         line = next_char(line); // i think line++ would be fine here?
5017 
5018       continue;
5019     }
5020 
5021     if (!strncmp(line, "ftp://", 6)) {
5022       line += 6;
5023 
5024       while (!strchr(" \t\r\n", *line))
5025         line = next_char(line);
5026 
5027       continue;
5028     }
5029 
5030     start = line;
5031 
5032     while (spell_isalpha(line))
5033       line = next_char(line);
5034 
5035     substr.str = start;
5036     substr.len = line - start;
5037     if (!g_slist_find_custom(spell_checkers, &substr, spellcheckword))
5038       memset(&checked[start - line_start], SPELLBADCHAR, line - start);
5039   }
5040 }
5041 #endif
5042 
open_chat_window(void)5043 static void open_chat_window(void)
5044 {
5045   last_activity_buddy = current_buddy;
5046   scr_check_auto_away(TRUE);
5047   scr_set_chatmode(TRUE);
5048   scr_show_buddy_window();
5049 }
5050 
clear_inputline(void)5051 static void clear_inputline(void)
5052 {
5053   ptr_inputline = inputLine;
5054   *ptr_inputline = 0;
5055   inputline_offset = 0;
5056 }
5057 
5058 /* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2:  For Vim users... */
5059