1 /*  conference.c
2  *
3  *
4  *  Copyright (C) 2014 Toxic All Rights Reserved.
5  *
6  *  This file is part of Toxic.
7  *
8  *  Toxic is free software: you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation, either version 3 of the License, or
11  *  (at your option) any later version.
12  *
13  *  Toxic is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with Toxic.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22 
23 #ifndef _GNU_SOURCE
24 #define _GNU_SOURCE    /* needed for strcasestr() and wcswidth() */
25 #endif
26 
27 #include <assert.h>
28 #include <inttypes.h>
29 #include <math.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
33 #include <unistd.h>
34 #include <wchar.h>
35 
36 #ifdef AUDIO
37 #ifdef __APPLE__
38 #include <OpenAL/al.h>
39 #include <OpenAL/alc.h>
40 #else
41 #include <AL/al.h>
42 #include <AL/alc.h>
43 /* compatibility with older versions of OpenAL */
44 #ifndef ALC_ALL_DEVICES_SPECIFIER
45 #include <AL/alext.h>
46 #endif /* ALC_ALL_DEVICES_SPECIFIER */
47 #endif /* __APPLE__ */
48 #endif /* AUDIO */
49 
50 #include "audio_device.h"
51 #include "autocomplete.h"
52 #include "conference.h"
53 #include "execute.h"
54 #include "help.h"
55 #include "input.h"
56 #include "line_info.h"
57 #include "log.h"
58 #include "misc_tools.h"
59 #include "notify.h"
60 #include "prompt.h"
61 #include "settings.h"
62 #include "toxic.h"
63 #include "toxic_strings.h"
64 #include "windows.h"
65 
66 #define MAX_CONFERENCE_NUM (MAX_WINDOWS_NUM - 2)
67 #define CONFERENCE_EVENT_WAIT 30
68 
69 static ConferenceChat conferences[MAX_CONFERENCE_NUM];
70 static int max_conference_index = 0;
71 
72 extern struct user_settings *user_settings;
73 extern struct Winthread Winthread;
74 
75 /* Array of conference command names used for tab completion. */
76 static const char *conference_cmd_list[] = {
77     "/accept",
78     "/add",
79 #ifdef AUDIO
80     "/audio",
81 #endif
82     "/avatar",
83     "/clear",
84     "/close",
85     "/connect",
86     "/decline",
87     "/exit",
88     "/conference",
89 #ifdef GAMES
90     "/game",
91 #endif
92     "/help",
93     "/log",
94 #ifdef AUDIO
95     "/mute",
96 #endif
97     "/myid",
98 #ifdef QRCODE
99     "/myqr",
100 #endif /* QRCODE */
101     "/nick",
102     "/note",
103     "/nospam",
104     "/quit",
105     "/requests",
106 #ifdef AUDIO
107     "/ptt",
108     "/sense",
109 #endif
110     "/status",
111     "/title",
112 
113 #ifdef PYTHON
114 
115     "/run",
116 
117 #endif /* PYTHON */
118 };
119 
120 static ToxWindow *new_conference_chat(uint32_t conferencenum);
121 
conference_set_title(ToxWindow * self,uint32_t conferencesnum,const char * title,size_t length)122 void conference_set_title(ToxWindow *self, uint32_t conferencesnum, const char *title, size_t length)
123 {
124     ConferenceChat *chat = &conferences[conferencesnum];
125 
126     if (!chat->active) {
127         return;
128     }
129 
130     if (length > CONFERENCE_MAX_TITLE_LENGTH) {
131         length = CONFERENCE_MAX_TITLE_LENGTH;
132     }
133 
134     memcpy(chat->title, title, length);
135     chat->title[length] = 0;
136     chat->title_length = length;
137 
138     set_window_title(self, title, length);
139 }
140 
kill_conference_window(ToxWindow * self)141 static void kill_conference_window(ToxWindow *self)
142 {
143     ChatContext *ctx = self->chatwin;
144 
145     log_disable(ctx->log);
146     line_info_cleanup(ctx->hst);
147     delwin(ctx->linewin);
148     delwin(ctx->history);
149     delwin(ctx->sidebar);
150     free(ctx->log);
151     free(ctx);
152     free(self->help);
153     kill_notifs(self->active_box);
154     del_window(self);
155 }
156 
init_conference_logging(ToxWindow * self,Tox * m,uint32_t conferencenum)157 static void init_conference_logging(ToxWindow *self, Tox *m, uint32_t conferencenum)
158 {
159     ChatContext *ctx = self->chatwin;
160 
161     char my_id[TOX_ADDRESS_SIZE];
162     tox_self_get_address(m, (uint8_t *) my_id);
163 
164     char conference_id[TOX_CONFERENCE_ID_SIZE];
165     tox_conference_get_id(m, conferencenum, (uint8_t *) conference_id);
166 
167     if (log_init(ctx->log, conferences[self->num].title, my_id, conference_id, LOG_TYPE_CHAT) != 0) {
168         line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Warning: Log failed to initialize.");
169         return;
170     }
171 
172     if (load_chat_history(self, ctx->log) != 0) {
173         line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to load chat history.");
174     }
175 
176     if (user_settings->autolog == AUTOLOG_ON) {
177         if (log_enable(ctx->log) != 0) {
178             line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to enable chat log.");
179         }
180     }
181 
182     execute(ctx->history, self, m, "/log", GLOBAL_COMMAND_MODE);  // print log state to screen
183 }
184 
init_conference_win(Tox * m,uint32_t conferencenum,uint8_t type,const char * title,size_t length)185 int init_conference_win(Tox *m, uint32_t conferencenum, uint8_t type, const char *title, size_t length)
186 {
187     if (conferencenum > MAX_CONFERENCE_NUM) {
188         return -1;
189     }
190 
191     ToxWindow *self = new_conference_chat(conferencenum);
192 
193     for (int i = 0; i <= max_conference_index; ++i) {
194         if (!conferences[i].active) {
195             // FIXME: it is assumed at various points in the code that
196             // toxcore's conferencenums agree with toxic's indices to conferences;
197             // probably it so happens that this will (at least typically) be
198             // the case, because toxic and tox maintain the indices in
199             // parallel ways. But it isn't guaranteed by the API.
200             conferences[i].chatwin = add_window(m, self);
201             conferences[i].active = true;
202             conferences[i].num_peers = 0;
203             conferences[i].type = type;
204             conferences[i].start_time = get_unix_time();
205             conferences[i].audio_enabled = false;
206             conferences[i].last_sent_audio = 0;
207 
208 #ifdef AUDIO
209             conferences[i].push_to_talk_enabled = user_settings->push_to_talk;
210 #endif
211 
212             set_active_window_index(conferences[i].chatwin);
213 
214             conference_set_title(self, conferencenum, title, length);
215 
216             init_conference_logging(self, m, conferencenum);
217 
218             if (i == max_conference_index) {
219                 ++max_conference_index;
220             }
221 
222             return conferences[i].chatwin;
223         }
224     }
225 
226     kill_conference_window(self);
227 
228     return -1;
229 }
230 
free_peer(ConferencePeer * peer)231 static void free_peer(ConferencePeer *peer)
232 {
233 #ifdef AUDIO
234 
235     if (peer->sending_audio) {
236         close_device(output, peer->audio_out_idx);
237     }
238 
239 #endif
240 }
241 
free_conference(ToxWindow * self,uint32_t conferencenum)242 void free_conference(ToxWindow *self, uint32_t conferencenum)
243 {
244     ConferenceChat *chat = &conferences[conferencenum];
245 
246     for (uint32_t i = 0; i < chat->num_peers; ++i) {
247         ConferencePeer *peer = &chat->peer_list[i];
248 
249         if (peer->active) {
250             free_peer(peer);
251         }
252     }
253 
254 #ifdef AUDIO
255 
256     if (chat->audio_enabled) {
257         close_device(input, chat->audio_in_idx);
258     }
259 
260 #endif
261 
262     free(chat->name_list);
263     free(chat->peer_list);
264     conferences[conferencenum] = (ConferenceChat) {
265         0
266     };
267 
268     int i;
269 
270     for (i = max_conference_index; i > 0; --i) {
271         if (conferences[i - 1].active) {
272             break;
273         }
274     }
275 
276     max_conference_index = i;
277     kill_conference_window(self);
278 }
279 
delete_conference(ToxWindow * self,Tox * m,uint32_t conferencenum)280 static void delete_conference(ToxWindow *self, Tox *m, uint32_t conferencenum)
281 {
282     tox_conference_delete(m, conferencenum, NULL);
283     free_conference(self, conferencenum);
284 }
285 
conference_rename_log_path(Tox * m,uint32_t conferencenum,const char * new_title)286 void conference_rename_log_path(Tox *m, uint32_t conferencenum, const char *new_title)
287 {
288     ConferenceChat *chat = &conferences[conferencenum];
289 
290     if (!chat->active) {
291         return;
292     }
293 
294     char myid[TOX_ADDRESS_SIZE];
295     tox_self_get_address(m, (uint8_t *) myid);
296 
297     char conference_id[TOX_CONFERENCE_ID_SIZE];
298     tox_conference_get_id(m, conferencenum, (uint8_t *) conference_id);
299 
300     if (rename_logfile(chat->title, new_title, myid, conference_id, chat->chatwin) != 0) {
301         fprintf(stderr, "Failed to rename conference log to `%s`\n", new_title);
302     }
303 }
304 
305 /* destroys and re-creates conference window with or without the peerlist */
redraw_conference_win(ToxWindow * self)306 void redraw_conference_win(ToxWindow *self)
307 {
308     ChatContext *ctx = self->chatwin;
309 
310     endwin();
311     refresh();
312     clear();
313 
314     int x2;
315     int y2;
316     getmaxyx(self->window, y2, x2);
317 
318     if (y2 <= 0 || x2 <= 0) {
319         return;
320     }
321 
322     if (ctx->sidebar) {
323         delwin(ctx->sidebar);
324         ctx->sidebar = NULL;
325     }
326 
327     delwin(ctx->linewin);
328     delwin(ctx->history);
329     delwin(self->window_bar);
330     delwin(self->window);
331 
332     self->window = newwin(y2, x2, 0, 0);
333     ctx->linewin = subwin(self->window, CHATBOX_HEIGHT, x2, y2 - CHATBOX_HEIGHT, 0);
334     self->window_bar = subwin(self->window, WINDOW_BAR_HEIGHT, x2, y2 - (CHATBOX_HEIGHT + WINDOW_BAR_HEIGHT), 0);
335 
336     if (self->show_peerlist) {
337         ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2 - SIDEBAR_WIDTH - 1, 0, 0);
338         ctx->sidebar = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, SIDEBAR_WIDTH, 0, x2 - SIDEBAR_WIDTH);
339     } else {
340         ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2, 0, 0);
341     }
342 
343     scrollok(ctx->history, 0);
344     wmove(self->window, y2 - CURS_Y_OFFSET, 0);
345 }
346 
conference_onConferenceMessage(ToxWindow * self,Tox * m,uint32_t conferencenum,uint32_t peernum,Tox_Message_Type type,const char * msg,size_t len)347 static void conference_onConferenceMessage(ToxWindow *self, Tox *m, uint32_t conferencenum, uint32_t peernum,
348         Tox_Message_Type type, const char *msg, size_t len)
349 {
350     UNUSED_VAR(len);
351 
352     if (self->num != conferencenum) {
353         return;
354     }
355 
356     ChatContext *ctx = self->chatwin;
357 
358     char nick[TOX_MAX_NAME_LENGTH];
359     get_conference_nick_truncate(m, nick, peernum, conferencenum);
360 
361     char selfnick[TOX_MAX_NAME_LENGTH];
362     tox_self_get_name(m, (uint8_t *) selfnick);
363 
364     size_t sn_len = tox_self_get_name_size(m);
365     selfnick[sn_len] = '\0';
366 
367     int nick_clr = strcmp(nick, selfnick) == 0 ? GREEN : CYAN;
368 
369     /* Only play sound if mentioned by someone else */
370     if (strcasestr(msg, selfnick) && strcmp(selfnick, nick)) {
371         if (self->active_box != -1) {
372             box_notify2(self, generic_message, NT_WNDALERT_0 | NT_NOFOCUS | user_settings->bell_on_message,
373                         self->active_box, "%s %s", nick, msg);
374         } else {
375             box_notify(self, generic_message, NT_WNDALERT_0 | NT_NOFOCUS | user_settings->bell_on_message,
376                        &self->active_box, self->name, "%s %s", nick, msg);
377         }
378 
379         nick_clr = RED;
380     } else {
381         sound_notify(self, silent, NT_WNDALERT_1, NULL);
382     }
383 
384     line_info_add(self, true, nick, NULL, type == TOX_MESSAGE_TYPE_NORMAL ? IN_MSG : IN_ACTION, 0, nick_clr, "%s", msg);
385     write_to_log(msg, nick, ctx->log, false);
386 }
387 
conference_onConferenceTitleChange(ToxWindow * self,Tox * m,uint32_t conferencenum,uint32_t peernum,const char * title,size_t length)388 static void conference_onConferenceTitleChange(ToxWindow *self, Tox *m, uint32_t conferencenum, uint32_t peernum,
389         const char *title,
390         size_t length)
391 {
392     ChatContext *ctx = self->chatwin;
393 
394     if (self->num != conferencenum) {
395         return;
396     }
397 
398     ConferenceChat *chat = &conferences[conferencenum];
399 
400     if (!chat->active) {
401         return;
402     }
403 
404     conference_rename_log_path(m, conferencenum, title);  // must be called first
405 
406     conference_set_title(self, conferencenum, title, length);
407 
408     /* don't announce title when we join the room */
409     if (!timed_out(conferences[conferencenum].start_time, CONFERENCE_EVENT_WAIT)) {
410         return;
411     }
412 
413     char nick[TOX_MAX_NAME_LENGTH];
414     get_conference_nick_truncate(m, nick, peernum, conferencenum);
415     line_info_add(self, true, nick, NULL, NAME_CHANGE, 0, 0, " set the conference title to: %s", title);
416 
417     char tmp_event[MAX_STR_SIZE];
418     snprintf(tmp_event, sizeof(tmp_event), "set title to %s", title);
419     write_to_log(tmp_event, nick, ctx->log, true);
420 }
421 
422 /* Puts `(NameListEntry *)`s in `entries` for each matched peer, up to a
423  * maximum of `maxpeers`.
424  * Maches each peer whose name or pubkey begins with `prefix`.
425  * If `prefix` is exactly the pubkey of a peer, matches only that peer.
426  * return number of entries placed in `entries`.
427  */
get_name_list_entries_by_prefix(uint32_t conferencenum,const char * prefix,NameListEntry ** entries,uint32_t maxpeers)428 uint32_t get_name_list_entries_by_prefix(uint32_t conferencenum, const char *prefix, NameListEntry **entries,
429         uint32_t maxpeers)
430 {
431     ConferenceChat *chat = &conferences[conferencenum];
432 
433     if (!chat->active) {
434         return 0;
435     }
436 
437     const size_t len = strlen(prefix);
438 
439     if (len == 2 * TOX_PUBLIC_KEY_SIZE) {
440         for (uint32_t i = 0; i < chat->num_peers; ++i) {
441             NameListEntry *entry = &chat->name_list[i];
442 
443             if (strcasecmp(prefix, entry->pubkey_str) == 0) {
444                 entries[0] = entry;
445                 return 1;
446             }
447         }
448     }
449 
450     uint32_t n = 0;
451 
452     for (uint32_t i = 0; i < chat->num_peers; ++i) {
453         NameListEntry *entry = &chat->name_list[i];
454 
455         if (strncmp(prefix, entry->name, len) == 0
456                 || strncasecmp(prefix, entry->pubkey_str, len) == 0) {
457             entries[n] = entry;
458             ++n;
459 
460             if (n == maxpeers) {
461                 return n;
462             }
463         }
464     }
465 
466     return n;
467 }
468 
469 
compare_name_list_entries(const void * a,const void * b)470 static int compare_name_list_entries(const void *a, const void *b)
471 {
472     const int cmp1 = qsort_strcasecmp_hlpr(
473                          ((const NameListEntry *)a)->name,
474                          ((const NameListEntry *)b)->name);
475 
476     if (cmp1 == 0) {
477         return qsort_strcasecmp_hlpr(
478                    ((const NameListEntry *)a)->pubkey_str,
479                    ((const NameListEntry *)b)->pubkey_str);
480     }
481 
482     return cmp1;
483 }
484 
conference_update_name_list(uint32_t conferencenum)485 static void conference_update_name_list(uint32_t conferencenum)
486 {
487     ConferenceChat *chat = &conferences[conferencenum];
488 
489     if (!chat->active) {
490         return;
491     }
492 
493     if (chat->name_list) {
494         free(chat->name_list);
495     }
496 
497     chat->name_list = malloc(chat->num_peers * sizeof(NameListEntry));
498 
499     if (chat->name_list == NULL) {
500         exit_toxic_err("failed in conference_update_name_list", FATALERR_MEMORY);
501     }
502 
503     uint32_t count = 0;
504 
505     for (uint32_t i = 0; i < chat->max_idx; ++i) {
506         const ConferencePeer *peer = &chat->peer_list[i];
507         NameListEntry *entry = &chat->name_list[count];
508 
509         if (peer->active) {
510             memcpy(entry->name, peer->name, peer->name_length + 1);
511             tox_pk_bytes_to_str(peer->pubkey, sizeof(peer->pubkey), entry->pubkey_str, sizeof(entry->pubkey_str));
512             entry->peernum = i;
513             ++count;
514         }
515     }
516 
517     if (count != chat->num_peers) {
518         fprintf(stderr, "WARNING: count != chat->num_peers\n");
519     }
520 
521     qsort(chat->name_list, count, sizeof(NameListEntry), compare_name_list_entries);
522 }
523 
524 /* Reallocates conferencenum's peer list.
525  *
526  * Returns 0 on success.
527  * Returns -1 on failure.
528  */
realloc_peer_list(ConferenceChat * chat,uint32_t num_peers)529 static int realloc_peer_list(ConferenceChat *chat, uint32_t num_peers)
530 {
531     if (!chat) {
532         return -1;
533     }
534 
535     if (num_peers == 0) {
536         free(chat->peer_list);
537         chat->peer_list = NULL;
538         return 0;
539     }
540 
541     ConferencePeer *tmp_list = realloc(chat->peer_list, num_peers * sizeof(ConferencePeer));
542 
543     if (!tmp_list) {
544         return -1;
545     }
546 
547     chat->peer_list = tmp_list;
548 
549     return 0;
550 }
551 
552 /* return NULL if peer or conference doesn't exist */
peer_in_conference(uint32_t conferencenum,uint32_t peernum)553 static ConferencePeer *peer_in_conference(uint32_t conferencenum, uint32_t peernum)
554 {
555     if (conferencenum >= MAX_CONFERENCE_NUM) {
556         return NULL;
557     }
558 
559     const ConferenceChat *chat = &conferences[conferencenum];
560 
561     if (!chat->active || peernum > chat->max_idx) {
562         return NULL;
563     }
564 
565     ConferencePeer *peer = &chat->peer_list[peernum];
566 
567     if (!peer->active) {
568         return NULL;
569     }
570 
571     return peer;
572 }
573 
574 #ifdef AUDIO
575 
576 /* Return true if ptt is disabled or enabled and active. */
conference_check_push_to_talk(ConferenceChat * chat)577 static bool conference_check_push_to_talk(ConferenceChat *chat)
578 {
579     if (!chat->push_to_talk_enabled) {
580         return true;
581     }
582 
583     return !timed_out(chat->ptt_last_pushed, 1);
584 }
585 
conference_enable_push_to_talk(ConferenceChat * chat)586 static void conference_enable_push_to_talk(ConferenceChat *chat)
587 {
588     chat->ptt_last_pushed = get_unix_time();
589 }
590 
set_peer_audio_position(Tox * m,uint32_t conferencenum,uint32_t peernum)591 static void set_peer_audio_position(Tox *m, uint32_t conferencenum, uint32_t peernum)
592 {
593     ConferenceChat *chat = &conferences[conferencenum];
594     ConferencePeer *peer = &chat->peer_list[peernum];
595 
596     if (peer == NULL || !peer->sending_audio) {
597         return;
598     }
599 
600     // Position peers at distance 1 in front of listener,
601     // ordered left to right by order in peerlist excluding self.
602     uint32_t num_posns = chat->num_peers;
603     uint32_t peer_posn = peernum;
604 
605     for (uint32_t i = 0; i < chat->num_peers; ++i) {
606         if (tox_conference_peer_number_is_ours(m, conferencenum, peernum, NULL)) {
607             if (i == peernum) {
608                 return;
609             }
610 
611             --num_posns;
612 
613             if (i < peernum) {
614                 --peer_posn;
615             }
616         }
617     }
618 
619     const float angle = asinf(peer_posn - (float)(num_posns - 1) / 2);
620     set_source_position(peer->audio_out_idx, sinf(angle), cosf(angle), 0);
621 }
622 #endif // AUDIO
623 
624 
find_peer_by_pubkey(const ConferencePeer * list,uint32_t num_peers,uint8_t * pubkey,uint32_t * idx)625 static bool find_peer_by_pubkey(const ConferencePeer *list, uint32_t num_peers, uint8_t *pubkey, uint32_t *idx)
626 {
627     for (uint32_t i = 0; i < num_peers; ++i) {
628         const ConferencePeer *peer = &list[i];
629 
630         if (peer->active && memcmp(peer->pubkey, pubkey, TOX_PUBLIC_KEY_SIZE) == 0) {
631             if (idx) {
632                 *idx = i;
633             }
634 
635             return true;
636         }
637     }
638 
639     return false;
640 }
641 
update_peer_list(ToxWindow * self,Tox * m,uint32_t conferencenum,uint32_t num_peers,uint32_t old_num_peers)642 static void update_peer_list(ToxWindow *self, Tox *m, uint32_t conferencenum, uint32_t num_peers,
643                              uint32_t old_num_peers)
644 {
645     ConferenceChat *chat = &conferences[conferencenum];
646 
647     if (!chat->active) {
648         return;
649     }
650 
651     ChatContext *ctx = self->chatwin;
652 
653     ConferencePeer *old_peer_list = malloc(old_num_peers * sizeof(ConferencePeer));
654 
655     if (!old_peer_list) {
656         exit_toxic_err("failed in update_peer_list", FATALERR_MEMORY);
657         return;
658     }
659 
660     if (chat->peer_list != NULL) {
661         memcpy(old_peer_list, chat->peer_list, old_num_peers * sizeof(ConferencePeer));
662     }
663 
664     if (realloc_peer_list(chat, num_peers) != 0) {
665         free(old_peer_list);
666         fprintf(stderr, "Warning: realloc_peer_list() failed in update_peer_list()\n");
667         return;
668     }
669 
670     for (uint32_t i = 0; i < num_peers; ++i) {
671         ConferencePeer *peer = &chat->peer_list[i];
672 
673         *peer = (struct ConferencePeer) {
674             0
675         };
676 
677         Tox_Err_Conference_Peer_Query err;
678         tox_conference_peer_get_public_key(m, conferencenum, i, peer->pubkey, &err);
679 
680         if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) {
681             continue;
682         }
683 
684         bool new_peer = true;
685         uint32_t j;
686 
687         if (find_peer_by_pubkey(old_peer_list, old_num_peers, peer->pubkey, &j)) {
688             ConferencePeer *old_peer = &old_peer_list[j];
689             memcpy(peer, old_peer, sizeof(ConferencePeer));
690             old_peer->active = false;
691             new_peer = false;
692         }
693 
694         size_t length = tox_conference_peer_get_name_size(m, conferencenum, i, &err);
695 
696         if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK || length >= TOX_MAX_NAME_LENGTH) {
697             // FIXME: length == TOX_MAX_NAME_LENGTH should not be an error!
698             continue;
699         }
700 
701         tox_conference_peer_get_name(m, conferencenum, i, (uint8_t *) peer->name, &err);
702         peer->name[length] = 0;
703 
704         if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) {
705             continue;
706         }
707 
708         peer->active = true;
709         peer->name_length = length;
710         peer->peernum = i;
711 
712         if (new_peer && peer->name_length > 0 && timed_out(chat->start_time, CONFERENCE_EVENT_WAIT)) {
713             const char *msg = "has joined the conference";
714             line_info_add(self, true, peer->name, NULL, CONNECTION, 0, GREEN, msg);
715             write_to_log(msg, peer->name, ctx->log, true);
716         }
717 
718 #ifdef AUDIO
719         set_peer_audio_position(m, conferencenum, i);
720 #endif
721     }
722 
723     conference_update_name_list(conferencenum);
724 
725     for (uint32_t i = 0; i < old_num_peers; ++i) {
726         ConferencePeer *old_peer = &old_peer_list[i];
727 
728         if (old_peer->active) {
729             if (old_peer->name_length > 0 && !find_peer_by_pubkey(chat->peer_list, chat->num_peers, old_peer->pubkey, NULL)) {
730                 const char *msg = "has left the conference";
731                 line_info_add(self, true, old_peer->name, NULL, DISCONNECTION, 0, RED, msg);
732                 write_to_log(msg, old_peer->name, ctx->log, true);
733             }
734 
735             free_peer(old_peer);
736         }
737     }
738 
739     free(old_peer_list);
740 }
741 
conference_onConferenceNameListChange(ToxWindow * self,Tox * m,uint32_t conferencenum)742 static void conference_onConferenceNameListChange(ToxWindow *self, Tox *m, uint32_t conferencenum)
743 {
744     if (self->num != conferencenum) {
745         return;
746     }
747 
748     if (conferencenum > max_conference_index) {
749         return;
750     }
751 
752     ConferenceChat *chat = &conferences[conferencenum];
753 
754     if (!chat->active) {
755         return;
756     }
757 
758     Tox_Err_Conference_Peer_Query err;
759 
760     uint32_t num_peers = tox_conference_peer_count(m, conferencenum, &err);
761 
762     if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) {
763         fprintf(stderr, "conference_onConferenceNameListChange() failed with error: %d\n", err);
764         return;
765     }
766 
767     const uint32_t old_num = chat->num_peers;
768 
769     chat->num_peers = num_peers;
770     chat->max_idx = num_peers;
771     update_peer_list(self, m, conferencenum, num_peers, old_num);
772 }
773 
conference_onConferencePeerNameChange(ToxWindow * self,Tox * m,uint32_t conferencenum,uint32_t peernum,const char * name,size_t length)774 static void conference_onConferencePeerNameChange(ToxWindow *self, Tox *m, uint32_t conferencenum, uint32_t peernum,
775         const char *name, size_t length)
776 {
777     UNUSED_VAR(length);
778 
779     if (self->num != conferencenum) {
780         return;
781     }
782 
783     const ConferencePeer *peer = peer_in_conference(conferencenum, peernum);
784 
785     if (peer != NULL) {
786         ChatContext *ctx = self->chatwin;
787 
788         if (peer->name_length > 0) {
789             char log_event[TOXIC_MAX_NAME_LENGTH * 2 + 32];
790             line_info_add(self, true, peer->name, (const char *) name, NAME_CHANGE, 0, 0, " is now known as ");
791 
792             snprintf(log_event, sizeof(log_event), "is now known as %s", (const char *) name);
793             write_to_log(log_event, peer->name, ctx->log, true);
794 
795             // this is kind of a hack; peers always join a group with no name set and then set it after
796         } else if (timed_out(conferences[conferencenum].start_time, CONFERENCE_EVENT_WAIT)) {
797             const char *msg = "has joined the conference";
798             line_info_add(self, true, name, NULL, CONNECTION, 0, GREEN, msg);
799             write_to_log(msg, name, ctx->log, true);
800         }
801     }
802 
803     conference_onConferenceNameListChange(self, m, conferencenum);
804 }
805 
send_conference_action(ToxWindow * self,ChatContext * ctx,Tox * m,char * action)806 static void send_conference_action(ToxWindow *self, ChatContext *ctx, Tox *m, char *action)
807 {
808     if (action == NULL) {
809         wprintw(ctx->history, "Invalid syntax.\n");
810         return;
811     }
812 
813     Tox_Err_Conference_Send_Message err;
814 
815     if (!tox_conference_send_message(m, self->num, TOX_MESSAGE_TYPE_ACTION, (uint8_t *) action, strlen(action), &err)) {
816         line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Failed to send action (error %d)", err);
817     }
818 }
819 
820 /* Offset for the peer number box at the top of the statusbar */
sidebar_offset(uint32_t conferencenum)821 static int sidebar_offset(uint32_t conferencenum)
822 {
823     return 2 + conferences[conferencenum].audio_enabled;
824 }
825 
826 /*
827  * Return true if input is recognized by handler
828  */
conference_onKey(ToxWindow * self,Tox * m,wint_t key,bool ltr)829 static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
830 {
831     ChatContext *ctx = self->chatwin;
832 
833     int x, y, y2, x2;
834     getyx(self->window, y, x);
835     getmaxyx(self->window, y2, x2);
836 
837     UNUSED_VAR(y);
838 
839     if (x2 <= 0 || y2 <= 0) {
840         return false;
841     }
842 
843     if (self->help->active) {
844         help_onKey(self, key);
845         return true;
846     }
847 
848     if (ctx->pastemode && key == L'\r') {
849         key = L'\n';
850     }
851 
852     if (ltr || key == L'\n') {    /* char is printable */
853         input_new_char(self, key, x, x2);
854         return true;
855     }
856 
857     if (line_info_onKey(self, key)) {
858         return true;
859     }
860 
861     if (input_handle(self, key, x, x2)) {
862         return true;
863     }
864 
865     bool input_ret = false;
866     ConferenceChat *chat = &conferences[self->num];
867 
868 #ifdef AUDIO
869 
870     if (chat->audio_enabled && chat->push_to_talk_enabled && key == KEY_F(2)) {
871         input_ret = true;
872         conference_enable_push_to_talk(chat);
873     }
874 
875 #endif // AUDIO
876 
877     if (key == L'\t') {  /* TAB key: auto-completes peer name or command */
878         input_ret = true;
879 
880         if (ctx->len > 0) {
881             int diff = -1;
882 
883             /* TODO: make this not suck */
884             if (ctx->line[0] != L'/' || wcscmp(ctx->line, L"/me") == 0) {
885                 const char **complete_strs = calloc(chat->num_peers, sizeof(const char *));
886 
887                 if (complete_strs) {
888                     for (uint32_t i = 0; i < chat->num_peers; ++i) {
889                         complete_strs[i] = (const char *) chat->name_list[i].name;
890                     }
891 
892                     diff = complete_line(self, complete_strs, chat->num_peers);
893                     free(complete_strs);
894                 }
895             } else if (wcsncmp(ctx->line, L"/avatar ", wcslen(L"/avatar ")) == 0) {
896                 diff = dir_match(self, m, ctx->line, L"/avatar");
897             }
898 
899 #ifdef PYTHON
900             else if (wcsncmp(ctx->line, L"/run ", wcslen(L"/run ")) == 0) {
901                 diff = dir_match(self, m, ctx->line, L"/run");
902             }
903 
904 #endif
905             else if (wcsncmp(ctx->line, L"/mute ", wcslen(L"/mute ")) == 0) {
906                 const char **complete_strs = calloc(chat->num_peers, sizeof(const char *));
907 
908                 if (complete_strs) {
909                     for (uint32_t i = 0; i < chat->num_peers; ++i) {
910                         complete_strs[i] = (const char *) chat->name_list[i].name;
911                     }
912 
913                     diff = complete_line(self, complete_strs, chat->num_peers);
914 
915                     if (diff == -1) {
916                         for (uint32_t i = 0; i < chat->num_peers; ++i) {
917                             complete_strs[i] = (const char *) chat->name_list[i].pubkey_str;
918                         }
919 
920                         diff = complete_line(self, complete_strs, chat->num_peers);
921                     }
922 
923                     free(complete_strs);
924                 }
925 
926             } else {
927                 diff = complete_line(self, conference_cmd_list, sizeof(conference_cmd_list) / sizeof(char *));
928             }
929 
930             if (diff != -1) {
931                 if (x + diff > x2 - 1) {
932                     int wlen = MAX(0, wcswidth(ctx->line, sizeof(ctx->line) / sizeof(wchar_t)));
933                     ctx->start = wlen < x2 ? 0 : wlen - x2 + 1;
934                 }
935             } else {
936                 sound_notify(self, notif_error, 0, NULL);
937             }
938         } else {
939             sound_notify(self, notif_error, 0, NULL);
940         }
941     } else if (key == T_KEY_C_DOWN) {    /* Scroll peerlist up and down one position */
942         input_ret = true;
943         const int L = y2 - CHATBOX_HEIGHT - sidebar_offset(self->num);
944 
945         if (chat->side_pos < (int64_t) chat->num_peers - L) {
946             ++chat->side_pos;
947         }
948     } else if (key == T_KEY_C_UP) {
949         input_ret = true;
950 
951         if (chat->side_pos > 0) {
952             --chat->side_pos;
953         }
954     } else if (key == L'\r') {
955         input_ret = true;
956         rm_trailing_spaces_buf(ctx);
957 
958         if (!wstring_is_empty(ctx->line)) {
959             add_line_to_hist(ctx);
960 
961             wstrsubst(ctx->line, L'¶', L'\n');
962 
963             char line[MAX_STR_SIZE];
964 
965             if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) {
966                 memset(line, 0, sizeof(line));
967             }
968 
969             if (line[0] == '/') {
970                 if (strcmp(line, "/close") == 0) {
971                     delete_conference(self, m, self->num);
972                     return true;
973                 } else if (strncmp(line, "/me ", strlen("/me ")) == 0) {
974                     send_conference_action(self, ctx, m, line + strlen("/me "));
975                 } else {
976                     execute(ctx->history, self, m, line, CONFERENCE_COMMAND_MODE);
977                 }
978             } else if (line[0]) {
979                 Tox_Err_Conference_Send_Message err;
980 
981                 if (!tox_conference_send_message(m, self->num, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) line, strlen(line), &err)) {
982                     line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Failed to send message (error %d)", err);
983                 }
984             } else {
985                 line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Failed to parse message.");
986             }
987         }
988 
989         wclear(ctx->linewin);
990         wmove(self->window, y2, 0);
991         reset_buf(ctx);
992     }
993 
994     return input_ret;
995 }
996 
draw_peer(ToxWindow * self,Tox * m,ChatContext * ctx,uint32_t i)997 static void draw_peer(ToxWindow *self, Tox *m, ChatContext *ctx, uint32_t i)
998 {
999     pthread_mutex_lock(&Winthread.lock);
1000     const uint32_t peer_idx = i + conferences[self->num].side_pos;
1001     const uint32_t peernum = conferences[self->num].name_list[peer_idx].peernum;
1002     const bool is_self = tox_conference_peer_number_is_ours(m, self->num, peernum, NULL);
1003     const bool audio = conferences[self->num].audio_enabled;
1004 
1005     if (audio) {
1006 #ifdef AUDIO
1007         const ConferencePeer *peer = peer_in_conference(self->num, peernum);
1008         const bool audio_active = is_self
1009                                   ? !timed_out(conferences[self->num].last_sent_audio, 2)
1010                                   : peer != NULL && peer->sending_audio && !timed_out(peer->last_audio_time, 2);
1011         const bool mute = audio_active &&
1012                           (is_self
1013                            ? device_is_muted(input, conferences[self->num].audio_in_idx)
1014                            : peer != NULL && device_is_muted(output, peer->audio_out_idx));
1015         pthread_mutex_unlock(&Winthread.lock);
1016 
1017         const int aud_attr = A_BOLD | COLOR_PAIR(audio_active && !mute ? GREEN : RED);
1018         wattron(ctx->sidebar, aud_attr);
1019         waddch(ctx->sidebar, audio_active ? (mute ? 'M' : '*') : '-');
1020         wattroff(ctx->sidebar, aud_attr);
1021         waddch(ctx->sidebar, ' ');
1022 #endif
1023     } else {
1024         pthread_mutex_unlock(&Winthread.lock);
1025     }
1026 
1027     /* truncate nick to fit in side panel without modifying list */
1028     char tmpnick[TOX_MAX_NAME_LENGTH];
1029     int maxlen = SIDEBAR_WIDTH - 2 - 2 * audio;
1030 
1031     pthread_mutex_lock(&Winthread.lock);
1032     memcpy(tmpnick, &conferences[self->num].name_list[peer_idx].name, maxlen);
1033     pthread_mutex_unlock(&Winthread.lock);
1034 
1035     tmpnick[maxlen] = '\0';
1036 
1037     if (is_self) {
1038         wattron(ctx->sidebar, COLOR_PAIR(GREEN));
1039     }
1040 
1041     wprintw(ctx->sidebar, "%s\n", tmpnick);
1042 
1043     if (is_self) {
1044         wattroff(ctx->sidebar, COLOR_PAIR(GREEN));
1045     }
1046 }
1047 
conference_onDraw(ToxWindow * self,Tox * m)1048 static void conference_onDraw(ToxWindow *self, Tox *m)
1049 {
1050     UNUSED_VAR(m);
1051 
1052     int x2, y2;
1053     getmaxyx(self->window, y2, x2);
1054 
1055     if (x2 <= 0 || y2 <= 0) {
1056         return;
1057     }
1058 
1059     ConferenceChat *chat = &conferences[self->num];
1060 
1061     if (!chat->active) {
1062         return;
1063     }
1064 
1065     ChatContext *ctx = self->chatwin;
1066 
1067     pthread_mutex_lock(&Winthread.lock);
1068     line_info_print(self);
1069     pthread_mutex_unlock(&Winthread.lock);
1070 
1071     wclear(ctx->linewin);
1072 
1073     curs_set(1);
1074 
1075     if (ctx->len > 0) {
1076         mvwprintw(ctx->linewin, 0, 0, "%ls", &ctx->line[ctx->start]);
1077     }
1078 
1079     wclear(ctx->sidebar);
1080 
1081     if (self->show_peerlist) {
1082         wattron(ctx->sidebar, COLOR_PAIR(BLUE));
1083         mvwvline(ctx->sidebar, 0, 0, ACS_VLINE, y2 - CHATBOX_HEIGHT);
1084         mvwaddch(ctx->sidebar, y2 - CHATBOX_HEIGHT, 0, ACS_BTEE);
1085         wattroff(ctx->sidebar, COLOR_PAIR(BLUE));
1086 
1087         pthread_mutex_lock(&Winthread.lock);
1088         const uint32_t num_peers = chat->num_peers;
1089         const bool audio = chat->audio_enabled;
1090         const int header_lines = sidebar_offset(self->num);
1091         pthread_mutex_unlock(&Winthread.lock);
1092 
1093         int line = 0;
1094 
1095         if (audio) {
1096 #ifdef AUDIO
1097             pthread_mutex_lock(&Winthread.lock);
1098             const bool ptt_idle = !conference_check_push_to_talk(chat) && chat->push_to_talk_enabled;
1099             const bool mic_on = !device_is_muted(input, chat->audio_in_idx);
1100             const float volume = get_input_volume();
1101             const float threshold = device_get_VAD_threshold(chat->audio_in_idx);
1102             pthread_mutex_unlock(&Winthread.lock);
1103 
1104             wmove(ctx->sidebar, line, 1);
1105             wattron(ctx->sidebar, A_BOLD);
1106             wprintw(ctx->sidebar, "Mic: ");
1107 
1108             if (!mic_on) {
1109                 wattron(ctx->sidebar, COLOR_PAIR(RED));
1110                 wprintw(ctx->sidebar, "MUTED");
1111                 wattroff(ctx->sidebar, COLOR_PAIR(RED));
1112             } else if (ptt_idle)  {
1113                 wattron(ctx->sidebar, COLOR_PAIR(GREEN));
1114                 wprintw(ctx->sidebar, "PTT");
1115                 wattroff(ctx->sidebar, COLOR_PAIR(GREEN));
1116             }  else {
1117                 const int color = volume > threshold ? GREEN : RED;
1118                 wattron(ctx->sidebar, COLOR_PAIR(color));
1119 
1120                 float v = volume;
1121 
1122                 if (v <= 0.0f) {
1123                     wprintw(ctx->sidebar, ".");
1124                 }
1125 
1126                 while (v > 0.0f) {
1127                     wprintw(ctx->sidebar, v > 10.0f ? (v > 15.0f ? "*" : "+") : (v > 5.0f ? "-" : "."));
1128                     v -= 20.0f;
1129                 }
1130 
1131                 wattroff(ctx->sidebar, COLOR_PAIR(color));
1132             }
1133 
1134             wattroff(ctx->sidebar, A_BOLD);
1135             ++line;
1136 #endif  // AUDIO
1137         }
1138 
1139         wmove(ctx->sidebar, line, 1);
1140         wattron(ctx->sidebar, A_BOLD);
1141         wprintw(ctx->sidebar, "Peers: %"PRIu32"\n", num_peers);
1142         wattroff(ctx->sidebar, A_BOLD);
1143         ++line;
1144 
1145         wattron(ctx->sidebar, COLOR_PAIR(BLUE));
1146         mvwaddch(ctx->sidebar, line, 0, ACS_LTEE);
1147         mvwhline(ctx->sidebar, line, 1, ACS_HLINE, SIDEBAR_WIDTH - 1);
1148         wattroff(ctx->sidebar, COLOR_PAIR(BLUE));
1149         ++line;
1150 
1151         for (uint32_t i = 0;
1152                 i < num_peers && i < y2 - header_lines - CHATBOX_HEIGHT;
1153                 ++i) {
1154             wmove(ctx->sidebar, i + header_lines, 1);
1155             draw_peer(self, m, ctx, i);
1156         }
1157     }
1158 
1159     int y, x;
1160     getyx(self->window, y, x);
1161 
1162     UNUSED_VAR(x);
1163 
1164     int new_x = ctx->start ? x2 - 1 : MAX(0, wcswidth(ctx->line, ctx->pos));
1165     wmove(self->window, y, new_x);
1166 
1167     draw_window_bar(self);
1168 
1169     wnoutrefresh(self->window);
1170 
1171     if (self->help->active) {
1172         help_onDraw(self);
1173     }
1174 }
1175 
conference_onInit(ToxWindow * self,Tox * m)1176 static void conference_onInit(ToxWindow *self, Tox *m)
1177 {
1178     int x2, y2;
1179     getmaxyx(self->window, y2, x2);
1180 
1181     if (x2 <= 0 || y2 <= 0) {
1182         exit_toxic_err("failed in conference_onInit", FATALERR_CURSES);
1183     }
1184 
1185     ChatContext *ctx = self->chatwin;
1186 
1187     ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2 - SIDEBAR_WIDTH - 1, 0, 0);
1188     self->window_bar = subwin(self->window, WINDOW_BAR_HEIGHT, x2, y2 - (CHATBOX_HEIGHT + WINDOW_BAR_HEIGHT), 0);
1189     ctx->linewin = subwin(self->window, CHATBOX_HEIGHT, x2, y2 - CHATBOX_HEIGHT, 0);
1190     ctx->sidebar = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, SIDEBAR_WIDTH, 0, x2 - SIDEBAR_WIDTH);
1191 
1192     ctx->hst = calloc(1, sizeof(struct history));
1193     ctx->log = calloc(1, sizeof(struct chatlog));
1194 
1195     if (ctx->log == NULL || ctx->hst == NULL) {
1196         exit_toxic_err("failed in conference_onInit", FATALERR_MEMORY);
1197     }
1198 
1199     line_info_init(ctx->hst);
1200 
1201     scrollok(ctx->history, 0);
1202     wmove(self->window, y2 - CURS_Y_OFFSET, 0);
1203 }
1204 
new_conference_chat(uint32_t conferencenum)1205 static ToxWindow *new_conference_chat(uint32_t conferencenum)
1206 {
1207     ToxWindow *ret = calloc(1, sizeof(ToxWindow));
1208 
1209     if (ret == NULL) {
1210         exit_toxic_err("failed in new_conference_chat", FATALERR_MEMORY);
1211     }
1212 
1213     ret->type = WINDOW_TYPE_CONFERENCE;
1214 
1215     ret->onKey = &conference_onKey;
1216     ret->onDraw = &conference_onDraw;
1217     ret->onInit = &conference_onInit;
1218     ret->onConferenceMessage = &conference_onConferenceMessage;
1219     ret->onConferenceNameListChange = &conference_onConferenceNameListChange;
1220     ret->onConferencePeerNameChange = &conference_onConferencePeerNameChange;
1221     ret->onConferenceTitleChange = &conference_onConferenceTitleChange;
1222 
1223     snprintf(ret->name, sizeof(ret->name), "Conference %u", conferencenum);
1224 
1225     ChatContext *chatwin = calloc(1, sizeof(ChatContext));
1226     Help *help = calloc(1, sizeof(Help));
1227 
1228     if (chatwin == NULL || help == NULL) {
1229         exit_toxic_err("failed in new_conference_chat", FATALERR_MEMORY);
1230     }
1231 
1232     ret->chatwin = chatwin;
1233     ret->help = help;
1234 
1235     ret->num = conferencenum;
1236     ret->show_peerlist = true;
1237     ret->active_box = -1;
1238 
1239     return ret;
1240 }
1241 
1242 #ifdef AUDIO
1243 
1244 #define CONFAV_SAMPLE_RATE 48000
1245 #define CONFAV_FRAME_DURATION 20
1246 #define CONFAV_SAMPLES_PER_FRAME (CONFAV_SAMPLE_RATE * CONFAV_FRAME_DURATION / 1000)
1247 
audio_conference_callback(void * tox,uint32_t conferencenum,uint32_t peernum,const int16_t * pcm,unsigned int samples,uint8_t channels,uint32_t sample_rate,void * userdata)1248 void audio_conference_callback(void *tox, uint32_t conferencenum, uint32_t peernum, const int16_t *pcm,
1249                                unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata)
1250 {
1251     ConferencePeer *peer = peer_in_conference(conferencenum, peernum);
1252 
1253     if (peer == NULL) {
1254         return;
1255     }
1256 
1257     if (!peer->sending_audio) {
1258         if (open_output_device(&peer->audio_out_idx,
1259                                sample_rate, CONFAV_FRAME_DURATION, channels) != de_None) {
1260             // TODO: error message?
1261             return;
1262         }
1263 
1264         peer->sending_audio = true;
1265 
1266         set_peer_audio_position(tox, conferencenum, peernum);
1267     }
1268 
1269     write_out(peer->audio_out_idx, pcm, samples, channels, sample_rate);
1270 
1271     peer->last_audio_time = get_unix_time();
1272 
1273     return;
1274 }
1275 
conference_read_device_callback(const int16_t * captured,uint32_t size,void * data)1276 static void conference_read_device_callback(const int16_t *captured, uint32_t size, void *data)
1277 {
1278     UNUSED_VAR(size);
1279 
1280     AudioInputCallbackData *audio_input_callback_data = (AudioInputCallbackData *)data;
1281 
1282     ConferenceChat *chat = &conferences[audio_input_callback_data->conferencenum];
1283 
1284     if (!conference_check_push_to_talk(chat)) {
1285         return;
1286     }
1287 
1288     chat->last_sent_audio = get_unix_time();
1289 
1290     int channels = user_settings->conference_audio_channels;
1291 
1292     toxav_group_send_audio(audio_input_callback_data->tox,
1293                            audio_input_callback_data->conferencenum,
1294                            captured, CONFAV_SAMPLES_PER_FRAME,
1295                            channels, CONFAV_SAMPLE_RATE);
1296 }
1297 
init_conference_audio_input(Tox * tox,uint32_t conferencenum)1298 bool init_conference_audio_input(Tox *tox, uint32_t conferencenum)
1299 {
1300     ConferenceChat *chat = &conferences[conferencenum];
1301 
1302     if (!chat->active || chat->audio_enabled) {
1303         return false;
1304     }
1305 
1306     const AudioInputCallbackData audio_input_callback_data = { tox, conferencenum };
1307     chat->audio_input_callback_data = audio_input_callback_data;
1308 
1309     int channels = user_settings->conference_audio_channels;
1310 
1311     bool success = (open_input_device(&chat->audio_in_idx,
1312                                       conference_read_device_callback, &chat->audio_input_callback_data,
1313                                       CONFAV_SAMPLE_RATE, CONFAV_FRAME_DURATION, channels)
1314                     == de_None);
1315 
1316     chat->audio_enabled = success;
1317 
1318     return success;
1319 }
1320 
toggle_conference_push_to_talk(uint32_t conferencenum,bool enabled)1321 bool toggle_conference_push_to_talk(uint32_t conferencenum, bool enabled)
1322 {
1323     ConferenceChat *chat = &conferences[conferencenum];
1324 
1325     if (!chat->active) {
1326         return false;
1327     }
1328 
1329     chat->push_to_talk_enabled = enabled;
1330 
1331     return true;
1332 }
1333 
enable_conference_audio(ToxWindow * self,Tox * tox,uint32_t conferencenum)1334 bool enable_conference_audio(ToxWindow *self, Tox *tox, uint32_t conferencenum)
1335 {
1336     if (!toxav_groupchat_av_enabled(tox, conferencenum)) {
1337         if (toxav_groupchat_enable_av(tox, conferencenum, audio_conference_callback, NULL) != 0) {
1338             return false;
1339         }
1340     }
1341 
1342     bool success = init_conference_audio_input(tox, conferencenum);
1343 
1344     if (success) {
1345         self->is_call = true;
1346     }
1347 
1348     return success;
1349 }
1350 
disable_conference_audio(ToxWindow * self,Tox * tox,uint32_t conferencenum)1351 bool disable_conference_audio(ToxWindow *self, Tox *tox, uint32_t conferencenum)
1352 {
1353     ConferenceChat *chat = &conferences[conferencenum];
1354 
1355     if (!chat->active) {
1356         return false;
1357     }
1358 
1359     if (chat->audio_enabled) {
1360         close_device(input, chat->audio_in_idx);
1361         chat->audio_enabled = false;
1362     }
1363 
1364     bool success = toxav_groupchat_disable_av(tox, conferencenum) == 0;
1365 
1366     if (success) {
1367         self->is_call = false;
1368     }
1369 
1370     return success;
1371 }
1372 
conference_mute_self(uint32_t conferencenum)1373 bool conference_mute_self(uint32_t conferencenum)
1374 {
1375     const ConferenceChat *chat = &conferences[conferencenum];
1376 
1377     if (!chat->active || !chat->audio_enabled) {
1378         return false;
1379     }
1380 
1381     device_mute(input, chat->audio_in_idx);
1382 
1383     return true;
1384 }
1385 
conference_mute_peer(const Tox * m,uint32_t conferencenum,uint32_t peernum)1386 bool conference_mute_peer(const Tox *m, uint32_t conferencenum, uint32_t peernum)
1387 {
1388     if (tox_conference_peer_number_is_ours(m, conferencenum, peernum, NULL)) {
1389         return conference_mute_self(conferencenum);
1390     }
1391 
1392     const ConferenceChat *chat = &conferences[conferencenum];
1393 
1394     if (!chat->active || !chat->audio_enabled
1395             || peernum > chat->max_idx) {
1396         return false;
1397     }
1398 
1399     const ConferencePeer *peer = peer_in_conference(conferencenum, peernum);
1400 
1401     if (peer == NULL || !peer->sending_audio) {
1402         return false;
1403     }
1404 
1405     device_mute(output, peer->audio_out_idx);
1406     return true;
1407 }
1408 
conference_set_VAD_threshold(uint32_t conferencenum,float threshold)1409 bool conference_set_VAD_threshold(uint32_t conferencenum, float threshold)
1410 {
1411     const ConferenceChat *chat = &conferences[conferencenum];
1412 
1413     if (!chat->active || !chat->audio_enabled) {
1414         return false;
1415     }
1416 
1417     return (device_set_VAD_threshold(chat->audio_in_idx, threshold) == de_None);
1418 }
1419 
conference_get_VAD_threshold(uint32_t conferencenum)1420 float conference_get_VAD_threshold(uint32_t conferencenum)
1421 {
1422     const ConferenceChat *chat = &conferences[conferencenum];
1423 
1424     if (!chat->active || !chat->audio_enabled) {
1425         return 0.0f;
1426     }
1427 
1428     return device_get_VAD_threshold(chat->audio_in_idx);
1429 }
1430 #endif  // AUDIO
1431