1 /*
2  * window_list.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 <string.h>
40 #include <assert.h>
41 #include <stdlib.h>
42 
43 #include <glib.h>
44 
45 #include "common.h"
46 #include "config/preferences.h"
47 #include "config/theme.h"
48 #include "plugins/plugins.h"
49 #include "ui/ui.h"
50 #include "ui/window_list.h"
51 #include "xmpp/xmpp.h"
52 #include "xmpp/roster_list.h"
53 #include "tools/http_upload.h"
54 
55 #ifdef HAVE_OMEMO
56 #include "omemo/omemo.h"
57 #endif
58 
59 static GHashTable* windows;
60 static int current;
61 static Autocomplete wins_ac;
62 static Autocomplete wins_close_ac;
63 
64 static int _wins_cmp_num(gconstpointer a, gconstpointer b);
65 static int _wins_get_next_available_num(GList* used);
66 
67 void
wins_init(void)68 wins_init(void)
69 {
70     windows = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)win_free);
71 
72     ProfWin* console = win_create_console();
73     g_hash_table_insert(windows, GINT_TO_POINTER(1), console);
74 
75     current = 1;
76 
77     wins_ac = autocomplete_new();
78     autocomplete_add(wins_ac, "console");
79 
80     wins_close_ac = autocomplete_new();
81     autocomplete_add(wins_close_ac, "all");
82     autocomplete_add(wins_close_ac, "read");
83 }
84 
85 ProfWin*
wins_get_console(void)86 wins_get_console(void)
87 {
88     return g_hash_table_lookup(windows, GINT_TO_POINTER(1));
89 }
90 
91 gboolean
wins_chat_exists(const char * const barejid)92 wins_chat_exists(const char* const barejid)
93 {
94     ProfChatWin* chatwin = wins_get_chat(barejid);
95     return (chatwin != NULL);
96 }
97 
98 ProfChatWin*
wins_get_chat(const char * const barejid)99 wins_get_chat(const char* const barejid)
100 {
101     GList* values = g_hash_table_get_values(windows);
102     GList* curr = values;
103 
104     while (curr) {
105         ProfWin* window = curr->data;
106         if (window->type == WIN_CHAT) {
107             ProfChatWin* chatwin = (ProfChatWin*)window;
108             if (g_strcmp0(chatwin->barejid, barejid) == 0) {
109                 g_list_free(values);
110                 return chatwin;
111             }
112         }
113         curr = g_list_next(curr);
114     }
115 
116     g_list_free(values);
117     return NULL;
118 }
119 
120 static gint
_cmp_unsubscribed_wins(ProfChatWin * a,ProfChatWin * b)121 _cmp_unsubscribed_wins(ProfChatWin* a, ProfChatWin* b)
122 {
123     return g_strcmp0(a->barejid, b->barejid);
124 }
125 
126 GList*
wins_get_chat_unsubscribed(void)127 wins_get_chat_unsubscribed(void)
128 {
129     GList* result = NULL;
130     GList* values = g_hash_table_get_values(windows);
131     GList* curr = values;
132 
133     while (curr) {
134         ProfWin* window = curr->data;
135         if (window->type == WIN_CHAT) {
136             ProfChatWin* chatwin = (ProfChatWin*)window;
137             PContact contact = roster_get_contact(chatwin->barejid);
138             if (contact == NULL) {
139                 result = g_list_insert_sorted(result, chatwin, (GCompareFunc)_cmp_unsubscribed_wins);
140             }
141         }
142         curr = g_list_next(curr);
143     }
144 
145     g_list_free(values);
146     return result;
147 }
148 
149 ProfConfWin*
wins_get_conf(const char * const roomjid)150 wins_get_conf(const char* const roomjid)
151 {
152     GList* values = g_hash_table_get_values(windows);
153     GList* curr = values;
154 
155     while (curr) {
156         ProfWin* window = curr->data;
157         if (window->type == WIN_CONFIG) {
158             ProfConfWin* confwin = (ProfConfWin*)window;
159             if (g_strcmp0(confwin->roomjid, roomjid) == 0) {
160                 g_list_free(values);
161                 return confwin;
162             }
163         }
164         curr = g_list_next(curr);
165     }
166 
167     g_list_free(values);
168     return NULL;
169 }
170 
171 ProfMucWin*
wins_get_muc(const char * const roomjid)172 wins_get_muc(const char* const roomjid)
173 {
174     GList* values = g_hash_table_get_values(windows);
175     GList* curr = values;
176 
177     while (curr) {
178         ProfWin* window = curr->data;
179         if (window->type == WIN_MUC) {
180             ProfMucWin* mucwin = (ProfMucWin*)window;
181             if (g_strcmp0(mucwin->roomjid, roomjid) == 0) {
182                 g_list_free(values);
183                 return mucwin;
184             }
185         }
186         curr = g_list_next(curr);
187     }
188 
189     g_list_free(values);
190     return NULL;
191 }
192 
193 ProfPrivateWin*
wins_get_private(const char * const fulljid)194 wins_get_private(const char* const fulljid)
195 {
196     GList* values = g_hash_table_get_values(windows);
197     GList* curr = values;
198 
199     while (curr) {
200         ProfWin* window = curr->data;
201         if (window->type == WIN_PRIVATE) {
202             ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
203             if (g_strcmp0(privatewin->fulljid, fulljid) == 0) {
204                 g_list_free(values);
205                 return privatewin;
206             }
207         }
208         curr = g_list_next(curr);
209     }
210 
211     g_list_free(values);
212     return NULL;
213 }
214 
215 ProfPluginWin*
wins_get_plugin(const char * const tag)216 wins_get_plugin(const char* const tag)
217 {
218     GList* values = g_hash_table_get_values(windows);
219     GList* curr = values;
220 
221     while (curr) {
222         ProfWin* window = curr->data;
223         if (window->type == WIN_PLUGIN) {
224             ProfPluginWin* pluginwin = (ProfPluginWin*)window;
225             if (g_strcmp0(pluginwin->tag, tag) == 0) {
226                 g_list_free(values);
227                 return pluginwin;
228             }
229         }
230         curr = g_list_next(curr);
231     }
232 
233     g_list_free(values);
234     return NULL;
235 }
236 
237 void
wins_close_plugin(char * tag)238 wins_close_plugin(char* tag)
239 {
240     ProfWin* toclose = wins_get_by_string(tag);
241     if (toclose == NULL) {
242         return;
243     }
244 
245     int index = wins_get_num(toclose);
246     ui_close_win(index);
247 
248     wins_tidy();
249 }
250 
251 GList*
wins_get_private_chats(const char * const roomjid)252 wins_get_private_chats(const char* const roomjid)
253 {
254     GList* result = NULL;
255     GString* prefix = g_string_new(roomjid);
256     g_string_append(prefix, "/");
257     GList* values = g_hash_table_get_values(windows);
258     GList* curr = values;
259 
260     while (curr) {
261         ProfWin* window = curr->data;
262         if (window->type == WIN_PRIVATE) {
263             ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
264             if (roomjid == NULL || g_str_has_prefix(privatewin->fulljid, prefix->str)) {
265                 result = g_list_append(result, privatewin);
266             }
267         }
268         curr = g_list_next(curr);
269     }
270 
271     g_list_free(values);
272     g_string_free(prefix, TRUE);
273     return result;
274 }
275 
276 void
wins_private_nick_change(const char * const roomjid,const char * const oldnick,const char * const newnick)277 wins_private_nick_change(const char* const roomjid, const char* const oldnick, const char* const newnick)
278 {
279     Jid* oldjid = jid_create_from_bare_and_resource(roomjid, oldnick);
280 
281     ProfPrivateWin* privwin = wins_get_private(oldjid->fulljid);
282     if (privwin) {
283         free(privwin->fulljid);
284 
285         Jid* newjid = jid_create_from_bare_and_resource(roomjid, newnick);
286         privwin->fulljid = strdup(newjid->fulljid);
287         win_println((ProfWin*)privwin, THEME_THEM, "!", "** %s is now known as %s.", oldjid->resourcepart, newjid->resourcepart);
288 
289         autocomplete_remove(wins_ac, oldjid->fulljid);
290         autocomplete_remove(wins_close_ac, oldjid->fulljid);
291         autocomplete_add(wins_ac, newjid->fulljid);
292         autocomplete_add(wins_close_ac, newjid->fulljid);
293 
294         jid_destroy(newjid);
295     }
296 
297     jid_destroy(oldjid);
298 }
299 
300 void
wins_change_nick(const char * const barejid,const char * const oldnick,const char * const newnick)301 wins_change_nick(const char* const barejid, const char* const oldnick, const char* const newnick)
302 {
303     ProfChatWin* chatwin = wins_get_chat(barejid);
304     if (chatwin) {
305         if (oldnick) {
306             autocomplete_remove(wins_ac, oldnick);
307             autocomplete_remove(wins_close_ac, oldnick);
308         }
309         autocomplete_add(wins_ac, newnick);
310         autocomplete_add(wins_close_ac, newnick);
311     }
312 }
313 
314 void
wins_remove_nick(const char * const barejid,const char * const oldnick)315 wins_remove_nick(const char* const barejid, const char* const oldnick)
316 {
317     ProfChatWin* chatwin = wins_get_chat(barejid);
318     if (chatwin) {
319         if (oldnick) {
320             autocomplete_remove(wins_ac, oldnick);
321             autocomplete_remove(wins_close_ac, oldnick);
322         }
323     }
324 }
325 
326 ProfWin*
wins_get_current(void)327 wins_get_current(void)
328 {
329     if (windows) {
330         return g_hash_table_lookup(windows, GINT_TO_POINTER(current));
331     } else {
332         return NULL;
333     }
334 }
335 
336 GList*
wins_get_nums(void)337 wins_get_nums(void)
338 {
339     return g_hash_table_get_keys(windows);
340 }
341 
342 void
wins_set_current_by_num(int i)343 wins_set_current_by_num(int i)
344 {
345     ProfWin* window = g_hash_table_lookup(windows, GINT_TO_POINTER(i));
346     if (window) {
347         current = i;
348         if (window->type == WIN_CHAT) {
349             ProfChatWin* chatwin = (ProfChatWin*)window;
350             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
351             chatwin->unread = 0;
352             plugins_on_chat_win_focus(chatwin->barejid);
353         } else if (window->type == WIN_MUC) {
354             ProfMucWin* mucwin = (ProfMucWin*)window;
355             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
356             mucwin->unread = 0;
357             mucwin->unread_mentions = FALSE;
358             mucwin->unread_triggers = FALSE;
359             plugins_on_room_win_focus(mucwin->roomjid);
360         } else if (window->type == WIN_PRIVATE) {
361             ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
362             privatewin->unread = 0;
363         }
364 
365         // if we switched to console
366         if (current == 0) {
367             // remove all alerts
368             cons_clear_alerts();
369         } else {
370             // remove alert from window where we switch to
371             cons_remove_alert(window);
372             // if there a no more alerts left
373             if (!cons_has_alerts()) {
374                 // dont highlight console (no news there)
375                 ProfWin* conswin = wins_get_console();
376                 status_bar_active(1, conswin->type, "console");
377             }
378         }
379     }
380 }
381 
382 ProfWin*
wins_get_by_num(int i)383 wins_get_by_num(int i)
384 {
385     return g_hash_table_lookup(windows, GINT_TO_POINTER(i));
386 }
387 
388 ProfWin*
wins_get_by_string(const char * str)389 wins_get_by_string(const char* str)
390 {
391     if (g_strcmp0(str, "console") == 0) {
392         ProfWin* conswin = wins_get_console();
393         return conswin;
394     }
395 
396     if (g_strcmp0(str, "xmlconsole") == 0) {
397         ProfXMLWin* xmlwin = wins_get_xmlconsole();
398         return (ProfWin*)xmlwin;
399     }
400 
401     ProfChatWin* chatwin = wins_get_chat(str);
402     if (chatwin) {
403         return (ProfWin*)chatwin;
404     }
405 
406     jabber_conn_status_t conn_status = connection_get_status();
407     if (conn_status == JABBER_CONNECTED) {
408         char* barejid = roster_barejid_from_name(str);
409         if (barejid) {
410             ProfChatWin* chatwin = wins_get_chat(barejid);
411             if (chatwin) {
412                 return (ProfWin*)chatwin;
413             }
414         }
415     }
416 
417     ProfMucWin* mucwin = wins_get_muc(str);
418     if (mucwin) {
419         return (ProfWin*)mucwin;
420     }
421 
422     ProfPrivateWin* privwin = wins_get_private(str);
423     if (privwin) {
424         return (ProfWin*)privwin;
425     }
426 
427     ProfPluginWin* pluginwin = wins_get_plugin(str);
428     if (pluginwin) {
429         return (ProfWin*)pluginwin;
430     }
431 
432     return NULL;
433 }
434 
435 ProfWin*
wins_get_next(void)436 wins_get_next(void)
437 {
438     // get and sort win nums
439     GList* keys = g_hash_table_get_keys(windows);
440     keys = g_list_sort(keys, _wins_cmp_num);
441     GList* curr = keys;
442 
443     // find our place in the list
444     while (curr) {
445         if (current == GPOINTER_TO_INT(curr->data)) {
446             break;
447         }
448         curr = g_list_next(curr);
449     }
450 
451     // if there is a next window return it
452     curr = g_list_next(curr);
453     if (curr) {
454         int next = GPOINTER_TO_INT(curr->data);
455         g_list_free(keys);
456         return wins_get_by_num(next);
457         // otherwise return the first window (console)
458     } else {
459         g_list_free(keys);
460         return wins_get_console();
461     }
462 }
463 
464 ProfWin*
wins_get_previous(void)465 wins_get_previous(void)
466 {
467     // get and sort win nums
468     GList* keys = g_hash_table_get_keys(windows);
469     keys = g_list_sort(keys, _wins_cmp_num);
470     GList* curr = keys;
471 
472     // find our place in the list
473     while (curr) {
474         if (current == GPOINTER_TO_INT(curr->data)) {
475             break;
476         }
477         curr = g_list_next(curr);
478     }
479 
480     // if there is a previous window return it
481     curr = g_list_previous(curr);
482     if (curr) {
483         int previous = GPOINTER_TO_INT(curr->data);
484         g_list_free(keys);
485         return wins_get_by_num(previous);
486         // otherwise return the last window
487     } else {
488         int new_num = GPOINTER_TO_INT(g_list_last(keys)->data);
489         g_list_free(keys);
490         return wins_get_by_num(new_num);
491     }
492 }
493 
494 int
wins_get_num(ProfWin * window)495 wins_get_num(ProfWin* window)
496 {
497     GList* keys = g_hash_table_get_keys(windows);
498     GList* curr = keys;
499 
500     while (curr) {
501         gconstpointer num_p = curr->data;
502         ProfWin* curr_win = g_hash_table_lookup(windows, num_p);
503         if (curr_win == window) {
504             g_list_free(keys);
505             return GPOINTER_TO_INT(num_p);
506         }
507         curr = g_list_next(curr);
508     }
509 
510     g_list_free(keys);
511     return -1;
512 }
513 
514 int
wins_get_current_num(void)515 wins_get_current_num(void)
516 {
517     return current;
518 }
519 
520 void
wins_close_by_num(int i)521 wins_close_by_num(int i)
522 {
523     // console cannot be closed
524     if (i != 1) {
525 
526         // go to console if closing current window
527         if (i == current) {
528             current = 1;
529             ProfWin* window = wins_get_current();
530             win_update_virtual(window);
531         }
532 
533         ProfWin* window = wins_get_by_num(i);
534         if (window) {
535             // cancel upload processes of this window
536             http_upload_cancel_processes(window);
537 
538             switch (window->type) {
539             case WIN_CHAT:
540             {
541                 ProfChatWin* chatwin = (ProfChatWin*)window;
542                 autocomplete_remove(wins_ac, chatwin->barejid);
543                 autocomplete_remove(wins_close_ac, chatwin->barejid);
544 
545                 jabber_conn_status_t conn_status = connection_get_status();
546                 if (conn_status == JABBER_CONNECTED) {
547                     PContact contact = roster_get_contact(chatwin->barejid);
548                     if (contact) {
549                         const char* nick = p_contact_name(contact);
550                         if (nick) {
551                             autocomplete_remove(wins_ac, nick);
552                             autocomplete_remove(wins_close_ac, nick);
553                         }
554                     }
555                 }
556                 autocomplete_free(window->urls_ac);
557                 break;
558             }
559             case WIN_MUC:
560             {
561                 ProfMucWin* mucwin = (ProfMucWin*)window;
562                 autocomplete_remove(wins_ac, mucwin->roomjid);
563                 autocomplete_remove(wins_close_ac, mucwin->roomjid);
564 
565                 if (mucwin->last_msg_timestamp) {
566                     g_date_time_unref(mucwin->last_msg_timestamp);
567                 }
568                 autocomplete_free(window->urls_ac);
569                 break;
570             }
571             case WIN_PRIVATE:
572             {
573                 ProfPrivateWin* privwin = (ProfPrivateWin*)window;
574                 autocomplete_remove(wins_ac, privwin->fulljid);
575                 autocomplete_remove(wins_close_ac, privwin->fulljid);
576                 autocomplete_free(window->urls_ac);
577                 break;
578             }
579             case WIN_XML:
580             {
581                 autocomplete_remove(wins_ac, "xmlconsole");
582                 autocomplete_remove(wins_close_ac, "xmlconsole");
583                 break;
584             }
585             case WIN_PLUGIN:
586             {
587                 ProfPluginWin* pluginwin = (ProfPluginWin*)window;
588                 plugins_close_win(pluginwin->plugin_name, pluginwin->tag);
589                 autocomplete_remove(wins_ac, pluginwin->tag);
590                 autocomplete_remove(wins_close_ac, pluginwin->tag);
591                 break;
592             }
593             case WIN_CONFIG:
594             default:
595                 break;
596             }
597         }
598 
599         g_hash_table_remove(windows, GINT_TO_POINTER(i));
600         status_bar_inactive(i);
601     }
602 }
603 
604 gboolean
wins_is_current(ProfWin * window)605 wins_is_current(ProfWin* window)
606 {
607     ProfWin* current_window = wins_get_current();
608 
609     if (current_window == window) {
610         return TRUE;
611     } else {
612         return FALSE;
613     }
614 }
615 
616 ProfWin*
wins_new_xmlconsole(void)617 wins_new_xmlconsole(void)
618 {
619     GList* keys = g_hash_table_get_keys(windows);
620     int result = _wins_get_next_available_num(keys);
621     g_list_free(keys);
622     ProfWin* newwin = win_create_xmlconsole();
623     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
624     autocomplete_add(wins_ac, "xmlconsole");
625     autocomplete_add(wins_close_ac, "xmlconsole");
626     return newwin;
627 }
628 
629 ProfWin*
wins_new_chat(const char * const barejid)630 wins_new_chat(const char* const barejid)
631 {
632     GList* keys = g_hash_table_get_keys(windows);
633     int result = _wins_get_next_available_num(keys);
634     g_list_free(keys);
635     ProfWin* newwin = win_create_chat(barejid);
636     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
637 
638     autocomplete_add(wins_ac, barejid);
639     autocomplete_add(wins_close_ac, barejid);
640     PContact contact = roster_get_contact(barejid);
641     if (contact) {
642         const char* nick = p_contact_name(contact);
643         if (nick) {
644             autocomplete_add(wins_ac, nick);
645             autocomplete_add(wins_close_ac, nick);
646         }
647     }
648     newwin->urls_ac = autocomplete_new();
649 
650     return newwin;
651 }
652 
653 ProfWin*
wins_new_muc(const char * const roomjid)654 wins_new_muc(const char* const roomjid)
655 {
656     GList* keys = g_hash_table_get_keys(windows);
657     int result = _wins_get_next_available_num(keys);
658     g_list_free(keys);
659     ProfWin* newwin = win_create_muc(roomjid);
660     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
661     autocomplete_add(wins_ac, roomjid);
662     autocomplete_add(wins_close_ac, roomjid);
663     newwin->urls_ac = autocomplete_new();
664 
665     return newwin;
666 }
667 
668 ProfWin*
wins_new_config(const char * const roomjid,DataForm * form,ProfConfWinCallback submit,ProfConfWinCallback cancel,const void * userdata)669 wins_new_config(const char* const roomjid, DataForm* form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void* userdata)
670 {
671     GList* keys = g_hash_table_get_keys(windows);
672     int result = _wins_get_next_available_num(keys);
673     g_list_free(keys);
674     ProfWin* newwin = win_create_config(roomjid, form, submit, cancel, userdata);
675     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
676 
677     return newwin;
678 }
679 
680 ProfWin*
wins_new_private(const char * const fulljid)681 wins_new_private(const char* const fulljid)
682 {
683     GList* keys = g_hash_table_get_keys(windows);
684     int result = _wins_get_next_available_num(keys);
685     g_list_free(keys);
686     ProfWin* newwin = win_create_private(fulljid);
687     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
688     autocomplete_add(wins_ac, fulljid);
689     autocomplete_add(wins_close_ac, fulljid);
690     newwin->urls_ac = autocomplete_new();
691 
692     return newwin;
693 }
694 
695 ProfWin*
wins_new_plugin(const char * const plugin_name,const char * const tag)696 wins_new_plugin(const char* const plugin_name, const char* const tag)
697 {
698     GList* keys = g_hash_table_get_keys(windows);
699     int result = _wins_get_next_available_num(keys);
700     g_list_free(keys);
701     ProfWin* newwin = win_create_plugin(plugin_name, tag);
702     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
703     autocomplete_add(wins_ac, tag);
704     autocomplete_add(wins_close_ac, tag);
705     return newwin;
706 }
707 
708 gboolean
wins_do_notify_remind(void)709 wins_do_notify_remind(void)
710 {
711     GList* values = g_hash_table_get_values(windows);
712     GList* curr = values;
713 
714     while (curr) {
715         ProfWin* window = curr->data;
716         if (win_notify_remind(window)) {
717             g_list_free(values);
718             return TRUE;
719         }
720         curr = g_list_next(curr);
721     }
722     g_list_free(values);
723     return FALSE;
724 }
725 
726 int
wins_get_total_unread(void)727 wins_get_total_unread(void)
728 {
729     int result = 0;
730     GList* values = g_hash_table_get_values(windows);
731     GList* curr = values;
732 
733     while (curr) {
734         ProfWin* window = curr->data;
735         result += win_unread(window);
736         curr = g_list_next(curr);
737     }
738     g_list_free(values);
739     return result;
740 }
741 
742 void
wins_resize_all(void)743 wins_resize_all(void)
744 {
745     GList* values = g_hash_table_get_values(windows);
746     GList* curr = values;
747     while (curr) {
748         ProfWin* window = curr->data;
749         win_resize(window);
750         curr = g_list_next(curr);
751     }
752     g_list_free(values);
753 
754     ProfWin* current_win = wins_get_current();
755     win_update_virtual(current_win);
756 }
757 
758 void
wins_hide_subwin(ProfWin * window)759 wins_hide_subwin(ProfWin* window)
760 {
761     win_hide_subwin(window);
762 
763     ProfWin* current_win = wins_get_current();
764     win_refresh_without_subwin(current_win);
765 }
766 
767 void
wins_show_subwin(ProfWin * window)768 wins_show_subwin(ProfWin* window)
769 {
770     win_show_subwin(window);
771 
772     // only mucwin and console have occupants/roster subwin
773     if (window->type != WIN_MUC && window->type != WIN_CONSOLE) {
774         return;
775     }
776 
777     ProfWin* current_win = wins_get_current();
778     win_refresh_with_subwin(current_win);
779 }
780 
781 ProfXMLWin*
wins_get_xmlconsole(void)782 wins_get_xmlconsole(void)
783 {
784     GList* values = g_hash_table_get_values(windows);
785     GList* curr = values;
786 
787     while (curr) {
788         ProfWin* window = curr->data;
789         if (window->type == WIN_XML) {
790             ProfXMLWin* xmlwin = (ProfXMLWin*)window;
791             assert(xmlwin->memcheck == PROFXMLWIN_MEMCHECK);
792             g_list_free(values);
793             return xmlwin;
794         }
795         curr = g_list_next(curr);
796     }
797 
798     g_list_free(values);
799     return NULL;
800 }
801 
802 GSList*
wins_get_chat_recipients(void)803 wins_get_chat_recipients(void)
804 {
805     GSList* result = NULL;
806     GList* values = g_hash_table_get_values(windows);
807     GList* curr = values;
808 
809     while (curr) {
810         ProfWin* window = curr->data;
811         if (window->type == WIN_CHAT) {
812             ProfChatWin* chatwin = (ProfChatWin*)window;
813             result = g_slist_append(result, chatwin->barejid);
814         }
815         curr = g_list_next(curr);
816     }
817     g_list_free(values);
818     return result;
819 }
820 
821 GSList*
wins_get_prune_wins(void)822 wins_get_prune_wins(void)
823 {
824     GSList* result = NULL;
825     GList* values = g_hash_table_get_values(windows);
826     GList* curr = values;
827 
828     while (curr) {
829         ProfWin* window = curr->data;
830         if (win_unread(window) == 0 && window->type != WIN_MUC && window->type != WIN_CONFIG && window->type != WIN_XML && window->type != WIN_CONSOLE) {
831             result = g_slist_append(result, window);
832         }
833         curr = g_list_next(curr);
834     }
835     g_list_free(values);
836     return result;
837 }
838 
839 void
wins_lost_connection(void)840 wins_lost_connection(void)
841 {
842     GList* values = g_hash_table_get_values(windows);
843     GList* curr = values;
844 
845     while (curr) {
846         ProfWin* window = curr->data;
847         if (window->type != WIN_CONSOLE) {
848             win_println(window, THEME_ERROR, "-", "Lost connection.");
849 
850             // if current win, set current_win_dirty
851             if (wins_is_current(window)) {
852                 win_update_virtual(window);
853             }
854         }
855         curr = g_list_next(curr);
856     }
857     g_list_free(values);
858 }
859 
860 void
wins_reestablished_connection(void)861 wins_reestablished_connection(void)
862 {
863     GList* values = g_hash_table_get_values(windows);
864     GList* curr = values;
865 
866     while (curr) {
867         ProfWin* window = curr->data;
868         if (window->type != WIN_CONSOLE) {
869             win_println(window, THEME_TEXT, "-", "Connection re-established.");
870 
871 #ifdef HAVE_OMEMO
872             if (window->type == WIN_CHAT) {
873                 ProfChatWin* chatwin = (ProfChatWin*)window;
874                 assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
875                 if (chatwin->is_omemo) {
876                     win_println(window, THEME_TEXT, "-", "Restarted OMEMO session.");
877                     omemo_start_session(chatwin->barejid);
878                 }
879             } else if (window->type == WIN_MUC) {
880                 ProfMucWin* mucwin = (ProfMucWin*)window;
881                 assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
882                 if (mucwin->is_omemo) {
883                     win_println(window, THEME_TEXT, "-", "Restarted OMEMO session.");
884                     omemo_start_muc_sessions(mucwin->roomjid);
885                 }
886             }
887 #endif
888 
889             // if current win, set current_win_dirty
890             if (wins_is_current(window)) {
891                 win_update_virtual(window);
892             }
893         }
894         curr = g_list_next(curr);
895     }
896     g_list_free(values);
897 }
898 
899 void
wins_swap(int source_win,int target_win)900 wins_swap(int source_win, int target_win)
901 {
902     ProfWin* source = g_hash_table_lookup(windows, GINT_TO_POINTER(source_win));
903     ProfWin* console = wins_get_console();
904 
905     if (source) {
906         ProfWin* target = g_hash_table_lookup(windows, GINT_TO_POINTER(target_win));
907 
908         // target window empty
909         if (target == NULL) {
910             g_hash_table_steal(windows, GINT_TO_POINTER(source_win));
911             g_hash_table_insert(windows, GINT_TO_POINTER(target_win), source);
912             status_bar_inactive(source_win);
913             char* identifier = win_get_tab_identifier(source);
914             if (win_unread(source) > 0) {
915                 status_bar_new(target_win, source->type, identifier);
916             } else {
917                 status_bar_active(target_win, source->type, identifier);
918             }
919             free(identifier);
920             if (wins_get_current_num() == source_win) {
921                 wins_set_current_by_num(target_win);
922                 ui_focus_win(console);
923             }
924 
925             // target window occupied
926         } else {
927             g_hash_table_steal(windows, GINT_TO_POINTER(source_win));
928             g_hash_table_steal(windows, GINT_TO_POINTER(target_win));
929             g_hash_table_insert(windows, GINT_TO_POINTER(source_win), target);
930             g_hash_table_insert(windows, GINT_TO_POINTER(target_win), source);
931             char* source_identifier = win_get_tab_identifier(source);
932             char* target_identifier = win_get_tab_identifier(target);
933             if (win_unread(source) > 0) {
934                 status_bar_new(target_win, source->type, source_identifier);
935             } else {
936                 status_bar_active(target_win, source->type, source_identifier);
937             }
938             if (win_unread(target) > 0) {
939                 status_bar_new(source_win, target->type, target_identifier);
940             } else {
941                 status_bar_active(source_win, target->type, target_identifier);
942             }
943             free(source_identifier);
944             free(target_identifier);
945             if ((wins_get_current_num() == source_win) || (wins_get_current_num() == target_win)) {
946                 ui_focus_win(console);
947             }
948         }
949     }
950 }
951 
952 static int
_wins_cmp_num(gconstpointer a,gconstpointer b)953 _wins_cmp_num(gconstpointer a, gconstpointer b)
954 {
955     int real_a = GPOINTER_TO_INT(a);
956     int real_b = GPOINTER_TO_INT(b);
957 
958     if (real_a == 0) {
959         real_a = 10;
960     }
961 
962     if (real_b == 0) {
963         real_b = 10;
964     }
965 
966     if (real_a < real_b) {
967         return -1;
968     } else if (real_a == real_b) {
969         return 0;
970     } else {
971         return 1;
972     }
973 }
974 
975 static int
_wins_get_next_available_num(GList * used)976 _wins_get_next_available_num(GList* used)
977 {
978     // only console used
979     if (g_list_length(used) == 1) {
980         return 2;
981     } else {
982         GList* sorted = NULL;
983         GList* curr = used;
984         while (curr) {
985             sorted = g_list_insert_sorted(sorted, curr->data, _wins_cmp_num);
986             curr = g_list_next(curr);
987         }
988 
989         int result = 0;
990         int last_num = 1;
991         curr = sorted;
992         // skip console
993         curr = g_list_next(curr);
994         while (curr) {
995             int curr_num = GPOINTER_TO_INT(curr->data);
996 
997             if (((last_num != 9) && ((last_num + 1) != curr_num)) || ((last_num == 9) && (curr_num != 0))) {
998                 result = last_num + 1;
999                 if (result == 10) {
1000                     result = 0;
1001                 }
1002                 g_list_free(sorted);
1003                 return (result);
1004 
1005             } else {
1006                 last_num = curr_num;
1007                 if (last_num == 0) {
1008                     last_num = 10;
1009                 }
1010             }
1011             curr = g_list_next(curr);
1012         }
1013         result = last_num + 1;
1014         if (result == 10) {
1015             result = 0;
1016         }
1017 
1018         g_list_free(sorted);
1019         return result;
1020     }
1021 }
1022 
1023 gboolean
wins_tidy(void)1024 wins_tidy(void)
1025 {
1026     gboolean tidy_required = FALSE;
1027     // check for gaps
1028     GList* keys = g_hash_table_get_keys(windows);
1029     keys = g_list_sort(keys, _wins_cmp_num);
1030 
1031     // get last used
1032     GList* last = g_list_last(keys);
1033     int last_num = GPOINTER_TO_INT(last->data);
1034 
1035     // find first free num TODO - Will sort again
1036     int next_available = _wins_get_next_available_num(keys);
1037 
1038     // found gap (next available before last window)
1039     if (_wins_cmp_num(GINT_TO_POINTER(next_available), GINT_TO_POINTER(last_num)) < 0) {
1040         tidy_required = TRUE;
1041     }
1042 
1043     if (tidy_required) {
1044         status_bar_set_all_inactive();
1045         GHashTable* new_windows = g_hash_table_new_full(g_direct_hash,
1046                                                         g_direct_equal, NULL, (GDestroyNotify)win_free);
1047 
1048         int num = 1;
1049         GList* curr = keys;
1050         while (curr) {
1051             ProfWin* window = g_hash_table_lookup(windows, curr->data);
1052             char* identifier = win_get_tab_identifier(window);
1053             g_hash_table_steal(windows, curr->data);
1054             if (num == 10) {
1055                 g_hash_table_insert(new_windows, GINT_TO_POINTER(0), window);
1056                 if (win_unread(window) > 0) {
1057                     status_bar_new(0, window->type, identifier);
1058                 } else {
1059                     status_bar_active(0, window->type, identifier);
1060                 }
1061             } else {
1062                 g_hash_table_insert(new_windows, GINT_TO_POINTER(num), window);
1063                 if (win_unread(window) > 0) {
1064                     status_bar_new(num, window->type, identifier);
1065                 } else {
1066                     status_bar_active(num, window->type, identifier);
1067                 }
1068             }
1069             free(identifier);
1070             num++;
1071             curr = g_list_next(curr);
1072         }
1073 
1074         g_hash_table_destroy(windows);
1075         windows = new_windows;
1076         current = 1;
1077         ProfWin* console = wins_get_console();
1078         ui_focus_win(console);
1079         g_list_free(keys);
1080         return TRUE;
1081     } else {
1082         g_list_free(keys);
1083         return FALSE;
1084     }
1085 }
1086 
1087 GSList*
wins_create_summary(gboolean unread)1088 wins_create_summary(gboolean unread)
1089 {
1090     if (unread && wins_get_total_unread() == 0) {
1091         return NULL;
1092     }
1093 
1094     GSList* result = NULL;
1095 
1096     GList* keys = g_hash_table_get_keys(windows);
1097     keys = g_list_sort(keys, _wins_cmp_num);
1098     GList* curr = keys;
1099 
1100     while (curr) {
1101         ProfWin* window = g_hash_table_lookup(windows, curr->data);
1102         if (!unread || (unread && win_unread(window) > 0)) {
1103             GString* line = g_string_new("");
1104 
1105             int ui_index = GPOINTER_TO_INT(curr->data);
1106             char* winstring = win_to_string(window);
1107             if (!winstring) {
1108                 g_string_free(line, TRUE);
1109                 continue;
1110             }
1111 
1112             g_string_append_printf(line, "%d: %s", ui_index, winstring);
1113             free(winstring);
1114 
1115             result = g_slist_append(result, strdup(line->str));
1116             g_string_free(line, TRUE);
1117         }
1118 
1119         curr = g_list_next(curr);
1120     }
1121 
1122     g_list_free(keys);
1123 
1124     return result;
1125 }
1126 
1127 GSList*
wins_create_summary_attention()1128 wins_create_summary_attention()
1129 {
1130     GSList* result = NULL;
1131 
1132     GList* keys = g_hash_table_get_keys(windows);
1133     keys = g_list_sort(keys, _wins_cmp_num);
1134     GList* curr = keys;
1135 
1136     while (curr) {
1137         ProfWin* window = g_hash_table_lookup(windows, curr->data);
1138         gboolean has_attention = FALSE;
1139         if (window->type == WIN_CHAT) {
1140             ProfChatWin* chatwin = (ProfChatWin*)window;
1141             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
1142             has_attention = chatwin->has_attention;
1143         } else if (window->type == WIN_MUC) {
1144             ProfMucWin* mucwin = (ProfMucWin*)window;
1145             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
1146             has_attention = mucwin->has_attention;
1147         }
1148         if (has_attention) {
1149             GString* line = g_string_new("");
1150 
1151             int ui_index = GPOINTER_TO_INT(curr->data);
1152             char* winstring = win_to_string(window);
1153             if (!winstring) {
1154                 g_string_free(line, TRUE);
1155                 continue;
1156             }
1157 
1158             g_string_append_printf(line, "%d: %s", ui_index, winstring);
1159             free(winstring);
1160 
1161             result = g_slist_append(result, strdup(line->str));
1162             g_string_free(line, TRUE);
1163         }
1164         curr = g_list_next(curr);
1165     }
1166 
1167     g_list_free(keys);
1168 
1169     return result;
1170 }
1171 
1172 char*
win_autocomplete(const char * const search_str,gboolean previous,void * context)1173 win_autocomplete(const char* const search_str, gboolean previous, void* context)
1174 {
1175     return autocomplete_complete(wins_ac, search_str, TRUE, previous);
1176 }
1177 
1178 char*
win_close_autocomplete(const char * const search_str,gboolean previous,void * context)1179 win_close_autocomplete(const char* const search_str, gboolean previous, void* context)
1180 {
1181     return autocomplete_complete(wins_close_ac, search_str, TRUE, previous);
1182 }
1183 
1184 void
win_reset_search_attempts(void)1185 win_reset_search_attempts(void)
1186 {
1187     autocomplete_reset(wins_ac);
1188 }
1189 
1190 void
win_close_reset_search_attempts(void)1191 win_close_reset_search_attempts(void)
1192 {
1193     autocomplete_reset(wins_close_ac);
1194 }
1195 
1196 void
wins_destroy(void)1197 wins_destroy(void)
1198 {
1199     g_hash_table_destroy(windows);
1200     autocomplete_free(wins_ac);
1201     autocomplete_free(wins_close_ac);
1202 }
1203 
1204 ProfWin*
wins_get_next_unread(void)1205 wins_get_next_unread(void)
1206 {
1207     // get and sort win nums
1208     GList* values = g_hash_table_get_keys(windows);
1209     values = g_list_sort(values, _wins_cmp_num);
1210     GList* curr = values;
1211 
1212     while (curr) {
1213         int curr_win_num = GPOINTER_TO_INT(curr->data);
1214         ProfWin* window = wins_get_by_num(curr_win_num);
1215 
1216         // test if window has unread messages
1217         if (win_unread(window) > 0) {
1218             g_list_free(values);
1219             return window;
1220         }
1221 
1222         curr = g_list_next(curr);
1223     }
1224 
1225     g_list_free(values);
1226     return NULL;
1227 }
1228 
1229 ProfWin*
wins_get_next_attention(void)1230 wins_get_next_attention(void)
1231 {
1232     // get and sort win nums
1233     GList* values = g_hash_table_get_values(windows);
1234     values = g_list_sort(values, _wins_cmp_num);
1235     GList* curr = values;
1236 
1237     ProfWin* current_window = wins_get_by_num(current);
1238 
1239     // search the current window
1240     while (curr) {
1241         ProfWin* window = curr->data;
1242         if (current_window == window) {
1243             current_window = window;
1244             curr = g_list_next(curr);
1245             break;
1246         }
1247         curr = g_list_next(curr);
1248     }
1249 
1250     // Start from current window
1251     while (current_window && curr) {
1252         ProfWin* window = curr->data;
1253         if (win_has_attention(window)) {
1254             g_list_free(values);
1255             return window;
1256         }
1257         curr = g_list_next(curr);
1258     }
1259     // Start from begin
1260     curr = values;
1261     while (current_window && curr) {
1262         ProfWin* window = curr->data;
1263         if (current_window == window) {
1264             // we are at current again
1265             break;
1266         }
1267         if (win_has_attention(window)) {
1268             g_list_free(values);
1269             return window;
1270         }
1271         curr = g_list_next(curr);
1272     }
1273 
1274     g_list_free(values);
1275     return NULL;
1276 }
1277 
1278 void
wins_add_urls_ac(const ProfWin * const win,const ProfMessage * const message)1279 wins_add_urls_ac(const ProfWin* const win, const ProfMessage* const message)
1280 {
1281     GRegex* regex;
1282     GMatchInfo* match_info;
1283 
1284     regex = g_regex_new("(https?|aesgcm)://\\S+", 0, 0, NULL);
1285     g_regex_match(regex, message->plain, 0, &match_info);
1286 
1287     while (g_match_info_matches(match_info)) {
1288         gchar* word = g_match_info_fetch(match_info, 0);
1289 
1290         autocomplete_add_reverse(win->urls_ac, word);
1291         // for people who run profanity a long time, we don't want to waste a lot of memory
1292         autocomplete_remove_older_than_max_reverse(win->urls_ac, 20);
1293 
1294         g_free(word);
1295         g_match_info_next(match_info, NULL);
1296     }
1297 
1298     g_match_info_free(match_info);
1299     g_regex_unref(regex);
1300 }
1301 
1302 char*
wins_get_url(const char * const search_str,gboolean previous,void * context)1303 wins_get_url(const char* const search_str, gboolean previous, void* context)
1304 {
1305     ProfWin* win = (ProfWin*)context;
1306 
1307     return autocomplete_complete(win->urls_ac, search_str, FALSE, previous);
1308 }
1309