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