From e47f2c05f338d787e40b39e630d8bcc80a95d05f Mon Sep 17 00:00:00 2001 From: mannol Date: Sun, 22 Jun 2014 02:18:23 +0200 Subject: [PATCH] Added VAD, changed device i/o, mute option, dynamic device changing and more --- build/Makefile.am | 21 +- configure.ac | 82 ++++-- src/audio_call.c | 115 +++++++-- src/audio_call.h | 24 +- src/chat.c | 222 ++++++++++------- src/chat.h | 3 +- src/chat_commands.c | 14 +- src/chat_commands.h | 12 +- src/configdir.c | 47 ---- src/configdir.h | 9 +- src/device.c | 67 ++++- src/device.h | 9 +- src/dns.c | 305 +++++++++++++++++++++++ src/dns.h | 32 +++ src/execute.c | 8 +- src/execute.h | 10 +- src/file_senders.c | 8 +- src/file_senders.h | 26 ++ src/friendlist.c | 10 +- src/friendlist.h | 13 +- src/global_commands.c | 149 +++++------ src/global_commands.h | 12 +- src/groupchat.c | 173 +++++++------ src/groupchat.h | 9 + src/line_info.c | 221 +++++++++------- src/line_info.h | 28 ++- src/log.c | 3 +- src/log.h | 5 + src/misc_tools.c | 36 +-- src/misc_tools.h | 22 +- src/prompt.c | 82 +++--- src/prompt.h | 4 +- src/settings.c | 146 ++++++++--- src/settings.h | 25 +- src/{main.c => toxic.c} | 388 +++++++++++++++-------------- src/toxic.h | 100 ++++++++ src/toxic_strings.c | 165 ++++++------ src/toxic_strings.h | 46 ++-- src/windows.c | 74 +++--- src/{toxic_windows.h => windows.h} | 110 ++------ 40 files changed, 1828 insertions(+), 1007 deletions(-) create mode 100644 src/dns.c create mode 100644 src/dns.h rename src/{main.c => toxic.c} (60%) create mode 100644 src/toxic.h rename src/{toxic_windows.h => windows.h} (58%) diff --git a/build/Makefile.am b/build/Makefile.am index b821b36..5e402ff 100644 --- a/build/Makefile.am +++ b/build/Makefile.am @@ -3,7 +3,8 @@ bin_PROGRAMS = toxic -toxic_SOURCES = $(top_srcdir)/src/main.c \ +toxic_SOURCES = $(top_srcdir)/src/toxic.c \ + $(top_srcdir)/src/toxic.h \ $(top_srcdir)/src/chat.h \ $(top_srcdir)/src/chat.c \ $(top_srcdir)/src/configdir.h \ @@ -12,8 +13,8 @@ toxic_SOURCES = $(top_srcdir)/src/main.c \ $(top_srcdir)/src/prompt.c \ $(top_srcdir)/src/friendlist.h \ $(top_srcdir)/src/friendlist.c \ - $(top_srcdir)/src/toxic_windows.h \ $(top_srcdir)/src/windows.c \ + $(top_srcdir)/src/windows.h \ $(top_srcdir)/src/groupchat.c \ $(top_srcdir)/src/groupchat.h \ $(top_srcdir)/src/global_commands.c \ @@ -33,13 +34,15 @@ toxic_SOURCES = $(top_srcdir)/src/main.c \ $(top_srcdir)/src/line_info.c \ $(top_srcdir)/src/line_info.h \ $(top_srcdir)/src/settings.c \ - $(top_srcdir)/src/settings.h + $(top_srcdir)/src/settings.h \ + $(top_srcdir)/src/dns.c \ + $(top_srcdir)/src/dns.h -toxic_CFLAGS = -I$(top_srcdir) \ - $(NCURSES_CFLAGS) \ - $(LIBSODIUM_CFLAGS) \ - $(LIBTOXCORE_CFLAGS) \ - $(PTHREAD_CFLAGS) +toxic_CFLAGS = -I$(top_srcdir) \ + $(NCURSES_CFLAGS) \ + $(LIBSODIUM_CFLAGS) \ + $(LIBTOXCORE_CFLAGS) \ + $(PTHREAD_CFLAGS) toxic_CPPFLAGS = '-DTOXICVER="$(TOXIC_VERSION)"' @@ -58,7 +61,7 @@ if BUILD_AV toxic_SOURCES += $(top_srcdir)/src/audio_call.c \ $(top_srcdir)/src/audio_call.h \ $(top_srcdir)/src/device.c \ - $(top_srcdir)/src/device.h + $(top_srcdir)/src/device.h toxic_CFLAGS += $(LIBTOXAV_CFLAGS) \ $(OPENAL_CFLAGS) diff --git a/configure.ac b/configure.ac index a2da511..a800b6a 100644 --- a/configure.ac +++ b/configure.ac @@ -2,9 +2,9 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.65]) -AC_INIT([toxic], [0.3.3], [https://tox.im/]) +AC_INIT([toxic], [0.4.2], [https://tox.im/]) AC_CONFIG_AUX_DIR(configure_aux) -AC_CONFIG_SRCDIR([src/main.c]) +AC_CONFIG_SRCDIR([src/toxic.c]) AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE([1.10 -Wall]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) @@ -37,7 +37,9 @@ if test -n "$DEPSEARCH"; then CFLAGS="$CFLAGS -I$DEPSEARCH/include" CPPFLAGS="$CPPFLAGS -I$DEPSEARCH/include" LDFLAGS="$LDFLAGS -L$DEPSEARCH/lib" - export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$DEPSEARCH/lib/pkgconfig + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$DEPSEARCH/lib/pkgconfig:/usr/local/lib/pkgconfig +else + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig fi AC_ARG_WITH(libtoxcore-headers, @@ -77,11 +79,15 @@ AC_ARG_WITH(libsodium-libs, ) WIN32=no +MACH=no AC_CANONICAL_HOST case $host_os in *mingw*) WIN32="yes" ;; + darwin*) + MACH=yes + ;; *freebsd*) LDFLAGS="$LDFLAGS -L/usr/local/lib" CFLAGS="$CFLAGS -I/usr/local/include" @@ -383,6 +389,15 @@ if test "x$LIBTOXCORE_FOUND" = "xno"; then AC_SUBST(LIBTOXCORE_LDFLAGS) fi +AC_CHECK_HEADER([resolv.h], [], + [ + AC_MSG_ERROR([resolv.h header was not found on your system]) + ]) + +AC_CHECK_LIB(resolv, __res_init, [], + [ + AC_MSG_ERROR([libresolv library was not found on your system]) + ]) #### #### A/V Stuff @@ -404,6 +419,8 @@ if test -n "$AV_SEARCH_DIR"; then CPPFLAGS="$CPPFLAGS -I$AV_SEARCH_DIR/include" LDFLAGS="$LDFLAGS -L$AV_SEARCH_DIR/lib" export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$AV_SEARCH_DIR/lib/pkgconfig +else + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig" fi # Check if specified enable @@ -422,29 +439,46 @@ AC_ARG_ENABLE([av], if test "x$BUILD_AV" = "xyes"; then PKG_CHECK_MODULES([OPENAL], [openal], - [ - export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig - - PKG_CHECK_MODULES([LIBTOXAV], [libtoxav], - [ - AC_CHECK_HEADER([tox/toxav.h], - [ - # Place define for audio support - AC_DEFINE([_SUPPORT_AUDIO], [], [Is audio supported]) - AC_MSG_NOTICE([Building with audio support]) - ], - [ - AC_MSG_NOTICE([No A/V headers; disabling A/V support]) - BUILD_AV="no" - ],) - ], - [ - AC_MSG_NOTICE([No A/V library; disabling A/V support]) + [], + [ + if test "x$MACH" = "xyes"; then + CFLAGS="$CFLAGS -framework OpenAL" + AC_CHECK_HEADER([OpenAL/al.h], + [ + OPENAL_CFLAGS="-framework OpenAL" + OPENAL_LIBS="-framework OpenAL" + AC_SUBST(OPENAL_CFLAGS) + AC_SUBST(OPENAL_LIBS) + ], + [ + AC_MSG_NOTICE([No openal framework; disabling A/V support]) + BUILD_AV="no" + ] + ) + CFLAGS="$CFLAGS_SAVE" + else + AC_MSG_NOTICE([No openal library; disabling A/V support]) BUILD_AV="no" - ]) + fi + ]) +fi + +if test "x$BUILD_AV" = "xyes"; then + PKG_CHECK_MODULES([LIBTOXAV], [libtoxav], + [ + AC_CHECK_HEADER([tox/toxav.h], + [ + # Place define for audio support + AC_DEFINE([_SUPPORT_AUDIO], [], [Is audio supported]) + AC_MSG_NOTICE([Building with audio support]) + ], + [ + AC_MSG_NOTICE([No A/V headers; disabling A/V support]) + BUILD_AV="no" + ],) ], [ - AC_MSG_NOTICE([No openal library; disabling A/V support]) + AC_MSG_NOTICE([No A/V library; disabling A/V support]) BUILD_AV="no" ]) fi @@ -469,7 +503,7 @@ if test "x$NCURSES_WIDECHAR_SUPPORT" = "xyes"; then AC_DEFINE([_XOPEN_SOURCE_EXTENDED], [1], [enable X/Open Portability Guide functionality]) fi - + AC_CONFIG_FILES([Makefile misc/Makefile build/Makefile]) diff --git a/src/audio_call.c b/src/audio_call.c index fd23838..50cfe57 100644 --- a/src/audio_call.c +++ b/src/audio_call.c @@ -1,22 +1,38 @@ -/* - * Toxic -- Tox Curses Client +/* audio_call.c + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "audio_call.h" #include "device.h" #include "chat_commands.h" #include "global_commands.h" -#include "toxic_windows.h" #include "line_info.h" #include -#include -#include #include #include #include @@ -29,16 +45,6 @@ #define frame_size (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000) -typedef struct _DeviceIx { - - ALCdevice *dhndl; /* Handle of device selected/opened */ - ALCcontext *ctx; /* Device context */ - const char *devices[MAX_DEVICES]; /* Container of available devices */ - int size; /* Size of above container */ - int dix; /* Index of default device */ - int index; /* Current index */ -} DeviceIx; - typedef struct _Call { pthread_t ttid; /* Transmission thread id */ _Bool ttas; /* Transmission thread active status (0 - stopped, 1- running) */ @@ -178,7 +184,7 @@ void *transmission(void *arg) if ( open_primary_device(input, &this_call->in_idx) != de_None ) goto cleanup; - if ( register_device_callback(this_call->in_idx, read_device_callback, &call_index, _True) != de_None) + if ( register_device_callback(call_index, this_call->in_idx, read_device_callback, &call_index, _True) != de_None) /* Set VAD as true for all; TODO: Make it more dynamic */ goto cleanup; @@ -582,7 +588,7 @@ on_error: print_err (self, error_str); } -void cmd_set_this_session_device(WINDOW * window, ToxWindow * self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +void cmd_ccur_device(WINDOW * window, ToxWindow * self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { uint8_t msg[MAX_STR_SIZE]; uint8_t *error_str; @@ -638,7 +644,7 @@ void cmd_set_this_session_device(WINDOW * window, ToxWindow * self, Tox *m, int close_device(input, this_call->in_idx); open_device(input, selection, &this_call->in_idx); /* Set VAD as true for all; TODO: Make it more dynamic */ - register_device_callback(this_call->in_idx, read_device_callback, &self->call_idx, _True); + register_device_callback(self->call_idx, this_call->in_idx, read_device_callback, &self->call_idx, _True); } } } @@ -646,6 +652,77 @@ void cmd_set_this_session_device(WINDOW * window, ToxWindow * self, Tox *m, int self->device_selection[type] = selection; return; + on_error: + print_err (self, error_str); +} + +void cmd_mute(WINDOW * window, ToxWindow * self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + uint8_t msg[MAX_STR_SIZE]; + uint8_t *error_str; + + if ( argc != 1 ) { + if ( argc < 1 ) error_str = "Type must be specified!"; + else error_str = "Only two arguments allowed!"; + + goto on_error; + } + + DeviceType type; + + if ( strcmp(argv[1], "in") == 0 ) /* Input devices */ + type = input; + + else if ( strcmp(argv[1], "out") == 0 ) /* Output devices */ + type = output; + + else { + snprintf(msg, sizeof(msg), "Invalid type: %s", argv[1]); + line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); + return; + } + + + /* If call is active, use this_call values */ + if ( self->call_idx > -1) { + Call* this_call = &ASettins.calls[self->call_idx]; + + pthread_mutex_lock(&this_call->mutex); + device_mute(type, type == input ? this_call->in_idx : this_call->out_idx); + pthread_mutex_unlock(&this_call->mutex); + } + + return; + + on_error: + print_err (self, error_str); +} + +void cmd_sense(WINDOW * window, ToxWindow * self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + uint8_t msg[MAX_STR_SIZE]; + uint8_t *error_str; + + if ( argc != 1 ) { + if ( argc < 1 ) error_str = "Must have value!"; + else error_str = "Only two arguments allowed!"; + + goto on_error; + } + + char *end; + float value = strtof(argv[1], &end); + + if ( *end ) { + error_str = "Invalid input"; + goto on_error; + } + + /* Call must be active */ + if ( self->call_idx > -1) device_set_VAD_treshold(ASettins.calls[self->call_idx].in_idx, value); + + return; + on_error: print_err (self, error_str); } \ No newline at end of file diff --git a/src/audio_call.h b/src/audio_call.h index 6b78e38..c33dc42 100644 --- a/src/audio_call.h +++ b/src/audio_call.h @@ -1,5 +1,23 @@ -/* - * Toxic -- Tox Curses Client +/* audio_call.h + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * */ #ifndef _audio_h @@ -23,8 +41,6 @@ typedef enum _AudioError { ToxAv *init_audio(ToxWindow *self, Tox *tox); void terminate_audio(); -int clear_call_settings_per_se(ToxWindow *self); - int start_transmission(ToxWindow *self); int stop_transmission(int call_index); int device_set(ToxWindow* self, DeviceType type, long int selection); diff --git a/src/chat.c b/src/chat.c index 5b5693a..3b486d0 100644 --- a/src/chat.c +++ b/src/chat.c @@ -28,7 +28,8 @@ #include #include -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "execute.h" #include "misc_tools.h" #include "friendlist.h" @@ -51,7 +52,7 @@ extern struct _Winthread Winthread; extern struct user_settings *user_settings; #ifdef _SUPPORT_AUDIO -#define AC_NUM_CHAT_COMMANDS 24 +#define AC_NUM_CHAT_COMMANDS 26 #else #define AC_NUM_CHAT_COMMANDS 18 #endif /* _SUPPORT_AUDIO */ @@ -85,6 +86,8 @@ static const uint8_t chat_cmd_list[AC_NUM_CHAT_COMMANDS][MAX_CMDNAME_SIZE] = { { "/reject" }, { "/hangup" }, { "/sdev" }, + { "/mute" }, + { "/sense" }, #endif /* _SUPPORT_AUDIO */ }; @@ -215,8 +218,11 @@ static void chat_onStatusMessageChange(ToxWindow *self, int32_t num, uint8_t *st return; StatusBar *statusbar = self->stb; + + status[len] = '\0'; + snprintf(statusbar->statusmsg, sizeof(statusbar->statusmsg), "%s", status); + len = strlen(statusbar->statusmsg); statusbar->statusmsg_len = len; - strcpy(statusbar->statusmsg, status); statusbar->statusmsg[len] = '\0'; } @@ -226,15 +232,21 @@ static void chat_onFileSendRequest(ToxWindow *self, Tox *m, int32_t num, uint8_t if (self->num != num) return; - uint8_t msg[MAX_STR_SIZE]; + uint8_t msg[MAX_STR_SIZE * 2]; uint8_t *errmsg; pathname[path_len] = '\0'; - uint8_t filename[MAX_STR_SIZE]; - get_file_name(pathname, filename); - snprintf(msg, sizeof(msg), "File transfer request for '%s' (%llu bytes).", filename, + /* holds the filename appended to the user specified path */ + uint8_t filename_path[MAX_STR_SIZE] = {0}; + + /* holds the lone filename */ + uint8_t filename_nopath[MAX_STR_SIZE]; + get_file_name(filename_nopath, pathname); + int len = strlen(filename_nopath); + + snprintf(msg, sizeof(msg), "File transfer request for '%s' (%llu bytes).", filename_nopath, (long long unsigned int)filesize); line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); @@ -244,17 +256,42 @@ static void chat_onFileSendRequest(ToxWindow *self, Tox *m, int32_t num, uint8_t return; } + /* use specified path in config if possible */ + if (user_settings->download_path[0]) { + snprintf(filename_path, sizeof(filename_path), "%s%s", user_settings->download_path, filename_nopath); + len += strlen(user_settings->download_path); + } + + if (len >= sizeof(friends[num].file_receiver.filenames[filenum])) { + errmsg = "File name too long; discarding."; + line_info_add(self, NULL, NULL, NULL, errmsg, SYS_MSG, 0, 0); + return; + } + + uint8_t filename[MAX_STR_SIZE]; + + if (filename_path[0]) + strcpy(filename, filename_path); + else + strcpy(filename, filename_nopath); + /* Append a number to duplicate file names */ FILE *filecheck = NULL; int count = 1; - int len = strlen(filename); while ((filecheck = fopen(filename, "r"))) { filename[len] = '\0'; char d[9]; sprintf(d, "(%d)", count++); + int d_len = strlen(d); + + if (len + d_len >= sizeof(filename)) { + len -= d_len; + filename[len] = '\0'; + } + strcat(filename, d); - filename[len + strlen(d)] = '\0'; + filename[len + d_len] = '\0'; if (count > 999) { errmsg = "Error saving file to disk."; @@ -291,21 +328,28 @@ static void chat_onFileControl(ToxWindow *self, Tox *m, int32_t num, uint8_t rec const uint8_t *filename; uint8_t msg[MAX_STR_SIZE] = {0}; + int i; /* file_sender index */ - if (receive_send == 0) + if (receive_send == 0) { filename = friends[num].file_receiver.filenames[filenum]; - else - filename = file_senders[filenum].pathname; + } else { + for (i = 0; i < MAX_FILES; ++i) { + if (file_senders[i].filenum == filenum) + break; + } + + filename = file_senders[i].pathname; + } switch (control_type) { case TOX_FILECONTROL_ACCEPT: - snprintf(msg, sizeof(msg), "File transfer for '%s' accepted (%.1f%%)", filename, 0.0); - file_senders[filenum].line_id = self->chatwin->hst->line_end->id + 1; + if (receive_send == 1) { + snprintf(msg, sizeof(msg), "File transfer for '%s' accepted (%.1f%%)", filename, 0.0); + file_senders[i].line_id = self->chatwin->hst->line_end->id + 1; + } + break; - /*case TOX_FILECONTROL_PAUSE: - wprintw(ctx->history, "File transfer for '%s' paused.\n", filename); - break; */ case TOX_FILECONTROL_KILL: snprintf(msg, sizeof(msg), "File transfer for '%s' failed.", filename); @@ -315,8 +359,11 @@ static void chat_onFileControl(ToxWindow *self, Tox *m, int32_t num, uint8_t rec break; case TOX_FILECONTROL_FINISHED: - snprintf(msg, sizeof(msg), "File transfer for '%s' complete.", filename); - chat_close_file_receiver(num, filenum); + if (receive_send == 0) { + snprintf(msg, sizeof(msg), "File transfer for '%s' complete.", filename); + chat_close_file_receiver(num, filenum); + } + break; } @@ -349,7 +396,7 @@ static void chat_onFileData(ToxWindow *self, Tox *m, int32_t num, uint8_t filenu const uint8_t *name = friends[num].file_receiver.filenames[filenum]; snprintf(msg, sizeof(msg), "Saving file as: '%s' (%.1Lf%%)", name, pct_remain); - line_info_set(self, friends[num].file_receiver.line_id, msg); + line_info_set(self, friends[num].file_receiver.line_id[filenum], msg); } @@ -365,8 +412,9 @@ static void chat_onGroupInvite(ToxWindow *self, Tox *m, int32_t friendnumber, ui n_len = MIN(n_len, TOXIC_MAX_NAME_LENGTH - 1); name[n_len] = '\0'; - snprintf(msg, sizeof(msg), "%s has invited you to a group chat.\n" - "Type \"/join\" to join the chat.", name); + snprintf(msg, sizeof(msg), "%s has invited you to a group chat.", name); + line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); + snprintf(msg, sizeof(msg), "Type \"/join\" to join the chat.", name); line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); memcpy(friends[friendnumber].pending_groupchat, group_pub_key, TOX_CLIENT_ID_SIZE); @@ -509,44 +557,44 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) int x, y, y2, x2; getyx(self->window, y, x); getmaxyx(self->window, y2, x2); - int cur_len = 0; - if (!ltr && (key == T_KEY_ESC)) { /* ESC key: Toggle history scroll mode */ - bool scroll = ctx->hst->scroll_mode ? false : true; - line_info_toggle_scroll(self, scroll); - } - - /* If we're in scroll mode ignore rest of function */ - if (ctx->hst->scroll_mode) { - line_info_onKey(self, key); + if (x2 <= 0) return; - } - if (ltr) { - /* prevents buffer overflows and strange behaviour when cursor goes past the window */ - if ( (ctx->len < MAX_STR_SIZE - 1) && (ctx->len < (x2 * (CHATBOX_HEIGHT - 1) - 1)) ) { - add_char_to_buf(ctx->line, &ctx->pos, &ctx->len, key); + int cur_len = 0; /* widechar size of current char */ + int x2_is_odd = x2 % 2 != 0; - if (x == x2 - 1) - wmove(self->window, y + 1, 0); - else + if (ltr) { /* char is printable */ + if (ctx->len < MAX_STR_SIZE - 1) { + add_char_to_buf(ctx, key); + + if (x >= x2 - 1) { + wmove(self->window, y, x2 / 2 + x2_is_odd); + ctx->start += x2 / 2; + } else { wmove(self->window, y, x + MAX(1, wcwidth(key))); + } } if (!ctx->self_is_typing && ctx->line[0] != '/') set_typingstatus(self, m, 1); } else { /* if (!ltr) */ + if (line_info_onKey(self, key)) + return; if (key == 0x107 || key == 0x8 || key == 0x7f) { /* BACKSPACE key */ if (ctx->pos > 0) { - cur_len = MAX(1, wcwidth(ctx->line[ctx->pos - 1])); - del_char_buf_bck(ctx->line, &ctx->pos, &ctx->len); + cur_len = MAX(1, wcwidth(ctx->line[ctx->pos])); + del_char_buf_bck(ctx); - if (x == 0) - wmove(self->window, y - 1, x2 - cur_len); - else + if (x == 0) { + ctx->start = ctx->start >= x2 ? ctx->start - x2 : 0; + int new_x = ctx->start == 0 ? ctx->pos : x2 - cur_len; + wmove(self->window, y, new_x); + } else { wmove(self->window, y, x - cur_len); + } } else { beep(); } @@ -554,14 +602,14 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_DC) { /* DEL key: Remove character at pos */ if (ctx->pos != ctx->len) - del_char_buf_frnt(ctx->line, &ctx->pos, &ctx->len); + del_char_buf_frnt(ctx); else beep(); } else if (key == T_KEY_DISCARD) { /* CTRL-U: Delete entire line behind pos */ if (ctx->pos > 0) { - discard_buf(ctx->line, &ctx->pos, &ctx->len); + discard_buf(ctx); wmove(self->window, y2 - CURS_Y_OFFSET, 0); } else { beep(); @@ -570,7 +618,7 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == T_KEY_KILL) { /* CTRL-K: Delete entire line in front of pos */ if (ctx->pos != ctx->len) - kill_buf(ctx->line, &ctx->pos, &ctx->len); + kill_buf(ctx); else beep(); } @@ -578,6 +626,7 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_HOME || key == T_KEY_C_A) { /* HOME/C-a key: Move cursor to start of line */ if (ctx->pos > 0) { ctx->pos = 0; + ctx->start = 0; wmove(self->window, y2 - CURS_Y_OFFSET, 0); } } @@ -585,7 +634,8 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_END || key == T_KEY_C_E) { /* END/C-e key: move cursor to end of line */ if (ctx->pos != ctx->len) { ctx->pos = ctx->len; - mv_curs_end(self->window, MAX(0, wcswidth(ctx->line, (CHATBOX_HEIGHT - 1)*x2)), y2, x2); + ctx->start = x2 * (ctx->len / x2); + mv_curs_end(self->window, ctx->len, y2, x2); } } @@ -594,10 +644,13 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) --ctx->pos; cur_len = MAX(1, wcwidth(ctx->line[ctx->pos])); - if (x == 0) - wmove(self->window, y - 1, x2 - cur_len); - else + if (x == 0) { + wmove(self->window, y, x2 - cur_len); + ctx->start = ctx->start >= x2 ? ctx->start - x2 : 0; + ctx->pos = ctx->start + x2 - 1; + } else { wmove(self->window, y, x - cur_len); + } } else { beep(); } @@ -605,39 +658,42 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_RIGHT) { if (ctx->pos < ctx->len) { - cur_len = MAX(1, wcwidth(ctx->line[ctx->pos])); ++ctx->pos; - if (x == x2 - 1) - wmove(self->window, y + 1, 0); - else + if (x == x2 - 1) { + wmove(self->window, y, 0); + ctx->start += x2; + ctx->pos = ctx->start; + } else { + cur_len = MAX(1, wcwidth(ctx->line[ctx->pos])); wmove(self->window, y, x + cur_len); + } } else { beep(); } } else if (key == KEY_UP) { /* fetches previous item in history */ - fetch_hist_item(ctx->line, &ctx->pos, &ctx->len, ctx->ln_history, ctx->hst_tot, - &ctx->hst_pos, MOVE_UP); + fetch_hist_item(ctx, MOVE_UP); + ctx->start = x2 * (ctx->len / x2); mv_curs_end(self->window, ctx->len, y2, x2); } else if (key == KEY_DOWN) { /* fetches next item in history */ - fetch_hist_item(ctx->line, &ctx->pos, &ctx->len, ctx->ln_history, ctx->hst_tot, - &ctx->hst_pos, MOVE_DOWN); + fetch_hist_item(ctx, MOVE_DOWN); + ctx->start = x2 * (ctx->len / x2); mv_curs_end(self->window, ctx->len, y2, x2); } else if (key == '\t') { /* TAB key: completes command */ if (ctx->len > 1 && ctx->line[0] == '/') { - int diff = complete_line(ctx->line, &ctx->pos, &ctx->len, chat_cmd_list, AC_NUM_CHAT_COMMANDS, - MAX_CMDNAME_SIZE); + int diff = complete_line(ctx, chat_cmd_list, AC_NUM_CHAT_COMMANDS, MAX_CMDNAME_SIZE); if (diff != -1) { if (x + diff > x2 - 1) { - int ofst = (x + diff - 1) - (x2 - 1); - wmove(self->window, y + 1, ofst); + //int ofst = x + diff - x2; + wmove(self->window, y, x + diff); + ctx->start += x2 / 2; } else { wmove(self->window, y, x + diff); } @@ -651,6 +707,8 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) /* RETURN key: Execute command or print line */ else if (key == '\n') { + rm_trailing_spaces_buf(ctx); + uint8_t line[MAX_STR_SIZE]; if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) @@ -660,7 +718,7 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) wmove(self->window, y2 - CURS_Y_OFFSET, 0); if (!string_is_empty(line)) - add_line_to_hist(ctx->line, ctx->len, ctx->ln_history, &ctx->hst_tot, &ctx->hst_pos); + add_line_to_hist(ctx); if (line[0] == '/') { if (strcmp(line, "/close") == 0) { @@ -692,7 +750,7 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) } } - reset_buf(ctx->line, &ctx->pos, &ctx->len); + reset_buf(ctx); } } @@ -710,21 +768,16 @@ static void chat_onDraw(ToxWindow *self, Tox *m) line_info_print(self); wclear(ctx->linewin); - if (ctx->hst->scroll_mode) { - line_info_onDraw(self); - } else { - curs_set(1); - scrollok(ctx->history, 1); + curs_set(1); - if (ctx->len > 0 && !ctx->hst->scroll_mode) { - uint8_t line[MAX_STR_SIZE]; + if (ctx->len > 0) { + uint8_t line[MAX_STR_SIZE]; - if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) { - reset_buf(ctx->line, &ctx->pos, &ctx->len); - wmove(self->window, y2 - CURS_Y_OFFSET, 0); - } else { - mvwprintw(ctx->linewin, 1, 0, "%s", line); - } + if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) { + reset_buf(ctx); + wmove(self->window, y2 - CURS_Y_OFFSET, 0); + } else { + mvwprintw(ctx->linewin, 1, 0, "%s", &line[ctx->start]); } } @@ -845,17 +898,13 @@ static void chat_onInit(ToxWindow *self, Tox *m) statusbar->topline = subwin(self->window, 2, x2, 0, 0); ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT + 1, x2, 0, 0); - scrollok(ctx->history, 1); ctx->linewin = subwin(self->window, CHATBOX_HEIGHT, x2, y2 - CHATBOX_HEIGHT, 0); ctx->hst = malloc(sizeof(struct history)); ctx->log = malloc(sizeof(struct chatlog)); - if (ctx->log == NULL || ctx->hst == NULL) { - endwin(); - fprintf(stderr, "malloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (ctx->log == NULL || ctx->hst == NULL) + exit_toxic_err("failed in chat_onInit", FATALERR_MEMORY); memset(ctx->hst, 0, sizeof(struct history)); memset(ctx->log, 0, sizeof(struct chatlog)); @@ -868,6 +917,7 @@ static void chat_onInit(ToxWindow *self, Tox *m) execute(ctx->history, self, m, "/help", CHAT_COMMAND_MODE); execute(ctx->history, self, m, "/log", GLOBAL_COMMAND_MODE); + scrollok(ctx->history, 0); wmove(self->window, y2 - CURS_Y_OFFSET, 0); } @@ -929,9 +979,7 @@ ToxWindow new_chat(Tox *m, int32_t friendnum) ret.chatwin = chatwin; ret.stb = stb; } else { - endwin(); - fprintf(stderr, "calloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); + exit_toxic_err("failed in new_chat", FATALERR_MEMORY); } ret.num = friendnum; diff --git a/src/chat.h b/src/chat.h index d3aeb3e..e6fc30b 100644 --- a/src/chat.h +++ b/src/chat.h @@ -23,7 +23,8 @@ #ifndef CHAT_H_6489PZ13 #define CHAT_H_6489PZ13 -#include "toxic_windows.h" +#include "windows.h" +#include "toxic.h" void kill_chat_window(ToxWindow *self); ToxWindow new_chat(Tox *m, int32_t friendnum); diff --git a/src/chat_commands.c b/src/chat_commands.c index afbbe27..8fd7ff8 100644 --- a/src/chat_commands.c +++ b/src/chat_commands.c @@ -27,7 +27,8 @@ #include #include -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "misc_tools.h" #include "friendlist.h" #include "execute.h" @@ -57,7 +58,7 @@ void cmd_chat_help(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*arg line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 1, CYAN); #ifdef _SUPPORT_AUDIO -#define NUMLINES 13 +#define NUMLINES 16 #else #define NUMLINES 9 #endif @@ -70,6 +71,9 @@ void cmd_chat_help(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*arg { " /answer : Answer incomming call" }, { " /reject : Reject incoming call" }, { " /hangup : Hangup active call" }, + { " /sdev : Change active device" }, + { " /mute : Mute active device if in call" }, + { " /sense : VAD sensitivity treshold" }, #endif /* _SUPPORT_AUDIO */ { " /invite : Invite friend to a group chat" }, { " /join : Join a pending group chat" }, @@ -86,7 +90,7 @@ void cmd_chat_help(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*arg for (i = 0; i < NUMLINES; ++i) line_info_add(self, NULL, NULL, NULL, lines[i], SYS_MSG, 0, 0); - msg = " * Use ESC key to toggle history scroll mode\n"; + msg = " * Use Page Up/Page Down to scroll chat history\n"; line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 1, CYAN); hst->line_start = start; @@ -185,7 +189,7 @@ void cmd_savefile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv uint8_t msg[MAX_STR_SIZE]; snprintf(msg, sizeof(msg), "Saving file as: '%s' (%.1f%%)", filename, 0.0); line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); - friends[self->num].file_receiver.line_id = self->chatwin->hst->line_end->id; + friends[self->num].file_receiver.line_id[filenum] = self->chatwin->hst->line_end->id + 1; if ((friends[self->num].file_receiver.files[filenum] = fopen(filename, "a")) == NULL) { errmsg = "* Error writing to file."; @@ -246,7 +250,7 @@ void cmd_sendfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv fseek(file_to_send, 0, SEEK_SET); uint8_t filename[MAX_STR_SIZE]; - get_file_name(path, filename); + get_file_name(filename, path); int filenum = tox_new_file_sender(m, self->num, filesize, filename, strlen(filename)); if (filenum == -1) { diff --git a/src/chat_commands.h b/src/chat_commands.h index 30ca9f1..d1fc34f 100644 --- a/src/chat_commands.h +++ b/src/chat_commands.h @@ -20,6 +20,12 @@ * */ +#ifndef _chat_commands_h +#define _chat_commands_h + +#include "windows.h" +#include "toxic.h" + void cmd_chat_help(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_groupinvite(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_join_group(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); @@ -33,5 +39,9 @@ void cmd_answer(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZ void cmd_reject(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_hangup(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_cancel(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); -void cmd_set_this_session_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_ccur_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_mute(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_sense(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); #endif /* _SUPPORT_AUDIO */ + +#endif /* #define _chat_commands_h */ diff --git a/src/configdir.c b/src/configdir.c index 4bb2411..f569ca9 100644 --- a/src/configdir.c +++ b/src/configdir.c @@ -30,14 +30,8 @@ #include #include #include - -#ifdef _WIN32 -#include -#include -#else /* WIN32 */ #include #include -#endif /* WIN32 */ #include "configdir.h" @@ -51,25 +45,6 @@ char *get_user_config_dir(void) { char *user_config_dir; -#ifdef _WIN32 -#warning Please fix configdir for Win32 - return NULL; -#if 0 - char appdata[MAX_PATH]; - BOOL ok; - - ok = SHGetSpecialFolderPathA(NULL, appdata, CSIDL_PROFILE, TRUE); - - if (!ok) { - return NULL; - } - - user_config_dir = strdup(appdata); - - return user_config_dir; -#endif - -#else /* WIN32 */ #ifndef NSS_BUFLEN_PASSWD #define NSS_BUFLEN_PASSWD 4096 @@ -128,7 +103,6 @@ char *get_user_config_dir(void) return user_config_dir; #undef NSS_BUFLEN_PASSWD -#endif /* WIN32 */ } /* @@ -136,26 +110,6 @@ char *get_user_config_dir(void) */ int create_user_config_dir(char *path) { -#ifdef _WIN32 -#warning Please fix configdir for Win32 - return -1; -#if 0 - char *fullpath = malloc(strlen(path) + strlen(CONFIGDIR) + 1); - strcpy(fullpath, path); - strcat(fullpath, CONFIGDIR); - - mkdir_err = _mkdir(fullpath); - struct __stat64 buf; - - if (mkdir_err && (errno != EEXIST || _wstat64(fullpath, &buf) || !S_ISDIR(buf.st_mode))) { - free(fullpath); - return -1; - } - - free(fullpath); -#endif - -#else int mkdir_err; mkdir_err = mkdir(path, 0700); @@ -178,5 +132,4 @@ int create_user_config_dir(char *path) free(fullpath); return 0; -#endif } diff --git a/src/configdir.h b/src/configdir.h index 6a1a7eb..0a8b01d 100644 --- a/src/configdir.h +++ b/src/configdir.h @@ -20,11 +20,10 @@ * */ -#ifdef _win32 -#define CONFIGDIR "\\tox\\" -#else +#ifndef _configdir_h +#define _configdir_h + #define CONFIGDIR "/tox/" -#endif #ifndef S_ISDIR #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) @@ -33,3 +32,5 @@ char *get_user_config_dir(void); int create_user_config_dir(char *path); + +#endif /* #define _configdir_h */ diff --git a/src/device.c b/src/device.c index e3f9212..ad638c1 100644 --- a/src/device.c +++ b/src/device.c @@ -1,9 +1,15 @@ -#include "toxic_windows.h" #include "audio_call.h" #include "line_info.h" + +#ifdef __APPLE__ +#include +#include +#else #include #include +#endif + #include #include #include @@ -22,11 +28,14 @@ typedef struct _Device { ALCcontext *ctx; /* Device context */ DataHandleCallback cb; /* Use this to handle data from input device usually */ void* cb_data; /* Data to be passed to callback */ + int32_t call_idx; /* ToxAv call index */ uint32_t source, buffers[openal_bufs]; /* Playback source/buffers */ size_t ref_count; int32_t selection; _Bool enable_VAD; + _Bool muted; + float VAD_treshold; /* 40 is usually recommended value */ } Device; const char *ddevice_names[2]; /* Default device */ @@ -100,6 +109,42 @@ DeviceError terminate_devices() return ae_None; } +DeviceError device_mute(DeviceType type, uint32_t device_idx) +{ + if (device_idx >= MAX_DEVICES) return de_InvalidSelection; + lock; + + Device* device = running[type][device_idx]; + + if (!device) { + unlock; + return de_DeviceNotActive; + } + + device->muted = !device->muted; + + unlock; + return de_None; +} + +DeviceError device_set_VAD_treshold(uint32_t device_idx, float value) +{ + if (device_idx >= MAX_DEVICES) return de_InvalidSelection; + lock; + + Device* device = running[input][device_idx]; + + if (!device) { + unlock; + return de_DeviceNotActive; + } + + device->VAD_treshold = value; + + unlock; + return de_None; +} + DeviceError set_primary_device(DeviceType type, int32_t selection) { if (size[type] <= selection || selection < 0) return de_InvalidSelection; @@ -147,6 +192,7 @@ DeviceError open_device(DeviceType type, int32_t selection, uint32_t* device_idx if (type == input) { device->dhndl = alcCaptureOpenDevice(devices_names[type][selection], av_DefaultSettings.audio_sample_rate, AL_FORMAT_MONO16, frame_size * 4); + device->VAD_treshold = 40.0; } else { device->dhndl = alcOpenDevice(devices_names[type][selection]); @@ -230,7 +276,7 @@ DeviceError close_device(DeviceType type, uint32_t device_idx) return de_None; } -DeviceError register_device_callback(uint32_t device_idx, DataHandleCallback callback, void* data, _Bool enable_VAD) +DeviceError register_device_callback( int32_t call_idx, uint32_t device_idx, DataHandleCallback callback, void* data, _Bool enable_VAD) { if (size[input] <= device_idx || !running[input][device_idx] || running[input][device_idx]->dhndl == NULL) return de_InvalidSelection; @@ -239,6 +285,7 @@ DeviceError register_device_callback(uint32_t device_idx, DataHandleCallback cal running[input][device_idx]->cb = callback; running[input][device_idx]->cb_data = data; running[input][device_idx]->enable_VAD = enable_VAD; + running[input][device_idx]->call_idx = call_idx; unlock; return de_None; @@ -265,7 +312,7 @@ inline__ DeviceError write_out(uint32_t device_idx, int16_t* data, uint32_t leng Device* device = running[output][device_idx]; - if (!device) return de_DeviceNotActive; + if (!device || device->muted) return de_DeviceNotActive; alcMakeContextCurrent(device->ctx); /* TODO: Check for error */ @@ -319,27 +366,27 @@ void* thread_poll (void* arg) // TODO: maybe use thread for every input source { lock; if (running[input][i] != NULL) -// do { - alcGetIntegerv(running[input][i]->dhndl, ALC_CAPTURE_SAMPLES, sizeof(int32_t), &sample); if (sample < f_size) { unlock; continue; } + Device* device = running[input][i]; int16_t frame[4096]; - alcCaptureSamples(running[input][i]->dhndl, frame, f_size); + alcCaptureSamples(device->dhndl, frame, f_size); - if ( running[input][i]->enable_VAD && !toxav_has_activity(frame, f_size, 88.5)) { unlock; continue; } /* Skip if no voice activity */ - if ( running[input][i]->cb ) running[input][i]->cb(frame, f_size, running[input][i]->cb_data); + if ( device->muted || + (device->enable_VAD && !toxav_has_activity(av, device->call_idx, frame, f_size, device->VAD_treshold))) + { unlock; continue; } /* Skip if no voice activity */ + if ( device->cb ) device->cb(frame, f_size, device->cb_data); } unlock; -// while (_True); } -// usleep(10); + usleep(5000); } } diff --git a/src/device.h b/src/device.h index e71ba75..9715854 100644 --- a/src/device.h +++ b/src/device.h @@ -14,7 +14,7 @@ #define MAX_DEVICES 32 #include -#include "toxic_windows.h" +#include "windows.h" #define _True 1 #define _False 0 @@ -43,9 +43,14 @@ DeviceError init_devices(ToxAv* av); DeviceError terminate_devices(); /* Callback handles ready data from INPUT device */ -DeviceError register_device_callback(uint32_t device_idx, DataHandleCallback callback, void* data, _Bool enable_VAD); +DeviceError register_device_callback(int32_t call_idx, uint32_t device_idx, DataHandleCallback callback, void* data, _Bool enable_VAD); void* get_device_callback_data(uint32_t device_idx); +/* toggle device mute */ +DeviceError device_mute(DeviceType type, uint32_t device_idx); + +DeviceError device_set_VAD_treshold(uint32_t device_idx, float value); + DeviceError set_primary_device(DeviceType type, int32_t selection); DeviceError open_primary_device(DeviceType type, uint32_t* device_idx); /* Start device */ diff --git a/src/dns.c b/src/dns.c new file mode 100644 index 0000000..8217cb2 --- /dev/null +++ b/src/dns.c @@ -0,0 +1,305 @@ +/* dns.c + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include + +#include "toxic.h" +#include "windows.h" +#include "line_info.h" +#include "dns.h" +#include "global_commands.h" +#include "misc_tools.h" + +#define MAX_DNS_REQST_SIZE 256 +#define NUM_DNS3_SERVERS 2 /* must correspond to number of items in dns3_servers array */ +#define TOX_DNS3_TXT_PREFIX "v=tox3;id=" +#define DNS3_KEY_SZ 32 + +/* TODO: process keys from key file instead of hard-coding like a noob */ +static struct dns3_server { + uint8_t *name; + uint8_t key[DNS3_KEY_SZ]; +} dns3_servers[] = { + { + "utox.org", + { + 0xD3, 0x15, 0x4F, 0x65, 0xD2, 0x8A, 0x5B, 0x41, 0xA0, 0x5D, 0x4A, 0xC7, 0xE4, 0xB3, 0x9C, 0x6B, + 0x1C, 0x23, 0x3C, 0xC8, 0x57, 0xFB, 0x36, 0x5C, 0x56, 0xE8, 0x39, 0x27, 0x37, 0x46, 0x2A, 0x12 + } + }, + { + "toxme.se", + { + 0x5D, 0x72, 0xC5, 0x17, 0xDF, 0x6A, 0xEC, 0x54, 0xF1, 0xE9, 0x77, 0xA6, 0xB6, 0xF2, 0x59, 0x14, + 0xEA, 0x4C, 0xF7, 0x27, 0x7A, 0x85, 0x02, 0x7C, 0xD9, 0xF5, 0x19, 0x6D, 0xF1, 0x7E, 0x0B, 0x13 + } + }, +}; + +static struct _thread_data { + ToxWindow *self; + uint8_t id_bin[TOX_FRIEND_ADDRESS_SIZE]; + uint8_t addr[MAX_STR_SIZE]; + uint8_t msg[MAX_STR_SIZE]; + uint8_t busy; + Tox *m; +} t_data; + +static struct _dns_thread { + pthread_t tid; + pthread_mutex_t lock; +} dns_thread; + + +static int dns_error(ToxWindow *self, uint8_t *errmsg) +{ + uint8_t msg[MAX_STR_SIZE]; + snprintf(msg, sizeof(msg), "DNS lookup failed: %s", errmsg); + + pthread_mutex_lock(&dns_thread.lock); + line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); + pthread_mutex_unlock(&dns_thread.lock); + + return -1; +} + +static void kill_dns_thread(void *dns_obj) +{ + if (dns_obj) + tox_dns3_kill(dns_obj); + + memset(&t_data, 0, sizeof(struct _thread_data)); + pthread_exit(0); +} + +/* puts TXT from dns response in buf. Returns length of TXT on success, -1 on fail.*/ +static int parse_dns_response(ToxWindow *self, u_char *answer, int ans_len, uint8_t *buf) +{ + uint8_t *ans_pt = answer + sizeof(HEADER); + uint8_t *ans_end = answer + ans_len; + uint8_t exp_ans[PACKETSZ]; + + int len = dn_expand(answer, ans_end, ans_pt, exp_ans, sizeof(exp_ans)); + + if (len == -1) + return dns_error(self, "dn_expand failed."); + + ans_pt += len; + + if (ans_pt > ans_end - 4) + return dns_error(self, "Reply was too short."); + + int type; + GETSHORT(type, ans_pt); + + if (type != T_TXT) + return dns_error(self, "Broken reply."); + + + ans_pt += INT16SZ; /* class */ + uint32_t size = 0; + + /* recurse through CNAME rr's */ + do { + ans_pt += size; + len = dn_expand(answer, ans_end, ans_pt, exp_ans, sizeof(exp_ans)); + + if (len == -1) + return dns_error(self, "Second dn_expand failed."); + + ans_pt += len; + + if (ans_pt > ans_end - 10) + return dns_error(self, "Reply was too short."); + + GETSHORT(type, ans_pt); + ans_pt += INT16SZ; + ans_pt += 4; + GETSHORT(size, ans_pt); + + if (ans_pt + size < answer || ans_pt + size > ans_end) + return dns_error(self, "RR overflow."); + + } while (type == T_CNAME); + + if (type != T_TXT) + return dns_error(self, "Not a TXT record."); + + uint32_t txt_len = *ans_pt; + + if (!size || txt_len >= size || !txt_len) + return dns_error(self, "No record found."); + + ans_pt++; + ans_pt[txt_len] = '\0'; + memcpy(buf, ans_pt, txt_len + 1); + + return txt_len; +} + +/* Takes address addr in the form "username@domain", puts the username in namebuf, + and the domain in dombuf. + + return length of username on success, -1 on failure */ +static int parse_addr(uint8_t *addr, uint8_t *namebuf, uint8_t *dombuf) +{ + uint8_t tmpaddr[MAX_STR_SIZE]; + uint8_t *tmpname, *tmpdom; + + strcpy(tmpaddr, addr); + tmpname = strtok(tmpaddr, "@"); + tmpdom = strtok(NULL, ""); + + if (tmpname == NULL || tmpdom == NULL) + return -1; + + str_to_lower(tmpdom); + strcpy(namebuf, tmpname); + strcpy(dombuf, tmpdom); + + return strlen(namebuf); +} + +/* Does DNS lookup for addr and puts resulting tox id in id_bin. */ +void *dns3_lookup_thread(void *data) +{ + ToxWindow *self = t_data.self; + + uint8_t domain[MAX_STR_SIZE]; + uint8_t name[MAX_STR_SIZE]; + + int namelen = parse_addr(t_data.addr, name, domain); + + if (namelen == -1) { + dns_error(self, "Must be a Tox ID or an address in the form username@domain"); + kill_dns_thread(NULL); + } + + /* get domain name/pub key */ + uint8_t *DNS_pubkey, *domname = NULL; + int i; + + for (i = 0; i < NUM_DNS3_SERVERS; ++i) { + if (strcmp(dns3_servers[i].name, domain) == 0) { + DNS_pubkey = dns3_servers[i].key; + domname = dns3_servers[i].name; + break; + } + } + + if (domname == NULL) { + dns_error(self, "Domain not found."); + kill_dns_thread(NULL); + } + + void *dns_obj = tox_dns3_new(DNS_pubkey); + + if (dns_obj == NULL) { + dns_error(self, "Core failed to create DNS object."); + kill_dns_thread(NULL); + } + + uint8_t string[MAX_DNS_REQST_SIZE]; + uint32_t request_id; + + int str_len = tox_generate_dns3_string(dns_obj, string, sizeof(string), &request_id, name, namelen); + + if (str_len == -1) { + dns_error(self, "Core failed to generate dns3 string."); + kill_dns_thread(dns_obj); + } + + string[str_len] = '\0'; + + u_char answer[PACKETSZ]; + uint8_t d_string[MAX_DNS_REQST_SIZE]; + + /* format string and create dns query */ + snprintf(d_string, sizeof(d_string), "_%s._tox.%s", string, domname); + int ans_len = res_query(d_string, C_IN, T_TXT, answer, sizeof(answer)); + + if (ans_len <= 0) { + dns_error(self, "Query failed."); + kill_dns_thread(dns_obj); + } + + uint8_t ans_id[MAX_DNS_REQST_SIZE]; + + /* extract TXT from DNS response */ + if (parse_dns_response(self, answer, ans_len, ans_id) == -1) + kill_dns_thread(dns_obj); + + uint8_t encrypted_id[MAX_DNS_REQST_SIZE]; + int prfx_len = strlen(TOX_DNS3_TXT_PREFIX); + + /* extract the encrypted ID from TXT response */ + if (strncmp(ans_id, TOX_DNS3_TXT_PREFIX, prfx_len) != 0) { + dns_error(self, "Bad dns3 TXT response."); + kill_dns_thread(dns_obj); + } + + memcpy(encrypted_id, ans_id + prfx_len, ans_len - prfx_len); + + if (tox_decrypt_dns3_TXT(dns_obj, t_data.id_bin, encrypted_id, strlen(encrypted_id), request_id) == -1) { + dns_error(self, "Core failed to decrypt response."); + kill_dns_thread(dns_obj); + } + + pthread_mutex_lock(&dns_thread.lock); + cmd_add_helper(self, t_data.m, t_data.id_bin, t_data.msg); + pthread_mutex_unlock(&dns_thread.lock); + + kill_dns_thread(dns_obj); +} + +/* creates new thread for dns3 lookup. Only allows one lookup at a time. */ +void dns3_lookup(ToxWindow *self, Tox *m, uint8_t *id_bin, uint8_t *addr, uint8_t *msg) +{ + if (t_data.busy) { + uint8_t *err = "Please wait for previous user lookup to finish."; + line_info_add(self, NULL, NULL, NULL, err, SYS_MSG, 0, 0); + return; + } + + snprintf(t_data.id_bin, sizeof(t_data.id_bin), "%s", id_bin); + snprintf(t_data.addr, sizeof(t_data.addr), "%s", addr); + snprintf(t_data.msg, sizeof(t_data.msg), "%s", msg); + t_data.self = self; + t_data.m = m; + t_data.busy = 1; + + if (pthread_create(&dns_thread.tid, NULL, dns3_lookup_thread, NULL) != 0) + exit_toxic_err("failed in dns3_lookup", FATALERR_THREAD_CREATE); + + if (pthread_mutex_init(&dns_thread.lock, NULL) != 0) + exit_toxic_err("failed in dns3_lookup", FATALERR_MUTEX_INIT); +} diff --git a/src/dns.h b/src/dns.h new file mode 100644 index 0000000..edd6579 --- /dev/null +++ b/src/dns.h @@ -0,0 +1,32 @@ +/* dns.c + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +/* Does DNS lookup for addr and puts resulting tox id in id_bin. + Return 0 on success, -1 on failure. */ + +#ifndef _dns_h +#define _dns_h + +/* creates new thread for dns3 lookup. Only allows one lookup at a time. */ +void dns3_lookup(ToxWindow *self, Tox *m, uint8_t *id_bin, uint8_t *addr, uint8_t *msg); + +#endif /* #define _dns_h */ diff --git a/src/execute.c b/src/execute.c index 7dc18cb..62057c5 100644 --- a/src/execute.c +++ b/src/execute.c @@ -28,7 +28,8 @@ #include #include -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "execute.h" #include "chat_commands.h" #include "global_commands.h" @@ -54,6 +55,7 @@ static struct cmd_func global_commands[] = { { "/q", cmd_quit }, { "/quit", cmd_quit }, { "/status", cmd_status }, + #ifdef _SUPPORT_AUDIO { "/lsdev", cmd_list_devices }, { "/sdev", cmd_change_device }, @@ -73,7 +75,9 @@ static struct cmd_func chat_commands[] = { { "/answer", cmd_answer }, { "/reject", cmd_reject }, { "/hangup", cmd_hangup }, - { "/sdev", cmd_set_this_session_device }, + { "/sdev", cmd_ccur_device }, + { "/mute", cmd_mute }, + { "/sense", cmd_sense }, #endif /* _SUPPORT_AUDIO */ }; diff --git a/src/execute.h b/src/execute.h index 57b0b26..20c55a3 100644 --- a/src/execute.h +++ b/src/execute.h @@ -20,11 +20,17 @@ * */ +#ifndef _execute_h +#define _execute_h + +#include "toxic.h" +#include "windows.h" + #define MAX_NUM_ARGS 4 /* Includes command */ #ifdef _SUPPORT_AUDIO #define GLOBAL_NUM_COMMANDS 16 -#define CHAT_NUM_COMMANDS 11 +#define CHAT_NUM_COMMANDS 13 #else #define GLOBAL_NUM_COMMANDS 14 #define CHAT_NUM_COMMANDS 5 @@ -37,3 +43,5 @@ enum { }; void execute(WINDOW *w, ToxWindow *self, Tox *m, char *cmd, int mode); + +#endif /* #define _execute_h */ diff --git a/src/file_senders.c b/src/file_senders.c index b8529d0..4d12a54 100644 --- a/src/file_senders.c +++ b/src/file_senders.c @@ -28,7 +28,9 @@ #include #include -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" +#include "file_senders.h" #include "line_info.h" FileSender file_senders[MAX_FILES]; @@ -107,9 +109,9 @@ void do_file_senders(Tox *m) if (remain) pct_remain = (1 - (remain / size)) * 100; - const uint8_t *name = file_senders[filenum].pathname; + const uint8_t *name = file_senders[i].pathname; snprintf(msg, sizeof(msg), "File transfer for '%s' accepted (%.1Lf%%)", name, pct_remain); - line_info_set(self, file_senders[filenum].line_id, msg); + line_info_set(self, file_senders[i].line_id, msg); } if (file_senders[i].piecelen == 0) { diff --git a/src/file_senders.h b/src/file_senders.h index 9ec1cdc..59cc640 100644 --- a/src/file_senders.h +++ b/src/file_senders.h @@ -20,7 +20,33 @@ * */ +#ifndef _filesenders_h +#define _filesenders_h + +#include "toxic.h" +#include "windows.h" + +#define FILE_PIECE_SIZE 2048 /* must be >= (MAX_CRYPTO_DATA_SIZE - 2) in toxcore/net_crypto.h */ +#define MAX_FILES 256 +#define TIMEOUT_FILESENDER 300 + +typedef struct { + FILE *file; + ToxWindow *toxwin; + int32_t friendnum; + bool active; + int filenum; + uint8_t nextpiece[FILE_PIECE_SIZE]; + uint16_t piecelen; + uint8_t pathname[MAX_STR_SIZE]; + uint64_t timestamp; + uint64_t size; + uint32_t line_id; +} FileSender; + /* Should only be called on exit */ void close_all_file_senders(void); void do_file_senders(Tox *m); + +#endif /* #define _filesenders_h */ diff --git a/src/friendlist.c b/src/friendlist.c index 874b01f..a1573de 100644 --- a/src/friendlist.c +++ b/src/friendlist.c @@ -31,6 +31,8 @@ #include +#include "toxic.h" +#include "windows.h" #include "chat.h" #include "friendlist.h" #include "misc_tools.h" @@ -54,7 +56,7 @@ extern struct user_settings *user_settings; ToxicFriend friends[MAX_FRIENDS_NUM]; static int friendlist_index[MAX_FRIENDS_NUM] = {0}; -struct _pendingDel { +static struct _pendingDel { int num; bool active; } pendingdelete; @@ -162,9 +164,11 @@ static void friendlist_onStatusMessageChange(ToxWindow *self, int32_t num, uint8 if (len > TOX_MAX_STATUSMESSAGE_LENGTH || num >= max_friends_index) return; - strcpy(friends[num].statusmsg, str); - friends[num].statusmsg[len] = '\0'; + str[len] = '\0'; + snprintf(friends[num].statusmsg, sizeof(friends[num].statusmsg), "%s", str); + len = strlen(friends[num].statusmsg); friends[num].statusmsg_len = len; + friends[num].statusmsg[len] = '\0'; } void friendlist_onFriendAdded(ToxWindow *self, Tox *m, int32_t num, bool sort) diff --git a/src/friendlist.h b/src/friendlist.h index 363cbea..f6cc9fd 100644 --- a/src/friendlist.h +++ b/src/friendlist.h @@ -24,10 +24,21 @@ #define FRIENDLIST_H_53I41IM #include -#include "toxic_windows.h" + +#include "toxic.h" +#include "windows.h" +#include "file_senders.h" #define TIME_STR_SIZE 16 +struct FileReceiver { + uint8_t filenames[MAX_FILES][MAX_STR_SIZE]; + FILE *files[MAX_FILES]; + bool pending[MAX_FILES]; + uint64_t size[MAX_FILES]; + uint32_t line_id[MAX_FILES]; +}; + struct LastOnline { uint64_t last_on; struct tm tm; diff --git a/src/global_commands.c b/src/global_commands.c index b51adca..cb0aa52 100644 --- a/src/global_commands.c +++ b/src/global_commands.c @@ -27,11 +27,13 @@ #include #include -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "misc_tools.h" #include "friendlist.h" #include "log.h" #include "line_info.h" +#include "dns.h" extern char *DATA_FILE; extern ToxWindow *prompt; @@ -88,6 +90,49 @@ void cmd_accept(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[ line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); } +void cmd_add_helper(ToxWindow *self, Tox *m, uint8_t *id_bin, uint8_t *msg) +{ + uint8_t *errmsg; + int32_t f_num = tox_add_friend(m, id_bin, msg, strlen(msg)); + + switch (f_num) { + case TOX_FAERR_TOOLONG: + errmsg = "Message is too long."; + break; + + case TOX_FAERR_NOMESSAGE: + errmsg = "Please add a message to your request."; + break; + + case TOX_FAERR_OWNKEY: + errmsg = "That appears to be your own ID."; + break; + + case TOX_FAERR_ALREADYSENT: + errmsg = "Friend request has already been sent."; + break; + + case TOX_FAERR_UNKNOWN: + errmsg = "Undefined error when adding friend."; + break; + + case TOX_FAERR_BADCHECKSUM: + errmsg = "Bad checksum in address."; + break; + + case TOX_FAERR_SETNEWNOSPAM: + errmsg = "Nospam was different."; + break; + + default: + errmsg = "Friend request sent."; + on_friendadded(m, f_num, true); + break; + } + + line_info_add(self, NULL, NULL, NULL, errmsg, SYS_MSG, 0, 0); +} + void cmd_add(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { uint8_t *errmsg; @@ -119,73 +164,33 @@ void cmd_add(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX snprintf(msg, sizeof(msg), "Hello, my name is %s. Care to Tox?", selfname); } - if (strlen(id) != 2 * TOX_FRIEND_ADDRESS_SIZE) { - errmsg = "Invalid ID length."; - line_info_add(self, NULL, NULL, NULL, errmsg, SYS_MSG, 0, 0); - return; - } + uint8_t id_bin[TOX_FRIEND_ADDRESS_SIZE] = {0}; + uint16_t id_len = strlen(id); - size_t i; - char xx[3]; - uint32_t x; - uint8_t id_bin[TOX_FRIEND_ADDRESS_SIZE]; + /* try to add tox ID */ + if (id_len == 2 * TOX_FRIEND_ADDRESS_SIZE) { + size_t i; + char xx[3]; + uint32_t x; - for (i = 0; i < TOX_FRIEND_ADDRESS_SIZE; ++i) { - xx[0] = id[2 * i]; - xx[1] = id[2 * i + 1]; - xx[2] = '\0'; + for (i = 0; i < TOX_FRIEND_ADDRESS_SIZE; ++i) { + xx[0] = id[2 * i]; + xx[1] = id[2 * i + 1]; + xx[2] = '\0'; - if (sscanf(xx, "%02x", &x) != 1) { - errmsg = "Invalid ID."; - line_info_add(self, NULL, NULL, NULL, errmsg, SYS_MSG, 0, 0); - return; + if (sscanf(xx, "%02x", &x) != 1) { + errmsg = "Invalid ID."; + line_info_add(self, NULL, NULL, NULL, errmsg, SYS_MSG, 0, 0); + return; + } + + id_bin[i] = x; } - id_bin[i] = x; + cmd_add_helper(self, m, id_bin, msg); + } else { /* assume id is a username@domain address and do DNS lookup */ + dns3_lookup(self, m, id_bin, id, msg); } - - for (i = 0; i < TOX_FRIEND_ADDRESS_SIZE; i++) { - id[i] = toupper(id[i]); - } - - int32_t f_num = tox_add_friend(m, id_bin, msg, strlen(msg)); - - switch (f_num) { - case TOX_FAERR_TOOLONG: - errmsg = "Message is too long."; - break; - - case TOX_FAERR_NOMESSAGE: - errmsg = "Please add a message to your request."; - break; - - case TOX_FAERR_OWNKEY: - errmsg = "That appears to be your own ID."; - break; - - case TOX_FAERR_ALREADYSENT: - errmsg = "Friend request has already been sent."; - break; - - case TOX_FAERR_UNKNOWN: - errmsg = "Undefined error when adding friend."; - break; - - case TOX_FAERR_BADCHECKSUM: - errmsg = "Bad checksum in address."; - break; - - case TOX_FAERR_SETNEWNOSPAM: - errmsg = "Nospam was different (is this contact already added?"; - break; - - default: - errmsg = "Friend request sent."; - on_friendadded(m, f_num, true); - break; - } - - line_info_add(self, NULL, NULL, NULL, errmsg, SYS_MSG, 0, 0); } void cmd_clear(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) @@ -422,16 +427,18 @@ void cmd_prompt_help(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*a for (i = 0; i < NUMLINES; ++i) line_info_add(self, NULL, NULL, NULL, lines[i], SYS_MSG, 0, 0); - msg = " * Argument messages must be enclosed in quotation marks.\n" - " * Use ctrl-o and ctrl-p to navigate through the tabs.\n"; + msg = " * Argument messages must be enclosed in quotation marks."; line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 1, CYAN); + msg = " * Use ctrl-o and ctrl-p to navigate through the tabs."; + line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 1, CYAN); + line_info_add(self, NULL, NULL, NULL, "", SYS_MSG, 0, 0); hst->line_start = start; } void cmd_quit(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { - exit_toxic(m); + exit_toxic_success(m); } void cmd_status(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) @@ -454,20 +461,16 @@ void cmd_status(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[ } char *status = argv[1]; + str_to_lower(status); int len = strlen(status); - char l_status[len + 1]; - int i; - - for (i = 0; i <= len; ++i) - l_status[i] = tolower(status[i]); TOX_USERSTATUS status_kind; - if (!strcmp(l_status, "online")) + if (!strcmp(status, "online")) status_kind = TOX_USERSTATUS_NONE; - else if (!strcmp(l_status, "away")) + else if (!strcmp(status, "away")) status_kind = TOX_USERSTATUS_AWAY; - else if (!strcmp(l_status, "busy")) + else if (!strcmp(status, "busy")) status_kind = TOX_USERSTATUS_BUSY; else { errmsg = "Invalid status. Valid statuses are: online, busy and away."; diff --git a/src/global_commands.h b/src/global_commands.h index f8aec5b..688b6af 100644 --- a/src/global_commands.h +++ b/src/global_commands.h @@ -20,6 +20,12 @@ * */ +#ifndef _global_commands_h +#define _global_commands_h + +#include "windows.h" +#include "toxic.h" + void cmd_accept(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_add(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_clear(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); @@ -33,7 +39,11 @@ void cmd_prompt_help(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_ST void cmd_quit(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_status(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_add_helper(ToxWindow *self, Tox *m, uint8_t *id_bin, uint8_t *msg); + #ifdef _SUPPORT_AUDIO void cmd_list_devices(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_change_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); -#endif /* _SUPPORT_AUDIO */ \ No newline at end of file +#endif /* _SUPPORT_AUDIO */ + +#endif /* #define _global_commands_h */ diff --git a/src/groupchat.c b/src/groupchat.c index 741e122..f81fea8 100644 --- a/src/groupchat.c +++ b/src/groupchat.c @@ -28,7 +28,8 @@ #include #include -#include "toxic_windows.h" +#include "windows.h" +#include "toxic.h" #include "execute.h" #include "misc_tools.h" #include "groupchat.h" @@ -41,7 +42,7 @@ extern char *DATA_FILE; extern int store_data(Tox *m, char *path); -static GroupChat groupchats[MAX_WINDOWS_NUM]; +static GroupChat groupchats[MAX_GROUPCHAT_NUM]; static int max_groupchat_index = 0; extern struct user_settings *user_settings; @@ -51,6 +52,9 @@ extern const uint8_t glob_cmd_list[AC_NUM_GLOB_COMMANDS][MAX_CMDNAME_SIZE]; int init_groupchat_win(ToxWindow *prompt, Tox *m, int groupnum) { + if (groupnum > MAX_GROUPCHAT_NUM) + return -1; + int i; for (i = 0; i <= max_groupchat_index; ++i) { @@ -146,12 +150,13 @@ static void print_groupchat_help(ToxWindow *self) for (i = 0; i < NUMLINES; ++i) line_info_add(self, NULL, NULL, NULL, lines[i], SYS_MSG, 0, 0); - msg = " * Use ESC key to toggle history scroll mode"; + msg = " * Use Page Up/Page Down keys to scroll chat history"; line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 1, CYAN); - msg = " * Scroll peer list with the Page Up/Page Down keys.\n"; + msg = " * Scroll peer list with the ctrl-] and ctrl-[ keys."; line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 1, CYAN); - msg = " * Notice, some friends will be missing names while finding peers\n"; + msg = " * Notice, some friends will be missing names while finding peers"; line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 1, 0); + line_info_add(self, NULL, NULL, NULL, "", SYS_MSG, 0, 0); hst->line_start = start; } @@ -255,16 +260,14 @@ static void copy_peernames(int gnum, uint8_t peerlist[][TOX_MAX_NAME_LENGTH], ui groupchats[gnum].peer_name_lengths = malloc(sizeof(uint16_t) * npeers); groupchats[gnum].oldpeer_name_lengths = malloc(sizeof(uint16_t) * npeers); + if (groupchats[gnum].peer_names == NULL || groupchats[gnum].oldpeer_names == NULL + || groupchats[gnum].peer_name_lengths == NULL || groupchats[gnum].oldpeer_name_lengths == NULL) { + exit_toxic_err("failed in copy_peernames", FATALERR_MEMORY); + } + memset(groupchats[gnum].peer_names, 0, sizeof(uint8_t) * npeers * N); memset(groupchats[gnum].peer_name_lengths, 0, sizeof(uint16_t) * npeers); - if (groupchats[gnum].peer_names == NULL || groupchats[gnum].oldpeer_names == NULL - || groupchats[gnum].peer_name_lengths == NULL || groupchats[gnum].oldpeer_name_lengths == NULL) { - endwin(); - fprintf(stderr, "malloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } - uint16_t unknown_len = strlen(UNKNOWN_NAME); int i; @@ -294,9 +297,15 @@ static void groupchat_onGroupNamelistChange(ToxWindow *self, Tox *m, int groupnu if (self->num != groupnum) return; + if (groupnum > max_groupchat_index) + return; + groupchats[groupnum].num_peers = tox_group_number_peers(m, groupnum); int num_peers = groupchats[groupnum].num_peers; + if (peernum >= num_peers) + return; + /* get old peer name before updating name list */ uint8_t oldpeername[TOX_MAX_NAME_LENGTH]; @@ -380,40 +389,42 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) int x, y, y2, x2; getyx(self->window, y, x); getmaxyx(self->window, y2, x2); - int cur_len = 0; - if (!ltr && (key == T_KEY_ESC) ) { /* ESC key: Toggle history scroll mode */ - bool scroll = ctx->hst->scroll_mode ? false : true; - line_info_toggle_scroll(self, scroll); - } - - /* If we're in scroll mode ignore rest of function */ - if (ctx->hst->scroll_mode) { - line_info_onKey(self, key); + if (x2 <= 0) return; - } - if (ltr) { - if ( (ctx->len < MAX_STR_SIZE - 1) && (ctx->len < (x2 * (CHATBOX_HEIGHT - 1) - 1)) ) { - add_char_to_buf(ctx->line, &ctx->pos, &ctx->len, key); + int cur_len = 0; /* widechar len of current char */ + int x2_is_odd = x2 % 2 != 0; - if (x == x2 - 1) - wmove(self->window, y + 1, 0); - else + if (ltr) { /* char is printable */ + if (ctx->len < MAX_STR_SIZE - 1) { + add_char_to_buf(ctx, key); + + if (x >= x2 - 1) { + wmove(self->window, y, x2 / 2 + x2_is_odd); + ctx->start += x2 / 2; + } else { wmove(self->window, y, x + MAX(1, wcwidth(key))); + } } } else { /* if (!ltr) */ - if (key == 0x107 || key == 0x8 || key == 0x7f) { /* BACKSPACE key: Remove character behind pos */ - if (ctx->pos > 0) { - cur_len = MAX(1, wcwidth(ctx->line[ctx->pos - 1])); - del_char_buf_bck(ctx->line, &ctx->pos, &ctx->len); + if (line_info_onKey(self, key)) + return; - if (x == 0) - wmove(self->window, y - 1, x2 - cur_len); - else + if (key == 0x107 || key == 0x8 || key == 0x7f) { /* BACKSPACE key */ + if (ctx->pos > 0) { + cur_len = MAX(1, wcwidth(ctx->line[ctx->pos])); + del_char_buf_bck(ctx); + + if (x == 0) { + ctx->start = ctx->start >= x2 ? ctx->start - x2 : 0; + int new_x = ctx->start == 0 ? ctx->pos : x2 - cur_len; + wmove(self->window, y, new_x); + } else { wmove(self->window, y, x - cur_len); + } } else { beep(); } @@ -421,14 +432,14 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_DC) { /* DEL key: Remove character at pos */ if (ctx->pos != ctx->len) - del_char_buf_frnt(ctx->line, &ctx->pos, &ctx->len); + del_char_buf_frnt(ctx); else beep(); } else if (key == T_KEY_DISCARD) { /* CTRL-U: Delete entire line behind pos */ if (ctx->pos > 0) { - discard_buf(ctx->line, &ctx->pos, &ctx->len); + discard_buf(ctx); wmove(self->window, y2 - CURS_Y_OFFSET, 0); } else { beep(); @@ -437,7 +448,7 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == T_KEY_KILL) { /* CTRL-K: Delete entire line in front of pos */ if (ctx->pos != ctx->len) - kill_buf(ctx->line, &ctx->pos, &ctx->len); + kill_buf(ctx); else beep(); } @@ -445,6 +456,7 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_HOME || key == T_KEY_C_A) { /* HOME/C-a key: Move cursor to start of line */ if (ctx->pos > 0) { ctx->pos = 0; + ctx->start = 0; wmove(self->window, y2 - CURS_Y_OFFSET, 0); } } @@ -452,7 +464,8 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_END || key == T_KEY_C_E) { /* END/C-e key: move cursor to end of line */ if (ctx->pos != ctx->len) { ctx->pos = ctx->len; - mv_curs_end(self->window, MAX(0, wcswidth(ctx->line, (CHATBOX_HEIGHT - 1)*x2)), y2, x2); + ctx->start = x2 * (ctx->len / x2); + mv_curs_end(self->window, ctx->len, y2, x2); } } @@ -461,10 +474,13 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) --ctx->pos; cur_len = MAX(1, wcwidth(ctx->line[ctx->pos])); - if (x == 0) - wmove(self->window, y - 1, x2 - cur_len); - else + if (x == 0) { + wmove(self->window, y, x2 - cur_len); + ctx->start = ctx->start >= x2 ? ctx->start - x2 : 0; + ctx->pos = ctx->start + x2 - 1; + } else { wmove(self->window, y, x - cur_len); + } } else { beep(); } @@ -472,27 +488,30 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_RIGHT) { if (ctx->pos < ctx->len) { - cur_len = MAX(1, wcwidth(ctx->line[ctx->pos])); ++ctx->pos; - if (x == x2 - 1) - wmove(self->window, y + 1, 0); - else + if (x == x2 - 1) { + wmove(self->window, y, 0); + ctx->start += x2; + ctx->pos = ctx->start; + } else { + cur_len = MAX(1, wcwidth(ctx->line[ctx->pos])); wmove(self->window, y, x + cur_len); + } } else { beep(); } } else if (key == KEY_UP) { /* fetches previous item in history */ - fetch_hist_item(ctx->line, &ctx->pos, &ctx->len, ctx->ln_history, ctx->hst_tot, - &ctx->hst_pos, MOVE_UP); + fetch_hist_item(ctx, MOVE_UP); + ctx->start = x2 * (ctx->len / x2); mv_curs_end(self->window, ctx->len, y2, x2); } else if (key == KEY_DOWN) { /* fetches next item in history */ - fetch_hist_item(ctx->line, &ctx->pos, &ctx->len, ctx->ln_history, ctx->hst_tot, - &ctx->hst_pos, MOVE_DOWN); + fetch_hist_item(ctx, MOVE_DOWN); + ctx->start = x2 * (ctx->len / x2); mv_curs_end(self->window, ctx->len, y2, x2); } @@ -501,11 +520,10 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) int diff; if ((ctx->line[0] != '/') || (ctx->line[1] == 'm' && ctx->line[2] == 'e')) - diff = complete_line(ctx->line, &ctx->pos, &ctx->len, groupchats[self->num].peer_names, + diff = complete_line(ctx, groupchats[self->num].peer_names, groupchats[self->num].num_peers, TOX_MAX_NAME_LENGTH); else - diff = complete_line(ctx->line, &ctx->pos, &ctx->len, glob_cmd_list, AC_NUM_GLOB_COMMANDS, - MAX_CMDNAME_SIZE); + diff = complete_line(ctx, glob_cmd_list, AC_NUM_GLOB_COMMANDS, MAX_CMDNAME_SIZE); if (diff != -1) { if (x + diff > x2 - 1) { @@ -523,20 +541,22 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) } /* Scroll peerlist up and down one position if list overflows window */ - else if (key == KEY_NPAGE) { + else if (key == T_KEY_C_RB) { int L = y2 - CHATBOX_HEIGHT - SDBAR_OFST; if (groupchats[self->num].side_pos < groupchats[self->num].num_peers - L) ++groupchats[self->num].side_pos; } - else if (key == KEY_PPAGE) { + else if (key == T_KEY_C_LB) { if (groupchats[self->num].side_pos > 0) --groupchats[self->num].side_pos; } /* RETURN key: Execute command or print line */ else if (key == '\n') { + rm_trailing_spaces_buf(ctx); + uint8_t line[MAX_STR_SIZE]; if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) @@ -547,7 +567,7 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) if (!string_is_empty(line)) - add_line_to_hist(ctx->line, ctx->len, ctx->ln_history, &ctx->hst_tot, &ctx->hst_pos); + add_line_to_hist(ctx); if (line[0] == '/') { if (strcmp(line, "/close") == 0) { @@ -571,7 +591,7 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) } } - reset_buf(ctx->line, &ctx->pos, &ctx->len); + reset_buf(ctx); } } } @@ -587,21 +607,17 @@ static void groupchat_onDraw(ToxWindow *self, Tox *m) line_info_print(self); wclear(ctx->linewin); - if (ctx->hst->scroll_mode) { - line_info_onDraw(self); - } else { - scrollok(ctx->history, 1); - curs_set(1); + scrollok(ctx->history, 0); + curs_set(1); - if (ctx->len > 0) { - uint8_t line[MAX_STR_SIZE]; + if (ctx->len > 0) { + uint8_t line[MAX_STR_SIZE]; - if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) { - reset_buf(ctx->line, &ctx->pos, &ctx->len); - wmove(self->window, y2 - CURS_Y_OFFSET, 0); - } else { - mvwprintw(ctx->linewin, 1, 0, "%s", line); - } + if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) { + reset_buf(ctx); + wmove(self->window, y2 - CURS_Y_OFFSET, 0); + } else { + mvwprintw(ctx->linewin, 1, 0, "%s", &line[ctx->start]); } } @@ -651,11 +667,8 @@ static void groupchat_onInit(ToxWindow *self, Tox *m) ctx->hst = malloc(sizeof(struct history)); ctx->log = malloc(sizeof(struct chatlog)); - if (ctx->log == NULL || ctx->hst == NULL) { - endwin(); - fprintf(stderr, "malloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (ctx->log == NULL || ctx->hst == NULL) + exit_toxic_err("failed in groupchat_onInit", FATALERR_MEMORY); memset(ctx->hst, 0, sizeof(struct history)); memset(ctx->log, 0, sizeof(struct chatlog)); @@ -690,14 +703,10 @@ ToxWindow new_group_chat(Tox *m, int groupnum) ChatContext *chatwin = calloc(1, sizeof(ChatContext)); - if (chatwin != NULL) - ret.chatwin = chatwin; - else { - endwin(); - fprintf(stderr, "calloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (chatwin == NULL) + exit_toxic_err("failed in new_group_chat", FATALERR_MEMORY); + ret.chatwin = chatwin; ret.num = groupnum; return ret; diff --git a/src/groupchat.h b/src/groupchat.h index afaa311..56f08b8 100644 --- a/src/groupchat.h +++ b/src/groupchat.h @@ -20,8 +20,15 @@ * */ +#ifndef _groupchat_h +#define _groupchat_h + +#include "toxic.h" +#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_GROUPCHAT_NUM MAX_WINDOWS_NUM - 2 typedef struct { int chatwin; @@ -37,3 +44,5 @@ typedef struct { void kill_groupchat_window(ToxWindow *self); int init_groupchat_win(ToxWindow *prompt, Tox *m, int groupnum); ToxWindow new_group_chat(Tox *m, int groupnum); + +#endif /* #define _groupchat_h */ diff --git a/src/line_info.c b/src/line_info.c index 6fc96bb..42d617f 100644 --- a/src/line_info.c +++ b/src/line_info.c @@ -28,56 +28,46 @@ #include #include -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "line_info.h" #include "groupchat.h" +#include "settings.h" + +extern struct user_settings *user_settings; void line_info_init(struct history *hst) { hst->line_root = malloc(sizeof(struct line_info)); - if (hst->line_root == NULL) { - endwin(); - fprintf(stderr, "malloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (hst->line_root == NULL) + exit_toxic_err("failed in line_info_init", FATALERR_MEMORY); memset(hst->line_root, 0, sizeof(struct line_info)); hst->line_start = hst->line_root; hst->line_end = hst->line_start; + hst->queue_sz = 0; } -/* resets line_start when scroll mode is disabled */ -static void line_info_reset_start(struct history *hst) +/* resets line_start */ +static void line_info_reset_start(ToxWindow *self, struct history *hst) { + int y2, x2; + getmaxyx(self->window, y2, x2); + struct line_info *line = hst->line_end; - uint32_t start_id = hst->start_id; - while (line) { - if (line->id == start_id) { - hst->line_start = line; - break; - } + uint16_t lncnt = 0; + int side_offst = self->is_groupchat ? SIDEBAR_WIDTH : 0; + int top_offst = self->is_chat ? 2 : 0; + int max_y = (y2 - CHATBOX_HEIGHT - top_offst); + while (line->prev && lncnt < max_y) { + lncnt += (1 + line->newlines) +( line->len / (x2 - side_offst)); line = line->prev; } -} -void line_info_toggle_scroll(ToxWindow *self, bool scroll) -{ - WINDOW *win = self->chatwin->history; - struct history *hst = self->chatwin->hst; - - if (scroll) { - hst->scroll_mode = true; - scrollok(win, 0); - curs_set(0); - } else { - hst->scroll_mode = false; - scrollok(win, 1); - curs_set(1); - line_info_reset_start(hst); - } + hst->line_start = line; } void line_info_cleanup(struct history *hst) @@ -107,75 +97,117 @@ static void line_info_root_fwd(struct history *hst) hst->line_root = tmp; } +/* adds a line_info line to queue */ +static void line_info_add_queue(struct history *hst, struct line_info *line) +{ + if (hst->queue_sz >= MAX_QUEUE) + return; + + hst->queue[hst->queue_sz++] = line; +} + +/* returns ptr to queue item 0 and removes it from queue */ +static struct line_info *line_info_ret_queue(struct history *hst) +{ + if (hst->queue_sz <= 0) + return NULL; + + struct line_info *ret = hst->queue[0]; + + int i; + + for (i = 0; i < hst->queue_sz; ++i) + hst->queue[i] = hst->queue[i + 1]; + + --hst->queue_sz; + + return ret; +} + +/* creates new line_info line and puts it in the queue */ void line_info_add(ToxWindow *self, uint8_t *tmstmp, uint8_t *name1, uint8_t *name2, uint8_t *msg, uint8_t type, uint8_t bold, uint8_t colour) { struct history *hst = self->chatwin->hst; struct line_info *new_line = malloc(sizeof(struct line_info)); - if (new_line == NULL) { - endwin(); - fprintf(stderr, "malloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (new_line == NULL) + exit_toxic_err("failed in line_info_add", FATALERR_MEMORY); memset(new_line, 0, sizeof(struct line_info)); - int len = 1; /* there will always be a newline */ + int len = 1; /* there will always be a newline */ /* for type-specific formatting in print function */ switch (type) { case ACTION: + case CONNECTION: len += 3; break; + case SYS_MSG: + break; + + case PROMPT: + ++len; + break; + default: len += 2; break; } if (msg) { - strcpy(new_line->msg, msg); - len += strlen(msg); + snprintf(new_line->msg, sizeof(new_line->msg), "%s", msg); + len += strlen(new_line->msg); + + int i; + + for (i = 0; msg[i]; ++i) { + if (msg[i] == '\n') + ++new_line->newlines; + } } if (tmstmp) { - strcpy(new_line->timestamp, tmstmp); - len += strlen(tmstmp); + snprintf(new_line->timestamp, sizeof(new_line->timestamp), "%s", tmstmp); + len += strlen(new_line->timestamp); } if (name1) { - strcpy(new_line->name1, name1); - len += strlen(name1); + snprintf(new_line->name1, sizeof(new_line->name1), "%s", name1); + len += strlen(new_line->name1); } if (name2) { - strcpy(new_line->name2, name2); - len += strlen(name2); + snprintf(new_line->name2, sizeof(new_line->name2), "%s", name2); + len += strlen(new_line->name2); } new_line->len = len; new_line->type = type; new_line->bold = bold; new_line->colour = colour; - new_line->id = hst->line_end->id + 1; - new_line->prev = hst->line_end; - hst->line_end->next = new_line; - hst->line_end = new_line; + line_info_add_queue(hst, new_line); +} - if (++hst->line_items > MAX_HISTORY) { - --hst->line_items; +/* adds a single queue item to hst if possible. only called once per call to line_info_print() */ +static void line_info_check_queue(ToxWindow *self) +{ + struct history *hst = self->chatwin->hst; + struct line_info *line = line_info_ret_queue(hst); + + if (line == NULL) + return; + + if (hst->start_id > user_settings->history_size) line_info_root_fwd(hst); - } - int newlines = 0; - int i; - - for (i = 0; msg[i]; ++i) { - if (msg[i] == '\n') - ++newlines; - } + line->id = hst->line_end->id + 1; + line->prev = hst->line_end; + hst->line_end->next = line; + hst->line_end = line; int y, y2, x, x2; getmaxyx(self->window, y2, x2); @@ -185,20 +217,15 @@ void line_info_add(ToxWindow *self, uint8_t *tmstmp, uint8_t *name1, uint8_t *na return; int offst = self->is_groupchat ? SIDEBAR_WIDTH : 0; /* offset width of groupchat sidebar */ - int lines = (1 + newlines + (len / (x2 - offst))); - hst->queue_lns += lines; - ++hst->queue; - + int lines = 1 + line->newlines + (line->len / (x2 - offst)); int max_y = self->is_prompt ? y2 : y2 - CHATBOX_HEIGHT; /* move line_start forward proportionate to the number of new lines */ - if (y + hst->queue_lns - hst->queue >= max_y) { - while (lines > 0) { + if (y + lines - 1 >= max_y) { + while (lines > 0 && hst->line_start->next) { + lines -= 1 + hst->line_start->next->newlines + (hst->line_start->next->len / (x2 - offst)); + hst->line_start = hst->line_start->next; ++hst->start_id; - lines -= (1 + hst->line_start->len / (x2 - offst)); - - if (!hst->scroll_mode && hst->line_start->next) - hst->line_start = hst->line_start->next; } } } @@ -206,17 +233,22 @@ void line_info_add(ToxWindow *self, uint8_t *tmstmp, uint8_t *name1, uint8_t *na void line_info_print(ToxWindow *self) { ChatContext *ctx = self->chatwin; + + if (ctx == NULL) + return; + + struct history *hst = ctx->hst; + + /* Only allow one new item to be added to chat window per call to this function */ + line_info_check_queue(self); + WINDOW *win = ctx->history; - - ctx->hst->queue = 0; - ctx->hst->queue_lns = 0; - wclear(win); int y2, x2; getmaxyx(self->window, y2, x2); if (self->is_prompt) - y2 = MAX_HISTORY; /* temporary fix to make prompt scroll */ + y2 = user_settings->history_size; /* temporary fix to make prompt scroll */ if (x2 <= SIDEBAR_WIDTH) return; @@ -226,7 +258,7 @@ void line_info_print(ToxWindow *self) else wmove(win, 2, 0); - struct line_info *line = ctx->hst->line_start->next; + struct line_info *line = hst->line_start->next; int offst = self->is_groupchat ? SIDEBAR_WIDTH : 0; int numlines = 0; @@ -342,6 +374,10 @@ void line_info_print(ToxWindow *self) line = line->next; } + + /* keep calling until queue is empty */ + if (hst->queue_sz > 0) + line_info_print(self); } void line_info_set(ToxWindow *self, uint32_t id, uint8_t *msg) @@ -399,46 +435,43 @@ static void line_info_page_down(ToxWindow *self, struct history *hst) hst->line_start = hst->line_start->next; } -void line_info_onKey(ToxWindow *self, wint_t key) +bool line_info_onKey(ToxWindow *self, wint_t key) { struct history *hst = self->chatwin->hst; + bool match = true; switch (key) { - case KEY_PPAGE: + /* TODO: Find good key bindings for all this stuff */ + case T_KEY_C_F: line_info_page_up(self, hst); break; - case KEY_NPAGE: + case T_KEY_C_V: line_info_page_down(self, hst); - break; + break; - case KEY_UP: + case KEY_PPAGE: line_info_scroll_up(hst); break; - case KEY_DOWN: + case KEY_NPAGE: line_info_scroll_down(hst); break; - case KEY_HOME: + /* case ?: line_info_goto_root(hst); - break; + break; */ - case KEY_END: - line_info_reset_start(hst); + case T_KEY_C_H: + line_info_reset_start(self, hst); + break; + + default: + match = false; break; } -} -void line_info_onDraw(ToxWindow *self) -{ - ChatContext *ctx = self->chatwin; - - wattron(ctx->linewin, A_BOLD | COLOR_PAIR(BLUE)); - mvwprintw(ctx->linewin, 1, 0, "Scroll mode:\n"); - wattroff(ctx->linewin, A_BOLD | COLOR_PAIR(BLUE)); - mvwprintw(ctx->linewin, 1, 13, "Use up/down arrows, page up/page down, and home/end to navigate.\n" - " ESC to exit.\n"); + return match; } void line_info_clear(struct history *hst) diff --git a/src/line_info.h b/src/line_info.h index be0e1e3..b04f7cc 100644 --- a/src/line_info.h +++ b/src/line_info.h @@ -20,7 +20,15 @@ * */ -#define MAX_HISTORY 700 +#ifndef _line_info_h +#define _line_info_h + +#include "windows.h" +#include "toxic.h" + +#define MAX_HISTORY 10000 +#define MIN_HISTORY 20 +#define MAX_QUEUE 32 enum { SYS_MSG, @@ -42,6 +50,7 @@ struct line_info { uint8_t colour; uint32_t id; uint16_t len; /* combined len of all strings */ + uint8_t newlines; struct line_info *prev; struct line_info *next; @@ -53,15 +62,12 @@ struct history { struct line_info *line_start; /* the first line we want to start printing at */ struct line_info *line_end; uint32_t start_id; /* keeps track of where line_start should be when at bottom of history */ - uint32_t line_items; - bool scroll_mode; - /* keeps track of lines added between window refreshes */ - uint32_t queue; - uint32_t queue_lns; + struct line_info *queue[MAX_QUEUE]; + int queue_sz; }; -/* adds a line to history (also moves line_start and/or line_root forward if necessary) */ +/* creates new line_info line and puts it in the queue */ void line_info_add(ToxWindow *self, uint8_t *tmstmp, uint8_t *name1, uint8_t *name2, uint8_t *msg, uint8_t type, uint8_t bold, uint8_t colour); @@ -71,9 +77,6 @@ void line_info_print(ToxWindow *self); /* frees all history lines */ void line_info_cleanup(struct history *hst); -/* Toggles scroll mode for current window */ -void line_info_toggle_scroll(ToxWindow *self, bool scroll); - /* clears the screen (does not delete anything) */ void line_info_clear(struct history *hst); @@ -81,5 +84,6 @@ void line_info_clear(struct history *hst); void line_info_set(ToxWindow *self, uint32_t id, uint8_t *msg); void line_info_init(struct history *hst); -void line_info_onKey(ToxWindow *self, wint_t key); -void line_info_onDraw(ToxWindow *self); +bool line_info_onKey(ToxWindow *self, wint_t key); /* returns true if key is a match */ + +#endif /* #define _line_info_h */ diff --git a/src/log.c b/src/log.c index 63f3d40..d965895 100644 --- a/src/log.c +++ b/src/log.c @@ -29,7 +29,8 @@ #include #include "configdir.h" -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "misc_tools.h" #include "log.h" #include "settings.h" diff --git a/src/log.h b/src/log.h index 9473eeb..0cb1ba2 100644 --- a/src/log.h +++ b/src/log.h @@ -20,6 +20,9 @@ * */ +#ifndef _log_h +#define _log_h + #define LOG_FLUSH_LIMIT 2 /* limits calls to fflush(logfile) to a max of one per LOG_FLUSH_LIMIT seconds */ struct chatlog { @@ -40,3 +43,5 @@ void log_enable(uint8_t *name, uint8_t *key, struct chatlog *log); /* disables logging for specified log and closes file */ void log_disable(struct chatlog *log); + +#endif /* #define _log_h */ diff --git a/src/misc_tools.c b/src/misc_tools.c index 928c6b4..15641b1 100644 --- a/src/misc_tools.c +++ b/src/misc_tools.c @@ -29,7 +29,8 @@ #include #include -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "misc_tools.h" #include "settings.h" @@ -63,11 +64,10 @@ void get_time_str(uint8_t *buf) strftime(buf, TIME_STR_SIZE, t, get_time()); } -/* XXX: FIX */ -unsigned char *hex_string_to_bin(char hex_string[]) +char *hex_string_to_bin(const char *hex_string) { size_t len = strlen(hex_string); - unsigned char *val = malloc(len); + char *val = malloc(len); if (val == NULL) { endwin(); @@ -75,11 +75,10 @@ unsigned char *hex_string_to_bin(char hex_string[]) exit(EXIT_FAILURE); } - char *pos = hex_string; size_t i; - for (i = 0; i < len; ++i, pos += 2) - sscanf(pos, "%2hhx", &val[i]); + for (i = 0; i < len; ++i, hex_string += 2) + sscanf(hex_string, "%2hhx", &val[i]); return val; } @@ -227,30 +226,35 @@ int valid_nick(uint8_t *nick) /* Moves cursor to the end of the line in given window */ void mv_curs_end(WINDOW *w, size_t len, int max_y, int max_x) { - int end_y = (len / max_x) + (max_y - CURS_Y_OFFSET); - int end_x = len % max_x; - wmove(w, end_y, end_x); + int new_x = len < max_x ? len : len % max_x; + wmove(w, max_y - CURS_Y_OFFSET, new_x); } /* gets base file name from path or original file name if no path is supplied */ -void get_file_name(uint8_t *pathname, uint8_t *namebuf) +void get_file_name(uint8_t *namebuf, uint8_t *pathname) { int idx = strlen(pathname) - 1; while (idx >= 0 && pathname[idx] == '/') pathname[idx--] = '\0'; - uint8_t *filename = strrchr(pathname, '/'); /* Try unix style paths */ + uint8_t *filename = strrchr(pathname, '/'); if (filename != NULL) { if (!strlen(++filename)) filename = pathname; } else { - filename = strrchr(pathname, '\\'); /* Try windows style paths */ - - if (filename == NULL) - filename = pathname; + filename = pathname; } snprintf(namebuf, MAX_STR_SIZE, "%s", filename); } + +/* converts str to all lowercase */ +void str_to_lower(uint8_t *str) +{ + int i; + + for (i = 0; str[i]; ++i) + str[i] = tolower(str[i]); +} diff --git a/src/misc_tools.h b/src/misc_tools.h index b5b2a93..87908f7 100644 --- a/src/misc_tools.h +++ b/src/misc_tools.h @@ -19,12 +19,22 @@ * along with Toxic. If not, see . * */ +#ifndef _misc_tools_h +#define _misc_tools_h +#include "windows.h" +#include "toxic.h" + +#ifndef MIN #define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#endif + +#ifndef MAX #define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif /* convert a hex string to binary */ -unsigned char *hex_string_to_bin(char hex_string[]); +char *hex_string_to_bin(const char *hex_string); /* get the current unix time */ uint64_t get_unix_time(void); @@ -63,9 +73,10 @@ void alert_window(ToxWindow *self, int type, bool is_beep); /* case-insensitive string compare function for use with qsort */ int qsort_strcasecmp_hlpr(const void *nick1, const void *nick2); -/* Returns true if nick is valid. A valid toxic nick: +/* Returns 1 if nick is valid, 0 if not. A valid toxic nick: - cannot be empty - cannot start with a space + - must not contain a forward slash (for logfile naming purposes) - must not contain contiguous spaces */ int valid_nick(uint8_t *nick); @@ -73,4 +84,9 @@ int valid_nick(uint8_t *nick); void mv_curs_end(WINDOW *w, size_t len, int max_y, int max_x); /* gets base file name from path or original file name if no path is supplied */ -void get_file_name(uint8_t *pathname, uint8_t *namebuf); +void get_file_name(uint8_t *namebuf, uint8_t *pathname); + +/* converts str to all lowercase */ +void str_to_lower(uint8_t *str); + +#endif /* #define _misc_tools_h */ diff --git a/src/prompt.c b/src/prompt.c index 1a9f6cd..3609574 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -27,7 +27,8 @@ #include #include -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "prompt.h" #include "execute.h" #include "misc_tools.h" @@ -74,7 +75,7 @@ void prompt_update_nick(ToxWindow *prompt, uint8_t *nick, uint16_t len) { StatusBar *statusbar = prompt->stb; snprintf(statusbar->nick, sizeof(statusbar->nick), "%s", nick); - statusbar->nick_len = len; + statusbar->nick_len = strlen(statusbar->nick); } /* Updates own statusmessage in prompt statusbar */ @@ -82,7 +83,7 @@ void prompt_update_statusmessage(ToxWindow *prompt, uint8_t *statusmsg, uint16_t { StatusBar *statusbar = prompt->stb; snprintf(statusbar->statusmsg, sizeof(statusbar->statusmsg), "%s", statusmsg); - statusbar->statusmsg_len = len; + statusbar->statusmsg_len = strlen(statusbar->statusmsg); } /* Updates own status in prompt statusbar */ @@ -101,7 +102,7 @@ void prompt_update_connectionstatus(ToxWindow *prompt, bool is_connected) /* Adds friend request to pending friend requests. Returns request number on success, -1 if queue is full or other error. */ -static int add_friend_request(uint8_t *public_key) +static int add_friend_request(const uint8_t *public_key) { if (num_frnd_requests >= MAX_FRIENDS_NUM) return -1; @@ -130,31 +131,16 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) getyx(ctx->history, y, x); getmaxyx(ctx->history, y2, x2); - /* TODO this is buggy */ - /* ESC key: Toggle history scroll mode */ - /* - if (key == T_KEY_ESC) { - bool scroll = ctx->hst->scroll_mode ? false : true; - line_info_toggle_scroll(self, scroll); - } - */ - - /* If we're in scroll mode ignore rest of function */ - if (ctx->hst->scroll_mode) { - line_info_onKey(self, key); - return; - } - if (ltr) { if (ctx->len < (MAX_STR_SIZE - 1)) { - add_char_to_buf(ctx->line, &ctx->pos, &ctx->len, key); + add_char_to_buf(ctx, key); } } else { /* if (!ltr) */ /* BACKSPACE key: Remove one character from line */ if (key == 0x107 || key == 0x8 || key == 0x7f) { if (ctx->pos > 0) { - del_char_buf_bck(ctx->line, &ctx->pos, &ctx->len); + del_char_buf_bck(ctx); wmove(ctx->history, y, x - 1); /* not necessary but fixes a display glitch */ } else { beep(); @@ -163,7 +149,7 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_DC) { /* DEL key: Remove character at pos */ if (ctx->pos != ctx->len) { - del_char_buf_frnt(ctx->line, &ctx->pos, &ctx->len); + del_char_buf_frnt(ctx); } else { beep(); } @@ -173,7 +159,7 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) if (ctx->pos > 0) { wmove(ctx->history, ctx->orig_y, X_OFST); wclrtobot(ctx->history); - discard_buf(ctx->line, &ctx->pos, &ctx->len); + discard_buf(ctx); } else { beep(); } @@ -181,7 +167,7 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == T_KEY_KILL) { /* CTRL-K: Delete entire line in front of pos */ if (ctx->len != ctx->pos) - kill_buf(ctx->line, &ctx->pos, &ctx->len); + kill_buf(ctx); else beep(); } @@ -212,8 +198,7 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_UP) { /* fetches previous item in history */ wmove(ctx->history, ctx->orig_y, X_OFST); - fetch_hist_item(ctx->line, &ctx->pos, &ctx->len, ctx->ln_history, ctx->hst_tot, - &ctx->hst_pos, MOVE_UP); + fetch_hist_item(ctx, MOVE_UP); /* adjust line y origin appropriately when window scrolls down */ if (ctx->at_bottom && ctx->len >= x2 - X_OFST) { @@ -233,14 +218,12 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) else if (key == KEY_DOWN) { /* fetches next item in history */ wmove(ctx->history, ctx->orig_y, X_OFST); - fetch_hist_item(ctx->line, &ctx->pos, &ctx->len, ctx->ln_history, ctx->hst_tot, - &ctx->hst_pos, MOVE_DOWN); + fetch_hist_item(ctx, MOVE_DOWN); } else if (key == '\t') { /* TAB key: completes command */ if (ctx->len > 1 && ctx->line[0] == '/') { - if (complete_line(ctx->line, &ctx->pos, &ctx->len, glob_cmd_list, AC_NUM_GLOB_COMMANDS, - MAX_CMDNAME_SIZE) == -1) + if (complete_line(ctx, glob_cmd_list, AC_NUM_GLOB_COMMANDS, MAX_CMDNAME_SIZE) == -1) beep(); } else { beep(); @@ -249,6 +232,8 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) /* RETURN key: execute command */ else if (key == '\n') { + rm_trailing_spaces_buf(ctx); + wprintw(ctx->history, "\n"); uint8_t line[MAX_STR_SIZE] = {0}; @@ -256,11 +241,11 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) memset(&line, 0, sizeof(line)); if (!string_is_empty(line)) - add_line_to_hist(ctx->line, ctx->len, ctx->ln_history, &ctx->hst_tot, &ctx->hst_pos); + add_line_to_hist(ctx); line_info_add(self, NULL, NULL, NULL, line, PROMPT, 0, 0); execute(ctx->history, self, m, line, GLOBAL_COMMAND_MODE); - reset_buf(ctx->line, &ctx->pos, &ctx->len); + reset_buf(ctx); } } } @@ -273,10 +258,8 @@ static void prompt_onDraw(ToxWindow *self, Tox *m) getyx(ctx->history, y, x); getmaxyx(ctx->history, y2, x2); - if (!ctx->hst->scroll_mode) { - curs_set(1); - scrollok(ctx->history, 1); - } + curs_set(1); + scrollok(ctx->history, 1); line_info_print(self); @@ -293,7 +276,7 @@ static void prompt_onDraw(ToxWindow *self, Tox *m) uint8_t line[MAX_STR_SIZE]; if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) - reset_buf(ctx->line, &ctx->pos, &ctx->len); + reset_buf(ctx); else mvwprintw(ctx->history, ctx->orig_y, X_OFST, line); @@ -403,10 +386,9 @@ static void prompt_onConnectionChange(ToxWindow *self, Tox *m, int32_t friendnum } } -static void prompt_onFriendRequest(ToxWindow *self, Tox *m, uint8_t *key, uint8_t *data, uint16_t length) +static void prompt_onFriendRequest(ToxWindow *self, Tox *m, const uint8_t *key, const uint8_t *data, + uint16_t length) { - data[length] = '\0'; - ChatContext *ctx = self->chatwin; uint8_t timefrmt[TIME_STR_SIZE]; @@ -486,11 +468,8 @@ static void prompt_onInit(ToxWindow *self, Tox *m) ctx->log = malloc(sizeof(struct chatlog)); ctx->hst = malloc(sizeof(struct history)); - if (ctx->log == NULL || ctx->hst == NULL) { - endwin(); - fprintf(stderr, "malloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (ctx->log == NULL || ctx->hst == NULL) + exit_toxic_err("failed in prompt_onInit", FATALERR_MEMORY); memset(ctx->log, 0, sizeof(struct chatlog)); memset(ctx->hst, 0, sizeof(struct history)); @@ -526,14 +505,11 @@ ToxWindow new_prompt(void) ChatContext *chatwin = calloc(1, sizeof(ChatContext)); StatusBar *stb = calloc(1, sizeof(StatusBar)); - if (stb != NULL && chatwin != NULL) { - ret.chatwin = chatwin; - ret.stb = stb; - } else { - endwin(); - fprintf(stderr, "calloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (stb == NULL || chatwin == NULL) + exit_toxic_err("failed in new_prompt", FATALERR_MEMORY); + + ret.chatwin = chatwin; + ret.stb = stb; return ret; } diff --git a/src/prompt.h b/src/prompt.h index b120a65..405a3ec 100644 --- a/src/prompt.h +++ b/src/prompt.h @@ -23,8 +23,10 @@ #ifndef PROMPT_H_UZYGWFFL #define PROMPT_H_UZYGWFFL -#define X_OFST 2 /* offset to account for prompt char */ +#include "toxic.h" +#include "windows.h" +#define X_OFST 2 /* offset to account for prompt char */ #ifdef _SUPPORT_AUDIO #define AC_NUM_GLOB_COMMANDS 17 diff --git a/src/settings.c b/src/settings.c index 81d5bc9..8964723 100644 --- a/src/settings.c +++ b/src/settings.c @@ -20,95 +20,162 @@ * */ - #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include -#include + +#include "toxic.h" +#include "windows.h" +#include "configdir.h" #ifdef _SUPPORT_AUDIO #include "device.h" #endif /* _SUPPORT_AUDIO */ -#include "toxic_windows.h" -#include "configdir.h" #include "settings.h" +#include "line_info.h" +static void uset_autolog(struct user_settings *s, const char *val); +static void uset_time(struct user_settings *s, const char *val); +static void uset_alerts(struct user_settings *s, const char *val); +static void uset_colours(struct user_settings *s, const char *val); +static void uset_hst_size(struct user_settings *s, const char *val); +static void uset_dwnld_path(struct user_settings *s, const char *val); -static void uset_autolog(struct user_settings *s, int val); -static void uset_time(struct user_settings *s, int val); -static void uset_alerts(struct user_settings *s, int val); -static void uset_colours(struct user_settings *s, int val); -static void uset_ain_dev(struct user_settings *s, int val); -static void uset_aout_dev(struct user_settings *s, int val); +#ifdef _SUPPORT_AUDIO +static void uset_ain_dev(struct user_settings *s, const char *val); +static void uset_aout_dev(struct user_settings *s, const char *val); +#endif struct { - const char *name; - void (*func)(struct user_settings *s, int val); + const char *key; + void (*func)(struct user_settings *s, const char *val); } user_settings_list[] = { { "autolog", uset_autolog }, { "time", uset_time }, { "disable_alerts", uset_alerts }, { "colour_theme", uset_colours }, - + { "history_size", uset_hst_size }, + { "download_path", uset_dwnld_path }, + #ifdef _SUPPORT_AUDIO { "audio_in_dev", uset_ain_dev }, { "audio_out_dev", uset_aout_dev }, #endif }; -static void uset_autolog(struct user_settings *s, int val) +static void uset_autolog(struct user_settings *s, const char *val) { + int n = atoi(val); + /* default off if invalid value */ - s->autolog = val == AUTOLOG_ON ? AUTOLOG_ON : AUTOLOG_OFF; + s->autolog = n == AUTOLOG_ON ? AUTOLOG_ON : AUTOLOG_OFF; } -static void uset_time(struct user_settings *s, int val) +static void uset_time(struct user_settings *s, const char *val) { + int n = atoi(val); + /* default to 24 hour time if invalid value */ - s->time = val == TIME_12 ? TIME_12 : TIME_24; + s->time = n == TIME_12 ? TIME_12 : TIME_24; } -static void uset_alerts(struct user_settings *s, int val) +static void uset_alerts(struct user_settings *s, const char *val) { + int n = atoi(val); + /* alerts default on if invalid value */ - s->alerts = val == ALERTS_DISABLED ? ALERTS_DISABLED : ALERTS_ENABLED; + s->alerts = n == ALERTS_DISABLED ? ALERTS_DISABLED : ALERTS_ENABLED; } -static void uset_colours(struct user_settings *s, int val) +static void uset_colours(struct user_settings *s, const char *val) { + int n = atoi(val); + /* use default toxic colours if invalid value */ - s->colour_theme = val == NATIVE_COLS ? NATIVE_COLS : DFLT_COLS; + s->colour_theme = n == NATIVE_COLS ? NATIVE_COLS : DFLT_COLS; } #ifdef _SUPPORT_AUDIO -static void uset_ain_dev(struct user_settings *s, int val) +static void uset_ain_dev(struct user_settings *s, const char *val) { - if (val < 0 || val > MAX_DEVICES) - val = 0; - s->audio_in_dev = val; + int n = atoi(val); + + if (n < 0 || n > MAX_DEVICES) + n = (long int) 0; + + s->audio_in_dev = (long int) n; } -static void uset_aout_dev(struct user_settings *s, int val) +static void uset_aout_dev(struct user_settings *s, const char *val) { - if (val < 0 || val > MAX_DEVICES) - val = 0; + int n = atoi(val); - s->audio_out_dev = val; + if (n < 0 || n > MAX_DEVICES) + n = (long int) 0; + + s->audio_out_dev = (long int) n; } #endif /* _SUPPORT_AUDIO */ +static void uset_hst_size(struct user_settings *s, const char *val) +{ + int n = atoi(val); + + /* if val is out of range use default history size */ + s->history_size = (n > MAX_HISTORY || n < MIN_HISTORY) ? DFLT_HST_SIZE : n; +} + +static void uset_dwnld_path(struct user_settings *s, const char *val) +{ + memset(s->download_path, 0, sizeof(s->download_path)); + + if (val == NULL) + return; + + int len = strlen(val); + + if (len >= sizeof(s->download_path) - 2) /* leave room for null and '/' */ + return; + + FILE *fp = fopen(val, "r"); + + if (fp == NULL) + return; + + strcpy(s->download_path, val); + + if (val[len - 1] != '/') + strcat(s->download_path, "/"); +} + +static void set_default_settings(struct user_settings *s) +{ + /* see settings_values enum in settings.h for defaults */ + uset_autolog(s, "0"); + uset_time(s, "24"); + uset_alerts(s, "0"); + uset_colours(s, "0"); + uset_hst_size(s, "700"); + uset_dwnld_path(s, NULL); + +#ifdef _SUPPORT_AUDIO + uset_ain_dev(s, "0"); + uset_aout_dev(s, "0"); +#endif +} + int settings_load(struct user_settings *s, char *path) { char *user_config_dir = get_user_config_dir(); FILE *fp = NULL; char dflt_path[MAX_STR_SIZE]; - + if (path) { fp = fopen(path, "r"); } else { @@ -118,34 +185,37 @@ int settings_load(struct user_settings *s, char *path) free(user_config_dir); + set_default_settings(s); + if (fp == NULL && !path) { if ((fp = fopen(dflt_path, "w")) == NULL) return -1; } else if (fp == NULL && path) { return -1; } - + char line[MAX_STR_SIZE]; while (fgets(line, sizeof(line), fp)) { if (line[0] == '#' || !line[0]) continue; - + const char *key = strtok(line, ":"); const char *val = strtok(NULL, ";"); - + if (key == NULL || val == NULL) continue; - + int i; + for (i = 0; i < NUM_SETTINGS; ++i) { - if (strcmp(user_settings_list[i].name, key) == 0) { - (user_settings_list[i].func)(s, atoi(val)); + if (strcmp(user_settings_list[i].key, key) == 0) { + (user_settings_list[i].func)(s, val); break; } } - } - + } + fclose(fp); return 0; } diff --git a/src/settings.h b/src/settings.h index 68a0e18..3400466 100644 --- a/src/settings.h +++ b/src/settings.h @@ -20,10 +20,15 @@ * */ +#ifndef _settings_h +#define _settings_h + +#include "toxic.h" + #ifdef _SUPPORT_AUDIO - #define NUM_SETTINGS 6 + #define NUM_SETTINGS 8 #else - #define NUM_SETTINGS 4 + #define NUM_SETTINGS 6 #endif /* _SUPPORT_AUDIO */ /* holds user setting values */ @@ -32,11 +37,13 @@ struct user_settings { int alerts; /* boolean */ int time; /* 12 or 24 */ int colour_theme; /* boolean (0 for default toxic colours) */ - + int history_size; /* int between MIN_HISTORY and MAX_HISTORY */ + char download_path[MAX_STR_SIZE]; + #ifdef _SUPPORT_AUDIO - int audio_in_dev; - int audio_out_dev; -#endif /* _SUPPORT_AUDIO */ + long int audio_in_dev; + long int audio_out_dev; +#endif }; enum { @@ -51,6 +58,10 @@ enum { NATIVE_COLS = 1, DFLT_COLS = 0, -}; + + DFLT_HST_SIZE = 700, +} settings_values; int settings_load(struct user_settings *s, char *path); + +#endif /* #define _settings_h */ diff --git a/src/main.c b/src/toxic.c similarity index 60% rename from src/main.c rename to src/toxic.c index ccd2f14..0eeefe2 100644 --- a/src/main.c +++ b/src/toxic.c @@ -39,31 +39,24 @@ #include #include #include -#include - -#ifdef _WIN32 -#include -#include -#include -#else +#include #include #include #include #include #include -#endif #include #include "configdir.h" -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "friendlist.h" #include "prompt.h" #include "misc_tools.h" #include "file_senders.h" #include "line_info.h" #include "settings.h" -#include "global_commands.h" #ifdef _SUPPORT_AUDIO #include "audio_call.h" @@ -81,30 +74,64 @@ ToxAv *av; char *DATA_FILE = NULL; ToxWindow *prompt = NULL; -static int f_loadfromfile; /* 1 if we want to load from/save the data file, 0 otherwise */ +struct arg_opts { + int ignore_data_file; + int use_ipv4; + char config_path[MAX_STR_SIZE]; + char nodes_path[MAX_STR_SIZE]; +} arg_opts; struct _Winthread Winthread; struct user_settings *user_settings = NULL; -void on_window_resize(int sig) +static void ignore_SIGINT(int sig) { - refresh(); - clear(); + return; +} + +void exit_toxic_success(Tox *m) +{ + store_data(m, DATA_FILE); + close_all_file_senders(); + kill_all_windows(); + log_disable(prompt->chatwin->log); + line_info_cleanup(prompt->chatwin->hst); + free(DATA_FILE); + free(prompt->stb); + free(prompt->chatwin->log); + free(prompt->chatwin->hst); + free(prompt->chatwin); + free(user_settings); +#ifdef _SUPPORT_AUDIO + terminate_audio(); +#endif /* _SUPPORT_AUDIO */ + tox_kill(m); + endwin(); + fprintf(stderr, "Toxic session ended gracefully.\n"); + exit(EXIT_SUCCESS); +} + +void exit_toxic_err(const char *errmsg, int errcode) +{ + if (errmsg == NULL) + errmsg = "No error message"; + + endwin(); + fprintf(stderr, "Toxic session aborted with error code %d (%s)\n", errcode, errmsg); + exit(EXIT_FAILURE); } static void init_term(void) { - /* Setup terminal */ signal(SIGWINCH, on_window_resize); + #if HAVE_WIDECHAR - if (setlocale(LC_ALL, "") == NULL) { - fprintf(stderr, "Could not set your locale, plese check your locale settings or" - "disable wide char support\n"); - exit(EXIT_FAILURE); - } - + if (setlocale(LC_ALL, "") == NULL) + exit_toxic_err("Could not set your locale, please check your locale settings or" + "disable wide char support", FATALERR_LOCALE_SET); #endif + initscr(); cbreak(); keypad(stdscr, 1); @@ -149,6 +176,9 @@ static Tox *init_tox(int ipv4) m = tox_new(0); } + if (ipv4) + fprintf(stderr, "Forcing IPv4 connection\n"); + if (m == NULL) return NULL; @@ -170,11 +200,9 @@ static Tox *init_tox(int ipv4) tox_callback_file_data(m, on_file_data, NULL); #ifdef __linux__ - tox_set_name(m, (uint8_t *) "Cool guy", strlen("Cool guy")); + tox_set_name(m, (uint8_t *) "Cool dude", strlen("Cool dude")); #elif defined(__FreeBSD__) - tox_set_name(m, (uint8_t *) "Very cool guy", strlen("Very cool guy")); -#elif defined(_WIN32) - tox_set_name(m, (uint8_t *) "I should buy a Mac", strlen("I should install buy a Mac")); + tox_set_name(m, (uint8_t *) "Nerd", strlen("Nerd")); #elif defined(__APPLE__) tox_set_name(m, (uint8_t *) "Hipster", strlen("Hipster")); /* This used to users of other Unixes are hipsters */ #else @@ -189,10 +217,12 @@ static Tox *init_tox(int ipv4) #define MAXNODES 50 #define NODELEN (MAXLINE - TOX_CLIENT_ID_SIZE - 7) -static int linecnt = 0; -static char nodes[MAXNODES][NODELEN]; -static uint16_t ports[MAXNODES]; -static uint8_t keys[MAXNODES][TOX_CLIENT_ID_SIZE]; +static struct _toxNodes { + int lines; + char nodes[MAXNODES][NODELEN]; + uint16_t ports[MAXNODES]; + uint8_t keys[MAXNODES][TOX_CLIENT_ID_SIZE]; +} toxNodes; static int nodelist_load(char *filename) { @@ -206,29 +236,29 @@ static int nodelist_load(char *filename) char line[MAXLINE]; - while (fgets(line, sizeof(line), fp) && linecnt < MAXNODES) { + while (fgets(line, sizeof(line), fp) && toxNodes.lines < MAXNODES) { if (strlen(line) > MINLINE) { - char *name = strtok(line, " "); - char *port = strtok(NULL, " "); - char *key_ascii = strtok(NULL, " "); + const char *name = strtok(line, " "); + const char *port = strtok(NULL, " "); + const char *key_ascii = strtok(NULL, " "); /* invalid line */ if (name == NULL || port == NULL || key_ascii == NULL) continue; - strncpy(nodes[linecnt], name, NODELEN); - nodes[linecnt][NODELEN - 1] = 0; - ports[linecnt] = htons(atoi(port)); + snprintf(toxNodes.nodes[toxNodes.lines], sizeof(toxNodes.nodes[toxNodes.lines]), "%s", name); + toxNodes.nodes[toxNodes.lines][NODELEN - 1] = 0; + toxNodes.ports[toxNodes.lines] = htons(atoi(port)); uint8_t *key_binary = hex_string_to_bin(key_ascii); - memcpy(keys[linecnt], key_binary, TOX_CLIENT_ID_SIZE); + memcpy(toxNodes.keys[toxNodes.lines], key_binary, TOX_CLIENT_ID_SIZE); free(key_binary); - linecnt++; + toxNodes.lines++; } } - if (linecnt < 1) { + if (toxNodes.lines < 1) { fclose(fp); return 2; } @@ -239,8 +269,8 @@ static int nodelist_load(char *filename) int init_connection_helper(Tox *m, int line) { - return tox_bootstrap_from_address(m, nodes[line], TOX_ENABLE_IPV6_DEFAULT, - ports[line], keys[line]); + return tox_bootstrap_from_address(m, toxNodes.nodes[line], TOX_ENABLE_IPV6_DEFAULT, + toxNodes.ports[line], toxNodes.keys[line]); } /* Connects to a random DHT node listed in the DHTnodes file @@ -257,8 +287,8 @@ static bool srvlist_loaded = false; int init_connection(Tox *m) { - if (linecnt > 0) /* already loaded nodelist */ - return init_connection_helper(m, rand() % linecnt) ? 0 : 3; + if (toxNodes.lines > 0) /* already loaded nodelist */ + return init_connection_helper(m, rand() % toxNodes.lines) ? 0 : 3; /* only once: * - load the nodelist @@ -266,17 +296,22 @@ int init_connection(Tox *m) */ if (!srvlist_loaded) { srvlist_loaded = true; - int res = nodelist_load(PACKAGE_DATADIR "/DHTnodes"); + int res; - if (linecnt < 1) + if (!arg_opts.nodes_path[0]) + res = nodelist_load(PACKAGE_DATADIR "/DHTnodes"); + else + res = nodelist_load(arg_opts.nodes_path); + + if (toxNodes.lines < 1) return res; res = 3; int i; - int n = MIN(NUM_INIT_NODES, linecnt); + int n = MIN(NUM_INIT_NODES, toxNodes.lines); for (i = 0; i < n; ++i) { - if (init_connection_helper(m, rand() % linecnt)) + if (init_connection_helper(m, rand() % toxNodes.lines)) res = 0; } @@ -287,30 +322,37 @@ int init_connection(Tox *m) return 4; } +#define TRY_CONNECT 10 + static void do_connection(Tox *m, ToxWindow *prompt) { uint8_t msg[MAX_STR_SIZE] = {0}; - static int conn_try = 0; static int conn_err = 0; - static bool dht_on = false; - + static bool was_connected = false; + static uint64_t last_conn_try = 0; + uint64_t curtime = get_unix_time(); bool is_connected = tox_isconnected(m); - if (!dht_on && !is_connected && !(conn_try++ % 100)) { - if (!conn_err) { - if ((conn_err = init_connection(m))) { - snprintf(msg, sizeof(msg), "\nAuto-connect failed with error code %d", conn_err); - } - } - } else if (!dht_on && is_connected) { - dht_on = true; - prompt_update_connectionstatus(prompt, dht_on); + if (was_connected && is_connected) + return; + + if (!was_connected && is_connected) { + was_connected = true; + prompt_update_connectionstatus(prompt, was_connected); snprintf(msg, sizeof(msg), "DHT connected."); - } else if (dht_on && !is_connected) { - dht_on = false; - prompt_update_connectionstatus(prompt, dht_on); - snprintf(msg, sizeof(msg), "\nDHT disconnected. Attempting to reconnect."); + } else if (was_connected && !is_connected) { + was_connected = false; + prompt_update_connectionstatus(prompt, was_connected); + snprintf(msg, sizeof(msg), "DHT disconnected. Attempting to reconnect."); + } else if (!was_connected && !is_connected && timed_out(last_conn_try, curtime, TRY_CONNECT)) { + /* if autoconnect has already failed there's no point in trying again */ + if (conn_err == 0) { + last_conn_try = curtime; + + if ((conn_err = init_connection(m)) != 0) + snprintf(msg, sizeof(msg), "Auto-connect failed with error code %d", conn_err); + } } if (msg[0]) @@ -336,7 +378,7 @@ static void load_friendlist(Tox *m) */ int store_data(Tox *m, char *path) { - if (f_loadfromfile == 0) /*If file loading/saving is disabled*/ + if (arg_opts.ignore_data_file) return 0; if (path == NULL) @@ -374,7 +416,7 @@ int store_data(Tox *m, char *path) static void load_data(Tox *m, char *path) { - if (f_loadfromfile == 0) /*If file loading/saving is disabled*/ + if (arg_opts.ignore_data_file) return; FILE *fd; @@ -390,17 +432,13 @@ static void load_data(Tox *m, char *path) if (buf == NULL) { fclose(fd); - endwin(); - fprintf(stderr, "malloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); + exit_toxic_err("failed in load_data", FATALERR_MEMORY); } if (fread(buf, len, 1, fd) != 1) { free(buf); fclose(fd); - endwin(); - fprintf(stderr, "fread() failed. Aborting...\n"); - exit(EXIT_FAILURE); + exit_toxic_err("failed in load_data", FATALERR_FREAD); } tox_load(m, buf, len); @@ -411,44 +449,18 @@ static void load_data(Tox *m, char *path) } else { int st; - if ((st = store_data(m, path)) != 0) { - endwin(); - fprintf(stderr, "Store messenger failed with return code: %d\n", st); - exit(EXIT_FAILURE); - } + if ((st = store_data(m, path)) != 0) + exit_toxic_err("failed in load_data", FATALERR_STORE_DATA); } } -void exit_toxic(Tox *m) -{ - store_data(m, DATA_FILE); - close_all_file_senders(); - kill_all_windows(); - log_disable(prompt->chatwin->log); - line_info_cleanup(prompt->chatwin->hst); - free(DATA_FILE); - free(prompt->stb); - free(prompt->chatwin->log); - free(prompt->chatwin->hst); - free(prompt->chatwin); - free(user_settings); - tox_kill(m); -#ifdef _SUPPORT_AUDIO - terminate_audio(); -#endif /* _SUPPORT_AUDIO */ - endwin(); - exit(EXIT_SUCCESS); -} - static void do_toxic(Tox *m, ToxWindow *prompt) { pthread_mutex_lock(&Winthread.lock); do_connection(m, prompt); do_file_senders(m); - - /* main tox-core loop */ - tox_do(m); + tox_do(m); /* main tox-core loop */ pthread_mutex_unlock(&Winthread.lock); } @@ -457,72 +469,86 @@ void *thread_winref(void *data) { Tox *m = (Tox *) data; - while (true) + while (true) { draw_active_window(m); + refresh_inactive_windows(); + } } -int exit_print(char* exe_name, char* invalid_arg, char* error_str) +static void print_usage(void) { - const char* help_str = - "usage: \n" - " -f tox protocol data file path \n" - " -s custom settings path \n" - " -n don't load from data file (create new profile) \n" - " -h print this help \n" - " -4 force ipv4 \n"; - - if (!error_str && !invalid_arg) { - printf("%s %s", exe_name, help_str); - return 0; - } else if (invalid_arg) { - fprintf(stderr, "%s invalid arg: %s", exe_name, invalid_arg); - return 1; - } else if (error_str) { - fprintf(stderr, "%s error: %s", exe_name, error_str); - return 1; + fprintf(stderr, "usage: toxic [OPTION] [FILE ...]\n"); + fprintf(stderr, " -f, --file Use specified data file\n"); + fprintf(stderr, " -x, --nodata Ignore data file\n"); + fprintf(stderr, " -4, --ipv4 Force IPv4 connection\n"); + fprintf(stderr, " -c, --config Use specified config file\n"); + fprintf(stderr, " -n, --nodes Use specified DHTnodes file\n"); + fprintf(stderr, " -h, --help Show this message and exit\n"); +} + +static void set_default_opts(void) +{ + arg_opts.use_ipv4 = 0; + arg_opts.ignore_data_file = 0; +} + +static void parse_args(int argc, char *argv[]) +{ + set_default_opts(); + + static struct option long_opts[] = { + {"file", required_argument, 0, 'f'}, + {"nodata", no_argument, 0, 'x'}, + {"ipv4", no_argument, 0, '4'}, + {"config", required_argument, 0, 'c'}, + {"nodes", required_argument, 0, 'n'}, + {"help", no_argument, 0, 'h'}, + }; + + const char *opts_str = "4xf:c:n:h"; + int opt, indexptr; + + while ((opt = getopt_long(argc, argv, opts_str, long_opts, &indexptr)) != -1) { + switch (opt) { + case 'f': + DATA_FILE = strdup(optarg); + break; + + case 'x': + arg_opts.ignore_data_file = 1; + break; + + case '4': + arg_opts.use_ipv4 = 1; + break; + + case 'c': + snprintf(arg_opts.config_path, sizeof(arg_opts.config_path), "%s", optarg); + break; + + case 'n': + snprintf(arg_opts.nodes_path, sizeof(arg_opts.nodes_path), "%s", optarg); + break; + + case 'h': + default: + print_usage(); + exit(EXIT_SUCCESS); + } } } int main(int argc, char *argv[]) { - char *user_config_dir = get_user_config_dir(), *settings_path = NULL; + char *user_config_dir = get_user_config_dir(); int config_err = 0; - f_loadfromfile = 1; - int i = 0; - int f_use_ipv4 = 0; + parse_args(argc, argv); /* Make sure all written files are read/writeable only by the current user. */ umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); - for (i = 1; i < argc; ++i) { - if (argv[i][0] == '-') { - if (argv[i][1] == 'f') { - ++i; - if (i < argc) - DATA_FILE = strdup(argv[i]); - else - return exit_print(argv[0], NULL, "argument 'f' requires value"); - - } else if (argv[i][1] == 'n') { - f_loadfromfile = 0; - } else if (argv[i][1] == 'h') { - return exit_print(argv[0], NULL, NULL); - } else if (argv[i][1] == '4') { - f_use_ipv4 = 1; - } else if (argv[i][1] == 's') { - ++i; - if (i < argc) - settings_path = argv[i]; - else - return exit_print(argv[0], NULL, "argument 's' requires value"); - } else { - return exit_print(argv[0], argv[i]+1, NULL); /* Invalid arg */ - } - } else { - return exit_print(argv[0], argv[i], NULL); /* Invalid value/arg */ - } - } + signal(SIGINT, ignore_SIGINT); config_err = create_user_config_dir(user_config_dir); @@ -532,15 +558,12 @@ int main(int argc, char *argv[]) } else { DATA_FILE = malloc(strlen(user_config_dir) + strlen(CONFIGDIR) + strlen("data") + 1); - if (DATA_FILE != NULL) { - strcpy(DATA_FILE, user_config_dir); - strcat(DATA_FILE, CONFIGDIR); - strcat(DATA_FILE, "data"); - } else { - endwin(); - fprintf(stderr, "malloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (DATA_FILE == NULL) + exit_toxic_err("failed in main", FATALERR_MEMORY); + + strcpy(DATA_FILE, user_config_dir); + strcat(DATA_FILE, CONFIGDIR); + strcat(DATA_FILE, "data"); } } @@ -549,41 +572,32 @@ int main(int argc, char *argv[]) /* init user_settings struct and load settings from conf file */ user_settings = malloc(sizeof(struct user_settings)); - if (user_settings == NULL) { - endwin(); - fprintf(stderr, "malloc() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (user_settings == NULL) + exit_toxic_err("failed in main", FATALERR_MEMORY); memset(user_settings, 0, sizeof(struct user_settings)); - int settings_err = settings_load(user_settings, settings_path); - Tox *m = init_tox(f_use_ipv4); + char *p = arg_opts.config_path[0] ? arg_opts.config_path : NULL; + int settings_err = settings_load(user_settings, p); + + Tox *m = init_tox(arg_opts.use_ipv4); init_term(); - if (m == NULL) { - endwin(); - fprintf(stderr, "Failed to initialize network. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (m == NULL) + exit_toxic_err("failed in main", FATALERR_NETWORKINIT); - if (f_loadfromfile) + if (!arg_opts.ignore_data_file) load_data(m, DATA_FILE); prompt = init_windows(m); /* create new thread for ncurses stuff */ - if (pthread_mutex_init(&Winthread.lock, NULL) != 0) { - endwin(); - fprintf(stderr, "Mutex init failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (pthread_mutex_init(&Winthread.lock, NULL) != 0) + exit_toxic_err("failed in main", FATALERR_MUTEX_INIT); - if (pthread_create(&Winthread.tid, NULL, thread_winref, (void *) m) != 0) { - endwin(); - fprintf(stderr, "Thread creation failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (pthread_create(&Winthread.tid, NULL, thread_winref, (void *) m) != 0) + exit_toxic_err("failed in main", FATALERR_THREAD_CREATE); + uint8_t *msg; @@ -591,18 +605,10 @@ int main(int argc, char *argv[]) av = init_audio(prompt, m); -// device_set(prompt, input, user_settings->audio_in_dev); -// device_set(prompt, output, user_settings->audio_out_dev); set_primary_device(input, user_settings->audio_in_dev); set_primary_device(output, user_settings->audio_out_dev); - char* string = malloc(1000); - sprintf(string, "i:%d o:%d", user_settings->audio_in_dev, user_settings->audio_out_dev); - - line_info_add(prompt, NULL, NULL, NULL, string, SYS_MSG, 0, 0); - cmd_myid(NULL, prompt, m, 0, NULL); - #endif /* _SUPPORT_AUDIO */ if (config_err) { diff --git a/src/toxic.h b/src/toxic.h new file mode 100644 index 0000000..1a78e64 --- /dev/null +++ b/src/toxic.h @@ -0,0 +1,100 @@ +/* toxic.h + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +#ifndef _toxic_h +#define _toxic_h + +#ifndef TOXICVER +#define TOXICVER "NOVER_" /* Use the -D flag to set this */ +#endif + +#include +#include + +#include + +#define UNKNOWN_NAME "Anonymous" + +#define MAX_FRIENDS_NUM 500 +#define MAX_STR_SIZE 1024 +#define MAX_CMDNAME_SIZE 64 +#define TOXIC_MAX_NAME_LENGTH 32 /* Must be <= TOX_MAX_NAME_LENGTH */ +#define KEY_IDENT_DIGITS 2 /* number of hex digits to display for the pub-key based identifier */ +#define TIME_STR_SIZE 16 + +/* ASCII key codes */ +#define T_KEY_KILL 0x0B /* ctrl-k */ +#define T_KEY_DISCARD 0x15 /* ctrl-u */ +#define T_KEY_NEXT 0x10 /* ctrl-p */ +#define T_KEY_PREV 0x0F /* ctrl-o */ +#define T_KEY_C_E 0x05 /* ctrl-e */ +#define T_KEY_C_A 0x01 /* ctrl-a */ +#define T_KEY_C_RB 0x1D /* ctrl-] */ +#define T_KEY_C_LB 0x1B /* ctrl-[ */ +#define T_KEY_C_V 0x16 /* ctrl-v */ +#define T_KEY_C_F 0x06 /* ctrl-f */ +#define T_KEY_C_H 0x08 /* ctrl-h */ + +enum { + MOVE_UP, + MOVE_DOWN, +} KEY_DIRS; + +typedef enum _FATAL_ERRS { + FATALERR_MEMORY = -1, /* malloc() or calloc() failed */ + FATALERR_FREAD = -2, /* fread() failed on critical read */ + FATALERR_THREAD_CREATE = -3, + FATALERR_MUTEX_INIT = -4, + FATALERR_LOCALE_SET = -5, + FATALERR_STORE_DATA = -6, + FATALERR_NETWORKINIT = -7, /* Tox network failed to init */ + FATALERR_INFLOOP = -8, /* infinite loop detected */ + FATALERR_WININIT = -9, /* window init failed */ +} FATAL_ERRS; + +/* Fixes text color problem on some terminals. + Uncomment if necessary */ +/* #define URXVT_FIX */ + +void exit_toxic_success(Tox *m); +void exit_toxic_err(const char *errmsg, int errcode); + +void on_request(Tox *m, const uint8_t *public_key, const uint8_t *data, uint16_t length, void *userdata); +void on_connectionchange(Tox *m, int32_t friendnumber, uint8_t status, void *userdata); +void on_message(Tox *m, int32_t friendnumber, uint8_t *string, uint16_t length, void *userdata); +void on_action(Tox *m, int32_t friendnumber, uint8_t *string, uint16_t length, void *userdata); +void on_nickchange(Tox *m, int32_t friendnumber, uint8_t *string, uint16_t length, void *userdata); +void on_statuschange(Tox *m, int32_t friendnumber, uint8_t status, void *userdata); +void on_statusmessagechange(Tox *m, int32_t friendnumber, uint8_t *string, uint16_t length, void *userdata); +void on_friendadded(Tox *m, int32_t friendnumber, bool sort); +void on_groupmessage(Tox *m, int groupnumber, int peernumber, uint8_t *message, uint16_t length, void *userdata); +void on_groupaction(Tox *m, int groupnumber, int peernumber, uint8_t *action, uint16_t length, void *userdata); +void on_groupinvite(Tox *m, int32_t friendnumber, uint8_t *group_pub_key, void *userdata); +void on_group_namelistchange(Tox *m, int groupnumber, int peernumber, uint8_t change, void *userdata); +void on_file_sendrequest(Tox *m, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, uint8_t *pathname, + uint16_t pathname_length, void *userdata); +void on_file_control(Tox *m, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, uint8_t control_type, + uint8_t *data, uint16_t length, void *userdata); +void on_file_data(Tox *m, int32_t friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length, void *userdata); +void on_typing_change(Tox *m, int32_t friendnumber, uint8_t is_typing, void *userdata); + +#endif /* #define _toxic_h */ diff --git a/src/toxic_strings.c b/src/toxic_strings.c index 994c0fd..d78af0e 100644 --- a/src/toxic_strings.c +++ b/src/toxic_strings.c @@ -27,160 +27,181 @@ #include #include -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "misc_tools.h" #include "toxic_strings.h" -/* Adds char to buffer at pos */ -void add_char_to_buf(wchar_t *buf, size_t *pos, size_t *len, wint_t ch) +/* Adds char to line at pos */ +void add_char_to_buf(ChatContext *ctx, wint_t ch) { - if (*pos < 0 || *len >= MAX_STR_SIZE) + if (ctx->pos < 0 || ctx->len >= MAX_STR_SIZE) return; /* move all chars including null in front of pos one space forward and insert char in pos */ int i; - for (i = *len; i >= *pos && i >= 0; --i) - buf[i + 1] = buf[i]; + for (i = ctx->len; i >= ctx->pos && i >= 0; --i) + ctx->line[i + 1] = ctx->line[i]; - buf[(*pos)++] = ch; - ++(*len); + ctx->line[ctx->pos++] = ch; + ++ctx->len; } /* Deletes the character before pos */ -void del_char_buf_bck(wchar_t *buf, size_t *pos, size_t *len) +void del_char_buf_bck(ChatContext *ctx) { - if (*pos <= 0) + if (ctx->pos <= 0) return; int i; /* similar to add_char_to_buf but deletes a char */ - for (i = *pos - 1; i <= *len; ++i) - buf[i] = buf[i + 1]; + for (i = ctx->pos - 1; i <= ctx->len; ++i) + ctx->line[i] = ctx->line[i + 1]; - --(*pos); - --(*len); + --ctx->pos; + --ctx->len; } /* Deletes the character at pos */ -void del_char_buf_frnt(wchar_t *buf, size_t *pos, size_t *len) +void del_char_buf_frnt(ChatContext *ctx) { - if (*pos < 0 || *pos >= *len) + if (ctx->pos < 0 || ctx->pos >= ctx->len) return; int i; - for (i = *pos; i < *len; ++i) - buf[i] = buf[i + 1]; + for (i = ctx->pos; i < ctx->len; ++i) + ctx->line[i] = ctx->line[i + 1]; - --(*len); + --ctx->len; } /* Deletes the line from beginning to pos */ -void discard_buf(wchar_t *buf, size_t *pos, size_t *len) +void discard_buf(ChatContext *ctx) { - if (*pos <= 0) + if (ctx->pos <= 0) return; int i; int c = 0; - for (i = *pos; i <= *len; ++i) - buf[c++] = buf[i]; + for (i = ctx->pos; i <= ctx->len; ++i) + ctx->line[c++] = ctx->line[i]; - *pos = 0; - *len = c - 1; + ctx->pos = 0; + ctx->start = 0; + ctx->len = c - 1; } /* Deletes the line from pos to len */ -void kill_buf(wchar_t *buf, size_t *pos, size_t *len) +void kill_buf(ChatContext *ctx) { - if (*len == *pos) + if (ctx->len == ctx->pos) return; - buf[*pos] = L'\0'; - *len = *pos; + ctx->line[ctx->pos] = L'\0'; + ctx->len = ctx->pos; } -/* nulls buf and sets pos and len to 0 */ -void reset_buf(wchar_t *buf, size_t *pos, size_t *len) +/* nulls line and sets pos, len and start to 0 */ +void reset_buf(ChatContext *ctx) { - buf[0] = L'\0'; - *pos = 0; - *len = 0; + ctx->line[0] = L'\0'; + ctx->pos = 0; + ctx->len = 0; + ctx->start = 0; +} + +/* Removes trailing spaces from line. */ +void rm_trailing_spaces_buf(ChatContext *ctx) +{ + if (ctx->len <= 0) + return; + + if (ctx->line[ctx->len - 1] != ' ') + return; + + int i; + + for (i = ctx->len - 1; i >= 0; --i) { + if (ctx->line[i] != ' ') + break; + } + + ctx->len = i + 1; + ctx->pos = MIN(ctx->pos, ctx->len); + ctx->line[ctx->len] = L'\0'; } #define HIST_PURGE MAX_LINE_HIST / 4 /* shifts hist items back and makes room for HIST_PURGE new entries */ -static void shift_hist_back(wchar_t (*hst)[MAX_STR_SIZE], int *hst_tot) +static void shift_hist_back(ChatContext *ctx) { int i; int n = MAX_LINE_HIST - HIST_PURGE; for (i = 0; i < n; ++i) - wmemcpy(hst[i], hst[i + HIST_PURGE], MAX_STR_SIZE); + wmemcpy(ctx->ln_history[i], ctx->ln_history[i + HIST_PURGE], MAX_STR_SIZE); - *hst_tot = n; + ctx->hst_tot = n; } /* adds a line to the ln_history buffer at hst_pos and sets hst_pos to end of history. */ -void add_line_to_hist(const wchar_t *buf, size_t len, wchar_t (*hst)[MAX_STR_SIZE], int *hst_tot, - int *hst_pos) +void add_line_to_hist(ChatContext *ctx) { - if (len > MAX_STR_SIZE) + if (ctx->len > MAX_STR_SIZE) return; - if (*hst_tot >= MAX_LINE_HIST) - shift_hist_back(hst, hst_tot); + if (ctx->hst_tot >= MAX_LINE_HIST) + shift_hist_back(ctx); - ++(*hst_tot); - *hst_pos = *hst_tot; + ++ctx->hst_tot; + ctx->hst_pos = ctx->hst_tot; - wmemcpy(hst[*hst_tot - 1], buf, len + 1); + wmemcpy(ctx->ln_history[ctx->hst_tot - 1], ctx->line, ctx->len + 1); } -/* copies history item at hst_pos to buf. Sets pos and len to the len of the history item. +/* copies history item at hst_pos to line. Sets pos and len to the len of the history item. hst_pos is decremented or incremented depending on key_dir. - resets buffer if at end of history */ -void fetch_hist_item(wchar_t *buf, size_t *pos, size_t *len, wchar_t (*hst)[MAX_STR_SIZE], - int hst_tot, int *hst_pos, int key_dir) + resets line if at end of history */ +void fetch_hist_item(ChatContext *ctx, int key_dir) { if (key_dir == MOVE_UP) { - if (--(*hst_pos) < 0) { - *hst_pos = 0; + if (--ctx->hst_pos < 0) { + ctx->hst_pos = 0; beep(); } } else { - if (++(*hst_pos) >= hst_tot) { - *hst_pos = hst_tot; - reset_buf(buf, pos, len); + if (++ctx->hst_pos >= ctx->hst_tot) { + ctx->hst_pos = ctx->hst_tot; + reset_buf(ctx); return; } } - const wchar_t *hst_line = hst[*hst_pos]; + const wchar_t *hst_line = ctx->ln_history[ctx->hst_pos]; size_t h_len = wcslen(hst_line); - wmemcpy(buf, hst_line, h_len + 1); - - *pos = h_len; - *len = h_len; + wmemcpy(ctx->line, hst_line, h_len + 1); + ctx->pos = h_len; + ctx->len = h_len; } -/* looks for the first instance in list that begins with the last entered word in buf according to pos, - then fills buf with the complete word. e.g. "Hello jo" would complete the buffer +/* looks for the first instance in list that begins with the last entered word in line according to pos, + then fills line with the complete word. e.g. "Hello jo" would complete the line with "Hello john". list is a pointer to the list of strings being compared, n_items is the number of items in the list, and size is the size of each item in the list. - Returns the difference between the old len and new len of buf on success, -1 if error */ -int complete_line(wchar_t *buf, size_t *pos, size_t *len, const void *list, int n_items, int size) + Returns the difference between the old len and new len of line on success, -1 if error */ +int complete_line(ChatContext *ctx, const void *list, int n_items, int size) { - if (*pos <= 0 || *len <= 0 || *len >= MAX_STR_SIZE) + if (ctx->pos <= 0 || ctx->len <= 0 || ctx->len >= MAX_STR_SIZE) return -1; const uint8_t *L = (uint8_t *) list; @@ -188,13 +209,13 @@ int complete_line(wchar_t *buf, size_t *pos, size_t *len, const void *list, int uint8_t ubuf[MAX_STR_SIZE]; /* work with multibyte string copy of buf for simplicity */ - if (wcs_to_mbs_buf(ubuf, buf, MAX_STR_SIZE) == -1) + if (wcs_to_mbs_buf(ubuf, ctx->line, MAX_STR_SIZE) == -1) return -1; /* isolate substring from space behind pos to pos */ uint8_t tmp[MAX_STR_SIZE]; snprintf(tmp, sizeof(tmp), "%s", ubuf); - tmp[*pos] = '\0'; + tmp[ctx->pos] = '\0'; uint8_t *sub = strrchr(tmp, ' '); int n_endchrs = 1; /* 1 = append space to end of match, 2 = append ": " */ @@ -227,14 +248,14 @@ int complete_line(wchar_t *buf, size_t *pos, size_t *len, const void *list, int /* put match in correct spot in buf and append endchars (space or ": ") */ const uint8_t *endchrs = n_endchrs == 1 ? " " : ": "; int m_len = strlen(match); - int strt = (int) * pos - s_len; + int strt = ctx->pos - s_len; int diff = m_len - s_len + n_endchrs; - if (*len + diff > MAX_STR_SIZE) + if (ctx->len + diff > MAX_STR_SIZE) return -1; uint8_t tmpend[MAX_STR_SIZE]; - strcpy(tmpend, &ubuf[*pos]); + strcpy(tmpend, &ubuf[ctx->pos]); strcpy(&ubuf[strt], match); strcpy(&ubuf[strt + m_len], endchrs); strcpy(&ubuf[strt + m_len + n_endchrs], tmpend); @@ -245,10 +266,10 @@ int complete_line(wchar_t *buf, size_t *pos, size_t *len, const void *list, int if (mbs_to_wcs_buf(newbuf, ubuf, MAX_STR_SIZE) == -1) return -1; - wcscpy(buf, newbuf); + wcscpy(ctx->line, newbuf); - *len += (size_t) diff; - *pos += (size_t) diff; + ctx->len += (size_t) diff; + ctx->pos += (size_t) diff; return diff; } diff --git a/src/toxic_strings.h b/src/toxic_strings.h index 84bb3f7..8c7e12a 100644 --- a/src/toxic_strings.h +++ b/src/toxic_strings.h @@ -20,39 +20,49 @@ * */ -/* Adds char to buffer at pos */ -void add_char_to_buf(wchar_t *buf, size_t *pos, size_t *len, wint_t ch); +#ifndef _toxic_strings_h +#define _toxic_strings_h + +#include "windows.h" + +/* Adds char to line at pos */ +void add_char_to_buf(ChatContext *ctx, wint_t ch); /* Deletes the character before pos */ -void del_char_buf_bck(wchar_t *buf, size_t *pos, size_t *len); +void del_char_buf_bck(ChatContext *ctx); /* Deletes the character at pos */ -void del_char_buf_frnt(wchar_t *buf, size_t *pos, size_t *len); +void del_char_buf_frnt(ChatContext *ctx); /* Deletes the line from beginning to pos */ -void discard_buf(wchar_t *buf, size_t *pos, size_t *len); +void discard_buf(ChatContext *ctx); /* Deletes the line from pos to len */ -void kill_buf(wchar_t *buf, size_t *pos, size_t *len); +void kill_buf(ChatContext *ctx); -/* nulls buf and sets pos and len to 0 */ -void reset_buf(wchar_t *buf, size_t *pos, size_t *len); +/* nulls line and sets pos, len and start to 0 */ +void reset_buf(ChatContext *ctx); -/* looks for the first instance in list that begins with the last entered word in buf according to pos, - then fills buf with the complete word. e.g. "Hello jo" would complete the buffer +/* Removes trailing spaces from line. */ +void rm_trailing_spaces_buf(ChatContext *ctx); + +/* looks for the first instance in list that begins with the last entered word in line according to pos, + then fills line with the complete word. e.g. "Hello jo" would complete the line with "Hello john". list is a pointer to the list of strings being compared, n_items is the number of items in the list, and size is the size of each item in the list. - Returns the difference between the old len and new len of buf on success, -1 if error */ -int complete_line(wchar_t *buf, size_t *pos, size_t *len, const void *list, int n_items, int size); + Returns the difference between the old len and new len of line on success, -1 if error */ +int complete_line(ChatContext *ctx, const void *list, int n_items, int size); /* adds a line to the ln_history buffer at hst_pos and sets hst_pos to last history item. */ -void add_line_to_hist(const wchar_t *buf, size_t len, wchar_t (*hst)[MAX_STR_SIZE], int *hst_tot, - int *hst_pos); +void add_line_to_hist(ChatContext *ctx); -/* copies history item at hst_pos to buf. Sets pos and len to the len of the history item. - hst_pos is decremented or incremented depending on key_dir. */ -void fetch_hist_item(wchar_t *buf, size_t *pos, size_t *len, wchar_t (*hst)[MAX_STR_SIZE], - int hst_tot, int *hst_pos, int key_dir); +/* copies history item at hst_pos to line. Sets pos and len to the len of the history item. + hst_pos is decremented or incremented depending on key_dir. + + resets line if at end of history */ +void fetch_hist_item(ChatContext *ctx, int key_dir); + +#endif /* #define _toxic_strings_h */ diff --git a/src/windows.c b/src/windows.c index 564da15..1295248 100644 --- a/src/windows.c +++ b/src/windows.c @@ -30,7 +30,8 @@ #include "friendlist.h" #include "prompt.h" -#include "toxic_windows.h" +#include "toxic.h" +#include "windows.h" #include "groupchat.h" extern char *DATA_FILE; @@ -43,7 +44,7 @@ extern ToxWindow *prompt; static int num_active_windows; /* CALLBACKS START */ -void on_request(Tox *m, uint8_t *public_key, uint8_t *data, uint16_t length, void *userdata) +void on_request(Tox *m, const uint8_t *public_key, const uint8_t *data, uint16_t length, void *userdata) { int i; @@ -283,11 +284,8 @@ void set_next_window(int ch) if (active_window->window) return; - if (active_window == inf) { /* infinite loop check */ - endwin(); - fprintf(stderr, "set_next_window() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (active_window == inf) /* infinite loop check */ + exit_toxic_err("failed in set_next_window", FATALERR_INFLOOP); } } @@ -303,11 +301,8 @@ ToxWindow *init_windows(Tox *m) { int n_prompt = add_window(m, new_prompt()); - if (n_prompt == -1 || add_window(m, new_friendlist()) == -1) { - endwin(); - fprintf(stderr, "add_window() failed. Aborting...\n"); - exit(EXIT_FAILURE); - } + if (n_prompt == -1 || add_window(m, new_friendlist()) == -1) + exit_toxic_err("failed in init_windows", FATALERR_WININIT); prompt = &windows[n_prompt]; active_window = prompt; @@ -315,6 +310,12 @@ ToxWindow *init_windows(Tox *m) return prompt; } +void on_window_resize(int sig) +{ + refresh(); + clear(); +} + static void draw_window_tab(ToxWindow toxwin) { /* alert0 takes priority */ @@ -351,25 +352,28 @@ static void draw_bar(void) int i; for (i = 0; i < MAX_WINDOWS_NUM; ++i) { - if (windows[i].active) { - if (windows + i == active_window) { -#ifdef URXVT_FIX - attron(A_BOLD | COLOR_PAIR(GREEN)); - } else { -#endif - attron(A_BOLD); - } + if (!windows[i].active) + continue; - draw_window_tab(windows[i]); + if (windows + i == active_window) - if (windows + i == active_window) { #ifdef URXVT_FIX - attroff(A_BOLD | COLOR_PAIR(GREEN)); - } else { + attron(A_BOLD | COLOR_PAIR(GREEN)); + else #endif - attroff(A_BOLD); - } - } + + attron(A_BOLD); + + draw_window_tab(windows[i]); + + if (windows + i == active_window) + +#ifdef URXVT_FIX + attroff(A_BOLD | COLOR_PAIR(GREEN)); + else +#endif + + attroff(A_BOLD); } refresh(); @@ -417,7 +421,7 @@ void draw_active_window(Tox *m) ltr = isprint(ch); #endif - if (!ltr && (ch == T_KEY_NEXT || ch == T_KEY_PREV) ) { + if (!ltr && (ch == T_KEY_NEXT || ch == T_KEY_PREV)) { set_next_window((int) ch); } else { pthread_mutex_lock(&Winthread.lock); @@ -426,6 +430,20 @@ void draw_active_window(Tox *m) } } +/* refresh inactive windows to prevent scrolling bugs. + call at least once per second */ +void refresh_inactive_windows(void) +{ + int i; + + for (i = 0; i < MAX_WINDOWS_NUM; ++i) { + ToxWindow *a = &windows[i]; + + if (a->active && a != active_window && (a->is_chat || a->is_groupchat)) + line_info_print(a); + } +} + int get_num_active_windows(void) { return num_active_windows; diff --git a/src/toxic_windows.h b/src/windows.h similarity index 58% rename from src/toxic_windows.h rename to src/windows.h index 29addf1..169701d 100644 --- a/src/toxic_windows.h +++ b/src/windows.h @@ -1,4 +1,4 @@ -/* toxic_windows.h +/* windows.h * * * Copyright (C) 2014 Toxic All Rights Reserved. @@ -23,11 +23,6 @@ #ifndef _windows_h #define _windows_h -#ifndef TOXICVER -#define TOXICVER "NOVER_" /* Use the -D flag to set this */ -#endif - -#include #include #include #include @@ -38,31 +33,11 @@ #include #endif /* _SUPPORT_AUDIO */ -#define UNKNOWN_NAME "Anonymous" +#include "toxic.h" #define MAX_WINDOWS_NUM 32 -#define MAX_FRIENDS_NUM 100 -#define MAX_STR_SIZE 256 -#define MAX_CMDNAME_SIZE 64 -#define KEY_SIZE_BYTES 32 -#define TOXIC_MAX_NAME_LENGTH 32 /* Must be <= TOX_MAX_NAME_LENGTH */ -#define N_DEFAULT_WINS 2 /* number of permanent default windows */ -#define CURS_Y_OFFSET 3 /* y-axis cursor offset for chat contexts */ -#define CHATBOX_HEIGHT 4 -#define KEY_IDENT_DIGITS 2 /* number of hex digits to display for the pub-key based identifier */ -#define TIME_STR_SIZE 16 - -#define EXIT_SUCCESS 0 -#define EXIT_FAILURE 1 - -/* ASCII key codes */ -#define T_KEY_KILL 0xB /* ctrl-k */ -#define T_KEY_DISCARD 0x15 /* ctrl-u */ -#define T_KEY_NEXT 0x10 /* ctrl-p */ -#define T_KEY_PREV 0x0F /* ctrl-o */ -#define T_KEY_C_E 0x05 /* ctrl-e */ -#define T_KEY_C_A 0x01 /* ctrl-a */ -#define T_KEY_ESC 0x1B /* ESC key */ +#define CURS_Y_OFFSET 1 /* y-axis cursor offset for chat contexts */ +#define CHATBOX_HEIGHT 2 /* Curses foreground colours (background is black) */ enum { @@ -74,24 +49,24 @@ enum { YELLOW, MAGENTA, BLACK, -}; +} C_COLOURS; /* tab alert types: lower types take priority */ enum { WINDOW_ALERT_0, WINDOW_ALERT_1, WINDOW_ALERT_2, -}; - -enum { - MOVE_UP, - MOVE_DOWN, -}; +} WINDOW_ALERTS; /* Fixes text color problem on some terminals. Uncomment if necessary */ /* #define URXVT_FIX */ +struct _Winthread { + pthread_t tid; + pthread_mutex_t lock; +}; + typedef struct ToxWindow ToxWindow; typedef struct StatusBar StatusBar; typedef struct PromptBuf PromptBuf; @@ -101,7 +76,7 @@ struct ToxWindow { void(*onKey)(ToxWindow *, Tox *, wint_t, bool); void(*onDraw)(ToxWindow *, Tox *); void(*onInit)(ToxWindow *, Tox *); - void(*onFriendRequest)(ToxWindow *, Tox *, uint8_t *, uint8_t *, uint16_t); + void(*onFriendRequest)(ToxWindow *, Tox *, const uint8_t *, const uint8_t *, uint16_t); void(*onFriendAdded)(ToxWindow *, Tox *, int32_t, bool); void(*onConnectionChange)(ToxWindow *, Tox *, int32_t, uint8_t); void(*onMessage)(ToxWindow *, Tox *, int32_t, uint8_t *, uint16_t); @@ -176,6 +151,7 @@ struct ChatContext { wchar_t line[MAX_STR_SIZE]; size_t pos; size_t len; + size_t start; /* the position to start printing line at */ wchar_t ln_history[MAX_LINE_HIST][MAX_STR_SIZE]; /* history for input lines/commands */ int hst_pos; @@ -195,67 +171,13 @@ struct ChatContext { int orig_y; /* y axis point of line origin */ }; -/* Start file transfer code */ - -#define MAX_FILES 256 -#define FILE_PIECE_SIZE 1024 -#define TIMEOUT_FILESENDER 300 - -typedef struct { - FILE *file; - ToxWindow *toxwin; - int32_t friendnum; - bool active; - int filenum; - uint8_t nextpiece[FILE_PIECE_SIZE]; - uint16_t piecelen; - uint8_t pathname[MAX_STR_SIZE]; - uint64_t timestamp; - uint64_t size; - uint32_t line_id; -} FileSender; - -struct FileReceiver { - uint8_t filenames[MAX_FILES][MAX_STR_SIZE]; - FILE *files[MAX_FILES]; - bool pending[MAX_FILES]; - uint64_t size[MAX_FILES]; - uint32_t line_id; -}; - -/* End file transfer code */ - -struct _Winthread { - pthread_t tid; - pthread_mutex_t lock; -}; - -void on_request(Tox *m, uint8_t *public_key, uint8_t *data, uint16_t length, void *userdata); -void on_connectionchange(Tox *m, int32_t friendnumber, uint8_t status, void *userdata); -void on_message(Tox *m, int32_t friendnumber, uint8_t *string, uint16_t length, void *userdata); -void on_action(Tox *m, int32_t friendnumber, uint8_t *string, uint16_t length, void *userdata); -void on_nickchange(Tox *m, int32_t friendnumber, uint8_t *string, uint16_t length, void *userdata); -void on_statuschange(Tox *m, int32_t friendnumber, uint8_t status, void *userdata); -void on_statusmessagechange(Tox *m, int32_t friendnumber, uint8_t *string, uint16_t length, void *userdata); -void on_friendadded(Tox *m, int32_t friendnumber, bool sort); -void on_groupmessage(Tox *m, int groupnumber, int peernumber, uint8_t *message, uint16_t length, void *userdata); -void on_groupaction(Tox *m, int groupnumber, int peernumber, uint8_t *action, uint16_t length, void *userdata); -void on_groupinvite(Tox *m, int32_t friendnumber, uint8_t *group_pub_key, void *userdata); -void on_group_namelistchange(Tox *m, int groupnumber, int peernumber, uint8_t change, void *userdata); -void on_file_sendrequest(Tox *m, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, uint8_t *pathname, - uint16_t pathname_length, void *userdata); -void on_file_control(Tox *m, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, uint8_t control_type, - uint8_t *data, uint16_t length, void *userdata); -void on_file_data(Tox *m, int32_t friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length, void *userdata); -void on_typing_change(Tox *m, int32_t friendnumber, uint8_t is_typing, void *userdata); - ToxWindow *init_windows(Tox *m); void draw_active_window(Tox *m); int add_window(Tox *m, ToxWindow w); void del_window(ToxWindow *w); void set_active_window(int ch); int get_num_active_windows(void); +void kill_all_windows(void); /* should only be called on shutdown */ +void on_window_resize(int sig); -/* cleans up all chat and groupchat windows (should only be called on shutdown) */ -void kill_all_windows(void); -#endif +#endif /* #define _windows_h */