diff --git a/doc/toxic.conf.5 b/doc/toxic.conf.5 index 0d3a0c3..e334ab6 100644 --- a/doc/toxic.conf.5 +++ b/doc/toxic.conf.5 @@ -2,12 +2,12 @@ .\" Title: toxic.conf .\" Author: [see the "AUTHORS" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 2014-08-26 +.\" Date: 2014-09-19 .\" Manual: Toxic Manual .\" Source: toxic __VERSION__ .\" Language: English .\" -.TH "TOXIC\&.CONF" "5" "2014\-08\-26" "toxic __VERSION__" "Toxic Manual" +.TH "TOXIC\&.CONF" "5" "2014\-09\-19" "toxic __VERSION__" "Toxic Manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -138,6 +138,11 @@ Configuration related to paths\&. Default path for downloads\&. String value\&. Absolute path for downloaded files\&. .RE .PP +\fBavatar_path\fR +.RS 4 +Path for your avatar (file must be a \&.png and cannot exceed 16\&.3 KiB) +.RE +.PP \fBchatlogs_path\fR .RS 4 Default path for chatlogs\&. String value\&. Absolute path for chatlog files\&. diff --git a/doc/toxic.conf.5.asc b/doc/toxic.conf.5.asc index 976cb00..0701d34 100644 --- a/doc/toxic.conf.5.asc +++ b/doc/toxic.conf.5.asc @@ -87,6 +87,9 @@ OPTIONS Default path for downloads. String value. Absolute path for downloaded files. + *avatar_path*;; + Path for your avatar (file must be a .png and cannot exceed 16.3 KiB) + *chatlogs_path*;; Default path for chatlogs. String value. Absolute path for chatlog files. diff --git a/src/autocomplete.c b/src/autocomplete.c index 81d58d6..1c7c94b 100644 --- a/src/autocomplete.c +++ b/src/autocomplete.c @@ -104,7 +104,9 @@ int complete_line(ToxWindow *self, const void *list, int n_items, int size) if (wcs_to_mbs_buf(ubuf, ctx->line, sizeof(ubuf)) == -1) return -1; - bool dir_search = strncmp(ubuf, "/sendfile", strlen("/sendfile")) == 0; + /* TODO: generalize this */ + bool dir_search = !strncmp(ubuf, "/sendfile", strlen("/sendfile")) + || !strncmp(ubuf, "/avatar", strlen("/avatar")); /* isolate substring from space behind pos to pos */ char tmp[MAX_STR_SIZE]; @@ -202,8 +204,8 @@ int complete_line(ToxWindow *self, const void *list, int n_items, int size) return diff; } -/* transforms a sendfile tab complete contaning the shorthand "~/" into the full home directory.*/ -static void complt_home_dir(ToxWindow *self, char *path, int pathsize) +/* transforms a tab complete starting with the shorthand "~" into the full home directory.*/ +static void complt_home_dir(ToxWindow *self, char *path, int pathsize, const char *cmd, int cmdlen) { ChatContext *ctx = self->chatwin; @@ -211,8 +213,8 @@ static void complt_home_dir(ToxWindow *self, char *path, int pathsize) get_home_dir(homedir, sizeof(homedir)); char newline[MAX_STR_SIZE]; - snprintf(newline, sizeof(newline), "/sendfile \"%s%s", homedir, path + 1); - snprintf(path, pathsize, "%s", &newline[11]); + snprintf(newline, sizeof(newline), "%s \"%s%s", cmd, homedir, path + 1); + snprintf(path, pathsize, "%s", &newline[cmdlen]); wchar_t wline[MAX_STR_SIZE]; @@ -229,23 +231,27 @@ static void complt_home_dir(ToxWindow *self, char *path, int pathsize) ctx->len = ctx->pos; } -/* attempts to match /sendfile "" line to matching directories. +/* attempts to match /command "" line to matching directories. if only one match, auto-complete line. return diff between old len and new len of ctx->line, -1 if no matches or > 1 match */ #define MAX_DIRS 512 -int dir_match(ToxWindow *self, Tox *m, const wchar_t *line) +int dir_match(ToxWindow *self, Tox *m, const wchar_t *line, const wchar_t *cmd) { char b_path[MAX_STR_SIZE]; char b_name[MAX_STR_SIZE]; - const wchar_t *tmpline = &line[11]; /* start after "/sendfile \"" */ + char b_cmd[MAX_STR_SIZE]; + const wchar_t *tmpline = &line[wcslen(cmd) + 2]; /* start after "/command \"" */ if (wcs_to_mbs_buf(b_path, tmpline, sizeof(b_path)) == -1) return -1; + if (wcs_to_mbs_buf(b_cmd, cmd, sizeof(b_cmd)) == -1) + return -1; + if (b_path[0] == '~') - complt_home_dir(self, b_path, sizeof(b_path)); + complt_home_dir(self, b_path, sizeof(b_path), b_cmd, strlen(b_cmd) + 2); int si = char_rfind(b_path, '/', strlen(b_path)); @@ -261,7 +267,6 @@ int dir_match(ToxWindow *self, Tox *m, const wchar_t *line) strcpy(b_name, &b_path[si + 1]); b_path[si + 1] = '\0'; int b_name_len = strlen(b_name); - DIR *dp = opendir(b_path); if (dp == NULL) diff --git a/src/autocomplete.h b/src/autocomplete.h index 981cd8f..19882cd 100644 --- a/src/autocomplete.h +++ b/src/autocomplete.h @@ -33,10 +33,10 @@ Returns the difference between the old len and new len of line on success, -1 if error */ int complete_line(ToxWindow *self, const void *list, int n_items, int size); -/* attempts to match /sendfile "" line to matching directories. +/* attempts to match /command "" line to matching directories. if only one match, auto-complete line. return diff between old len and new len of ctx->line, -1 if no matches or > 1 match */ -int dir_match(ToxWindow *self, Tox *m, const wchar_t *line); +int dir_match(ToxWindow *self, Tox *m, const wchar_t *line, const wchar_t *cmd); #endif /* #define AUTOCOMPLETE_H */ diff --git a/src/chat.c b/src/chat.c index b86377f..7776fc0 100644 --- a/src/chat.c +++ b/src/chat.c @@ -64,15 +64,16 @@ static void kill_infobox(ToxWindow *self); #endif /* AUDIO */ #ifdef AUDIO -#define AC_NUM_CHAT_COMMANDS 26 +#define AC_NUM_CHAT_COMMANDS 27 #else -#define AC_NUM_CHAT_COMMANDS 19 +#define AC_NUM_CHAT_COMMANDS 20 #endif /* AUDIO */ /* Array of chat command names used for tab completion. */ static const char chat_cmd_list[AC_NUM_CHAT_COMMANDS][MAX_CMDNAME_SIZE] = { { "/accept" }, { "/add" }, + { "/avatar" }, { "/cancel" }, { "/clear" }, { "/close" }, @@ -879,8 +880,11 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) if (key == '\t' && ctx->len > 1 && ctx->line[0] == '/') { /* TAB key: auto-complete */ int diff = -1; + /* TODO: make this not suck */ if (wcsncmp(ctx->line, L"/sendfile \"", wcslen(L"/sendfile \"")) == 0) { - diff = dir_match(self, m, ctx->line); + diff = dir_match(self, m, ctx->line, L"/sendfile"); + } else if (wcsncmp(ctx->line, L"/avatar \"", wcslen(L"/avatar \"")) == 0) { + diff = dir_match(self, m, ctx->line, L"/avatar"); } else { diff = complete_line(self, chat_cmd_list, AC_NUM_CHAT_COMMANDS, MAX_CMDNAME_SIZE); } diff --git a/src/execute.c b/src/execute.c index 0a85c90..640af5f 100644 --- a/src/execute.c +++ b/src/execute.c @@ -41,6 +41,7 @@ struct cmd_func { static struct cmd_func global_commands[] = { { "/accept", cmd_accept }, { "/add", cmd_add }, + { "/avatar", cmd_avatar }, { "/clear", cmd_clear }, { "/connect", cmd_connect }, { "/decline", cmd_decline }, diff --git a/src/global_commands.c b/src/global_commands.c index 0855ea1..bd8e8b2 100644 --- a/src/global_commands.c +++ b/src/global_commands.c @@ -184,6 +184,73 @@ void cmd_add(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX } } +void cmd_avatar(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + if (argc < 2) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set avatar: No file path supplied."); + return; + } + + if (string_is_empty(argv[1])) + return; + + if (argv[1][0] != '\"') { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Note must be enclosed in quotes."); + return; + } + + /* remove opening and closing quotes */ + char path[MAX_STR_SIZE]; + snprintf(path, sizeof(path), "%s", &argv[1][1]); + int len = strlen(path) - 1; + path[len] = '\0'; + + off_t sz = file_size(path); + + if (sz <= 8) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set avatar: Invalid file."); + return; + } + + if (sz > TOX_AVATAR_MAX_DATA_LENGTH) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set avatar: File is too large."); + return; + } + + FILE *fp = fopen(path, "rb"); + + if (fp == NULL) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set avatar: Could not open file."); + return; + } + + char PNG_signature[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; + + if (check_file_signature(PNG_signature, sizeof(PNG_signature), fp) != 0) { + fclose(fp); + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set avatar: File type not supported."); + return; + } + + char *avatar = malloc(sz); + + if (avatar == NULL) + exit_toxic_err("Failed in set_avatar", FATALERR_MEMORY); + + if (fread(avatar, sz, 1, fp) == -1) { + fclose(fp); + free(avatar); + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set avatar: Read fail."); + return; + } + + if (tox_set_avatar(m, TOX_AVATAR_FORMAT_PNG, (const uint8_t *) avatar, (uint32_t) sz) == -1) + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set avatar: Core error."); + + fclose(fp); + free(avatar); +} + void cmd_clear(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { line_info_clear(self->chatwin->hst); diff --git a/src/global_commands.h b/src/global_commands.h index 0597e19..9d3f34b 100644 --- a/src/global_commands.h +++ b/src/global_commands.h @@ -28,6 +28,7 @@ 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_avatar(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_clear(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_connect(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_decline(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]); diff --git a/src/groupchat.c b/src/groupchat.c index 02ae251..1bab012 100644 --- a/src/groupchat.c +++ b/src/groupchat.c @@ -372,11 +372,15 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) if (ctx->len > 0) { int diff; - if ((ctx->line[0] != '/') || (ctx->line[1] == 'm' && ctx->line[2] == 'e')) - diff = complete_line(self, groupchats[self->num].peer_names, - groupchats[self->num].num_peers, TOX_MAX_NAME_LENGTH); - else + /* TODO: make this not suck */ + if (ctx->line[0] != L'/' || wcscmp(ctx->line, L"/me") == 0) { + diff = complete_line(self, groupchats[self->num].peer_names, groupchats[self->num].num_peers, + TOX_MAX_NAME_LENGTH); + } else if (wcsncmp(ctx->line, L"/avatar \"", wcslen(L"/avatar \"")) == 0) { + diff = dir_match(self, m, ctx->line, L"/avatar"); + } else { diff = complete_line(self, glob_cmd_list, AC_NUM_GLOB_COMMANDS, MAX_CMDNAME_SIZE); + } if (diff != -1) { if (x + diff > x2 - 1) { diff --git a/src/help.c b/src/help.c index d2ca337..59389ae 100644 --- a/src/help.c +++ b/src/help.c @@ -138,6 +138,7 @@ static void help_draw_global(ToxWindow *self) wprintw(win, " /add : Add contact with optional message\n"); wprintw(win, " /accept : Accept friend request\n"); + wprintw(win, " /avatar : Set a personal avatar\n"); wprintw(win, " /decline : Decline friend request\n"); wprintw(win, " /requests : List pending friend requests\n"); wprintw(win, " /connect : Manually connect to a DHT node\n"); @@ -266,9 +267,9 @@ void help_onKey(ToxWindow *self, wint_t key) case 'g': #ifdef AUDIO - help_init_window(self, 23, 80); + help_init_window(self, 24, 80); #else - help_init_window(self, 19, 80); + help_init_window(self, 20, 80); #endif self->help->type = HELP_GLOBAL; break; diff --git a/src/misc_tools.c b/src/misc_tools.c index cc958b7..1afcec7 100644 --- a/src/misc_tools.c +++ b/src/misc_tools.c @@ -322,3 +322,22 @@ off_t file_size(const char *path) return st.st_size; } + +/* compares the first size bytes of fp to signature. + Returns 0 if they are the same, 1 if they differ, and -1 on error. + + On success this function will seek back to the beginning of fp */ +int check_file_signature(const char *signature, size_t size, FILE *fp) +{ + char buf[size]; + + if (fread(buf, size, 1, fp) == -1) + return -1; + + int ret = memcmp(signature, buf, size); + + if (fseek(fp, 0L, SEEK_SET) == -1) + return -1; + + return ret == 0 ? 0 : 1; +} diff --git a/src/misc_tools.h b/src/misc_tools.h index 472fdfe..1190b04 100644 --- a/src/misc_tools.h +++ b/src/misc_tools.h @@ -115,4 +115,10 @@ bool file_exists(const char *path); /* returns file size or -1 on error */ off_t file_size(const char *path); +/* compares the first size bytes of fp and signature. + Returns 0 if they are the same, 1 if they differ, and -1 on error. + + On success this function will seek back to the beginning of fp and will not close fp */ +int check_file_signature(const char *signature, size_t size, FILE *fp); + #endif /* #define MISC_TOOLS_H */ diff --git a/src/prompt.c b/src/prompt.c index 03e98db..cb28197 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -52,6 +52,7 @@ _FriendRequests FriendRequests; const char glob_cmd_list[AC_NUM_GLOB_COMMANDS][MAX_CMDNAME_SIZE] = { { "/accept" }, { "/add" }, + { "/avatar" }, { "/clear" }, { "/close" }, /* rm /close when groupchats gets its own list */ { "/connect" }, @@ -183,7 +184,12 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) if (key == '\t') { /* TAB key: auto-completes command */ if (ctx->len > 1 && ctx->line[0] == '/') { - int diff = complete_line(self, glob_cmd_list, AC_NUM_GLOB_COMMANDS, MAX_CMDNAME_SIZE); + int diff = -1; + + if (wcsncmp(ctx->line, L"/avatar \"", wcslen(L"/avatar \"")) == 0) + diff = dir_match(self, m, ctx->line, L"/avatar"); + else + diff = complete_line(self, glob_cmd_list, AC_NUM_GLOB_COMMANDS, MAX_CMDNAME_SIZE); if (diff != -1) { if (x + diff > x2 - 1) { diff --git a/src/prompt.h b/src/prompt.h index 21563cb..e006e86 100644 --- a/src/prompt.h +++ b/src/prompt.h @@ -27,9 +27,9 @@ #include "windows.h" #ifdef AUDIO -#define AC_NUM_GLOB_COMMANDS 18 +#define AC_NUM_GLOB_COMMANDS 19 #else -#define AC_NUM_GLOB_COMMANDS 16 +#define AC_NUM_GLOB_COMMANDS 17 #endif /* AUDIO */ #define MAX_FRIEND_REQUESTS 32 diff --git a/src/settings.c b/src/settings.c index 1c6a362..dde7268 100644 --- a/src/settings.c +++ b/src/settings.c @@ -123,16 +123,19 @@ static const struct tox_strings { const char* self; const char* download_path; const char* chatlogs_path; + const char* avatar_path; } tox_strings = { "tox", "download_path", "chatlogs_path", + "avatar_path", }; static void tox_defaults(struct user_settings* settings) { strcpy(settings->download_path, ""); strcpy(settings->chatlogs_path, ""); + strcpy(settings->avatar_path, ""); } #ifdef AUDIO @@ -280,6 +283,14 @@ int settings_load(struct user_settings *s, const char *patharg) else if (s->chatlogs_path[len - 1] != '/') strcat(&s->chatlogs_path[len - 1], "/"); } + + if ( config_setting_lookup_string(setting, tox_strings.avatar_path, &str) ) { + snprintf(s->avatar_path, sizeof(s->avatar_path), "%s", str); + int len = strlen(s->avatar_path); + + if (len >= sizeof(s->avatar_path)) + s->avatar_path[0] = '\0'; + } } /* keys */ diff --git a/src/settings.h b/src/settings.h index feaf871..1e5746a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -39,9 +39,10 @@ struct user_settings { char download_path[PATH_MAX]; char chatlogs_path[PATH_MAX]; + char avatar_path[PATH_MAX]; - int key_next_tab; /* character code */ - int key_prev_tab; /* character code */ + int key_next_tab; + int key_prev_tab; int key_scroll_line_up; int key_scroll_line_down; int key_half_page_up; @@ -49,6 +50,7 @@ struct user_settings { int key_page_bottom; int key_peer_list_up; int key_peer_list_down; + #ifdef AUDIO int audio_in_dev; int audio_out_dev; diff --git a/src/toxic.c b/src/toxic.c index 126fef3..0bba549 100644 --- a/src/toxic.c +++ b/src/toxic.c @@ -56,6 +56,7 @@ #include "notify.h" #include "device.h" #include "message_queue.h" +#include "execute.h" #ifdef AUDIO #include "audio_call.h" @@ -1004,7 +1005,7 @@ int main(int argc, char *argv[]) Tox *m = init_tox(); if (m == NULL) - exit_toxic_err("failed in main", FATALERR_NETWORKINIT); + exit_toxic_err("failed in main", FATALERR_NETWORKINIT); if (!arg_opts.ignore_data_file) { if (arg_opts.encrypt_data && !datafile_exists) @@ -1061,6 +1062,10 @@ int main(int argc, char *argv[]) print_init_messages(prompt); cleanup_init_messages(); + char avatarstr[MAX_STR_SIZE]; + snprintf(avatarstr, sizeof(avatarstr), "/avatar \"%s\"", user_settings->avatar_path); + execute(prompt->chatwin->history, prompt, m, avatarstr, GLOBAL_COMMAND_MODE); + uint64_t last_save = (uint64_t) time(NULL); uint64_t looptimer = last_save; useconds_t msleepval = 40000;