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(×tamp));
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(×tamp));
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(×tamp));
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