1 /*
2 * statusbar.c
3 * vim: expandtab:ts=4:sts=4:sw=4
4 *
5 * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
6 * Copyright (C) 2019 - 2021 Michael Vetter <jubalh@iodoru.org>
7 *
8 * This file is part of Profanity.
9 *
10 * Profanity is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * Profanity is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with Profanity. If not, see <https://www.gnu.org/licenses/>.
22 *
23 * In addition, as a special exception, the copyright holders give permission to
24 * link the code of portions of this program with the OpenSSL library under
25 * certain conditions as described in each individual source file, and
26 * distribute linked combinations including the two.
27 *
28 * You must obey the GNU General Public License in all respects for all of the
29 * code used other than OpenSSL. If you modify file(s) with this exception, you
30 * may extend this exception to your version of the file(s), but you are not
31 * obligated to do so. If you do not wish to do so, delete this exception
32 * statement from your version. If you delete this exception statement from all
33 * source files in the program, then also delete it here.
34 *
35 */
36
37 #include "config.h"
38
39 #include <assert.h>
40 #include <string.h>
41 #include <stdlib.h>
42
43 #ifdef HAVE_NCURSESW_NCURSES_H
44 #include <ncursesw/ncurses.h>
45 #elif HAVE_NCURSES_H
46 #include <ncurses.h>
47 #elif HAVE_CURSES_H
48 #include <curses.h>
49 #endif
50
51 #include "config/theme.h"
52 #include "config/preferences.h"
53 #include "ui/ui.h"
54 #include "ui/statusbar.h"
55 #include "ui/inputwin.h"
56 #include "ui/screen.h"
57 #include "xmpp/roster_list.h"
58 #include "xmpp/contact.h"
59
60 typedef struct _status_bar_tab_t
61 {
62 win_type_t window_type;
63 char* identifier;
64 gboolean highlight;
65 char* display_name;
66 } StatusBarTab;
67
68 typedef struct _status_bar_t
69 {
70 gchar* time;
71 char* prompt;
72 char* fulljid;
73 GHashTable* tabs;
74 int current_tab;
75 } StatusBar;
76
77 static GTimeZone* tz;
78 static StatusBar* statusbar;
79 static WINDOW* statusbar_win;
80
81 static int _status_bar_draw_time(int pos);
82 static void _status_bar_draw_maintext(int pos);
83 static int _status_bar_draw_bracket(gboolean current, int pos, char* ch);
84 static int _status_bar_draw_extended_tabs(int pos);
85 static int _status_bar_draw_tab(StatusBarTab* tab, int pos, int num);
86 static void _destroy_tab(StatusBarTab* tab);
87 static int _tabs_width(void);
88 static char* _display_name(StatusBarTab* tab);
89 static gboolean _extended_new(void);
90
91 void
status_bar_init(void)92 status_bar_init(void)
93 {
94 tz = g_time_zone_new_local();
95
96 statusbar = malloc(sizeof(StatusBar));
97 statusbar->time = NULL;
98 statusbar->prompt = NULL;
99 statusbar->fulljid = NULL;
100 statusbar->tabs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)_destroy_tab);
101 StatusBarTab* console = calloc(1, sizeof(StatusBarTab));
102 console->window_type = WIN_CONSOLE;
103 console->identifier = strdup("console");
104 console->display_name = NULL;
105 g_hash_table_insert(statusbar->tabs, GINT_TO_POINTER(1), console);
106 statusbar->current_tab = 1;
107
108 int row = screen_statusbar_row();
109 int cols = getmaxx(stdscr);
110 statusbar_win = newwin(1, cols, row, 0);
111
112 status_bar_draw();
113 }
114
115 void
status_bar_close(void)116 status_bar_close(void)
117 {
118 if (tz) {
119 g_time_zone_unref(tz);
120 }
121 if (statusbar) {
122 if (statusbar->time) {
123 g_free(statusbar->time);
124 }
125 if (statusbar->prompt) {
126 free(statusbar->prompt);
127 }
128 if (statusbar->fulljid) {
129 free(statusbar->fulljid);
130 }
131 if (statusbar->tabs) {
132 g_hash_table_destroy(statusbar->tabs);
133 }
134 free(statusbar);
135 }
136 }
137
138 void
status_bar_resize(void)139 status_bar_resize(void)
140 {
141 int cols = getmaxx(stdscr);
142 werase(statusbar_win);
143 int row = screen_statusbar_row();
144 wresize(statusbar_win, 1, cols);
145 mvwin(statusbar_win, row, 0);
146
147 status_bar_draw();
148 }
149
150 void
status_bar_set_all_inactive(void)151 status_bar_set_all_inactive(void)
152 {
153 g_hash_table_remove_all(statusbar->tabs);
154 }
155
156 void
status_bar_current(int i)157 status_bar_current(int i)
158 {
159 if (i == 0) {
160 statusbar->current_tab = 10;
161 } else {
162 statusbar->current_tab = i;
163 }
164
165 status_bar_draw();
166 }
167
168 void
status_bar_inactive(const int win)169 status_bar_inactive(const int win)
170 {
171 int true_win = win;
172 if (true_win == 0) {
173 true_win = 10;
174 }
175
176 g_hash_table_remove(statusbar->tabs, GINT_TO_POINTER(true_win));
177
178 status_bar_draw();
179 }
180
181 void
_create_tab(const int win,win_type_t wintype,char * identifier,gboolean highlight)182 _create_tab(const int win, win_type_t wintype, char* identifier, gboolean highlight)
183 {
184 int true_win = win;
185 if (true_win == 0) {
186 true_win = 10;
187 }
188
189 StatusBarTab* tab = malloc(sizeof(StatusBarTab));
190 tab->identifier = strdup(identifier);
191 tab->highlight = highlight;
192 tab->window_type = wintype;
193 tab->display_name = NULL;
194
195 if (tab->window_type == WIN_CHAT) {
196 PContact contact = NULL;
197 if (roster_exists()) {
198 contact = roster_get_contact(tab->identifier);
199 }
200 if (contact && p_contact_name(contact)) {
201 tab->display_name = strdup(p_contact_name(contact));
202 } else {
203 char* pref = prefs_get_string(PREF_STATUSBAR_CHAT);
204 if (g_strcmp0("user", pref) == 0) {
205 Jid* jidp = jid_create(tab->identifier);
206 if (jidp) {
207 tab->display_name = jidp->localpart != NULL ? strdup(jidp->localpart) : strdup(jidp->barejid);
208 jid_destroy(jidp);
209 } else {
210 tab->display_name = strdup(tab->identifier);
211 }
212 } else {
213 tab->display_name = strdup(tab->identifier);
214 }
215 g_free(pref);
216 }
217 }
218
219 g_hash_table_replace(statusbar->tabs, GINT_TO_POINTER(true_win), tab);
220
221 status_bar_draw();
222 }
223
224 void
status_bar_active(const int win,win_type_t wintype,char * identifier)225 status_bar_active(const int win, win_type_t wintype, char* identifier)
226 {
227 _create_tab(win, wintype, identifier, FALSE);
228 }
229
230 void
status_bar_new(const int win,win_type_t wintype,char * identifier)231 status_bar_new(const int win, win_type_t wintype, char* identifier)
232 {
233 _create_tab(win, wintype, identifier, TRUE);
234 }
235
236 void
status_bar_set_prompt(const char * const prompt)237 status_bar_set_prompt(const char* const prompt)
238 {
239 if (statusbar->prompt) {
240 free(statusbar->prompt);
241 statusbar->prompt = NULL;
242 }
243 statusbar->prompt = strdup(prompt);
244
245 status_bar_draw();
246 }
247
248 void
status_bar_clear_prompt(void)249 status_bar_clear_prompt(void)
250 {
251 if (statusbar->prompt) {
252 free(statusbar->prompt);
253 statusbar->prompt = NULL;
254 }
255
256 status_bar_draw();
257 }
258
259 void
status_bar_set_fulljid(const char * const fulljid)260 status_bar_set_fulljid(const char* const fulljid)
261 {
262 if (statusbar->fulljid) {
263 free(statusbar->fulljid);
264 statusbar->fulljid = NULL;
265 }
266 statusbar->fulljid = strdup(fulljid);
267
268 status_bar_draw();
269 }
270
271 void
status_bar_clear_fulljid(void)272 status_bar_clear_fulljid(void)
273 {
274 if (statusbar->fulljid) {
275 free(statusbar->fulljid);
276 statusbar->fulljid = NULL;
277 }
278
279 status_bar_draw();
280 }
281
282 void
status_bar_draw(void)283 status_bar_draw(void)
284 {
285 werase(statusbar_win);
286 wbkgd(statusbar_win, theme_attrs(THEME_STATUS_TEXT));
287
288 int pos = 1;
289
290 pos = _status_bar_draw_time(pos);
291
292 _status_bar_draw_maintext(pos);
293
294 pos = getmaxx(stdscr) - _tabs_width();
295 if (pos < 0) {
296 pos = 0;
297 }
298 gint max_tabs = prefs_get_statusbartabs();
299 for (int i = 1; i <= max_tabs; i++) {
300 StatusBarTab* tab = g_hash_table_lookup(statusbar->tabs, GINT_TO_POINTER(i));
301 if (tab) {
302 pos = _status_bar_draw_tab(tab, pos, i);
303 }
304 }
305
306 _status_bar_draw_extended_tabs(pos);
307
308 wnoutrefresh(statusbar_win);
309 inp_put_back();
310 }
311
312 static gboolean
_extended_new(void)313 _extended_new(void)
314 {
315 gint max_tabs = prefs_get_statusbartabs();
316 int tabs_count = g_hash_table_size(statusbar->tabs);
317 if (tabs_count <= max_tabs) {
318 return FALSE;
319 }
320
321 for (int i = max_tabs + 1; i <= tabs_count; i++) {
322 StatusBarTab* tab = g_hash_table_lookup(statusbar->tabs, GINT_TO_POINTER(i));
323 if (tab && tab->highlight) {
324 return TRUE;
325 }
326 }
327
328 return FALSE;
329 }
330
331 static int
_status_bar_draw_extended_tabs(int pos)332 _status_bar_draw_extended_tabs(int pos)
333 {
334 gint max_tabs = prefs_get_statusbartabs();
335 if (max_tabs == 0) {
336 return pos;
337 }
338
339 if (g_hash_table_size(statusbar->tabs) > max_tabs) {
340 gboolean is_current = statusbar->current_tab > max_tabs;
341
342 pos = _status_bar_draw_bracket(is_current, pos, "[");
343
344 int status_attrs;
345 if (is_current) {
346 // currently selected
347 status_attrs = theme_attrs(THEME_STATUS_CURRENT);
348 } else if (_extended_new()) {
349 // new one
350 status_attrs = theme_attrs(THEME_STATUS_NEW);
351 } else {
352 // all other
353 status_attrs = theme_attrs(THEME_STATUS_ACTIVE);
354 }
355 wattron(statusbar_win, status_attrs);
356 mvwprintw(statusbar_win, 0, pos, ">");
357 wattroff(statusbar_win, status_attrs);
358 pos++;
359
360 pos = _status_bar_draw_bracket(is_current, pos, "]");
361 }
362
363 return pos;
364 }
365
366 static int
_status_bar_draw_tab(StatusBarTab * tab,int pos,int num)367 _status_bar_draw_tab(StatusBarTab* tab, int pos, int num)
368 {
369 int display_num = num == 10 ? 0 : num;
370 gboolean is_current = num == statusbar->current_tab;
371
372 gboolean show_number = prefs_get_boolean(PREF_STATUSBAR_SHOW_NUMBER);
373 gboolean show_name = prefs_get_boolean(PREF_STATUSBAR_SHOW_NAME);
374 gboolean show_read = prefs_get_boolean(PREF_STATUSBAR_SHOW_READ);
375
376 // dont show this
377 if (!show_read && !is_current && !tab->highlight)
378 return pos;
379
380 pos = _status_bar_draw_bracket(is_current, pos, "[");
381
382 int status_attrs;
383 if (is_current) {
384 status_attrs = theme_attrs(THEME_STATUS_CURRENT);
385 } else if (tab->highlight) {
386 status_attrs = theme_attrs(THEME_STATUS_NEW);
387 } else {
388 status_attrs = theme_attrs(THEME_STATUS_ACTIVE);
389 }
390 wattron(statusbar_win, status_attrs);
391 if (show_number) {
392 mvwprintw(statusbar_win, 0, pos, "%d", display_num);
393 pos++;
394 }
395 if (show_number && show_name) {
396 mvwprintw(statusbar_win, 0, pos, ":");
397 pos++;
398 }
399 if (show_name) {
400 char* display_name = _display_name(tab);
401 mvwprintw(statusbar_win, 0, pos, display_name);
402 pos += utf8_display_len(display_name);
403 free(display_name);
404 }
405 wattroff(statusbar_win, status_attrs);
406
407 pos = _status_bar_draw_bracket(is_current, pos, "]");
408
409 return pos;
410 }
411
412 static int
_status_bar_draw_bracket(gboolean current,int pos,char * ch)413 _status_bar_draw_bracket(gboolean current, int pos, char* ch)
414 {
415 int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
416 wattron(statusbar_win, bracket_attrs);
417 if (current) {
418 mvwprintw(statusbar_win, 0, pos, "-");
419 } else {
420 mvwprintw(statusbar_win, 0, pos, ch);
421 }
422 wattroff(statusbar_win, bracket_attrs);
423 pos++;
424
425 return pos;
426 }
427
428 static int
_status_bar_draw_time(int pos)429 _status_bar_draw_time(int pos)
430 {
431 char* time_pref = prefs_get_string(PREF_TIME_STATUSBAR);
432 if (g_strcmp0(time_pref, "off") == 0) {
433 g_free(time_pref);
434 return pos;
435 }
436
437 if (statusbar->time) {
438 g_free(statusbar->time);
439 statusbar->time = NULL;
440 }
441
442 GDateTime* datetime = g_date_time_new_now(tz);
443 statusbar->time = g_date_time_format(datetime, time_pref);
444 assert(statusbar->time != NULL);
445 g_date_time_unref(datetime);
446
447 int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
448 int time_attrs = theme_attrs(THEME_STATUS_TIME);
449
450 size_t len = strlen(statusbar->time);
451 wattron(statusbar_win, bracket_attrs);
452 mvwaddch(statusbar_win, 0, pos, '[');
453 pos++;
454 wattroff(statusbar_win, bracket_attrs);
455 wattron(statusbar_win, time_attrs);
456 mvwprintw(statusbar_win, 0, pos, statusbar->time);
457 pos += len;
458 wattroff(statusbar_win, time_attrs);
459 wattron(statusbar_win, bracket_attrs);
460 mvwaddch(statusbar_win, 0, pos, ']');
461 wattroff(statusbar_win, bracket_attrs);
462 pos += 2;
463
464 g_free(time_pref);
465
466 return pos;
467 }
468
469 static void
_status_bar_draw_maintext(int pos)470 _status_bar_draw_maintext(int pos)
471 {
472 if (statusbar->prompt) {
473 mvwprintw(statusbar_win, 0, pos, statusbar->prompt);
474 return;
475 }
476
477 gboolean stop = FALSE;
478
479 if (statusbar->fulljid) {
480 char* pref = prefs_get_string(PREF_STATUSBAR_SELF);
481
482 if (g_strcmp0(pref, "off") == 0) {
483 stop = true;
484 } else if (g_strcmp0(pref, "user") == 0) {
485 Jid* jidp = jid_create(statusbar->fulljid);
486 mvwprintw(statusbar_win, 0, pos, jidp->localpart);
487 jid_destroy(jidp);
488 stop = true;
489 } else if (g_strcmp0(pref, "barejid") == 0) {
490 Jid* jidp = jid_create(statusbar->fulljid);
491 mvwprintw(statusbar_win, 0, pos, jidp->barejid);
492 jid_destroy(jidp);
493 stop = true;
494 }
495
496 g_free(pref);
497 if (stop) {
498 return;
499 }
500 mvwprintw(statusbar_win, 0, pos, statusbar->fulljid);
501 }
502 }
503
504 static void
_destroy_tab(StatusBarTab * tab)505 _destroy_tab(StatusBarTab* tab)
506 {
507 if (tab) {
508 if (tab->identifier) {
509 free(tab->identifier);
510 }
511 if (tab->display_name) {
512 free(tab->display_name);
513 }
514 free(tab);
515 }
516 }
517
518 static int
_tabs_width(void)519 _tabs_width(void)
520 {
521 gboolean show_number = prefs_get_boolean(PREF_STATUSBAR_SHOW_NUMBER);
522 gboolean show_name = prefs_get_boolean(PREF_STATUSBAR_SHOW_NAME);
523 gboolean show_read = prefs_get_boolean(PREF_STATUSBAR_SHOW_READ);
524 gint max_tabs = prefs_get_statusbartabs();
525
526 if (show_name && show_number) {
527 int width = g_hash_table_size(statusbar->tabs) > max_tabs ? 4 : 1;
528 for (int i = 1; i <= max_tabs; i++) {
529 StatusBarTab* tab = g_hash_table_lookup(statusbar->tabs, GINT_TO_POINTER(i));
530 if (tab) {
531 gboolean is_current = i == statusbar->current_tab;
532 // dont calculate this in because not shown
533 if (!show_read && !is_current && !tab->highlight)
534 continue;
535
536 char* display_name = _display_name(tab);
537 width += utf8_display_len(display_name);
538 width += 4;
539 free(display_name);
540 }
541 }
542 return width;
543 }
544
545 if (show_name && !show_number) {
546 int width = g_hash_table_size(statusbar->tabs) > max_tabs ? 4 : 1;
547 for (int i = 1; i <= max_tabs; i++) {
548 StatusBarTab* tab = g_hash_table_lookup(statusbar->tabs, GINT_TO_POINTER(i));
549 if (tab) {
550 gboolean is_current = i == statusbar->current_tab;
551 // dont calculate this in because not shown
552 if (!show_read && !is_current && !tab->highlight)
553 continue;
554
555 char* display_name = _display_name(tab);
556 width += utf8_display_len(display_name);
557 width += 2;
558 free(display_name);
559 }
560 }
561 return width;
562 }
563
564 if (g_hash_table_size(statusbar->tabs) > max_tabs) {
565 return max_tabs * 3 + (g_hash_table_size(statusbar->tabs) > max_tabs ? 4 : 1);
566 }
567 return g_hash_table_size(statusbar->tabs) * 3 + (g_hash_table_size(statusbar->tabs) > max_tabs ? 4 : 1);
568 }
569
570 static char*
_display_name(StatusBarTab * tab)571 _display_name(StatusBarTab* tab)
572 {
573 char* fullname = NULL;
574
575 if (tab->window_type == WIN_CONSOLE) {
576 fullname = strdup("console");
577 } else if (tab->window_type == WIN_XML) {
578 fullname = strdup("xmlconsole");
579 } else if (tab->window_type == WIN_PLUGIN) {
580 fullname = strdup(tab->identifier);
581 } else if (tab->window_type == WIN_CHAT) {
582 if (tab && tab->display_name) {
583 fullname = strdup(tab->display_name);
584 }
585 } else if (tab->window_type == WIN_MUC) {
586 char* pref = prefs_get_string(PREF_STATUSBAR_ROOM);
587 if (g_strcmp0("room", pref) == 0) {
588 Jid* jidp = jid_create(tab->identifier);
589 char* room = strdup(jidp->localpart);
590 jid_destroy(jidp);
591 fullname = room;
592 } else {
593 fullname = strdup(tab->identifier);
594 }
595 g_free(pref);
596 } else if (tab->window_type == WIN_CONFIG) {
597 char* pref = prefs_get_string(PREF_STATUSBAR_ROOM);
598 GString* display_str = g_string_new("");
599
600 if (g_strcmp0("room", pref) == 0) {
601 Jid* jidp = jid_create(tab->identifier);
602 g_string_append(display_str, jidp->localpart);
603 jid_destroy(jidp);
604 } else {
605 g_string_append(display_str, tab->identifier);
606 }
607
608 g_free(pref);
609 g_string_append(display_str, " conf");
610 char* result = strdup(display_str->str);
611 g_string_free(display_str, TRUE);
612 fullname = result;
613 } else if (tab->window_type == WIN_PRIVATE) {
614 char* pref = prefs_get_string(PREF_STATUSBAR_ROOM);
615 if (g_strcmp0("room", pref) == 0) {
616 GString* display_str = g_string_new("");
617 Jid* jidp = jid_create(tab->identifier);
618 g_string_append(display_str, jidp->localpart);
619 g_string_append(display_str, "/");
620 g_string_append(display_str, jidp->resourcepart);
621 jid_destroy(jidp);
622 char* result = strdup(display_str->str);
623 g_string_free(display_str, TRUE);
624 fullname = result;
625 } else {
626 fullname = strdup(tab->identifier);
627 }
628 g_free(pref);
629 } else {
630 fullname = strdup("window");
631 }
632
633 gint tablen = prefs_get_statusbartablen();
634 if (tablen == 0) {
635 return fullname;
636 }
637
638 int namelen = utf8_display_len(fullname);
639 if (namelen < tablen) {
640 return fullname;
641 }
642
643 gchar* trimmed = g_utf8_substring(fullname, 0, tablen);
644 free(fullname);
645 char* trimmedname = strdup(trimmed);
646 g_free(trimmed);
647
648 return trimmedname;
649 }
650