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