From ddcf224db22a88339529008ae89e8c89631d4d2f Mon Sep 17 00:00:00 2001 From: zugz Date: Thu, 7 May 2020 00:00:00 +0000 Subject: [PATCH] Implement groupAV --- src/audio_call.c | 18 +- src/audio_device.c | 61 ++++- src/audio_device.h | 4 + src/chat.c | 8 +- src/chat_commands.c | 43 ++- src/conference.c | 536 ++++++++++++++++++++++++++++++++++---- src/conference.h | 51 +++- src/conference_commands.c | 68 +++++ src/conference_commands.h | 2 + src/execute.c | 10 +- src/global_commands.c | 45 +++- src/help.c | 16 +- src/misc_tools.c | 20 ++ src/misc_tools.h | 7 + src/toxic.c | 25 +- 15 files changed, 794 insertions(+), 120 deletions(-) diff --git a/src/audio_call.c b/src/audio_call.c index db87906..a5d4bff 100644 --- a/src/audio_call.c +++ b/src/audio_call.c @@ -61,10 +61,10 @@ extern FriendsList Friends; extern ToxWindow *windows[MAX_WINDOWS_NUM]; -extern pthread_mutex_t tox_lock; - struct CallControl CallControl; +extern struct Winthread Winthread; + #define cbend pthread_exit(NULL) #define frame_size (CallControl.audio_sample_rate * CallControl.audio_frame_duration / 1000) @@ -188,14 +188,10 @@ void read_device_callback(const int16_t *captured, uint32_t size, void *data) return; } - pthread_mutex_lock(&tox_lock); - toxav_audio_send_frame(CallControl.av, friend_number, captured, sample_count, CallControl.audio_channels, CallControl.audio_sample_rate, &error); - - pthread_mutex_unlock(&tox_lock); } void write_device_callback(uint32_t friend_number, const int16_t *PCM, uint16_t sample_count, uint8_t channels, @@ -752,11 +748,7 @@ void cmd_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA const char *error_str; if (argc != 1) { - if (argc < 1) { - error_str = "Type must be specified!"; - } else { - error_str = "Only two arguments allowed!"; - } + error_str = "Specify type: \"/mute in\" or \"/mute out\"."; goto on_error; } @@ -785,10 +777,10 @@ void cmd_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA if (type == input) { device_mute(type, this_call->in_idx); - self->chatwin->infobox.in_is_muted ^= 1; + self->chatwin->infobox.in_is_muted = device_is_muted(type, this_call->in_idx); } else { device_mute(type, this_call->out_idx); - self->chatwin->infobox.out_is_muted ^= 1; + self->chatwin->infobox.out_is_muted = device_is_muted(type, this_call->out_idx); } pthread_mutex_unlock(&this_call->mutex); diff --git a/src/audio_device.c b/src/audio_device.c index 4f9ac49..17f8d56 100644 --- a/src/audio_device.c +++ b/src/audio_device.c @@ -44,9 +44,8 @@ #include #include -#define inline__ inline __attribute__((always_inline)) - extern struct user_settings *user_settings; +extern struct Winthread Winthread; typedef struct FrameInfo { uint32_t samples_per_frame; @@ -91,6 +90,8 @@ typedef struct AudioState { FrameInfo capture_frame_info; + // mutexes to prevent changes to input resp. output devices and al_devices + // during poll_input iterations resp. calls to write_out pthread_mutex_t mutex[2]; // TODO: unused @@ -231,6 +232,21 @@ DeviceError device_mute(DeviceType type, uint32_t device_idx) return de_None; } +bool device_is_muted(DeviceType type, uint32_t device_idx) +{ + if (device_idx >= MAX_DEVICES) { + return false; + } + + Device *device = &audio_state->devices[type][device_idx]; + + if (!device->active) { + return false; + } + + return device->muted; +} + #ifdef AUDIO DeviceError device_set_VAD_treshold(uint32_t device_idx, float value) { @@ -253,6 +269,31 @@ DeviceError device_set_VAD_treshold(uint32_t device_idx, float value) } #endif +DeviceError set_source_position(uint32_t device_idx, float x, float y, float z) +{ + if (device_idx >= MAX_DEVICES) { + return de_InvalidSelection; + } + + Device *device = &audio_state->devices[output][device_idx]; + + if (!device->active) { + return de_DeviceNotActive; + } + + lock(output); + + alSource3f(device->source, AL_POSITION, x, y, z); + + unlock(output); + + if (!audio_state->al_device[output] || alcGetError(audio_state->al_device[output]) != AL_NO_ERROR) { + return de_AlError; + } + + return de_None; +} + static DeviceError close_al_device(DeviceType type) { if (audio_state->al_device[type] == NULL) { @@ -535,8 +576,8 @@ DeviceError close_device(DeviceType type, uint32_t device_idx) return err; } -inline__ DeviceError write_out(uint32_t device_idx, const int16_t *data, uint32_t sample_count, uint8_t channels, - uint32_t sample_rate) +DeviceError write_out(uint32_t device_idx, const int16_t *data, uint32_t sample_count, uint8_t channels, + uint32_t sample_rate) { if (device_idx >= MAX_DEVICES) { return de_InvalidSelection; @@ -634,17 +675,19 @@ static void *poll_input(void *arg) if (available_samples >= f_size && f_size <= FRAME_BUF_SIZE) { alcCaptureSamples(audio_state->al_device[input], frame_buf, f_size); + unlock(input); + pthread_mutex_lock(&Winthread.lock); + lock(input); + for (int i = 0; i < MAX_DEVICES; i++) { Device *device = &audio_state->devices[input][i]; if (device->active && !device->muted && device->cb) { - const DataHandleCallback cb = device->cb; - void *const cb_data = device->cb_data; - unlock(input); - cb(frame_buf, f_size, cb_data); - lock(input); + device->cb(frame_buf, f_size, device->cb_data); } } + + pthread_mutex_unlock(&Winthread.lock); } } diff --git a/src/audio_device.h b/src/audio_device.h index 5dbe539..01fe0e9 100644 --- a/src/audio_device.h +++ b/src/audio_device.h @@ -64,10 +64,14 @@ DeviceError terminate_devices(void); /* toggle device mute */ DeviceError device_mute(DeviceType type, uint32_t device_idx); +bool device_is_muted(DeviceType type, uint32_t device_idx); + #ifdef AUDIO DeviceError device_set_VAD_treshold(uint32_t device_idx, float value); #endif +DeviceError set_source_position(uint32_t device_idx, float x, float y, float z); + DeviceError set_al_device(DeviceType type, int32_t selection); /* Start device */ diff --git a/src/chat.c b/src/chat.c index 2587883..9926b4f 100644 --- a/src/chat.c +++ b/src/chat.c @@ -740,13 +740,15 @@ static void chat_onConferenceInvite(ToxWindow *self, Tox *m, int32_t friendnumbe char name[TOX_MAX_NAME_LENGTH]; get_nick_truncate(m, name, friendnumber); + const char *description = type == TOX_CONFERENCE_TYPE_AV ? "an audio conference" : "a conference"; + if (self->active_box != -1) { - box_silent_notify2(self, NT_WNDALERT_2 | NT_NOFOCUS, self->active_box, "invites you to join conference"); + box_silent_notify2(self, NT_WNDALERT_2 | NT_NOFOCUS, self->active_box, "invites you to join %s", description); } else { - box_silent_notify(self, NT_WNDALERT_2 | NT_NOFOCUS, &self->active_box, name, "invites you to join conference"); + box_silent_notify(self, NT_WNDALERT_2 | NT_NOFOCUS, &self->active_box, name, "invites you to join %s", description); } - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s has invited you to a conference.", name); + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s has invited you to %s.", name, description); line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Type \"/join\" to join the chat."); } diff --git a/src/chat_commands.c b/src/chat_commands.c index 9b26e59..ebc5415 100644 --- a/src/chat_commands.c +++ b/src/chat_commands.c @@ -126,17 +126,31 @@ void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char return; } - if (type != TOX_CONFERENCE_TYPE_TEXT) { - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Toxic does not support audio conferences."); + uint32_t conferencenum; + + if (type == TOX_CONFERENCE_TYPE_TEXT) { + Tox_Err_Conference_Join err; + conferencenum = tox_conference_join(m, self->num, (const uint8_t *) conferencekey, length, &err); + + if (err != TOX_ERR_CONFERENCE_JOIN_OK) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference instance failed to initialize (error %d)", err); + return; + } + } else if (type == TOX_CONFERENCE_TYPE_AV) { +#ifdef AUDIO + conferencenum = toxav_join_av_groupchat(m, self->num, (const uint8_t *) conferencekey, length, audio_conference_callback, NULL); + + if (conferencenum == (uint32_t) -1) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio conference instance failed to initialize"); + return; + } + +#else + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio support disabled by compile-time option."); return; - } - - Tox_Err_Conference_Join err; - - uint32_t conferencenum = tox_conference_join(m, self->num, (const uint8_t *) conferencekey, length, &err); - - if (err != TOX_ERR_CONFERENCE_JOIN_OK) { - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference instance failed to initialize (error %d)", err); +#endif + } else { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Unknown conference type %d", type); return; } @@ -146,6 +160,15 @@ void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char return; } +#ifdef AUDIO + + if (type == TOX_CONFERENCE_TYPE_AV) { + if (!init_conference_audio_input(m, conferencenum)) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio capture failed; use \"/audio on\" to try again."); + } + } + +#endif } void cmd_savefile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) diff --git a/src/conference.c b/src/conference.c index c13a98a..901ac43 100644 --- a/src/conference.c +++ b/src/conference.c @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef AUDIO #ifdef __APPLE__ @@ -74,6 +75,9 @@ extern struct Winthread Winthread; static const char *conference_cmd_list[] = { "/accept", "/add", +#ifdef AUDIO + "/audio", +#endif "/avatar", "/clear", "/close", @@ -83,6 +87,9 @@ static const char *conference_cmd_list[] = { "/conference", "/help", "/log", +#ifdef AUDIO + "/mute", +#endif "/myid", #ifdef QRCODE "/myqr", @@ -92,6 +99,9 @@ static const char *conference_cmd_list[] = { "/nospam", "/quit", "/requests", +#ifdef AUDIO + "/sense", +#endif "/status", "/title", @@ -131,11 +141,18 @@ int init_conference_win(Tox *m, uint32_t conferencenum, uint8_t type, const char for (int i = 0; i <= max_conference_index; ++i) { if (!conferences[i].active) { + // FIXME: it is assumed at various points in the code that + // toxcore's conferencenums agree with toxic's indices to conferences; + // probably it so happens that this will (at least typically) be + // the case, because toxic and tox maintain the indices in + // parallel ways. But it isn't guaranteed by the API. conferences[i].chatwin = add_window(m, self); conferences[i].active = true; conferences[i].num_peers = 0; conferences[i].type = type; conferences[i].start_time = get_unix_time(); + conferences[i].audio_enabled = false; + conferences[i].last_sent_audio = 0; set_active_window_index(conferences[i].chatwin); set_window_title(self, title, title_length); @@ -144,7 +161,7 @@ int init_conference_win(Tox *m, uint32_t conferencenum, uint8_t type, const char ++max_conference_index; } - return 0; + return conferences[i].chatwin; } } @@ -153,11 +170,39 @@ int init_conference_win(Tox *m, uint32_t conferencenum, uint8_t type, const char return -1; } +static void free_peer(ConferencePeer *peer) +{ +#ifdef AUDIO + + if (peer->sending_audio) { + close_device(output, peer->audio_out_idx); + } + +#endif +} + void free_conference(ToxWindow *self, uint32_t conferencenum) { - free_ptr_array((void **) conferences[conferencenum].name_list); - free(conferences[conferencenum].peer_list); + ConferenceChat *chat = &conferences[conferencenum]; + for (uint32_t i = 0; i < chat->num_peers; ++i) { + ConferencePeer *peer = &chat->peer_list[i]; + + if (peer->active) { + free_peer(peer); + } + } + +#ifdef AUDIO + + if (chat->audio_enabled) { + close_device(input, chat->audio_in_idx); + } + +#endif + + free(chat->name_list); + free(chat->peer_list); conferences[conferencenum] = (ConferenceChat) { 0 }; @@ -293,6 +338,65 @@ static void conference_onConferenceTitleChange(ToxWindow *self, Tox *m, uint32_t write_to_log(tmp_event, nick, ctx->log, true); } +/* Puts `(NameListEntry *)`s in `entries` for each matched peer, up to a + * maximum of `maxpeers`. + * Maches each peer whose name or pubkey begins with `prefix`. + * If `prefix` is exactly the pubkey of a peer, matches only that peer. + * return number of entries placed in `entries`. + */ +uint32_t get_name_list_entries_by_prefix(uint32_t conferencenum, const char *prefix, NameListEntry **entries, + uint32_t maxpeers) +{ + ConferenceChat *chat = &conferences[conferencenum]; + + if (!chat->active) { + return 0; + } + + const int len = strlen(prefix); + + if (len == 2 * TOX_PUBLIC_KEY_SIZE) { + for (uint32_t i = 0; i < chat->num_peers; ++i) { + if (strcasecmp(prefix, chat->name_list[i].pubkey_str) == 0) { + entries[0] = &chat->name_list[i]; + return 1; + } + } + } + + uint32_t n = 0; + + for (uint32_t i = 0; i < chat->num_peers; ++i) { + if (strncmp(prefix, chat->name_list[i].name, len) == 0 + || strncasecmp(prefix, chat->name_list[i].pubkey_str, len) == 0) { + entries[n] = &chat->name_list[i]; + ++n; + + if (n == maxpeers) { + return n; + } + } + } + + return n; +} + + +static int compare_name_list_entries(const void *a, const void *b) +{ + const int cmp1 = qsort_strcasecmp_hlpr( + ((const NameListEntry *)a)->name, + ((const NameListEntry *)b)->name); + + if (cmp1 == 0) { + return qsort_strcasecmp_hlpr( + ((const NameListEntry *)a)->pubkey_str, + ((const NameListEntry *)b)->pubkey_str); + } + + return cmp1; +} + static void conference_update_name_list(uint32_t conferencenum) { ConferenceChat *chat = &conferences[conferencenum]; @@ -301,17 +405,24 @@ static void conference_update_name_list(uint32_t conferencenum) return; } - if (!chat->name_list) { - fprintf(stderr, "WARNING: name_list is NULL\n"); - return; + if (chat->name_list) { + free(chat->name_list); } - size_t count = 0; + chat->name_list = malloc(chat->num_peers * sizeof(NameListEntry)); - for (uint32_t i = 0; i < chat->max_idx && count < chat->num_peers; ++i) { + if (chat->name_list == NULL) { + exit_toxic_err("failed in conference_update_name_list", FATALERR_MEMORY); + } + + uint32_t count = 0; + + for (uint32_t i = 0; i < chat->max_idx; ++i) { if (chat->peer_list[i].active) { - memcpy(chat->name_list[count], chat->peer_list[i].name, chat->peer_list[i].name_length); - chat->name_list[count][chat->peer_list[i].name_length] = 0; + memcpy(chat->name_list[count].name, chat->peer_list[i].name, chat->peer_list[i].name_length + 1); + bin_pubkey_to_string(chat->peer_list[i].pubkey, sizeof(chat->peer_list[i].pubkey), + chat->name_list[count].pubkey_str, sizeof(chat->name_list[count].pubkey_str)); + chat->name_list[count].peernum = i; ++count; } } @@ -320,10 +431,10 @@ static void conference_update_name_list(uint32_t conferencenum) fprintf(stderr, "WARNING: count != chat->num_peers\n"); } - qsort(chat->name_list, count, sizeof(char *), qsort_ptr_char_array_helper); + qsort(chat->name_list, count, sizeof(NameListEntry), compare_name_list_entries); } -/* Reallocates conferencenum's peer list. Increase is true if the list needs to grow. +/* Reallocates conferencenum's peer list. * * Returns 0 on success. * Returns -1 on failure. @@ -351,7 +462,56 @@ static int realloc_peer_list(ConferenceChat *chat, uint32_t num_peers) return 0; } -static void update_peer_list(Tox *m, uint32_t conferencenum, uint32_t num_peers) +#ifdef AUDIO +static void set_peer_audio_position(Tox *m, uint32_t conferencenum, uint32_t peernum) +{ + ConferenceChat *chat = &conferences[conferencenum]; + ConferencePeer *peer = &chat->peer_list[peernum]; + + if (!peer->sending_audio) { + return; + } + + // Position peers at distance 1 in front of listener, + // ordered left to right by order in peerlist excluding self. + uint32_t num_posns = chat->num_peers; + uint32_t peer_posn = peernum; + + for (uint32_t i = 0; i < chat->num_peers; ++i) { + if (tox_conference_peer_number_is_ours(m, conferencenum, peernum, NULL)) { + if (i == peernum) { + return; + } + + --num_posns; + + if (i < peernum) { + --peer_posn; + } + } + } + + const float angle = asinf(peer_posn - (float)(num_posns - 1) / 2); + set_source_position(peer->audio_out_idx, sinf(angle), cosf(angle), 0); +} +#endif + + +static bool find_peer_by_pubkey(ConferencePeer *list, uint32_t num_peers, uint8_t *pubkey, uint32_t *idx) +{ + for (uint32_t i = 0; i < num_peers; ++i) { + ConferencePeer *peer = &list[i]; + + if (peer->active && memcmp(peer->pubkey, pubkey, TOX_PUBLIC_KEY_SIZE) == 0) { + *idx = i; + return true; + } + } + + return false; +} + +static void update_peer_list(Tox *m, uint32_t conferencenum, uint32_t num_peers, uint32_t old_num_peers) { ConferenceChat *chat = &conferences[conferencenum]; @@ -359,15 +519,42 @@ static void update_peer_list(Tox *m, uint32_t conferencenum, uint32_t num_peers) return; } + ConferencePeer *old_peer_list = malloc(old_num_peers * sizeof(ConferencePeer)); + + if (!old_peer_list) { + exit_toxic_err("failed in update_peer_list", FATALERR_MEMORY); + return; + } + + if (chat->peer_list != NULL) { + memcpy(old_peer_list, chat->peer_list, old_num_peers * sizeof(ConferencePeer)); + } + realloc_peer_list(chat, num_peers); + memset(chat->peer_list, 0, num_peers * sizeof(ConferencePeer)); for (uint32_t i = 0; i < num_peers; ++i) { ConferencePeer *peer = &chat->peer_list[i]; Tox_Err_Conference_Peer_Query err; + tox_conference_peer_get_public_key(m, conferencenum, i, peer->pubkey, &err); + + if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { + continue; + } + + uint32_t j; + + if (find_peer_by_pubkey(old_peer_list, old_num_peers, peer->pubkey, &j)) { + ConferencePeer *old_peer = &old_peer_list[j]; + memcpy(peer, old_peer, sizeof(ConferencePeer)); + old_peer->active = false; + } + size_t length = tox_conference_peer_get_name_size(m, conferencenum, i, &err); if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK || length >= TOX_MAX_NAME_LENGTH) { + // FIXME: length == TOX_MAX_NAME_LENGTH should not be an error! continue; } @@ -381,8 +568,22 @@ static void update_peer_list(Tox *m, uint32_t conferencenum, uint32_t num_peers) peer->active = true; peer->name_length = length; peer->peernumber = i; + +#ifdef AUDIO + set_peer_audio_position(m, conferencenum, i); +#endif } + for (uint32_t i = 0; i < old_num_peers; ++i) { + ConferencePeer *old_peer = &old_peer_list[i]; + + if (old_peer->active) { + free_peer(old_peer); + } + } + + free(old_peer_list); + conference_update_name_list(conferencenum); } @@ -402,12 +603,6 @@ static void conference_onConferenceNameListChange(ToxWindow *self, Tox *m, uint3 return; } - if (chat->name_list) { - free_ptr_array((void **) chat->name_list); - chat->name_list = NULL; - chat->num_peers = 0; - } - Tox_Err_Conference_Peer_Query err; uint32_t num_peers = tox_conference_peer_count(m, conferencenum, &err); @@ -417,16 +612,11 @@ static void conference_onConferenceNameListChange(ToxWindow *self, Tox *m, uint3 return; } - chat->name_list = (char **) malloc_ptr_array(num_peers, TOX_MAX_NAME_LENGTH + 1); - - if (chat->name_list == NULL) { - fprintf(stderr, "conference_onConferenceNameListChange(): Out of memory.\n"); - return; - } + const uint32_t old_num = chat->num_peers; chat->num_peers = num_peers; chat->max_idx = num_peers; - update_peer_list(m, conferencenum, num_peers); + update_peer_list(m, conferencenum, num_peers, old_num); } static void conference_onConferencePeerNameChange(ToxWindow *self, Tox *m, uint32_t conferencenum, uint32_t peernum, @@ -447,7 +637,6 @@ static void conference_onConferencePeerNameChange(ToxWindow *self, Tox *m, uint3 for (uint32_t i = 0; i < chat->max_idx; ++i) { ConferencePeer *peer = &chat->peer_list[i]; - // Test against default tox name to prevent nick change spam on initial join (TODO: this is disgusting) if (peer->active && peer->peernumber == peernum && peer->name_length > 0) { ChatContext *ctx = self->chatwin; char timefrmt[TIME_STR_SIZE]; @@ -480,6 +669,13 @@ static void send_conference_action(ToxWindow *self, ChatContext *ctx, Tox *m, ch } } +/* Offset for the peer number box at the top of the statusbar */ +static int sidebar_offset(uint32_t conferencenum) +{ + return 2 + conferences[conferencenum].audio_enabled; +} + + /* * Return true if input is recognized by handler */ @@ -525,11 +721,20 @@ static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) input_ret = true; if (ctx->len > 0) { - int diff; + int diff = -1; /* TODO: make this not suck */ if (ctx->line[0] != L'/' || wcscmp(ctx->line, L"/me") == 0) { - diff = complete_line(self, (const char **) conferences[self->num].name_list, conferences[self->num].num_peers); + const char **complete_strs = calloc(conferences[self->num].num_peers, sizeof(const char *)); + + if (complete_strs) { + for (uint32_t i = 0; i < conferences[self->num].num_peers; ++i) { + complete_strs[i] = (const char *) conferences[self->num].name_list[i].name; + } + + diff = complete_line(self, complete_strs, conferences[self->num].num_peers); + free(complete_strs); + } } else if (wcsncmp(ctx->line, L"/avatar ", wcslen(L"/avatar ")) == 0) { diff = dir_match(self, m, ctx->line, L"/avatar"); } @@ -540,7 +745,27 @@ static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) } #endif - else { + else if (wcsncmp(ctx->line, L"/mute ", wcslen(L"/mute ")) == 0) { + const char **complete_strs = calloc(conferences[self->num].num_peers, sizeof(const char *)); + + if (complete_strs) { + for (uint32_t i = 0; i < conferences[self->num].num_peers; ++i) { + complete_strs[i] = (const char *) conferences[self->num].name_list[i].name; + } + + diff = complete_line(self, complete_strs, conferences[self->num].num_peers); + + if (diff == -1) { + for (uint32_t i = 0; i < conferences[self->num].num_peers; ++i) { + complete_strs[i] = (const char *) conferences[self->num].name_list[i].pubkey_str; + } + diff = complete_line(self, complete_strs, conferences[self->num].num_peers); + } + + free(complete_strs); + } + + } else { diff = complete_line(self, conference_cmd_list, sizeof(conference_cmd_list) / sizeof(char *)); } @@ -557,7 +782,7 @@ static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) } } else if (key == T_KEY_C_DOWN) { /* Scroll peerlist up and down one position */ input_ret = true; - const int L = y2 - CHATBOX_HEIGHT - SDBAR_OFST; + const int L = y2 - CHATBOX_HEIGHT - sidebar_offset(self->num); if (conferences[self->num].side_pos < (int64_t) conferences[self->num].num_peers - L) { ++conferences[self->num].side_pos; @@ -609,6 +834,57 @@ static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) return input_ret; } +static void draw_peer(ToxWindow *self, Tox *m, ChatContext *ctx, uint32_t i) +{ + pthread_mutex_lock(&Winthread.lock); + const uint32_t peer_idx = i + conferences[self->num].side_pos; + const uint32_t peernum = conferences[self->num].name_list[peer_idx].peernum; + const bool is_self = tox_conference_peer_number_is_ours(m, self->num, peernum, NULL); + const bool audio = conferences[self->num].audio_enabled; + pthread_mutex_unlock(&Winthread.lock); + + /* truncate nick to fit in side panel without modifying list */ + char tmpnick[TOX_MAX_NAME_LENGTH]; + int maxlen = SIDEBAR_WIDTH - 2 - 2 * audio; + + if (audio) { +#ifdef AUDIO + pthread_mutex_lock(&Winthread.lock); + const ConferencePeer *peer = &conferences[self->num].peer_list[peernum]; + const bool audio_active = is_self + ? !timed_out(conferences[self->num].last_sent_audio, 2) + : peer->active && peer->sending_audio && !timed_out(peer->last_audio_time, 2); + const bool mute = audio_active && + (is_self + ? device_is_muted(input, conferences[self->num].audio_in_idx) + : device_is_muted(output, peer->audio_out_idx)); + pthread_mutex_unlock(&Winthread.lock); + + const int aud_attr = A_BOLD | COLOR_PAIR(audio_active && !mute ? GREEN : RED); + wattron(ctx->sidebar, aud_attr); + waddch(ctx->sidebar, audio_active ? (mute ? 'M' : '*') : '-'); + wattroff(ctx->sidebar, aud_attr); + waddch(ctx->sidebar, ' '); +#endif + } + + pthread_mutex_lock(&Winthread.lock); + memcpy(tmpnick, &conferences[self->num].name_list[peer_idx].name, maxlen); + pthread_mutex_unlock(&Winthread.lock); + + tmpnick[maxlen] = '\0'; + + if (is_self) { + wattron(ctx->sidebar, COLOR_PAIR(GREEN)); + } + + wprintw(ctx->sidebar, "%s\n", tmpnick); + + if (is_self) { + wattroff(ctx->sidebar, COLOR_PAIR(GREEN)); + } +} + static void conference_onDraw(ToxWindow *self, Tox *m) { UNUSED_VAR(m); @@ -642,37 +918,47 @@ static void conference_onDraw(ToxWindow *self, Tox *m) mvwaddch(ctx->sidebar, y2 - CHATBOX_HEIGHT, 0, ACS_BTEE); pthread_mutex_lock(&Winthread.lock); - uint32_t num_peers = conferences[self->num].num_peers; + const uint32_t num_peers = conferences[self->num].num_peers; + const bool audio = conferences[self->num].audio_enabled; + const int header_lines = sidebar_offset(self->num); pthread_mutex_unlock(&Winthread.lock); - wmove(ctx->sidebar, 0, 1); + int line = 0; + + if (audio) { +#ifdef AUDIO + pthread_mutex_lock(&Winthread.lock); + const bool mic_on = !device_is_muted(input, conferences[self->num].audio_in_idx); + pthread_mutex_unlock(&Winthread.lock); + + wmove(ctx->sidebar, line, 1); + wattron(ctx->sidebar, A_BOLD); + wprintw(ctx->sidebar, "Mic: "); + const int color = mic_on ? GREEN : RED; + wattron(ctx->sidebar, COLOR_PAIR(color)); + wprintw(ctx->sidebar, mic_on ? "ON" : "OFF"); + wattroff(ctx->sidebar, COLOR_PAIR(color)); + wattroff(ctx->sidebar, A_BOLD); + ++line; +#endif + } + + wmove(ctx->sidebar, line, 1); wattron(ctx->sidebar, A_BOLD); wprintw(ctx->sidebar, "Peers: %"PRIu32"\n", num_peers); wattroff(ctx->sidebar, A_BOLD); + ++line; - mvwaddch(ctx->sidebar, 1, 0, ACS_LTEE); - mvwhline(ctx->sidebar, 1, 1, ACS_HLINE, SIDEBAR_WIDTH - 1); + mvwaddch(ctx->sidebar, line, 0, ACS_LTEE); + mvwhline(ctx->sidebar, line, 1, ACS_HLINE, SIDEBAR_WIDTH - 1); + ++line; - int maxlines = y2 - SDBAR_OFST - CHATBOX_HEIGHT; + int maxlines = y2 - header_lines - CHATBOX_HEIGHT; + uint32_t i; - for (uint32_t i = 0; i < num_peers && i < maxlines; ++i) { - wmove(ctx->sidebar, i + 2, 1); - - pthread_mutex_lock(&Winthread.lock); - uint32_t peer = i + conferences[self->num].side_pos; - pthread_mutex_unlock(&Winthread.lock); - - /* truncate nick to fit in side panel without modifying list */ - char tmpnck[TOX_MAX_NAME_LENGTH]; - int maxlen = SIDEBAR_WIDTH - 2; - - pthread_mutex_lock(&Winthread.lock); - memcpy(tmpnck, conferences[self->num].name_list[peer], maxlen); - pthread_mutex_unlock(&Winthread.lock); - - tmpnck[maxlen] = '\0'; - - wprintw(ctx->sidebar, "%s\n", tmpnck); + for (i = 0; i < num_peers && i < maxlines; ++i) { + wmove(ctx->sidebar, i + header_lines, 1); + draw_peer(self, m, ctx, i); } } @@ -766,3 +1052,145 @@ static ToxWindow *new_conference_chat(uint32_t conferencenum) return ret; } + +#ifdef AUDIO + +#define CONFAV_SAMPLE_RATE 48000 +#define CONFAV_FRAME_DURATION 20 +#define CONFAV_AUDIO_CHANNELS 1 +#define CONFAV_SAMPLES_PER_FRAME (CONFAV_SAMPLE_RATE * CONFAV_FRAME_DURATION / 1000) + +void audio_conference_callback(void *tox, uint32_t conferencenum, uint32_t peernumber, const int16_t *pcm, + unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) +{ + ConferenceChat *chat = &conferences[conferencenum]; + + if (!chat->active) { + return; + } + + for (uint32_t i = 0; i < chat->max_idx; ++i) { + ConferencePeer *peer = &chat->peer_list[i]; + + if (!peer->active || peer->peernumber != peernumber) { + continue; + } + + if (!peer->sending_audio) { + if (open_output_device(&peer->audio_out_idx, + sample_rate, CONFAV_FRAME_DURATION, channels) != de_None) { + // TODO: error message? + return; + } + + peer->sending_audio = true; + + set_peer_audio_position(tox, conferencenum, i); + } + + write_out(peer->audio_out_idx, pcm, samples, channels, sample_rate); + + peer->last_audio_time = get_unix_time(); + + return; + } +} + +static void conference_read_device_callback(const int16_t *captured, uint32_t size, void *data) +{ + UNUSED_VAR(size); + + AudioInputCallbackData *audio_input_callback_data = (AudioInputCallbackData *)data; + + conferences[audio_input_callback_data->conferencenum].last_sent_audio = get_unix_time(); + + toxav_group_send_audio(audio_input_callback_data->tox, + audio_input_callback_data->conferencenum, + captured, CONFAV_SAMPLES_PER_FRAME, + CONFAV_AUDIO_CHANNELS, CONFAV_SAMPLE_RATE); +} + +bool init_conference_audio_input(Tox *tox, uint32_t conferencenum) +{ + ConferenceChat *chat = &conferences[conferencenum]; + + if (!chat->active || chat->audio_enabled) { + return false; + } + + const AudioInputCallbackData audio_input_callback_data = { tox, conferencenum }; + chat->audio_input_callback_data = audio_input_callback_data; + + bool success = (open_input_device(&chat->audio_in_idx, + conference_read_device_callback, &chat->audio_input_callback_data, true, + CONFAV_SAMPLE_RATE, CONFAV_FRAME_DURATION, CONFAV_AUDIO_CHANNELS) + == de_None); + + chat->audio_enabled = success; + + return success; +} + +bool enable_conference_audio(Tox *tox, uint32_t conferencenum) +{ + if (!toxav_groupchat_av_enabled(tox, conferencenum)) { + if (toxav_groupchat_enable_av(tox, conferencenum, audio_conference_callback, NULL) != 0) { + return false; + } + } + + return init_conference_audio_input(tox, conferencenum); +} + +bool disable_conference_audio(Tox *tox, uint32_t conferencenum) +{ + ConferenceChat *chat = &conferences[conferencenum]; + + if (!chat->active) { + return false; + } + + if (chat->audio_enabled) { + close_device(input, chat->audio_in_idx); + chat->audio_enabled = false; + } + + return (toxav_groupchat_disable_av(tox, conferencenum) == 0); +} + +bool conference_mute_self(uint32_t conferencenum) +{ + ConferenceChat *chat = &conferences[conferencenum]; + + if (!chat->active || !chat->audio_enabled) { + return false; + } + + device_mute(input, chat->audio_in_idx); + + return true; +} + +bool conference_mute_peer(const Tox *m, uint32_t conferencenum, uint32_t peernum) +{ + if (tox_conference_peer_number_is_ours(m, conferencenum, peernum, NULL)) { + return conference_mute_self(conferencenum); + } + + ConferenceChat *chat = &conferences[conferencenum]; + + if (!chat->active || !chat->audio_enabled + || peernum > chat->max_idx) { + return false; + } + + const ConferencePeer *peer = &chat->peer_list[peernum]; + + if (!peer->active || !peer->sending_audio) { + return false; + } + + device_mute(output, peer->audio_out_idx); + return true; +} +#endif diff --git a/src/conference.h b/src/conference.h index c3e36ef..b048446 100644 --- a/src/conference.h +++ b/src/conference.h @@ -27,17 +27,36 @@ #include "windows.h" #define SIDEBAR_WIDTH 16 -#define SDBAR_OFST 2 /* Offset for the peer number box at the top of the statusbar */ -#define MAX_CONFERENCE_NUM (MAX_WINDOWS_NUM - 2) +#define MAX_CONFERENCE_NUM MAX_WINDOWS_NUM - 2 #define CONFERENCE_EVENT_WAIT 3 typedef struct ConferencePeer { bool active; + + uint8_t pubkey[TOX_PUBLIC_KEY_SIZE]; + uint32_t peernumber; + char name[TOX_MAX_NAME_LENGTH]; size_t name_length; - uint32_t peernumber; + + bool sending_audio; + uint32_t audio_out_idx; + time_t last_audio_time; } ConferencePeer; +typedef struct AudioInputCallbackData { + Tox *tox; + uint32_t conferencenum; +} AudioInputCallbackData; + +#define PUBKEY_STRING_SIZE (2 * TOX_PUBLIC_KEY_SIZE + 1) +typedef struct NameListEntry { + char name[TOX_MAX_NAME_LENGTH]; + char pubkey_str[PUBKEY_STRING_SIZE]; + uint32_t peernum; +} NameListEntry; + + typedef struct { int chatwin; bool active; @@ -48,8 +67,13 @@ typedef struct { ConferencePeer *peer_list; uint32_t max_idx; - char **name_list; + NameListEntry *name_list; uint32_t num_peers; + + bool audio_enabled; + time_t last_sent_audio; + uint32_t audio_in_idx; + AudioInputCallbackData audio_input_callback_data; } ConferenceChat; /* Frees all Toxic associated data structures for a conference (does not call tox_conference_delete() ) */ @@ -60,4 +84,23 @@ int init_conference_win(Tox *m, uint32_t conferencenum, uint8_t type, const char /* destroys and re-creates conference window with or without the peerlist */ void redraw_conference_win(ToxWindow *self); +/* Puts `(NameListEntry *)`s in `entries` for each matched peer, up to a maximum + * of `maxpeers`. + * Maches each peer whose name or pubkey begins with `prefix`. + * If `prefix` is exactly the pubkey of a peer, matches only that peer. + * return number of entries placed in `entries`. + */ +uint32_t get_name_list_entries_by_prefix(uint32_t conferencenum, const char *prefix, NameListEntry **entries, + uint32_t maxpeers); + +bool init_conference_audio_input(Tox *tox, uint32_t conferencenum); +bool enable_conference_audio(Tox *tox, uint32_t conferencenum); +bool disable_conference_audio(Tox *tox, uint32_t conferencenum); +void audio_conference_callback(void *tox, uint32_t conferencenum, uint32_t peernumber, + const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t + sample_rate, void *userdata); + +bool conference_mute_self(uint32_t conferencenum); +bool conference_mute_peer(const Tox *m, uint32_t conferencenum, uint32_t peernum); + #endif /* CONFERENCE_H */ diff --git a/src/conference_commands.c b/src/conference_commands.c index 45ee914..75c5592 100644 --- a/src/conference_commands.c +++ b/src/conference_commands.c @@ -27,6 +27,7 @@ #include "line_info.h" #include "misc_tools.h" #include "log.h" +#include "conference.h" void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { @@ -79,3 +80,70 @@ void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, snprintf(tmp_event, sizeof(tmp_event), "set title to %s", title); write_to_log(tmp_event, selfnick, self->chatwin->log, true); } + +void cmd_enable_audio(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + UNUSED_VAR(window); + +#ifdef AUDIO + bool enable; + + if (argc == 1 && !strcasecmp(argv[1], "on")) { + enable = true; + } else if (argc == 1 && !strcasecmp(argv[1], "off")) { + enable = false; + } else { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Please specify: on | off"); + return; + } + + if ((enable ? enable_conference_audio : disable_conference_audio)(m, self->num)) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, enable ? "Enabled conference audio" : "Disabled conference audio"); + } else { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, enable ? "Failed to enable audio" : "Failed to disable audio"); + } + +#endif +} + +void cmd_conference_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + UNUSED_VAR(window); + +#ifdef AUDIO + + if (argc < 1) { + if (conference_mute_self(self->num)) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Toggled self audio mute status"); + } else { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No audio input to mute"); + } + } else { + NameListEntry *entries[16]; + uint32_t n = get_name_list_entries_by_prefix(self->num, argv[1], entries, 16); + + if (n == 0) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No such peer"); + return; + } + + if (n > 1) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, + "Multiple matching peers (use /mute [public key] to disambiguate):"); + + for (uint32_t i = 0; i < n; ++i) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s: %s", entries[i]->pubkey_str, entries[i]->name); + } + + return; + } + + if (conference_mute_peer(m, self->num, entries[0]->peernum)) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Toggled audio mute status of %s", entries[0]->name); + } else { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No such audio peer"); + } + } + +#endif +} diff --git a/src/conference_commands.h b/src/conference_commands.h index ddfe687..b8deabf 100644 --- a/src/conference_commands.h +++ b/src/conference_commands.h @@ -27,5 +27,7 @@ #include "toxic.h" void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_enable_audio(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_conference_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]); #endif /* CONFERENCE_COMMANDS_H */ diff --git a/src/execute.c b/src/execute.c index e119e37..6b76f9a 100644 --- a/src/execute.c +++ b/src/execute.c @@ -101,17 +101,18 @@ static struct cmd_func conference_commands[] = { { "/title", cmd_conference_set_title }, #ifdef AUDIO - { "/mute", cmd_mute }, - { "/sense", cmd_sense }, + { "/audio", cmd_enable_audio }, + { "/mute", cmd_conference_mute }, + { "/sense", cmd_sense }, #endif /* AUDIO */ { NULL, NULL }, }; #ifdef PYTHON -#define SPECIAL_COMMANDS 6 +#define SPECIAL_COMMANDS 7 #else -#define SPECIAL_COMMANDS 5 +#define SPECIAL_COMMANDS 6 #endif /* PYTHON */ /* Special commands are commands that only take one argument even if it contains spaces */ @@ -124,6 +125,7 @@ static const char special_commands[SPECIAL_COMMANDS][MAX_CMDNAME_SIZE] = { #endif /* PYTHON */ "/sendfile", "/title", + "/mute", }; /* Returns true if input command is in the special_commands array. */ diff --git a/src/global_commands.c b/src/global_commands.c index 58b9b93..a9efee1 100644 --- a/src/global_commands.c +++ b/src/global_commands.c @@ -365,17 +365,32 @@ void cmd_conference(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar return; } - if (type != TOX_CONFERENCE_TYPE_TEXT) { - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Toxic does not support audio conferences."); + uint32_t conferencenum; + + if (type == TOX_CONFERENCE_TYPE_TEXT) { + Tox_Err_Conference_New err; + + conferencenum = tox_conference_new(m, &err); + + if (err != TOX_ERR_CONFERENCE_NEW_OK) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference instance failed to initialize (error %d)", err); + return; + } + } else if (type == TOX_CONFERENCE_TYPE_AV) { +#ifdef AUDIO + conferencenum = toxav_add_av_groupchat(m, audio_conference_callback, NULL); + + if (conferencenum == (uint32_t) -1) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio conference instance failed to initialize"); + return; + } + +#else + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio support disabled by compile-time option."); return; - } - - Tox_Err_Conference_New err; - - uint32_t conferencenum = tox_conference_new(m, &err); - - if (err != TOX_ERR_CONFERENCE_NEW_OK) { - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference instance failed to initialize (error %d)", err); +#endif + } else { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Unknown conference type %d", type); return; } @@ -385,6 +400,16 @@ void cmd_conference(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar return; } +#ifdef AUDIO + + if (type == TOX_CONFERENCE_TYPE_AV) { + if (!init_conference_audio_input(m, conferencenum)) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio capture failed; use \"/audio on\" to try again."); + } + } + +#endif + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference [%d] created.", conferencenum); } diff --git a/src/help.c b/src/help.c index 4a85d6c..0ab2223 100644 --- a/src/help.c +++ b/src/help.c @@ -304,7 +304,15 @@ static void help_draw_conference(ToxWindow *self) wprintw(win, "Conference commands:\n"); wattroff(win, A_BOLD | COLOR_PAIR(RED)); - wprintw(win, " /title : Set conference title (show current title if no msg)\n\n"); + wprintw(win, " /title : Set conference title (show current title if no msg)\n"); +#ifdef AUDIO + wattron(win, A_BOLD); + wprintw(win, "\n Audio:\n"); + wattroff(win, A_BOLD); + wprintw(win, " /audio or : Enable/disable audio in an audio conference\n"); + wprintw(win, " /mute : Toggle self audio mute status\n"); + wprintw(win, " /mute or : Toggle peer audio mute status\n\n"); +#endif help_draw_bottom_menu(win); @@ -390,7 +398,11 @@ void help_onKey(ToxWindow *self, wint_t key) break; case L'o': - help_init_window(self, 6, 80); + height = 6; +#ifdef AUDIO + height += 5; +#endif + help_init_window(self, height, 80); self->help->type = HELP_CONFERENCE; break; diff --git a/src/misc_tools.c b/src/misc_tools.c index 6f70e07..f373373 100644 --- a/src/misc_tools.c +++ b/src/misc_tools.c @@ -186,6 +186,26 @@ int bin_id_to_string(const char *bin_id, size_t bin_id_size, char *output, size_ return 0; } +/* Converts a binary representation of a Tox public key into a string. + * + * Returns 0 on success. + * Returns -1 on failure. + */ +int bin_pubkey_to_string(const uint8_t *bin_pubkey, size_t bin_pubkey_size, char *output, size_t output_size) +{ + if (bin_pubkey_size != TOX_PUBLIC_KEY_SIZE || output_size < (TOX_PUBLIC_KEY_SIZE * 2 + 1)) { + return -1; + } + + size_t i; + + for (i = 0; i < TOX_PUBLIC_KEY_SIZE; ++i) { + snprintf(&output[i * 2], output_size - (i * 2), "%02X", bin_pubkey[i] & 0xff); + } + + return 0; +} + /* Returns 1 if the string is empty, 0 otherwise */ int string_is_empty(const char *string) { diff --git a/src/misc_tools.h b/src/misc_tools.h index db66ea4..c9cc21e 100644 --- a/src/misc_tools.h +++ b/src/misc_tools.h @@ -71,6 +71,13 @@ int hex_string_to_bytes(char *buf, int size, const char *keystr); */ int bin_id_to_string(const char *bin_id, size_t bin_id_size, char *output, size_t output_size); +/* Converts a binary representation of a Tox public key into a string. + * + * Returns 0 on success. + * Returns -1 on failure. + */ +int bin_pubkey_to_string(const uint8_t *bin_pubkey, size_t bin_pubkey_size, char *output, size_t output_size); + /* get the current unix time (not thread safe) */ time_t get_unix_time(void); diff --git a/src/toxic.c b/src/toxic.c index 224c200..8d75191 100644 --- a/src/toxic.c +++ b/src/toxic.c @@ -103,8 +103,6 @@ struct av_thread av_thread; struct arg_opts arg_opts; struct user_settings *user_settings = NULL; -pthread_mutex_t tox_lock; - static struct user_password { bool data_is_encrypted; char pass[MAX_PASSWORD_LEN + 1]; @@ -407,10 +405,22 @@ static void load_conferences(Tox *m) title[length] = 0; - if (init_conference_win(m, conferencenum, type, (const char *) title, length) == -1) { + int win_idx = init_conference_win(m, conferencenum, type, (const char *) title, length); + + if (win_idx == -1) { tox_conference_delete(m, conferencenum, NULL); continue; } + + if (type == TOX_CONFERENCE_TYPE_AV) { + line_info_add(get_window_ptr(win_idx), NULL, NULL, NULL, SYS_MSG, 0, 0, +#ifdef AUDIO + "Use \"/audio on\" to enable audio in this conference." +#else + "Audio support disabled by compile-time option." +#endif + ); + } } free(chatlist); @@ -946,12 +956,9 @@ static void do_toxic(Tox *m) return; } - pthread_mutex_lock(&tox_lock); - tox_iterate(m, NULL); do_tox_connection(m); - pthread_mutex_unlock(&tox_lock); pthread_mutex_unlock(&Winthread.lock); } @@ -1396,10 +1403,6 @@ int main(int argc, char **argv) exit_toxic_err("failed in main", FATALERR_MEMORY); } - if (pthread_mutex_init(&tox_lock, NULL) != 0) { - exit_toxic_err("failed in main", FATALERR_MUTEX_INIT); - } - const char *p = arg_opts.config_path[0] ? arg_opts.config_path : NULL; if (settings_load(user_settings, p) == -1) { @@ -1470,7 +1473,7 @@ int main(int argc, char **argv) #elif SOUND_NOTIFY - if (init_audio() == de_InternalError) { + if (init_devices() == de_InternalError) { queue_init_message("Failed to init audio devices"); }