1 /*
2  * window.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 <stdlib.h>
40 #include <string.h>
41 #include <time.h>
42 #include <assert.h>
43 #include <wchar.h>
44 
45 #include <glib.h>
46 
47 #ifdef HAVE_NCURSESW_NCURSES_H
48 #include <ncursesw/ncurses.h>
49 #elif HAVE_NCURSES_H
50 #include <ncurses.h>
51 #elif HAVE_CURSES_H
52 #include <curses.h>
53 #endif
54 
55 #include "log.h"
56 #include "config/theme.h"
57 #include "config/preferences.h"
58 #include "ui/ui.h"
59 #include "ui/window.h"
60 #include "ui/screen.h"
61 #include "xmpp/xmpp.h"
62 #include "xmpp/roster_list.h"
63 
64 #define CONS_WIN_TITLE "Profanity. Type /help for help information."
65 #define XML_WIN_TITLE  "XML Console"
66 
67 #define CEILING(X) (X - (int)(X) > 0 ? (int)(X + 1) : (int)(X))
68 
69 static void
70 _win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...);
71 static void _win_print_internal(ProfWin* window, const char* show_char, int pad_indent, GDateTime* time,
72                                 int flags, theme_item_t theme_item, const char* const from, const char* const message, DeliveryReceipt* receipt);
73 static void _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pad_indent);
74 
75 int
win_roster_cols(void)76 win_roster_cols(void)
77 {
78     int roster_win_percent = prefs_get_roster_size();
79     int cols = getmaxx(stdscr);
80     return CEILING((((double)cols) / 100) * roster_win_percent);
81 }
82 
83 int
win_occpuants_cols(void)84 win_occpuants_cols(void)
85 {
86     int occupants_win_percent = prefs_get_occupants_size();
87     int cols = getmaxx(stdscr);
88     return CEILING((((double)cols) / 100) * occupants_win_percent);
89 }
90 
91 static ProfLayout*
_win_create_simple_layout(void)92 _win_create_simple_layout(void)
93 {
94     int cols = getmaxx(stdscr);
95 
96     ProfLayoutSimple* layout = malloc(sizeof(ProfLayoutSimple));
97     layout->base.type = LAYOUT_SIMPLE;
98     layout->base.win = newpad(PAD_SIZE, cols);
99     wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
100     layout->base.buffer = buffer_create();
101     layout->base.y_pos = 0;
102     layout->base.paged = 0;
103     scrollok(layout->base.win, TRUE);
104 
105     return &layout->base;
106 }
107 
108 static ProfLayout*
_win_create_split_layout(void)109 _win_create_split_layout(void)
110 {
111     int cols = getmaxx(stdscr);
112 
113     ProfLayoutSplit* layout = malloc(sizeof(ProfLayoutSplit));
114     layout->base.type = LAYOUT_SPLIT;
115     layout->base.win = newpad(PAD_SIZE, cols);
116     wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
117     layout->base.buffer = buffer_create();
118     layout->base.y_pos = 0;
119     layout->base.paged = 0;
120     scrollok(layout->base.win, TRUE);
121     layout->subwin = NULL;
122     layout->sub_y_pos = 0;
123     layout->memcheck = LAYOUT_SPLIT_MEMCHECK;
124 
125     return &layout->base;
126 }
127 
128 ProfWin*
win_create_console(void)129 win_create_console(void)
130 {
131     ProfConsoleWin* new_win = malloc(sizeof(ProfConsoleWin));
132     new_win->window.type = WIN_CONSOLE;
133     new_win->window.layout = _win_create_split_layout();
134 
135     return &new_win->window;
136 }
137 
138 ProfWin*
win_create_chat(const char * const barejid)139 win_create_chat(const char* const barejid)
140 {
141     ProfChatWin* new_win = malloc(sizeof(ProfChatWin));
142     new_win->window.type = WIN_CHAT;
143     new_win->window.layout = _win_create_simple_layout();
144 
145     new_win->barejid = strdup(barejid);
146     new_win->resource_override = NULL;
147     new_win->is_otr = FALSE;
148     new_win->otr_is_trusted = FALSE;
149     new_win->pgp_recv = FALSE;
150     new_win->pgp_send = FALSE;
151     new_win->is_omemo = FALSE;
152     new_win->is_ox = FALSE;
153     new_win->history_shown = FALSE;
154     new_win->unread = 0;
155     new_win->state = chat_state_new();
156     new_win->enctext = NULL;
157     new_win->incoming_char = NULL;
158     new_win->outgoing_char = NULL;
159     new_win->last_message = NULL;
160     new_win->last_msg_id = NULL;
161     new_win->has_attention = FALSE;
162     new_win->memcheck = PROFCHATWIN_MEMCHECK;
163 
164     return &new_win->window;
165 }
166 
167 ProfWin*
win_create_muc(const char * const roomjid)168 win_create_muc(const char* const roomjid)
169 {
170     ProfMucWin* new_win = malloc(sizeof(ProfMucWin));
171     int cols = getmaxx(stdscr);
172 
173     new_win->window.type = WIN_MUC;
174     ProfLayoutSplit* layout = malloc(sizeof(ProfLayoutSplit));
175     layout->base.type = LAYOUT_SPLIT;
176 
177     if (prefs_get_boolean(PREF_OCCUPANTS)) {
178         int subwin_cols = win_occpuants_cols();
179         layout->base.win = newpad(PAD_SIZE, cols - subwin_cols);
180         wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
181         layout->subwin = newpad(PAD_SIZE, subwin_cols);
182         wbkgd(layout->subwin, theme_attrs(THEME_TEXT));
183     } else {
184         layout->base.win = newpad(PAD_SIZE, (cols));
185         wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
186         layout->subwin = NULL;
187     }
188     layout->sub_y_pos = 0;
189     layout->memcheck = LAYOUT_SPLIT_MEMCHECK;
190     layout->base.buffer = buffer_create();
191     layout->base.y_pos = 0;
192     layout->base.paged = 0;
193     scrollok(layout->base.win, TRUE);
194     new_win->window.layout = (ProfLayout*)layout;
195 
196     new_win->roomjid = strdup(roomjid);
197     new_win->room_name = NULL;
198     new_win->unread = 0;
199     new_win->unread_mentions = FALSE;
200     new_win->unread_triggers = FALSE;
201     if (prefs_get_boolean(PREF_OCCUPANTS_JID)) {
202         new_win->showjid = TRUE;
203     } else {
204         new_win->showjid = FALSE;
205     }
206     if (prefs_get_boolean(PREF_OCCUPANTS_OFFLINE)) {
207         new_win->showoffline = TRUE;
208     } else {
209         new_win->showoffline = FALSE;
210     }
211     new_win->enctext = NULL;
212     new_win->message_char = NULL;
213     new_win->is_omemo = FALSE;
214     new_win->last_message = NULL;
215     new_win->last_msg_id = NULL;
216     new_win->has_attention = FALSE;
217 
218     new_win->memcheck = PROFMUCWIN_MEMCHECK;
219 
220     return &new_win->window;
221 }
222 
223 ProfWin*
win_create_config(const char * const roomjid,DataForm * form,ProfConfWinCallback submit,ProfConfWinCallback cancel,const void * userdata)224 win_create_config(const char* const roomjid, DataForm* form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void* userdata)
225 {
226     ProfConfWin* new_win = malloc(sizeof(ProfConfWin));
227     new_win->window.type = WIN_CONFIG;
228     new_win->window.layout = _win_create_simple_layout();
229     new_win->roomjid = strdup(roomjid);
230     new_win->form = form;
231     new_win->submit = submit;
232     new_win->cancel = cancel;
233     new_win->userdata = userdata;
234 
235     new_win->memcheck = PROFCONFWIN_MEMCHECK;
236 
237     return &new_win->window;
238 }
239 
240 ProfWin*
win_create_private(const char * const fulljid)241 win_create_private(const char* const fulljid)
242 {
243     ProfPrivateWin* new_win = malloc(sizeof(ProfPrivateWin));
244     new_win->window.type = WIN_PRIVATE;
245     new_win->window.layout = _win_create_simple_layout();
246     new_win->fulljid = strdup(fulljid);
247     new_win->unread = 0;
248     new_win->occupant_offline = FALSE;
249     new_win->room_left = FALSE;
250 
251     new_win->memcheck = PROFPRIVATEWIN_MEMCHECK;
252 
253     return &new_win->window;
254 }
255 
256 ProfWin*
win_create_xmlconsole(void)257 win_create_xmlconsole(void)
258 {
259     ProfXMLWin* new_win = malloc(sizeof(ProfXMLWin));
260     new_win->window.type = WIN_XML;
261     new_win->window.layout = _win_create_simple_layout();
262 
263     new_win->memcheck = PROFXMLWIN_MEMCHECK;
264 
265     return &new_win->window;
266 }
267 
268 ProfWin*
win_create_plugin(const char * const plugin_name,const char * const tag)269 win_create_plugin(const char* const plugin_name, const char* const tag)
270 {
271     ProfPluginWin* new_win = malloc(sizeof(ProfPluginWin));
272     new_win->window.type = WIN_PLUGIN;
273     new_win->window.layout = _win_create_simple_layout();
274 
275     new_win->tag = strdup(tag);
276     new_win->plugin_name = strdup(plugin_name);
277 
278     new_win->memcheck = PROFPLUGINWIN_MEMCHECK;
279 
280     return &new_win->window;
281 }
282 
283 char*
win_get_title(ProfWin * window)284 win_get_title(ProfWin* window)
285 {
286     if (window == NULL) {
287         return strdup(CONS_WIN_TITLE);
288     }
289     if (window->type == WIN_CONSOLE) {
290         return strdup(CONS_WIN_TITLE);
291     }
292     if (window->type == WIN_CHAT) {
293         ProfChatWin* chatwin = (ProfChatWin*)window;
294         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
295         jabber_conn_status_t conn_status = connection_get_status();
296         if (conn_status == JABBER_CONNECTED) {
297             PContact contact = roster_get_contact(chatwin->barejid);
298             if (contact) {
299                 const char* name = p_contact_name_or_jid(contact);
300                 return strdup(name);
301             } else {
302                 return strdup(chatwin->barejid);
303             }
304         } else {
305             return strdup(chatwin->barejid);
306         }
307     }
308     if (window->type == WIN_MUC) {
309         ProfMucWin* mucwin = (ProfMucWin*)window;
310         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
311 
312         gboolean show_titlebar_jid = prefs_get_boolean(PREF_TITLEBAR_MUC_TITLE_JID);
313         gboolean show_titlebar_name = prefs_get_boolean(PREF_TITLEBAR_MUC_TITLE_NAME);
314         GString* title = g_string_new("");
315 
316         if (show_titlebar_name && mucwin->room_name) {
317             g_string_append(title, mucwin->room_name);
318             g_string_append(title, " ");
319         }
320         if (show_titlebar_jid) {
321             g_string_append(title, mucwin->roomjid);
322         }
323 
324         char* title_str = title->str;
325         g_string_free(title, FALSE);
326         return title_str;
327     }
328     if (window->type == WIN_CONFIG) {
329         ProfConfWin* confwin = (ProfConfWin*)window;
330         assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
331         GString* title = g_string_new(confwin->roomjid);
332         g_string_append(title, " config");
333         if (confwin->form->modified) {
334             g_string_append(title, " *");
335         }
336         char* title_str = title->str;
337         g_string_free(title, FALSE);
338         return title_str;
339     }
340     if (window->type == WIN_PRIVATE) {
341         ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
342         assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
343         return strdup(privatewin->fulljid);
344     }
345     if (window->type == WIN_XML) {
346         return strdup(XML_WIN_TITLE);
347     }
348     if (window->type == WIN_PLUGIN) {
349         ProfPluginWin* pluginwin = (ProfPluginWin*)window;
350         assert(pluginwin->memcheck == PROFPLUGINWIN_MEMCHECK);
351         return strdup(pluginwin->tag);
352     }
353 
354     return NULL;
355 }
356 
357 char*
win_get_tab_identifier(ProfWin * window)358 win_get_tab_identifier(ProfWin* window)
359 {
360     assert(window != NULL);
361 
362     switch (window->type) {
363     case WIN_CONSOLE:
364     {
365         return strdup("console");
366     }
367     case WIN_CHAT:
368     {
369         ProfChatWin* chatwin = (ProfChatWin*)window;
370         return strdup(chatwin->barejid);
371     }
372     case WIN_MUC:
373     {
374         ProfMucWin* mucwin = (ProfMucWin*)window;
375         return strdup(mucwin->roomjid);
376     }
377     case WIN_CONFIG:
378     {
379         ProfConfWin* confwin = (ProfConfWin*)window;
380         return strdup(confwin->roomjid);
381     }
382     case WIN_PRIVATE:
383     {
384         ProfPrivateWin* privwin = (ProfPrivateWin*)window;
385         return strdup(privwin->fulljid);
386     }
387     case WIN_PLUGIN:
388     {
389         ProfPluginWin* pluginwin = (ProfPluginWin*)window;
390         return strdup(pluginwin->tag);
391     }
392     case WIN_XML:
393     {
394         return strdup("xmlconsole");
395     }
396     default:
397         return strdup("UNKNOWN");
398     }
399 }
400 
401 char*
win_to_string(ProfWin * window)402 win_to_string(ProfWin* window)
403 {
404     assert(window != NULL);
405 
406     switch (window->type) {
407     case WIN_CONSOLE:
408     {
409         ProfConsoleWin* conswin = (ProfConsoleWin*)window;
410         return cons_get_string(conswin);
411     }
412     case WIN_CHAT:
413     {
414         ProfChatWin* chatwin = (ProfChatWin*)window;
415         return chatwin_get_string(chatwin);
416     }
417     case WIN_MUC:
418     {
419         ProfMucWin* mucwin = (ProfMucWin*)window;
420         return mucwin_get_string(mucwin);
421     }
422     case WIN_CONFIG:
423     {
424         ProfConfWin* confwin = (ProfConfWin*)window;
425         return confwin_get_string(confwin);
426     }
427     case WIN_PRIVATE:
428     {
429         ProfPrivateWin* privwin = (ProfPrivateWin*)window;
430         return privwin_get_string(privwin);
431     }
432     case WIN_XML:
433     {
434         ProfXMLWin* xmlwin = (ProfXMLWin*)window;
435         return xmlwin_get_string(xmlwin);
436     }
437     case WIN_PLUGIN:
438     {
439         ProfPluginWin* pluginwin = (ProfPluginWin*)window;
440         GString* gstring = g_string_new("");
441         g_string_append_printf(gstring, "Plugin: %s", pluginwin->tag);
442         char* res = gstring->str;
443         g_string_free(gstring, FALSE);
444         return res;
445     }
446     default:
447         return NULL;
448     }
449 }
450 
451 void
win_hide_subwin(ProfWin * window)452 win_hide_subwin(ProfWin* window)
453 {
454     if (window->layout->type == LAYOUT_SPLIT) {
455         ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
456         if (layout->subwin) {
457             delwin(layout->subwin);
458         }
459         layout->subwin = NULL;
460         layout->sub_y_pos = 0;
461         int cols = getmaxx(stdscr);
462         wresize(layout->base.win, PAD_SIZE, cols);
463         win_redraw(window);
464     } else {
465         int cols = getmaxx(stdscr);
466         wresize(window->layout->win, PAD_SIZE, cols);
467         win_redraw(window);
468     }
469 }
470 
471 void
win_show_subwin(ProfWin * window)472 win_show_subwin(ProfWin* window)
473 {
474     int cols = getmaxx(stdscr);
475     int subwin_cols = 0;
476 
477     if (window->layout->type != LAYOUT_SPLIT) {
478         return;
479     }
480 
481     if (window->type == WIN_MUC) {
482         subwin_cols = win_occpuants_cols();
483     } else if (window->type == WIN_CONSOLE) {
484         subwin_cols = win_roster_cols();
485     }
486 
487     ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
488     layout->subwin = newpad(PAD_SIZE, subwin_cols);
489     wbkgd(layout->subwin, theme_attrs(THEME_TEXT));
490     wresize(layout->base.win, PAD_SIZE, cols - subwin_cols);
491     win_redraw(window);
492 }
493 
494 void
win_free(ProfWin * window)495 win_free(ProfWin* window)
496 {
497     if (window->layout->type == LAYOUT_SPLIT) {
498         ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
499         if (layout->subwin) {
500             delwin(layout->subwin);
501         }
502         buffer_free(layout->base.buffer);
503         delwin(layout->base.win);
504     } else {
505         buffer_free(window->layout->buffer);
506         delwin(window->layout->win);
507     }
508     free(window->layout);
509 
510     switch (window->type) {
511     case WIN_CHAT:
512     {
513         ProfChatWin* chatwin = (ProfChatWin*)window;
514         free(chatwin->barejid);
515         free(chatwin->resource_override);
516         free(chatwin->enctext);
517         free(chatwin->incoming_char);
518         free(chatwin->outgoing_char);
519         free(chatwin->last_message);
520         free(chatwin->last_msg_id);
521         chat_state_free(chatwin->state);
522         break;
523     }
524     case WIN_MUC:
525     {
526         ProfMucWin* mucwin = (ProfMucWin*)window;
527         free(mucwin->roomjid);
528         free(mucwin->room_name);
529         free(mucwin->enctext);
530         free(mucwin->message_char);
531         free(mucwin->last_message);
532         free(mucwin->last_msg_id);
533         break;
534     }
535     case WIN_CONFIG:
536     {
537         ProfConfWin* conf = (ProfConfWin*)window;
538         free(conf->roomjid);
539         form_destroy(conf->form);
540         break;
541     }
542     case WIN_PRIVATE:
543     {
544         ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
545         free(privatewin->fulljid);
546         break;
547     }
548     case WIN_PLUGIN:
549     {
550         ProfPluginWin* pluginwin = (ProfPluginWin*)window;
551         free(pluginwin->tag);
552         free(pluginwin->plugin_name);
553         break;
554     }
555     default:
556         break;
557     }
558 
559     free(window);
560 }
561 
562 void
win_page_up(ProfWin * window)563 win_page_up(ProfWin* window)
564 {
565     int rows = getmaxy(stdscr);
566     int y = getcury(window->layout->win);
567     int page_space = rows - 4;
568     int* page_start = &(window->layout->y_pos);
569 
570     *page_start -= page_space;
571 
572     // went past beginning, show first page
573     if (*page_start < 0)
574         *page_start = 0;
575 
576     window->layout->paged = 1;
577     win_update_virtual(window);
578 
579     // switch off page if last line and space line visible
580     if ((y) - *page_start == page_space) {
581         window->layout->paged = 0;
582     }
583 }
584 
585 void
win_page_down(ProfWin * window)586 win_page_down(ProfWin* window)
587 {
588     int rows = getmaxy(stdscr);
589     int y = getcury(window->layout->win);
590     int page_space = rows - 4;
591     int* page_start = &(window->layout->y_pos);
592 
593     *page_start += page_space;
594 
595     // only got half a screen, show full screen
596     if ((y - (*page_start)) < page_space)
597         *page_start = y - page_space;
598 
599     // went past end, show full screen
600     else if (*page_start >= y)
601         *page_start = y - page_space - 1;
602 
603     window->layout->paged = 1;
604     win_update_virtual(window);
605 
606     // switch off page if last line and space line visible
607     if ((y) - *page_start == page_space) {
608         window->layout->paged = 0;
609     }
610 }
611 
612 void
win_sub_page_down(ProfWin * window)613 win_sub_page_down(ProfWin* window)
614 {
615     if (window->layout->type == LAYOUT_SPLIT) {
616         int rows = getmaxy(stdscr);
617         int page_space = rows - 4;
618         ProfLayoutSplit* split_layout = (ProfLayoutSplit*)window->layout;
619         int sub_y = getcury(split_layout->subwin);
620         int* sub_y_pos = &(split_layout->sub_y_pos);
621 
622         *sub_y_pos += page_space;
623 
624         // only got half a screen, show full screen
625         if ((sub_y - (*sub_y_pos)) < page_space)
626             *sub_y_pos = sub_y - page_space;
627 
628         // went past end, show full screen
629         else if (*sub_y_pos >= sub_y)
630             *sub_y_pos = sub_y - page_space - 1;
631 
632         win_update_virtual(window);
633     }
634 }
635 
636 void
win_sub_page_up(ProfWin * window)637 win_sub_page_up(ProfWin* window)
638 {
639     if (window->layout->type == LAYOUT_SPLIT) {
640         int rows = getmaxy(stdscr);
641         int page_space = rows - 4;
642         ProfLayoutSplit* split_layout = (ProfLayoutSplit*)window->layout;
643         int* sub_y_pos = &(split_layout->sub_y_pos);
644 
645         *sub_y_pos -= page_space;
646 
647         // went past beginning, show first page
648         if (*sub_y_pos < 0)
649             *sub_y_pos = 0;
650 
651         win_update_virtual(window);
652     }
653 }
654 
655 void
win_clear(ProfWin * window)656 win_clear(ProfWin* window)
657 {
658     if (!prefs_get_boolean(PREF_CLEAR_PERSIST_HISTORY)) {
659         werase(window->layout->win);
660         buffer_free(window->layout->buffer);
661         window->layout->buffer = buffer_create();
662         return;
663     }
664 
665     int y = getcury(window->layout->win);
666     int* page_start = &(window->layout->y_pos);
667     *page_start = y;
668     window->layout->paged = 1;
669     win_update_virtual(window);
670 }
671 
672 void
win_resize(ProfWin * window)673 win_resize(ProfWin* window)
674 {
675     int cols = getmaxx(stdscr);
676 
677     if (window->layout->type == LAYOUT_SPLIT) {
678         ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
679         if (layout->subwin) {
680             int subwin_cols = 0;
681             if (window->type == WIN_CONSOLE) {
682                 subwin_cols = win_roster_cols();
683             } else if (window->type == WIN_MUC) {
684                 subwin_cols = win_occpuants_cols();
685             }
686             wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
687             wresize(layout->base.win, PAD_SIZE, cols - subwin_cols);
688             wbkgd(layout->subwin, theme_attrs(THEME_TEXT));
689             wresize(layout->subwin, PAD_SIZE, subwin_cols);
690             if (window->type == WIN_CONSOLE) {
691                 rosterwin_roster();
692             } else if (window->type == WIN_MUC) {
693                 ProfMucWin* mucwin = (ProfMucWin*)window;
694                 assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
695                 occupantswin_occupants(mucwin->roomjid);
696             }
697         } else {
698             wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
699             wresize(layout->base.win, PAD_SIZE, cols);
700         }
701     } else {
702         wbkgd(window->layout->win, theme_attrs(THEME_TEXT));
703         wresize(window->layout->win, PAD_SIZE, cols);
704     }
705 
706     win_redraw(window);
707 }
708 
709 void
win_update_virtual(ProfWin * window)710 win_update_virtual(ProfWin* window)
711 {
712     int cols = getmaxx(stdscr);
713 
714     int row_start = screen_mainwin_row_start();
715     int row_end = screen_mainwin_row_end();
716     if (window->layout->type == LAYOUT_SPLIT) {
717         ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
718         if (layout->subwin) {
719             int subwin_cols = 0;
720             if (window->type == WIN_MUC) {
721                 subwin_cols = win_occpuants_cols();
722             } else {
723                 subwin_cols = win_roster_cols();
724             }
725             pnoutrefresh(layout->base.win, layout->base.y_pos, 0, row_start, 0, row_end, (cols - subwin_cols) - 1);
726             pnoutrefresh(layout->subwin, layout->sub_y_pos, 0, row_start, (cols - subwin_cols), row_end, cols - 1);
727         } else {
728             pnoutrefresh(layout->base.win, layout->base.y_pos, 0, row_start, 0, row_end, cols - 1);
729         }
730     } else {
731         pnoutrefresh(window->layout->win, window->layout->y_pos, 0, row_start, 0, row_end, cols - 1);
732     }
733 }
734 
735 void
win_refresh_without_subwin(ProfWin * window)736 win_refresh_without_subwin(ProfWin* window)
737 {
738     int cols = getmaxx(stdscr);
739 
740     if ((window->type == WIN_MUC) || (window->type == WIN_CONSOLE)) {
741         int row_start = screen_mainwin_row_start();
742         int row_end = screen_mainwin_row_end();
743         pnoutrefresh(window->layout->win, window->layout->y_pos, 0, row_start, 0, row_end, cols - 1);
744     }
745 }
746 
747 void
win_refresh_with_subwin(ProfWin * window)748 win_refresh_with_subwin(ProfWin* window)
749 {
750     int subwin_cols = 0;
751     int cols = getmaxx(stdscr);
752     int row_start = screen_mainwin_row_start();
753     int row_end = screen_mainwin_row_end();
754     ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
755 
756     if (window->type == WIN_MUC) {
757         subwin_cols = win_occpuants_cols();
758     } else if (window->type == WIN_CONSOLE) {
759         subwin_cols = win_roster_cols();
760     } else {
761         // Other window types don't support subwindows, we shouldn't be here
762         return;
763     }
764 
765     pnoutrefresh(layout->base.win, layout->base.y_pos, 0, row_start, 0, row_end, (cols - subwin_cols) - 1);
766     pnoutrefresh(layout->subwin, layout->sub_y_pos, 0, row_start, (cols - subwin_cols), row_end, cols - 1);
767 }
768 
769 void
win_move_to_end(ProfWin * window)770 win_move_to_end(ProfWin* window)
771 {
772     window->layout->paged = 0;
773 
774     int rows = getmaxy(stdscr);
775     int y = getcury(window->layout->win);
776     int size = rows - 3;
777 
778     window->layout->y_pos = y - (size - 1);
779     if (window->layout->y_pos < 0) {
780         window->layout->y_pos = 0;
781     }
782 }
783 
784 void
win_show_occupant(ProfWin * window,Occupant * occupant)785 win_show_occupant(ProfWin* window, Occupant* occupant)
786 {
787     const char* presence_str = string_from_resource_presence(occupant->presence);
788 
789     theme_item_t presence_colour = theme_main_presence_attrs(presence_str);
790 
791     win_print(window, presence_colour, "-", "%s", occupant->nick);
792     win_append(window, presence_colour, " is %s", presence_str);
793 
794     if (occupant->status) {
795         win_append(window, presence_colour, ", \"%s\"", occupant->status);
796     }
797 
798     win_appendln(window, presence_colour, "");
799 }
800 
801 void
win_show_contact(ProfWin * window,PContact contact)802 win_show_contact(ProfWin* window, PContact contact)
803 {
804     const char* barejid = p_contact_barejid(contact);
805     const char* name = p_contact_name(contact);
806     const char* presence = p_contact_presence(contact);
807     const char* status = p_contact_status(contact);
808     GDateTime* last_activity = p_contact_last_activity(contact);
809 
810     theme_item_t presence_colour = theme_main_presence_attrs(presence);
811 
812     if (name) {
813         win_print(window, presence_colour, "-", "%s", name);
814     } else {
815         win_print(window, presence_colour, "-", "%s", barejid);
816     }
817 
818     win_append(window, presence_colour, " is %s", presence);
819 
820     if (last_activity) {
821         GDateTime* now = g_date_time_new_now_local();
822         GTimeSpan span = g_date_time_difference(now, last_activity);
823         g_date_time_unref(now);
824 
825         int hours = span / G_TIME_SPAN_HOUR;
826         span = span - hours * G_TIME_SPAN_HOUR;
827         int minutes = span / G_TIME_SPAN_MINUTE;
828         span = span - minutes * G_TIME_SPAN_MINUTE;
829         int seconds = span / G_TIME_SPAN_SECOND;
830 
831         if (hours > 0) {
832             win_append(window, presence_colour, ", idle %dh%dm%ds", hours, minutes, seconds);
833         } else {
834             win_append(window, presence_colour, ", idle %dm%ds", minutes, seconds);
835         }
836     }
837 
838     if (status) {
839         win_append(window, presence_colour, ", \"%s\"", p_contact_status(contact));
840     }
841 
842     win_appendln(window, presence_colour, "");
843 }
844 
845 void
win_show_occupant_info(ProfWin * window,const char * const room,Occupant * occupant)846 win_show_occupant_info(ProfWin* window, const char* const room, Occupant* occupant)
847 {
848     const char* presence_str = string_from_resource_presence(occupant->presence);
849     const char* occupant_affiliation = muc_occupant_affiliation_str(occupant);
850     const char* occupant_role = muc_occupant_role_str(occupant);
851 
852     theme_item_t presence_colour = theme_main_presence_attrs(presence_str);
853 
854     win_print(window, presence_colour, "!", "%s", occupant->nick);
855     win_append(window, presence_colour, " is %s", presence_str);
856 
857     if (occupant->status) {
858         win_append(window, presence_colour, ", \"%s\"", occupant->status);
859     }
860 
861     win_newline(window);
862 
863     if (occupant->jid) {
864         win_println(window, THEME_DEFAULT, "!", "  Jid: %s", occupant->jid);
865     }
866 
867     win_println(window, THEME_DEFAULT, "!", "  Affiliation: %s", occupant_affiliation);
868     win_println(window, THEME_DEFAULT, "!", "  Role: %s", occupant_role);
869 
870     Jid* jidp = jid_create_from_bare_and_resource(room, occupant->nick);
871     EntityCapabilities* caps = caps_lookup(jidp->fulljid);
872     jid_destroy(jidp);
873 
874     if (caps) {
875         // show identity
876         if (caps->identity) {
877             DiscoIdentity* identity = caps->identity;
878             win_print(window, THEME_DEFAULT, "!", "  Identity: ");
879             if (identity->name) {
880                 win_append(window, THEME_DEFAULT, "%s", identity->name);
881                 if (identity->category || identity->type) {
882                     win_append(window, THEME_DEFAULT, " ");
883                 }
884             }
885             if (identity->type) {
886                 win_append(window, THEME_DEFAULT, "%s", identity->type);
887                 if (identity->category) {
888                     win_append(window, THEME_DEFAULT, " ");
889                 }
890             }
891             if (identity->category) {
892                 win_append(window, THEME_DEFAULT, "%s", identity->category);
893             }
894             win_newline(window);
895         }
896 
897         if (caps->software_version) {
898             SoftwareVersion* software_version = caps->software_version;
899             if (software_version->software) {
900                 win_print(window, THEME_DEFAULT, "!", "  Software: %s", software_version->software);
901             }
902             if (software_version->software_version) {
903                 win_append(window, THEME_DEFAULT, ", %s", software_version->software_version);
904             }
905             if (software_version->software || software_version->software_version) {
906                 win_newline(window);
907             }
908             if (software_version->os) {
909                 win_print(window, THEME_DEFAULT, "!", "  OS: %s", software_version->os);
910             }
911             if (software_version->os_version) {
912                 win_append(window, THEME_DEFAULT, ", %s", software_version->os_version);
913             }
914             if (software_version->os || software_version->os_version) {
915                 win_newline(window);
916             }
917         }
918 
919         caps_destroy(caps);
920     }
921 
922     win_println(window, THEME_DEFAULT, "-", "");
923 }
924 
925 void
win_show_info(ProfWin * window,PContact contact)926 win_show_info(ProfWin* window, PContact contact)
927 {
928     const char* barejid = p_contact_barejid(contact);
929     const char* name = p_contact_name(contact);
930     const char* presence = p_contact_presence(contact);
931     const char* sub = p_contact_subscription(contact);
932     GDateTime* last_activity = p_contact_last_activity(contact);
933 
934     theme_item_t presence_colour = theme_main_presence_attrs(presence);
935 
936     win_println(window, THEME_DEFAULT, "-", "");
937     win_print(window, presence_colour, "-", "%s", barejid);
938     if (name) {
939         win_append(window, presence_colour, " (%s)", name);
940     }
941     win_appendln(window, THEME_DEFAULT, ":");
942 
943     if (sub) {
944         win_println(window, THEME_DEFAULT, "-", "Subscription: %s", sub);
945     }
946 
947     if (last_activity) {
948         GDateTime* now = g_date_time_new_now_local();
949         GTimeSpan span = g_date_time_difference(now, last_activity);
950 
951         int hours = span / G_TIME_SPAN_HOUR;
952         span = span - hours * G_TIME_SPAN_HOUR;
953         int minutes = span / G_TIME_SPAN_MINUTE;
954         span = span - minutes * G_TIME_SPAN_MINUTE;
955         int seconds = span / G_TIME_SPAN_SECOND;
956 
957         if (hours > 0) {
958             win_println(window, THEME_DEFAULT, "-", "Last activity: %dh%dm%ds", hours, minutes, seconds);
959         } else {
960             win_println(window, THEME_DEFAULT, "-", "Last activity: %dm%ds", minutes, seconds);
961         }
962 
963         g_date_time_unref(now);
964     }
965 
966     GList* resources = p_contact_get_available_resources(contact);
967     GList* ordered_resources = NULL;
968     if (resources) {
969         win_println(window, THEME_DEFAULT, "-", "Resources:");
970 
971         // sort in order of availability
972         GList* curr = resources;
973         while (curr) {
974             Resource* resource = curr->data;
975             ordered_resources = g_list_insert_sorted(ordered_resources,
976                                                      resource, (GCompareFunc)resource_compare_availability);
977             curr = g_list_next(curr);
978         }
979     }
980     g_list_free(resources);
981 
982     GList* curr = ordered_resources;
983     while (curr) {
984         Resource* resource = curr->data;
985         const char* resource_presence = string_from_resource_presence(resource->presence);
986         theme_item_t presence_colour = theme_main_presence_attrs(resource_presence);
987         win_print(window, presence_colour, "-", "  %s (%d), %s", resource->name, resource->priority, resource_presence);
988         if (resource->status) {
989             win_append(window, presence_colour, ", \"%s\"", resource->status);
990         }
991         win_newline(window);
992 
993         Jid* jidp = jid_create_from_bare_and_resource(barejid, resource->name);
994         EntityCapabilities* caps = caps_lookup(jidp->fulljid);
995         jid_destroy(jidp);
996 
997         if (caps) {
998             // show identity
999             if (caps->identity) {
1000                 DiscoIdentity* identity = caps->identity;
1001                 win_print(window, THEME_DEFAULT, "-", "    Identity: ");
1002                 if (identity->name) {
1003                     win_append(window, THEME_DEFAULT, "%s", identity->name);
1004                     if (identity->category || identity->type) {
1005                         win_append(window, THEME_DEFAULT, " ");
1006                     }
1007                 }
1008                 if (identity->type) {
1009                     win_append(window, THEME_DEFAULT, "%s", identity->type);
1010                     if (identity->category) {
1011                         win_append(window, THEME_DEFAULT, " ");
1012                     }
1013                 }
1014                 if (identity->category) {
1015                     win_append(window, THEME_DEFAULT, "%s", identity->category);
1016                 }
1017                 win_newline(window);
1018             }
1019 
1020             if (caps->software_version) {
1021                 SoftwareVersion* software_version = caps->software_version;
1022                 if (software_version->software) {
1023                     win_print(window, THEME_DEFAULT, "-", "    Software: %s", software_version->software);
1024                 }
1025                 if (software_version->software_version) {
1026                     win_append(window, THEME_DEFAULT, ", %s", software_version->software_version);
1027                 }
1028                 if (software_version->software || software_version->software_version) {
1029                     win_newline(window);
1030                 }
1031                 if (software_version->os) {
1032                     win_print(window, THEME_DEFAULT, "-", "    OS: %s", software_version->os);
1033                 }
1034                 if (software_version->os_version) {
1035                     win_append(window, THEME_DEFAULT, ", %s", software_version->os_version);
1036                 }
1037                 if (software_version->os || software_version->os_version) {
1038                     win_newline(window);
1039                 }
1040             }
1041 
1042             caps_destroy(caps);
1043         }
1044 
1045         curr = g_list_next(curr);
1046     }
1047     g_list_free(ordered_resources);
1048 }
1049 
1050 void
win_show_status_string(ProfWin * window,const char * const from,const char * const show,const char * const status,GDateTime * last_activity,const char * const pre,const char * const default_show)1051 win_show_status_string(ProfWin* window, const char* const from,
1052                        const char* const show, const char* const status,
1053                        GDateTime* last_activity, const char* const pre,
1054                        const char* const default_show)
1055 {
1056     theme_item_t presence_colour;
1057 
1058     if (show) {
1059         presence_colour = theme_main_presence_attrs(show);
1060     } else if (strcmp(default_show, "online") == 0) {
1061         presence_colour = THEME_ONLINE;
1062     } else {
1063         presence_colour = THEME_OFFLINE;
1064     }
1065 
1066     win_print(window, presence_colour, "-", "%s %s", pre, from);
1067 
1068     if (show)
1069         win_append(window, presence_colour, " is %s", show);
1070     else
1071         win_append(window, presence_colour, " is %s", default_show);
1072 
1073     if (last_activity) {
1074         gchar* date_fmt = NULL;
1075         char* time_pref = prefs_get_string(PREF_TIME_LASTACTIVITY);
1076         date_fmt = g_date_time_format(last_activity, time_pref);
1077         g_free(time_pref);
1078         assert(date_fmt != NULL);
1079 
1080         win_append(window, presence_colour, ", last activity: %s", date_fmt);
1081 
1082         g_free(date_fmt);
1083     }
1084 
1085     if (status)
1086         win_append(window, presence_colour, ", \"%s\"", status);
1087 
1088     win_appendln(window, presence_colour, "");
1089 }
1090 
1091 static void
_win_correct(ProfWin * window,const char * const message,const char * const id,const char * const replace_id,const char * const from_jid)1092 _win_correct(ProfWin* window, const char* const message, const char* const id, const char* const replace_id, const char* const from_jid)
1093 {
1094     ProfBuffEntry* entry = buffer_get_entry_by_id(window->layout->buffer, replace_id);
1095     if (!entry) {
1096         log_debug("Replace ID %s could not be found in buffer. Message: %s", replace_id, message);
1097         return;
1098     }
1099 
1100     if (g_strcmp0(entry->from_jid, from_jid) != 0) {
1101         log_debug("Illicit LMC attempt from %s for message from %s with: %s", from_jid, entry->from_jid, message);
1102         cons_show("Illicit LMC attempt from %s for message from %s", from_jid, entry->from_jid);
1103         return;
1104     }
1105 
1106     /*TODO: set date?
1107     if (entry->date) {
1108         if (entry->date->timestamp) {
1109             g_date_time_unref(entry->date->timestamp);
1110         }
1111         free(entry->date);
1112     }
1113 
1114     entry->date = buffer_date_new_now();
1115     */
1116 
1117     free(entry->show_char);
1118     entry->show_char = prefs_get_correction_char();
1119 
1120     if (entry->message) {
1121         free(entry->message);
1122     }
1123     entry->message = strdup(message);
1124 
1125     if (entry->id) {
1126         free(entry->id);
1127     }
1128     entry->id = strdup(id);
1129 
1130     win_redraw(window);
1131 }
1132 
1133 void
win_print_incoming(ProfWin * window,const char * const display_name_from,ProfMessage * message)1134 win_print_incoming(ProfWin* window, const char* const display_name_from, ProfMessage* message)
1135 {
1136     int flags = NO_ME;
1137 
1138     if (!message->trusted) {
1139         flags |= UNTRUSTED;
1140     }
1141 
1142     switch (window->type) {
1143     case WIN_CHAT:
1144     {
1145         char* enc_char;
1146         ProfChatWin* chatwin = (ProfChatWin*)window;
1147 
1148         if (chatwin->incoming_char) {
1149             enc_char = strdup(chatwin->incoming_char);
1150         } else if (message->enc == PROF_MSG_ENC_OTR) {
1151             enc_char = prefs_get_otr_char();
1152         } else if (message->enc == PROF_MSG_ENC_PGP) {
1153             enc_char = prefs_get_pgp_char();
1154         } else if (message->enc == PROF_MSG_ENC_OX) { // XEP-0373: OpenPGP for XMPP
1155             enc_char = prefs_get_ox_char();
1156         } else if (message->enc == PROF_MSG_ENC_OMEMO) {
1157             enc_char = prefs_get_omemo_char();
1158         } else {
1159             enc_char = strdup("-");
1160         }
1161 
1162         if (prefs_get_boolean(PREF_CORRECTION_ALLOW) && message->replace_id) {
1163             _win_correct(window, message->plain, message->id, message->replace_id, message->from_jid->barejid);
1164         } else {
1165             _win_printf(window, enc_char, 0, message->timestamp, flags, THEME_TEXT_THEM, display_name_from, message->from_jid->barejid, message->id, "%s", message->plain);
1166         }
1167 
1168         free(enc_char);
1169         break;
1170     }
1171     case WIN_PRIVATE:
1172         _win_printf(window, "-", 0, message->timestamp, flags, THEME_TEXT_THEM, display_name_from, message->from_jid->barejid, message->id, "%s", message->plain);
1173         break;
1174     default:
1175         assert(FALSE);
1176         break;
1177     }
1178 }
1179 
1180 void
win_print_them(ProfWin * window,theme_item_t theme_item,const char * const show_char,int flags,const char * const them)1181 win_print_them(ProfWin* window, theme_item_t theme_item, const char* const show_char, int flags, const char* const them)
1182 {
1183     _win_printf(window, show_char, 0, NULL, flags | NO_ME | NO_EOL, theme_item, them, NULL, NULL, "");
1184 }
1185 
1186 void
win_println_incoming_muc_msg(ProfWin * window,char * show_char,int flags,const ProfMessage * const message)1187 win_println_incoming_muc_msg(ProfWin* window, char* show_char, int flags, const ProfMessage* const message)
1188 {
1189     if (prefs_get_boolean(PREF_CORRECTION_ALLOW) && message->replace_id) {
1190         _win_correct(window, message->plain, message->id, message->replace_id, message->from_jid->fulljid);
1191     } else {
1192         _win_printf(window, show_char, 0, message->timestamp, flags | NO_ME, THEME_TEXT_THEM, message->from_jid->resourcepart, message->from_jid->fulljid, message->id, "%s", message->plain);
1193     }
1194 
1195     inp_nonblocking(TRUE);
1196 }
1197 
1198 void
win_print_outgoing_muc_msg(ProfWin * window,char * show_char,const char * const me,const char * const id,const char * const replace_id,const char * const message)1199 win_print_outgoing_muc_msg(ProfWin* window, char* show_char, const char* const me, const char* const id, const char* const replace_id, const char* const message)
1200 {
1201     GDateTime* timestamp = g_date_time_new_now_local();
1202 
1203     if (prefs_get_boolean(PREF_CORRECTION_ALLOW) && replace_id) {
1204         _win_correct(window, message, id, replace_id, me);
1205     } else {
1206         _win_printf(window, show_char, 0, timestamp, 0, THEME_TEXT_ME, me, me, id, "%s", message);
1207     }
1208 
1209     inp_nonblocking(TRUE);
1210     g_date_time_unref(timestamp);
1211 }
1212 
1213 void
win_print_outgoing(ProfWin * window,const char * show_char,const char * const id,const char * const replace_id,const char * const message)1214 win_print_outgoing(ProfWin* window, const char* show_char, const char* const id, const char* const replace_id, const char* const message)
1215 {
1216     GDateTime* timestamp = g_date_time_new_now_local();
1217 
1218     const char* myjid = connection_get_fulljid();
1219     if (replace_id) {
1220         _win_correct(window, message, id, replace_id, myjid);
1221     } else {
1222         _win_printf(window, show_char, 0, timestamp, 0, THEME_TEXT_ME, "me", myjid, id, "%s", message);
1223     }
1224 
1225     inp_nonblocking(TRUE);
1226     g_date_time_unref(timestamp);
1227 }
1228 
1229 void
win_print_history(ProfWin * window,const ProfMessage * const message)1230 win_print_history(ProfWin* window, const ProfMessage* const message)
1231 {
1232     g_date_time_ref(message->timestamp);
1233 
1234     char* display_name;
1235     int flags = 0;
1236     const char* jid = connection_get_fulljid();
1237     Jid* jidp = jid_create(jid);
1238 
1239     if (g_strcmp0(jidp->barejid, message->from_jid->barejid) == 0) {
1240         display_name = strdup("me");
1241     } else {
1242         display_name = roster_get_msg_display_name(message->from_jid->barejid, message->from_jid->resourcepart);
1243     }
1244 
1245     jid_destroy(jidp);
1246 
1247     buffer_append(window->layout->buffer, "-", 0, message->timestamp, flags, THEME_TEXT_HISTORY, display_name, NULL, message->plain, NULL, NULL);
1248     _win_print_internal(window, "-", 0, message->timestamp, flags, THEME_TEXT_HISTORY, display_name, message->plain, NULL);
1249 
1250     free(display_name);
1251 
1252     inp_nonblocking(TRUE);
1253     g_date_time_unref(message->timestamp);
1254 }
1255 
1256 void
win_print(ProfWin * window,theme_item_t theme_item,const char * show_char,const char * const message,...)1257 win_print(ProfWin* window, theme_item_t theme_item, const char* show_char, const char* const message, ...)
1258 {
1259     GDateTime* timestamp = g_date_time_new_now_local();
1260 
1261     va_list arg;
1262     va_start(arg, message);
1263     GString* fmt_msg = g_string_new(NULL);
1264     g_string_vprintf(fmt_msg, message, arg);
1265 
1266     buffer_append(window->layout->buffer, show_char, 0, timestamp, NO_EOL, theme_item, "", NULL, fmt_msg->str, NULL, NULL);
1267     _win_print_internal(window, show_char, 0, timestamp, NO_EOL, theme_item, "", fmt_msg->str, NULL);
1268 
1269     inp_nonblocking(TRUE);
1270     g_date_time_unref(timestamp);
1271 
1272     g_string_free(fmt_msg, TRUE);
1273     va_end(arg);
1274 }
1275 
1276 void
win_println(ProfWin * window,theme_item_t theme_item,const char * show_char,const char * const message,...)1277 win_println(ProfWin* window, theme_item_t theme_item, const char* show_char, const char* const message, ...)
1278 {
1279     GDateTime* timestamp = g_date_time_new_now_local();
1280 
1281     va_list arg;
1282     va_start(arg, message);
1283     GString* fmt_msg = g_string_new(NULL);
1284     g_string_vprintf(fmt_msg, message, arg);
1285 
1286     buffer_append(window->layout->buffer, show_char, 0, timestamp, 0, theme_item, "", NULL, fmt_msg->str, NULL, NULL);
1287     _win_print_internal(window, show_char, 0, timestamp, 0, theme_item, "", fmt_msg->str, NULL);
1288 
1289     inp_nonblocking(TRUE);
1290     g_date_time_unref(timestamp);
1291 
1292     g_string_free(fmt_msg, TRUE);
1293     va_end(arg);
1294 }
1295 
1296 void
win_println_indent(ProfWin * window,int pad,const char * const message,...)1297 win_println_indent(ProfWin* window, int pad, const char* const message, ...)
1298 {
1299     GDateTime* timestamp = g_date_time_new_now_local();
1300 
1301     va_list arg;
1302     va_start(arg, message);
1303     GString* fmt_msg = g_string_new(NULL);
1304     g_string_vprintf(fmt_msg, message, arg);
1305 
1306     buffer_append(window->layout->buffer, "-", pad, timestamp, 0, THEME_DEFAULT, "", NULL, fmt_msg->str, NULL, NULL);
1307     _win_print_internal(window, "-", pad, timestamp, 0, THEME_DEFAULT, "", fmt_msg->str, NULL);
1308 
1309     inp_nonblocking(TRUE);
1310     g_date_time_unref(timestamp);
1311 
1312     g_string_free(fmt_msg, TRUE);
1313     va_end(arg);
1314 }
1315 
1316 void
win_append(ProfWin * window,theme_item_t theme_item,const char * const message,...)1317 win_append(ProfWin* window, theme_item_t theme_item, const char* const message, ...)
1318 {
1319     GDateTime* timestamp = g_date_time_new_now_local();
1320 
1321     va_list arg;
1322     va_start(arg, message);
1323     GString* fmt_msg = g_string_new(NULL);
1324     g_string_vprintf(fmt_msg, message, arg);
1325 
1326     buffer_append(window->layout->buffer, "-", 0, timestamp, NO_DATE | NO_EOL, theme_item, "", NULL, fmt_msg->str, NULL, NULL);
1327     _win_print_internal(window, "-", 0, timestamp, NO_DATE | NO_EOL, theme_item, "", fmt_msg->str, NULL);
1328 
1329     inp_nonblocking(TRUE);
1330     g_date_time_unref(timestamp);
1331 
1332     g_string_free(fmt_msg, TRUE);
1333     va_end(arg);
1334 }
1335 
1336 void
win_appendln(ProfWin * window,theme_item_t theme_item,const char * const message,...)1337 win_appendln(ProfWin* window, theme_item_t theme_item, const char* const message, ...)
1338 {
1339     GDateTime* timestamp = g_date_time_new_now_local();
1340 
1341     va_list arg;
1342     va_start(arg, message);
1343     GString* fmt_msg = g_string_new(NULL);
1344     g_string_vprintf(fmt_msg, message, arg);
1345 
1346     buffer_append(window->layout->buffer, "-", 0, timestamp, NO_DATE, theme_item, "", NULL, fmt_msg->str, NULL, NULL);
1347     _win_print_internal(window, "-", 0, timestamp, NO_DATE, theme_item, "", fmt_msg->str, NULL);
1348 
1349     inp_nonblocking(TRUE);
1350     g_date_time_unref(timestamp);
1351 
1352     g_string_free(fmt_msg, TRUE);
1353     va_end(arg);
1354 }
1355 
1356 void
win_append_highlight(ProfWin * window,theme_item_t theme_item,const char * const message,...)1357 win_append_highlight(ProfWin* window, theme_item_t theme_item, const char* const message, ...)
1358 {
1359     GDateTime* timestamp = g_date_time_new_now_local();
1360 
1361     va_list arg;
1362     va_start(arg, message);
1363     GString* fmt_msg = g_string_new(NULL);
1364     g_string_vprintf(fmt_msg, message, arg);
1365 
1366     buffer_append(window->layout->buffer, "-", 0, timestamp, NO_DATE | NO_ME | NO_EOL, theme_item, "", NULL, fmt_msg->str, NULL, NULL);
1367     _win_print_internal(window, "-", 0, timestamp, NO_DATE | NO_ME | NO_EOL, theme_item, "", fmt_msg->str, NULL);
1368 
1369     inp_nonblocking(TRUE);
1370     g_date_time_unref(timestamp);
1371 
1372     g_string_free(fmt_msg, TRUE);
1373     va_end(arg);
1374 }
1375 
1376 void
win_appendln_highlight(ProfWin * window,theme_item_t theme_item,const char * const message,...)1377 win_appendln_highlight(ProfWin* window, theme_item_t theme_item, const char* const message, ...)
1378 {
1379     GDateTime* timestamp = g_date_time_new_now_local();
1380 
1381     va_list arg;
1382     va_start(arg, message);
1383     GString* fmt_msg = g_string_new(NULL);
1384     g_string_vprintf(fmt_msg, message, arg);
1385 
1386     buffer_append(window->layout->buffer, "-", 0, timestamp, NO_DATE | NO_ME, theme_item, "", NULL, fmt_msg->str, NULL, NULL);
1387     _win_print_internal(window, "-", 0, timestamp, NO_DATE | NO_ME, theme_item, "", fmt_msg->str, NULL);
1388 
1389     inp_nonblocking(TRUE);
1390     g_date_time_unref(timestamp);
1391 
1392     g_string_free(fmt_msg, TRUE);
1393     va_end(arg);
1394 }
1395 
1396 void
win_print_http_transfer(ProfWin * window,const char * const message,char * url)1397 win_print_http_transfer(ProfWin* window, const char* const message, char* url)
1398 {
1399     win_print_outgoing_with_receipt(window, "!", NULL, message, url, NULL);
1400 }
1401 
1402 void
win_print_outgoing_with_receipt(ProfWin * window,const char * show_char,const char * const from,const char * const message,char * id,const char * const replace_id)1403 win_print_outgoing_with_receipt(ProfWin* window, const char* show_char, const char* const from, const char* const message, char* id, const char* const replace_id)
1404 {
1405     GDateTime* time = g_date_time_new_now_local();
1406 
1407     DeliveryReceipt* receipt = malloc(sizeof(struct delivery_receipt_t));
1408     receipt->received = FALSE;
1409 
1410     const char* myjid = connection_get_fulljid();
1411     if (replace_id) {
1412         _win_correct(window, message, id, replace_id, myjid);
1413         free(receipt); //TODO: probably we should use this in _win_correct()
1414     } else {
1415         buffer_append(window->layout->buffer, show_char, 0, time, 0, THEME_TEXT_ME, from, myjid, message, receipt, id);
1416         _win_print_internal(window, show_char, 0, time, 0, THEME_TEXT_ME, from, message, receipt);
1417     }
1418 
1419     // TODO: cross-reference.. this should be replaced by a real event-based system
1420     inp_nonblocking(TRUE);
1421     g_date_time_unref(time);
1422 }
1423 
1424 void
win_mark_received(ProfWin * window,const char * const id)1425 win_mark_received(ProfWin* window, const char* const id)
1426 {
1427     gboolean received = buffer_mark_received(window->layout->buffer, id);
1428     if (received) {
1429         win_redraw(window);
1430     }
1431 }
1432 
1433 void
win_update_entry_message(ProfWin * window,const char * const id,const char * const message)1434 win_update_entry_message(ProfWin* window, const char* const id, const char* const message)
1435 {
1436     ProfBuffEntry* entry = buffer_get_entry_by_id(window->layout->buffer, id);
1437     if (entry) {
1438         free(entry->message);
1439         entry->message = strdup(message);
1440         win_redraw(window);
1441     }
1442 }
1443 
1444 void
win_remove_entry_message(ProfWin * window,const char * const id)1445 win_remove_entry_message(ProfWin* window, const char* const id)
1446 {
1447     buffer_remove_entry_by_id(window->layout->buffer, id);
1448     win_redraw(window);
1449 }
1450 
1451 void
win_newline(ProfWin * window)1452 win_newline(ProfWin* window)
1453 {
1454     win_appendln(window, THEME_DEFAULT, "");
1455 }
1456 
1457 static void
_win_printf(ProfWin * window,const char * show_char,int pad_indent,GDateTime * timestamp,int flags,theme_item_t theme_item,const char * const display_from,const char * const from_jid,const char * const message_id,const char * const message,...)1458 _win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...)
1459 {
1460     if (timestamp == NULL) {
1461         timestamp = g_date_time_new_now_local();
1462     } else {
1463         g_date_time_ref(timestamp);
1464     }
1465 
1466     va_list arg;
1467     va_start(arg, message);
1468     GString* fmt_msg = g_string_new(NULL);
1469     g_string_vprintf(fmt_msg, message, arg);
1470 
1471     buffer_append(window->layout->buffer, show_char, pad_indent, timestamp, flags, theme_item, display_from, from_jid, fmt_msg->str, NULL, message_id);
1472 
1473     _win_print_internal(window, show_char, pad_indent, timestamp, flags, theme_item, display_from, fmt_msg->str, NULL);
1474 
1475     inp_nonblocking(TRUE);
1476     g_date_time_unref(timestamp);
1477 
1478     g_string_free(fmt_msg, TRUE);
1479     va_end(arg);
1480 }
1481 
1482 static void
_win_print_internal(ProfWin * window,const char * show_char,int pad_indent,GDateTime * time,int flags,theme_item_t theme_item,const char * const from,const char * const message,DeliveryReceipt * receipt)1483 _win_print_internal(ProfWin* window, const char* show_char, int pad_indent, GDateTime* time,
1484                     int flags, theme_item_t theme_item, const char* const from, const char* const message, DeliveryReceipt* receipt)
1485 {
1486     // flags : 1st bit =  0/1 - me/not me. define: NO_ME
1487     //         2nd bit =  0/1 - date/no date. define: NO_DATE
1488     //         3rd bit =  0/1 - eol/no eol. define: NO_EOL
1489     //         4th bit =  0/1 - color from/no color from. define: NO_COLOUR_FROM
1490     //         5th bit =  0/1 - color date/no date. define: NO_COLOUR_DATE
1491     //         6th bit =  0/1 - trusted/untrusted. define: UNTRUSTED
1492     gboolean me_message = FALSE;
1493     int offset = 0;
1494     int colour = theme_attrs(THEME_ME);
1495     size_t indent = 0;
1496 
1497     char* time_pref = NULL;
1498     switch (window->type) {
1499     case WIN_CHAT:
1500         time_pref = prefs_get_string(PREF_TIME_CHAT);
1501         break;
1502     case WIN_MUC:
1503         time_pref = prefs_get_string(PREF_TIME_MUC);
1504         break;
1505     case WIN_CONFIG:
1506         time_pref = prefs_get_string(PREF_TIME_CONFIG);
1507         break;
1508     case WIN_PRIVATE:
1509         time_pref = prefs_get_string(PREF_TIME_PRIVATE);
1510         break;
1511     case WIN_XML:
1512         time_pref = prefs_get_string(PREF_TIME_XMLCONSOLE);
1513         break;
1514     default:
1515         time_pref = prefs_get_string(PREF_TIME_CONSOLE);
1516         break;
1517     }
1518 
1519     gchar* date_fmt = NULL;
1520     if (g_strcmp0(time_pref, "off") == 0 || time == NULL) {
1521         date_fmt = g_strdup("");
1522     } else {
1523         date_fmt = g_date_time_format(time, time_pref);
1524     }
1525     g_free(time_pref);
1526     assert(date_fmt != NULL);
1527 
1528     if (strlen(date_fmt) != 0) {
1529         indent = 3 + strlen(date_fmt);
1530     }
1531 
1532     if ((flags & NO_DATE) == 0) {
1533         if (date_fmt && strlen(date_fmt)) {
1534             if ((flags & NO_COLOUR_DATE) == 0) {
1535                 wbkgdset(window->layout->win, theme_attrs(THEME_TIME));
1536                 wattron(window->layout->win, theme_attrs(THEME_TIME));
1537             }
1538             wprintw(window->layout->win, "%s %s ", date_fmt, show_char);
1539             if ((flags & NO_COLOUR_DATE) == 0) {
1540                 wattroff(window->layout->win, theme_attrs(THEME_TIME));
1541             }
1542         }
1543     }
1544 
1545     if (from && strlen(from) > 0) {
1546         if (flags & NO_ME) {
1547             colour = theme_attrs(THEME_THEM);
1548         }
1549 
1550         char* color_pref = prefs_get_string(PREF_COLOR_NICK);
1551         if (color_pref != NULL && (strcmp(color_pref, "false") != 0)) {
1552             if (flags & NO_ME || (!(flags & NO_ME) && prefs_get_boolean(PREF_COLOR_NICK_OWN))) {
1553                 colour = theme_hash_attrs(from);
1554             }
1555         }
1556         g_free(color_pref);
1557 
1558         if (flags & NO_COLOUR_FROM) {
1559             colour = 0;
1560         }
1561 
1562         if (receipt && !receipt->received) {
1563             colour = theme_attrs(THEME_RECEIPT_SENT);
1564         }
1565 
1566         wbkgdset(window->layout->win, colour);
1567         wattron(window->layout->win, colour);
1568         if (strncmp(message, "/me ", 4) == 0) {
1569             wprintw(window->layout->win, "*%s ", from);
1570             offset = 4;
1571             me_message = TRUE;
1572         } else {
1573             wprintw(window->layout->win, "%s: ", from);
1574             wattroff(window->layout->win, colour);
1575         }
1576     }
1577 
1578     if (!me_message) {
1579         if (receipt && !receipt->received) {
1580             wbkgdset(window->layout->win, theme_attrs(THEME_RECEIPT_SENT));
1581             wattron(window->layout->win, theme_attrs(THEME_RECEIPT_SENT));
1582         } else if (flags & UNTRUSTED) {
1583             wbkgdset(window->layout->win, theme_attrs(THEME_UNTRUSTED));
1584             wattron(window->layout->win, theme_attrs(THEME_UNTRUSTED));
1585         } else {
1586             wbkgdset(window->layout->win, theme_attrs(theme_item));
1587             wattron(window->layout->win, theme_attrs(theme_item));
1588         }
1589     }
1590 
1591     if (prefs_get_boolean(PREF_WRAP)) {
1592         _win_print_wrapped(window->layout->win, message + offset, indent, pad_indent);
1593     } else {
1594         wprintw(window->layout->win, "%s", message + offset);
1595     }
1596 
1597     if ((flags & NO_EOL) == 0) {
1598         int curx = getcurx(window->layout->win);
1599         if (curx != 0) {
1600             wprintw(window->layout->win, "\n");
1601         }
1602     }
1603 
1604     if (me_message) {
1605         wattroff(window->layout->win, colour);
1606     } else {
1607         if (receipt && !receipt->received) {
1608             wattroff(window->layout->win, theme_attrs(THEME_RECEIPT_SENT));
1609         } else {
1610             wattroff(window->layout->win, theme_attrs(theme_item));
1611         }
1612     }
1613 
1614     g_free(date_fmt);
1615 }
1616 
1617 static void
_win_indent(WINDOW * win,int size)1618 _win_indent(WINDOW* win, int size)
1619 {
1620     for (int i = 0; i < size; i++) {
1621         waddch(win, ' ');
1622     }
1623 }
1624 
1625 static void
_win_print_wrapped(WINDOW * win,const char * const message,size_t indent,int pad_indent)1626 _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pad_indent)
1627 {
1628     int starty = getcury(win);
1629     int wordi = 0;
1630     char* word = malloc(strlen(message) + 1);
1631 
1632     gchar* curr_ch = g_utf8_offset_to_pointer(message, 0);
1633 
1634     while (*curr_ch != '\0') {
1635 
1636         // handle space
1637         if (*curr_ch == ' ') {
1638             waddch(win, ' ');
1639             curr_ch = g_utf8_next_char(curr_ch);
1640 
1641             // handle newline
1642         } else if (*curr_ch == '\n') {
1643             waddch(win, '\n');
1644             _win_indent(win, indent + pad_indent);
1645             curr_ch = g_utf8_next_char(curr_ch);
1646 
1647             // handle word
1648         } else {
1649             wordi = 0;
1650             int wordlen = 0;
1651             while (*curr_ch != ' ' && *curr_ch != '\n' && *curr_ch != '\0') {
1652                 size_t ch_len = mbrlen(curr_ch, MB_CUR_MAX, NULL);
1653                 if ((ch_len == (size_t)-2) || (ch_len == (size_t)-1)) {
1654                     curr_ch++;
1655                     continue;
1656                 }
1657                 int offset = 0;
1658                 while (offset < ch_len) {
1659                     word[wordi++] = curr_ch[offset++];
1660                 }
1661                 curr_ch = g_utf8_next_char(curr_ch);
1662             }
1663             word[wordi] = '\0';
1664             wordlen = utf8_display_len(word);
1665 
1666             int curx = getcurx(win);
1667             int cury;
1668             int maxx = getmaxx(win);
1669 
1670             // wrap required
1671             if (curx + wordlen > maxx) {
1672                 int linelen = maxx - (indent + pad_indent);
1673 
1674                 // word larger than line
1675                 if (wordlen > linelen) {
1676                     gchar* word_ch = g_utf8_offset_to_pointer(word, 0);
1677                     while (*word_ch != '\0') {
1678                         curx = getcurx(win);
1679                         cury = getcury(win);
1680                         gboolean firstline = cury == starty;
1681 
1682                         if (firstline && curx < indent) {
1683                             _win_indent(win, indent);
1684                         }
1685                         if (!firstline && curx < (indent + pad_indent)) {
1686                             _win_indent(win, indent + pad_indent);
1687                         }
1688 
1689                         gchar copy[wordi + 1];
1690                         g_utf8_strncpy(copy, word_ch, 1);
1691                         waddstr(win, copy);
1692 
1693                         word_ch = g_utf8_next_char(word_ch);
1694                     }
1695 
1696                     // newline and print word
1697                 } else {
1698                     waddch(win, '\n');
1699                     curx = getcurx(win);
1700                     cury = getcury(win);
1701                     gboolean firstline = cury == starty;
1702 
1703                     if (firstline && curx < indent) {
1704                         _win_indent(win, indent);
1705                     }
1706                     if (!firstline && curx < (indent + pad_indent)) {
1707                         _win_indent(win, indent + pad_indent);
1708                     }
1709                     waddstr(win, word);
1710                 }
1711 
1712                 // no wrap required
1713             } else {
1714                 curx = getcurx(win);
1715                 cury = getcury(win);
1716                 gboolean firstline = cury == starty;
1717 
1718                 if (firstline && curx < indent) {
1719                     _win_indent(win, indent);
1720                 }
1721                 if (!firstline && curx < (indent + pad_indent)) {
1722                     _win_indent(win, indent + pad_indent);
1723                 }
1724                 waddstr(win, word);
1725             }
1726         }
1727 
1728         // consume first space of next line
1729         int curx = getcurx(win);
1730         int cury = getcury(win);
1731         gboolean firstline = (cury == starty);
1732 
1733         if (!firstline && curx == 0 && *curr_ch == ' ') {
1734             curr_ch = g_utf8_next_char(curr_ch);
1735         }
1736     }
1737 
1738     free(word);
1739 }
1740 
1741 void
win_print_trackbar(ProfWin * window)1742 win_print_trackbar(ProfWin* window)
1743 {
1744     int cols = getmaxx(window->layout->win);
1745 
1746     wbkgdset(window->layout->win, theme_attrs(THEME_TRACKBAR));
1747     wattron(window->layout->win, theme_attrs(THEME_TRACKBAR));
1748 
1749     for (int i = 1; i <= cols; i++) {
1750         wprintw(window->layout->win, "-");
1751     }
1752 
1753     wattroff(window->layout->win, theme_attrs(THEME_TRACKBAR));
1754 }
1755 
1756 void
win_redraw(ProfWin * window)1757 win_redraw(ProfWin* window)
1758 {
1759     int size;
1760     werase(window->layout->win);
1761     size = buffer_size(window->layout->buffer);
1762 
1763     for (int i = 0; i < size; i++) {
1764         ProfBuffEntry* e = buffer_get_entry(window->layout->buffer, i);
1765 
1766         if (e->display_from == NULL && e->message && e->message[0] == '-') {
1767             // just an indicator to print the trackbar/separator not the actual message
1768             win_print_trackbar(window);
1769         } else {
1770             // regular thing to print
1771             _win_print_internal(window, e->show_char, e->pad_indent, e->time, e->flags, e->theme_item, e->display_from, e->message, e->receipt);
1772         }
1773     }
1774 }
1775 
1776 gboolean
win_has_active_subwin(ProfWin * window)1777 win_has_active_subwin(ProfWin* window)
1778 {
1779     if (window->layout->type == LAYOUT_SPLIT) {
1780         ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
1781         return (layout->subwin != NULL);
1782     } else {
1783         return FALSE;
1784     }
1785 }
1786 
1787 gboolean
win_notify_remind(ProfWin * window)1788 win_notify_remind(ProfWin* window)
1789 {
1790     switch (window->type) {
1791     case WIN_CHAT:
1792     {
1793         ProfChatWin* chatwin = (ProfChatWin*)window;
1794         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
1795 
1796         if (prefs_get_boolean(PREF_NOTIFY_CHAT) && chatwin->unread > 0) {
1797             return TRUE;
1798         } else {
1799             return FALSE;
1800         }
1801     }
1802     case WIN_MUC:
1803     {
1804         ProfMucWin* mucwin = (ProfMucWin*)window;
1805         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
1806 
1807         return prefs_do_room_notify_mention(mucwin->roomjid, mucwin->unread, mucwin->unread_mentions, mucwin->unread_triggers);
1808     }
1809     case WIN_PRIVATE:
1810     {
1811         ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
1812         assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
1813 
1814         if (prefs_get_boolean(PREF_NOTIFY_CHAT) && privatewin->unread > 0) {
1815             return TRUE;
1816         } else {
1817             return FALSE;
1818         }
1819     }
1820     default:
1821         return FALSE;
1822     }
1823 }
1824 
1825 int
win_unread(ProfWin * window)1826 win_unread(ProfWin* window)
1827 {
1828     if (window->type == WIN_CHAT) {
1829         ProfChatWin* chatwin = (ProfChatWin*)window;
1830         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
1831         return chatwin->unread;
1832     } else if (window->type == WIN_MUC) {
1833         ProfMucWin* mucwin = (ProfMucWin*)window;
1834         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
1835         return mucwin->unread;
1836     } else if (window->type == WIN_PRIVATE) {
1837         ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
1838         assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
1839         return privatewin->unread;
1840     } else {
1841         return 0;
1842     }
1843 }
1844 
1845 gboolean
win_has_attention(ProfWin * window)1846 win_has_attention(ProfWin* window)
1847 {
1848     if (window->type == WIN_CHAT) {
1849         ProfChatWin* chatwin = (ProfChatWin*)window;
1850         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
1851         return chatwin->has_attention;
1852     } else if (window->type == WIN_MUC) {
1853         ProfMucWin* mucwin = (ProfMucWin*)window;
1854         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
1855         return mucwin->has_attention;
1856     }
1857     return FALSE;
1858 }
1859 
1860 gboolean
win_toggle_attention(ProfWin * window)1861 win_toggle_attention(ProfWin* window)
1862 {
1863     if (window->type == WIN_CHAT) {
1864         ProfChatWin* chatwin = (ProfChatWin*)window;
1865         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
1866         chatwin->has_attention = !chatwin->has_attention;
1867         return chatwin->has_attention;
1868     } else if (window->type == WIN_MUC) {
1869         ProfMucWin* mucwin = (ProfMucWin*)window;
1870         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
1871         mucwin->has_attention = !mucwin->has_attention;
1872         return mucwin->has_attention;
1873     }
1874     return FALSE;
1875 }
1876 
1877 
1878 void
win_sub_print(WINDOW * win,char * msg,gboolean newline,gboolean wrap,int indent)1879 win_sub_print(WINDOW* win, char* msg, gboolean newline, gboolean wrap, int indent)
1880 {
1881     int maxx = getmaxx(win);
1882     int curx = getcurx(win);
1883     int cury = getcury(win);
1884 
1885     if (wrap) {
1886         _win_print_wrapped(win, msg, 1, indent);
1887     } else {
1888         waddnstr(win, msg, maxx - curx);
1889     }
1890 
1891     if (newline) {
1892         wmove(win, cury + 1, 0);
1893     }
1894 }
1895 
1896 void
win_sub_newline_lazy(WINDOW * win)1897 win_sub_newline_lazy(WINDOW* win)
1898 {
1899     int curx;
1900 
1901     if (win == NULL) {
1902         return;
1903     }
1904     curx = getcurx(win);
1905     if (curx > 0) {
1906         int cury = getcury(win);
1907         wmove(win, cury + 1, 0);
1908     }
1909 }
1910 
1911 void
win_command_list_error(ProfWin * window,const char * const error)1912 win_command_list_error(ProfWin* window, const char* const error)
1913 {
1914     assert(window != NULL);
1915 
1916     win_println(window, THEME_ERROR, "!", "Error retrieving command list: %s", error);
1917 }
1918 
1919 void
win_command_exec_error(ProfWin * window,const char * const command,const char * const error,...)1920 win_command_exec_error(ProfWin* window, const char* const command, const char* const error, ...)
1921 {
1922     assert(window != NULL);
1923     va_list arg;
1924     va_start(arg, error);
1925     GString* msg = g_string_new(NULL);
1926     g_string_vprintf(msg, error, arg);
1927 
1928     win_println(window, THEME_ERROR, "!", "Error executing command %s: %s", command, msg->str);
1929 
1930     g_string_free(msg, TRUE);
1931     va_end(arg);
1932 }
1933 
1934 void
win_handle_command_list(ProfWin * window,GSList * cmds)1935 win_handle_command_list(ProfWin* window, GSList* cmds)
1936 {
1937     assert(window != NULL);
1938 
1939     if (cmds) {
1940         win_println(window, THEME_DEFAULT, "!", "Ad hoc commands:");
1941         GSList* curr_cmd = cmds;
1942         while (curr_cmd) {
1943             const char* cmd = curr_cmd->data;
1944             win_println(window, THEME_DEFAULT, "!", "  %s", cmd);
1945             curr_cmd = g_slist_next(curr_cmd);
1946         }
1947         win_println(window, THEME_DEFAULT, "!", "");
1948     } else {
1949         win_println(window, THEME_DEFAULT, "!", "No commands found");
1950         win_println(window, THEME_DEFAULT, "!", "");
1951     }
1952 }
1953 
1954 void
win_handle_command_exec_status(ProfWin * window,const char * const command,const char * const value)1955 win_handle_command_exec_status(ProfWin* window, const char* const command, const char* const value)
1956 {
1957     assert(window != NULL);
1958     win_println(window, THEME_DEFAULT, "!", "%s %s", command, value);
1959 }
1960 
1961 void
win_handle_command_exec_result_note(ProfWin * window,const char * const type,const char * const value)1962 win_handle_command_exec_result_note(ProfWin* window, const char* const type, const char* const value)
1963 {
1964     assert(window != NULL);
1965     win_println(window, THEME_DEFAULT, "!", value);
1966 }
1967 
1968 void
win_insert_last_read_position_marker(ProfWin * window,char * id)1969 win_insert_last_read_position_marker(ProfWin* window, char* id)
1970 {
1971     int size;
1972     size = buffer_size(window->layout->buffer);
1973 
1974     // TODO: this is somewhat costly. We should improve this later.
1975     // check if we already have a separator present
1976     for (int i = 0; i < size; i++) {
1977         ProfBuffEntry* e = buffer_get_entry(window->layout->buffer, i);
1978 
1979         // if yes, don't print a new one
1980         if (e->id && (g_strcmp0(e->id, id) == 0)) {
1981             return;
1982         }
1983     }
1984 
1985     GDateTime* time = g_date_time_new_now_local();
1986 
1987     // the trackbar/separator will actually be print in win_redraw().
1988     // this only puts it in the buffer and win_redraw() will interpret it.
1989     // so that we have the correct length even when resizing.
1990     buffer_append(window->layout->buffer, " ", 0, time, 0, THEME_TEXT, NULL, NULL, "-", NULL, id);
1991     win_redraw(window);
1992 
1993     g_date_time_unref(time);
1994 }
1995