From 0ee8312a71c38e35bcc06fbdab2d04b52d45e640 Mon Sep 17 00:00:00 2001 From: jfreegman Date: Wed, 17 Jan 2024 10:29:25 -0500 Subject: [PATCH] feat: add support for friend config settings Currently we only have one setting, but this can easily be expanded for a number of things such as auto-accepting file transfers, auto-logging, custom nicknames, message notifications etc. --- doc/toxic.conf.5 | 14 ++++- doc/toxic.conf.5.asc | 6 ++ misc/toxic.conf.example | 14 +++++ src/chat.c | 4 ++ src/friendlist.c | 68 ++++++++++++++++++++ src/friendlist.h | 22 +++++++ src/global_commands.c | 20 ++---- src/misc_tools.c | 33 ++++++++++ src/misc_tools.h | 10 +++ src/settings.c | 134 +++++++++++++++++++++++++++++++++------- src/settings.h | 3 +- src/toxic.c | 11 +++- 12 files changed, 297 insertions(+), 42 deletions(-) diff --git a/doc/toxic.conf.5 b/doc/toxic.conf.5 index ef062fc38..b206025a8 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 vsnapshot -.\" Date: 2023-03-04 +.\" Date: 2023-12-19 .\" Manual: Toxic Manual .\" Source: toxic __VERSION__ .\" Language: English .\" -.TH "TOXIC\&.CONF" "5" "2023\-03\-04" "toxic __VERSION__" "Toxic Manual" +.TH "TOXIC\&.CONF" "5" "2023\-12\-19" "toxic __VERSION__" "Toxic Manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -298,6 +298,16 @@ Replace password prompt by running this command and using its output as the pass .RE .RE .PP +\fBfriends\fR +.RS 4 +Friend config settings +.PP +\fBtab_name_colour\fR +.RS 4 +The colour of the friend\(cqs tab window name\&. Valid colours are: white, red, green, cyan, purple, yellow, black\&. +.RE +.RE +.PP \fBsounds\fR .RS 4 Configuration related to notification sounds\&. Special value "silent" can be used to disable a specific notification\&. diff --git a/doc/toxic.conf.5.asc b/doc/toxic.conf.5.asc index ce00112b3..fb5bb84f3 100644 --- a/doc/toxic.conf.5.asc +++ b/doc/toxic.conf.5.asc @@ -186,6 +186,12 @@ OPTIONS Replace password prompt by running this command and using its output as the password. +*friends*:: + Friend config settings + + *tab_name_colour*;; + The colour of the friend's tab window name. Valid colours are: white, red, green, cyan, purple, yellow, black. + *sounds*:: Configuration related to notification sounds. Special value "silent" can be used to disable a specific notification. + diff --git a/misc/toxic.conf.example b/misc/toxic.conf.example index 2cbcb302a..e403d0c37 100644 --- a/misc/toxic.conf.example +++ b/misc/toxic.conf.example @@ -130,6 +130,20 @@ tox = { // chatlogs_path="/home/USERNAME/toxic_chatlogs/"; }; +// Friend config settings. Public keys are used as friend identifiers +// and must begin with the prefix "pk_". +friends = { + pk_PUBLIC_KEY_1 = { + // The colour of the friend's window tab name. + // Valid colours are: white, red, green, cyan, purple, yellow, black. + tab_name_colour="white"; + }; + + pk_PUBLIC_KEY_2 = { + tab_name_colour="white"; + }; +}; + // To disable a sound set the path to "silent" sounds = { error="__DATADIR__/sounds/ToxicError.wav"; diff --git a/src/chat.c b/src/chat.c index 582cfded0..f5dd09962 100644 --- a/src/chat.c +++ b/src/chat.c @@ -1598,6 +1598,10 @@ static void chat_onInit(ToxWindow *self, Tox *tox) line_info_init(ctx->hst); + const int tab_name_colour = friend_config_get_tab_name_colour(self->num); + + self->colour = tab_name_colour > 0 ? tab_name_colour : WHITE_BAR_FG; + chat_init_log(self, tox, nick); execute(ctx->history, self, tox, "/log", GLOBAL_COMMAND_MODE); // Print log status to screen diff --git a/src/friendlist.c b/src/friendlist.c index 4989c6913..b2cc02e49 100644 --- a/src/friendlist.c +++ b/src/friendlist.c @@ -482,6 +482,8 @@ static void friendlist_onStatusMessageChange(ToxWindow *self, uint32_t num, cons Friends.list[num].statusmsg_len = strlen(Friends.list[num].statusmsg); } +static void set_default_friend_config_settings(ToxicFriend *friend); + void friendlist_onFriendAdded(ToxWindow *self, Tox *tox, uint32_t num, bool sort) { UNUSED_VAR(self); @@ -505,6 +507,7 @@ void friendlist_onFriendAdded(ToxWindow *self, Tox *tox, uint32_t num, bool sort Friends.list[i].connection_status = TOX_CONNECTION_NONE; Friends.list[i].status = TOX_USER_STATUS_NONE; Friends.list[i].logging_on = (bool) user_settings->autolog == AUTOLOG_ON; + set_default_friend_config_settings(&Friends.list[i]); Tox_Err_Friend_Get_Public_Key pkerr; tox_friend_get_public_key(tox, num, (uint8_t *) Friends.list[i].pub_key, &pkerr); @@ -1441,6 +1444,71 @@ bool friend_get_auto_accept_files(uint32_t friendnumber) return Friends.list[friendnumber].auto_accept_files; } +/* + * Returns a pointer to the FriendSettings object associated with `public_key`. + * Returns NULL on failure. + */ +static FriendSettings *get_friend_settings_by_key(const char *public_key) +{ + char pk_bin[TOX_PUBLIC_KEY_SIZE]; + + if (tox_pk_string_to_bytes(public_key, strlen(public_key), pk_bin, sizeof(pk_bin)) != 0) { + return NULL; + } + + for (size_t i = 0; i < Friends.max_idx; ++i) { + ToxicFriend *friend = &Friends.list[i]; + + if (memcmp(pk_bin, friend->pub_key, sizeof(friend->pub_key)) == 0) { + return &friend->settings; + } + } + + return NULL; +} + +bool friend_config_set_tab_name_colour(const char *public_key, const char *colour) +{ + FriendSettings *settings = get_friend_settings_by_key(public_key); + + if (settings == NULL) { + return false; + } + + const int colour_val = colour_string_to_int(colour); + + if (colour_val < 0) { + return false; + } + + settings->tab_name_colour = colour_val; + + return true; +} + +int friend_config_get_tab_name_colour(uint32_t friendnumber) +{ + if (friendnumber >= Friends.max_idx) { + fprintf(stderr, "failed to get friend tab name colour for friend %u\n", friendnumber); + return -1; + } + + const ToxicFriend *friend = &Friends.list[friendnumber]; + + return friend->settings.tab_name_colour; +} + +static void set_default_friend_config_settings(ToxicFriend *friend) +{ + if (friend == NULL) { + return; + } + + FriendSettings *settings = &friend->settings; + + settings->tab_name_colour = DefaultFriendSettingsTabNameColour; +} + ToxWindow *new_friendlist(void) { ToxWindow *ret = calloc(1, sizeof(ToxWindow)); diff --git a/src/friendlist.h b/src/friendlist.h index d78814462..04cdb22d0 100644 --- a/src/friendlist.h +++ b/src/friendlist.h @@ -63,6 +63,14 @@ struct GameInvite { #endif // GAMES +typedef enum DefaultFriendSettings { + DefaultFriendSettingsTabNameColour = WHITE_BAR_FG, +} DefaultFriendSettings; + +typedef struct FriendSettings { + int tab_name_colour; +} FriendSettings; + typedef struct { char name[TOXIC_MAX_NAME_LENGTH + 1]; uint16_t namelength; @@ -90,6 +98,8 @@ typedef struct { struct FileTransfer file_receiver[MAX_FILES]; struct FileTransfer file_sender[MAX_FILES]; PendingFileTransfer file_send_queue[MAX_FILES]; + + FriendSettings settings; } ToxicFriend; typedef struct { @@ -142,4 +152,16 @@ void friend_set_auto_file_accept(uint32_t friendnumber, bool auto_accept); */ bool friend_get_auto_accept_files(uint32_t friendnumber); +/* + * Sets the tab name colour config option for the friend associated with `public_key` to `colour`. + * + * Return true on success. + */ +bool friend_config_set_tab_name_colour(const char *public_key, const char *colour); + +/* Returns a friend's tab name colour. + * Returns -1 on error. + */ +int friend_config_get_tab_name_colour(uint32_t friendnumber); + #endif /* end of include guard: FRIENDLIST_H */ diff --git a/src/global_commands.c b/src/global_commands.c index 982660cd9..f3f7c2a84 100644 --- a/src/global_commands.c +++ b/src/global_commands.c @@ -265,24 +265,14 @@ void cmd_colour(WINDOW *window, ToxWindow *self, Tox *tox, int argc, char (*argv const char *colour = argv[1]; - if (strcasecmp(colour, "white") == 0) { - self->colour = WHITE_BAR_FG; - } else if (strcasecmp(colour, "red") == 0) { - self->colour = RED_BAR_FG; - } else if (strcasecmp(colour, "green") == 0) { - self->colour = GREEN_BAR_FG; - } else if (strcasecmp(colour, "yellow") == 0) { - self->colour = YELLOW_BAR_FG; - } else if (strcasecmp(colour, "cyan") == 0) { - self->colour = CYAN_BAR_FG; - } else if (strcasecmp(colour, "purple") == 0) { - self->colour = PURPLE_BAR_FG; - } else if (strcasecmp(colour, "black") == 0) { - self->colour = BLACK_BAR_FG; - } else { + const int colour_val = colour_string_to_int(colour); + + if (colour_val < 0) { line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid colour"); return; } + + self->colour = colour_val; } void cmd_connect(WINDOW *window, ToxWindow *self, Tox *tox, int argc, char (*argv)[MAX_STR_SIZE]) diff --git a/src/misc_tools.c b/src/misc_tools.c index 278e5e294..6ef018c06 100644 --- a/src/misc_tools.c +++ b/src/misc_tools.c @@ -751,3 +751,36 @@ unsigned int rand_not_secure(void) return n; } + +int colour_string_to_int(const char *colour) +{ + if (strcasecmp(colour, "white") == 0) { + return WHITE_BAR_FG; + } + + if (strcasecmp(colour, "red") == 0) { + return RED_BAR_FG; + } + + if (strcasecmp(colour, "green") == 0) { + return GREEN_BAR_FG; + } + + if (strcasecmp(colour, "yellow") == 0) { + return YELLOW_BAR_FG; + } + + if (strcasecmp(colour, "cyan") == 0) { + return CYAN_BAR_FG; + } + + if (strcasecmp(colour, "purple") == 0) { + return PURPLE_BAR_FG; + } + + if (strcasecmp(colour, "black") == 0) { + return BLACK_BAR_FG; + } + + return -1; +} diff --git a/src/misc_tools.h b/src/misc_tools.h index 769ba6e1c..14657d847 100644 --- a/src/misc_tools.h +++ b/src/misc_tools.h @@ -250,4 +250,14 @@ unsigned int rand_range_not_secure(unsigned int upper_bound); */ unsigned int rand_not_secure(void); +/* + * Returns an integer associated with an ncurses foreground colour, per the C_COLOURS enum + * in windows.h. + * + * Valid colour strings are: white, red, green, cyan, purple, yellow, black. + * + * Returns -1 if colour is invalid. + */ +int colour_string_to_int(const char *colour); + #endif /* MISC_TOOLS_H */ diff --git a/src/settings.c b/src/settings.c index 1588c5b45..0b54ffbbc 100644 --- a/src/settings.c +++ b/src/settings.c @@ -26,6 +26,7 @@ #include #include "configdir.h" +#include "friendlist.h" #include "misc_tools.h" #include "notify.h" #include "toxic.h" @@ -273,6 +274,14 @@ static const struct sound_strings { }; #endif +static const struct friend_strings { + const char *self; + const char *tab_name_colour; +} friend_strings = { + "friends", + "tab_name_colour", +}; + static int key_parse(const char **bind) { int len = strlen(*bind); @@ -303,10 +312,104 @@ static void set_key_binding(int *key, const char **bind) } } -int settings_load(struct user_settings *s, const char *patharg) +static bool get_settings_path(char *path, unsigned int path_size, const char *patharg) +{ + if (patharg != NULL) { + snprintf(path, path_size, "%s", patharg); + return true; + } + + char *user_config_dir = get_user_config_dir(); + snprintf(path, path_size, "%s%stoxic.conf", user_config_dir, CONFIGDIR); + free(user_config_dir); + + /* make sure path exists or is created on first time running */ + if (!file_exists(path)) { + FILE *fp = fopen(path, "w"); + + if (fp == NULL) { + fprintf(stderr, "failed to open config path: %s\n", path); + return false; + } + + fclose(fp); + } + + return true; +} + +#define TOXIC_CONFIG_PUBLIC_KEY_PREFIX "pk_" + +int settings_load_friends(const char *patharg) +{ + config_t cfg[1]; + const char *str = NULL; + + config_init(cfg); + + char path[MAX_STR_SIZE] = {0}; + + if (!get_settings_path(path, sizeof(path), patharg)) { + config_destroy(cfg); + return -1; + } + + if (!config_read_file(cfg, path)) { + fprintf(stderr, "settings read error: %s:%d - %s\n", config_error_file(cfg), + config_error_line(cfg), config_error_text(cfg)); + config_destroy(cfg); + return -2; + } + + const config_setting_t *setting = config_lookup(cfg, friend_strings.self); + + if (setting == NULL) { + config_destroy(cfg); + return -3 ; + } + + const int num_friends = config_setting_length(setting); + + for (int i = 0; i < num_friends; ++i) { + const config_setting_t *keys = config_setting_get_elem(setting, i); + + if (keys == NULL) { + continue; + } + + const char *public_key = config_setting_name(keys); + + if (public_key == NULL) { + continue; + } + + const uint16_t prefix_len = (uint16_t) strlen(TOXIC_CONFIG_PUBLIC_KEY_PREFIX); + + if (strlen(public_key) != TOX_PUBLIC_KEY_SIZE * 2 + prefix_len) { + fprintf(stderr, "config error: invalid public key: %s\n", public_key); + continue; + } + + if (memcmp(public_key, TOXIC_CONFIG_PUBLIC_KEY_PREFIX, prefix_len) != 0) { + fprintf(stderr, "config error: invalid public key prefix\n"); + continue; + } + + if (config_setting_lookup_string(keys, friend_strings.tab_name_colour, &str)) { + if (!friend_config_set_tab_name_colour(&public_key[prefix_len], str)) { + fprintf(stderr, "config error: failed to set tab name colour for %s: (colour: %s)\n", public_key, str); + } + } + } + + config_destroy(cfg); + return 0; +} + +int settings_load_main(struct user_settings *s, const char *patharg) { config_t cfg[1]; - config_setting_t *setting; + config_setting_t *setting = NULL; const char *str = NULL; /* Load default settings */ @@ -320,29 +423,16 @@ int settings_load(struct user_settings *s, const char *patharg) config_init(cfg); - char path[MAX_STR_SIZE]; - - /* use default config file path */ - if (patharg == NULL) { - char *user_config_dir = get_user_config_dir(); - snprintf(path, sizeof(path), "%s%stoxic.conf", user_config_dir, CONFIGDIR); - free(user_config_dir); + char path[MAX_STR_SIZE] = {0}; - /* make sure path exists or is created on first time running */ - if (!file_exists(path)) { - FILE *fp = fopen(path, "w"); - - if (fp == NULL) { - return -1; - } - - fclose(fp); - } - } else { - snprintf(path, sizeof(path), "%s", patharg); + if (!get_settings_path(path, sizeof(path), patharg)) { + config_destroy(cfg); + return -1; } if (!config_read_file(cfg, path)) { + fprintf(stderr, "settings read error: %s:%d - %s\n", config_error_file(cfg), + config_error_line(cfg), config_error_text(cfg)); config_destroy(cfg); return -1; } @@ -548,6 +638,7 @@ int settings_load(struct user_settings *s, const char *patharg) #ifdef AUDIO + /* Audio */ if ((setting = config_lookup(cfg, audio_strings.self)) != NULL) { config_setting_lookup_int(setting, audio_strings.input_device, &s->audio_in_dev); s->audio_in_dev = s->audio_in_dev < 0 || s->audio_in_dev > MAX_DEVICES ? 0 : s->audio_in_dev; @@ -571,6 +662,7 @@ int settings_load(struct user_settings *s, const char *patharg) #ifdef SOUND_NOTIFY + /* Sound notifications */ if ((setting = config_lookup(cfg, sound_strings.self)) != NULL) { if ((config_setting_lookup_string(setting, sound_strings.notif_error, &str) != CONFIG_TRUE) || !set_sound(notif_error, str)) { diff --git a/src/settings.h b/src/settings.h index 84dd4b8d1..71b1d1d02 100644 --- a/src/settings.h +++ b/src/settings.h @@ -141,6 +141,7 @@ enum settings_values { #define LOG_TIMESTAMP_DEFAULT "%Y/%m/%d [%H:%M:%S]" #define MPLEX_AWAY_NOTE "Away from keyboard, be back soon!" -int settings_load(struct user_settings *s, const char *patharg); +int settings_load_main(struct user_settings *s, const char *patharg); +int settings_load_friends(const char *patharg); #endif /* SETTINGS_H */ diff --git a/src/toxic.c b/src/toxic.c index 91e190631..74abc61c8 100644 --- a/src/toxic.c +++ b/src/toxic.c @@ -1629,7 +1629,6 @@ int main(int argc, char **argv) first_time_encrypt("Encrypt existing data file? Y/n (q to quit)"); } - /* init user_settings struct and load settings from conf file */ user_settings = calloc(1, sizeof(struct user_settings)); @@ -1637,9 +1636,9 @@ int main(int argc, char **argv) exit_toxic_err("failed in main", FATALERR_MEMORY); } - const char *p = arg_opts.config_path[0] ? arg_opts.config_path : NULL; + const char *config_path = arg_opts.config_path[0] ? arg_opts.config_path : NULL; - if (settings_load(user_settings, p) == -1) { + if (settings_load_main(user_settings, config_path) == -1) { queue_init_message("Failed to load user settings"); } @@ -1678,6 +1677,12 @@ int main(int argc, char **argv) arg_opts.encrypt_data = 0; } + const int fs_ret = settings_load_friends(config_path); + + if (fs_ret != 0) { + queue_init_message("Failed to load friend config settings: error %d", fs_ret); + } + init_term(); prompt = init_windows(toxic->tox);