diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/command/command.c | 82 | ||||
-rw-r--r-- | src/command/commands.c | 333 | ||||
-rw-r--r-- | src/command/commands.h | 1 | ||||
-rw-r--r-- | src/main.c | 1 | ||||
-rw-r--r-- | src/server_events.c | 30 | ||||
-rw-r--r-- | src/server_events.h | 3 | ||||
-rw-r--r-- | src/tools/autocomplete.c | 10 | ||||
-rw-r--r-- | src/ui/core.c | 319 | ||||
-rw-r--r-- | src/ui/statusbar.c | 9 | ||||
-rw-r--r-- | src/ui/titlebar.c | 10 | ||||
-rw-r--r-- | src/ui/ui.h | 9 | ||||
-rw-r--r-- | src/ui/window.c | 2 | ||||
-rw-r--r-- | src/ui/window.h | 3 | ||||
-rw-r--r-- | src/ui/windows.c | 21 | ||||
-rw-r--r-- | src/xmpp/capabilities.c | 13 | ||||
-rw-r--r-- | src/xmpp/form.c | 628 | ||||
-rw-r--r-- | src/xmpp/form.h | 43 | ||||
-rw-r--r-- | src/xmpp/iq.c | 125 | ||||
-rw-r--r-- | src/xmpp/stanza.c | 161 | ||||
-rw-r--r-- | src/xmpp/stanza.h | 17 | ||||
-rw-r--r-- | src/xmpp/xmpp.h | 59 |
21 files changed, 1718 insertions, 161 deletions
diff --git a/src/command/command.c b/src/command/command.c index 6d707afe..6e233238 100644 --- a/src/command/command.c +++ b/src/command/command.c @@ -65,6 +65,7 @@ #include "xmpp/xmpp.h" #include "xmpp/bookmark.h" #include "ui/ui.h" +#include "ui/windows.h" typedef char*(*autocompleter)(char*, int*); @@ -86,7 +87,7 @@ static char * _statuses_autocomplete(char *input, int *size); static char * _alias_autocomplete(char *input, int *size); static char * _join_autocomplete(char *input, int *size); static char * _log_autocomplete(char *input, int *size); -static char * _room_autocomplete(char *input, int *size); +static char * _form_autocomplete(char *input, int *size); GHashTable *commands = NULL; @@ -305,11 +306,27 @@ static struct cmd_t command_defs[] = NULL } } }, { "/room", - cmd_room, parse_args, 2, 2, NULL, - { "/room config accept|cancel", "Room configuration.", - { "/room config accept|cncel", - "-------------------------", - "Accept or cancel default room configuration.", + cmd_room, parse_args, 1, 1, NULL, + { "/room accept|destroy|config", "Room configuration.", + { "/room accept|destroy|config", + "---------------------------", + "accept - Accept default room configuration.", + "destroy - Reject default room configuration.", + "config - Edit room configuration.", + NULL } } }, + + { "/form", + cmd_form, parse_args, 1, 3, NULL, + { "/form show|submit|cancel|set|add|remove|help [tag] [value]", "Form manipulation.", + { "/form show|submit|cancel|set|add|remove|help [tag] [value]", + "----------------------------------------------------------", + "set tag value - Set tagged form field to value.", + "add tag value - Add value to tagged form field.", + "remove tag value - Remove value from tagged form field.", + "show - Show the current form.", + "submit - Submit the current form.", + "cancel - Cancel changes to the current form.", + "help [tag] - Display help for form, or a specific field.", NULL } } }, { "/rooms", @@ -953,7 +970,7 @@ static Autocomplete alias_ac; static Autocomplete aliases_ac; static Autocomplete join_property_ac; static Autocomplete room_ac; -static Autocomplete room_config_ac; +static Autocomplete form_ac; /* * Initialise command autocompleter and history @@ -1206,11 +1223,18 @@ cmd_init(void) autocomplete_add(alias_ac, "list"); room_ac = autocomplete_new(); + autocomplete_add(room_ac, "accept"); + autocomplete_add(room_ac, "destroy"); autocomplete_add(room_ac, "config"); - room_config_ac = autocomplete_new(); - autocomplete_add(room_config_ac, "accept"); - autocomplete_add(room_config_ac, "cancel"); + form_ac = autocomplete_new(); + autocomplete_add(form_ac, "submit"); + autocomplete_add(form_ac, "cancel"); + autocomplete_add(form_ac, "show"); + autocomplete_add(form_ac, "set"); + autocomplete_add(form_ac, "add"); + autocomplete_add(form_ac, "remove"); + autocomplete_add(form_ac, "help"); cmd_history_init(); } @@ -1256,7 +1280,7 @@ cmd_uninit(void) autocomplete_free(aliases_ac); autocomplete_free(join_property_ac); autocomplete_free(room_ac); - autocomplete_free(room_config_ac); + autocomplete_free(form_ac); } gboolean @@ -1381,7 +1405,7 @@ cmd_reset_autocomplete() autocomplete_reset(aliases_ac); autocomplete_reset(join_property_ac); autocomplete_reset(room_ac); - autocomplete_reset(room_config_ac); + autocomplete_reset(form_ac); bookmark_autocomplete_reset(); } @@ -1631,8 +1655,8 @@ _cmd_complete_parameters(char *input, int *size) } } - gchar *cmds[] = { "/help", "/prefs", "/disco", "/close", "/wins" }; - Autocomplete completers[] = { help_ac, prefs_ac, disco_ac, close_ac, wins_ac }; + gchar *cmds[] = { "/help", "/prefs", "/disco", "/close", "/wins", "/room" }; + Autocomplete completers[] = { help_ac, prefs_ac, disco_ac, close_ac, wins_ac, room_ac }; for (i = 0; i < ARRAY_SIZE(cmds); i++) { result = autocomplete_param_with_ac(input, size, cmds[i], completers[i], TRUE); @@ -1660,7 +1684,7 @@ _cmd_complete_parameters(char *input, int *size) g_hash_table_insert(ac_funcs, "/statuses", _statuses_autocomplete); g_hash_table_insert(ac_funcs, "/alias", _alias_autocomplete); g_hash_table_insert(ac_funcs, "/join", _join_autocomplete); - g_hash_table_insert(ac_funcs, "/room", _room_autocomplete); + g_hash_table_insert(ac_funcs, "/form", _form_autocomplete); char parsed[*size+1]; i = 0; @@ -2071,16 +2095,34 @@ _theme_autocomplete(char *input, int *size) } static char * -_room_autocomplete(char *input, int *size) +_form_autocomplete(char *input, int *size) { char *result = NULL; - result = autocomplete_param_with_ac(input, size, "/room config", room_config_ac, TRUE); - if (result != NULL) { - return result; + ProfWin *current = wins_get_current(); + if (current != NULL) { + DataForm *form = current->form; + if (form != NULL) { + result = autocomplete_param_with_ac(input, size, "/form set", form->tag_ac, TRUE); + if (result != NULL) { + return result; + } + result = autocomplete_param_with_ac(input, size, "/form add", form->tag_ac, TRUE); + if (result != NULL) { + return result; + } + result = autocomplete_param_with_ac(input, size, "/form remove", form->tag_ac, TRUE); + if (result != NULL) { + return result; + } + result = autocomplete_param_with_ac(input, size, "/form help", form->tag_ac, TRUE); + if (result != NULL) { + return result; + } + } } - result = autocomplete_param_with_ac(input, size, "/room", room_ac, TRUE); + result = autocomplete_param_with_ac(input, size, "/form", form_ac, TRUE); if (result != NULL) { return result; } diff --git a/src/command/commands.c b/src/command/commands.c index 7b49ad61..fa439955 100644 --- a/src/command/commands.c +++ b/src/command/commands.c @@ -634,12 +634,9 @@ cmd_help(gchar **args, struct cmd_help_t help) } cons_show(""); - if (help_text != NULL) { - int i; - for (i = 0; help_text[i] != NULL; i++) { - cons_show(help_text[i]); - } + ProfWin *console = wins_get_console(); + ui_show_lines(console, help_text); } else { cons_show("No such command."); } @@ -1788,7 +1785,7 @@ cmd_decline(gchar **args, struct cmd_help_t help) } gboolean -cmd_room(gchar **args, struct cmd_help_t help) +cmd_form(gchar **args, struct cmd_help_t help) { jabber_conn_status_t conn_status = jabber_get_connection_status(); @@ -1798,18 +1795,283 @@ cmd_room(gchar **args, struct cmd_help_t help) } win_type_t win_type = ui_current_win_type(); - if (win_type != WIN_MUC) { - cons_show("Command /room only usable in chat rooms."); + if (win_type != WIN_MUC_CONFIG) { + cons_show("Command '/form' does not apply to this window."); return TRUE; } - if (g_strcmp0(args[0], "config") != 0) { + if ((g_strcmp0(args[0], "submit") != 0) && + (g_strcmp0(args[0], "cancel") != 0) && + (g_strcmp0(args[0], "show") != 0) && + (g_strcmp0(args[0], "help") != 0) && + (g_strcmp0(args[0], "set") != 0) && + (g_strcmp0(args[0], "add") != 0) && + (g_strcmp0(args[0], "remove") != 0)) { cons_show("Usage: %s", help.usage); return TRUE; } - if ((g_strcmp0(args[1], "accept") != 0) && - (g_strcmp0(args[1], "cancel") != 0)) { + char *recipient = ui_current_recipient(); + ProfWin *current = wins_get_current(); + gchar **split_recipient = g_strsplit(recipient, " ", 2); + char *room = split_recipient[0]; + + if (g_strcmp0(args[0], "show") == 0) { + ui_show_form(current, room, current->form); + g_strfreev(split_recipient); + return TRUE; + } + + if (g_strcmp0(args[0], "help") == 0) { + char *tag = args[1]; + if (tag != NULL) { + ui_show_form_field_help(current, current->form, tag); + } else { + ui_show_form_help(current, current->form); + + const gchar **help_text = NULL; + Command *command = g_hash_table_lookup(commands, "/form"); + + if (command != NULL) { + help_text = command->help.long_help; + } + + ui_show_lines(current, help_text); + } + ui_current_print_line(""); + g_strfreev(split_recipient); + return TRUE; + } + + if (g_strcmp0(args[0], "submit") == 0) { + iq_submit_room_config(room, current->form); + + } + if (g_strcmp0(args[0], "cancel") == 0) { + iq_room_config_cancel(room); + } + if (g_strcmp0(args[0], "set") == 0) { + char *tag = NULL; + char *value = NULL; + if (args[1] != NULL) { + tag = args[1]; + } else { + ui_current_print_line("/room set command requires a field tag and value"); + g_strfreev(split_recipient); + return TRUE; + } + if (args[2] != NULL) { + value = args[2]; + } else { + ui_current_print_line("/room set command requires a field tag and value"); + g_strfreev(split_recipient); + return TRUE; + } + if (!form_tag_exists(current->form, tag)) { + ui_current_print_line("Form does not contain a field with tag %s", tag); + } else { + form_field_type_t field_type = form_get_field_type(current->form, tag); + gboolean valid = FALSE; + switch (field_type) { + case FIELD_TEXT_SINGLE: + case FIELD_TEXT_PRIVATE: + case FIELD_JID_SINGLE: + form_set_value(current->form, tag, value); + ui_show_form_field(current, current->form, tag); + break; + case FIELD_BOOLEAN: + if (g_strcmp0(value, "on") == 0) { + form_set_value(current->form, tag, "1"); + ui_show_form_field(current, current->form, tag); + } else if (g_strcmp0(value, "off") == 0) { + form_set_value(current->form, tag, "0"); + ui_show_form_field(current, current->form, tag); + } else { + ui_current_print_line("Value %s not valid for boolean field: %s", value, tag); + } + break; + case FIELD_LIST_SINGLE: + valid = form_field_contains_option(current->form, tag, value); + if (valid == TRUE) { + form_set_value(current->form, tag, value); + ui_show_form_field(current, current->form, tag); + } else { + ui_current_print_line("Value %s not a valid option for field: %s", value, tag); + } + break; + default: + ui_current_print_line("Set command not valid for field: %s", tag); + break; + } + } + } + + if (g_strcmp0(args[0], "add") == 0) { + char *tag = NULL; + char *value = NULL; + if (args[1] != NULL) { + tag = args[1]; + } else { + ui_current_print_line("/room add command requires a field tag and value"); + g_strfreev(split_recipient); + return TRUE; + } + if (args[2] != NULL) { + value = args[2]; + } else { + ui_current_print_line("/room add command requires a field tag and value"); + g_strfreev(split_recipient); + return TRUE; + } + if (!form_tag_exists(current->form, tag)) { + ui_current_print_line("Form does not contain a field with tag %s", tag); + } else { + form_field_type_t field_type = form_get_field_type(current->form, tag); + gboolean valid = FALSE; + gboolean added = FALSE; + switch (field_type) { + case FIELD_LIST_MULTI: + valid = form_field_contains_option(current->form, tag, value); + if (valid) { + added = form_add_unique_value(current->form, tag, value); + if (added) { + ui_show_form_field(current, current->form, tag); + } else { + ui_current_print_line("Value %s already selected for %s", value, tag); + } + } else { + ui_current_print_line("Value %s not a valid option for field: %s", value, tag); + } + break; + case FIELD_TEXT_MULTI: + form_add_value(current->form, tag, value); + ui_show_form_field(current, current->form, tag); + break; + case FIELD_JID_MULTI: + added = form_add_unique_value(current->form, tag, value); + if (added) { + ui_show_form_field(current, current->form, tag); + } else { + ui_current_print_line("JID %s already exists in %s", value, tag); + } + break; + default: + ui_current_print_line("Add command not valid for field: %s", tag); + break; + } + } + } + + if (g_strcmp0(args[0], "remove") == 0) { + char *tag = NULL; + char *value = NULL; + if (args[1] != NULL) { + tag = args[1]; + } else { + ui_current_print_line("/room remove command requires a field tag and value"); + g_strfreev(split_recipient); + return TRUE; + } + if (args[2] != NULL) { + value = args[2]; + } else { + ui_current_print_line("/room remove command requires a field tag and value"); + g_strfreev(split_recipient); + return TRUE; + } + if (!form_tag_exists(current->form, tag)) { + ui_current_print_line("Form does not contain a field with tag %s", tag); + } else { + form_field_type_t field_type = form_get_field_type(current->form, tag); + gboolean valid = FALSE; + gboolean removed = FALSE; + switch (field_type) { + case FIELD_LIST_MULTI: + valid = form_field_contains_option(current->form, tag, value); + if (valid == TRUE) { + removed = form_remove_value(current->form, tag, value); + if (removed) { + ui_show_form_field(current, current->form, tag); + } else { + ui_current_print_line("Value %s is not currently set for %s", value, tag); + } + } else { + ui_current_print_line("Value %s not a valid option for field: %s", value, tag); + } + break; + case FIELD_TEXT_MULTI: + if (!g_str_has_prefix(value, "val")) { + ui_current_print_line("No such value %s for %s", value, tag); + break; + } + if (strlen(value) < 4) { + ui_current_print_line("No such value %s for %s", value, tag); + break; + } + + int index = strtol(&value[3], NULL, 10); + if ((index < 1) || (index > form_get_value_count(current->form, tag))) { + ui_current_print_line("No such value %s for %s", value, tag); + break; + } + + removed = form_remove_text_multi_value(current->form, tag, index); + if (removed) { + ui_show_form_field(current, current->form, tag); + } else { + ui_current_print_line("Could not remove %s from %s", value, tag); + } + break; + case FIELD_JID_MULTI: + removed = form_remove_value(current->form, tag, value); + if (removed) { + ui_show_form_field(current, current->form, tag); + } else { + ui_current_print_line("Field %s does not contain %s", tag, value); + } + break; + default: + ui_current_print_line("Remove command not valid for field: %s", tag); + break; + } + } + } + + if ((g_strcmp0(args[0], "submit") == 0) || + (g_strcmp0(args[0], "cancel") == 0)) { + wins_close_current(); + current = wins_get_by_recipient(room); + if (current == NULL) { + current = wins_get_console(); + } + int num = wins_get_num(current); + ui_switch_win(num); + } + + g_strfreev(split_recipient); + + return TRUE; +} + +gboolean +cmd_room(gchar **args, struct cmd_help_t help) +{ + jabber_conn_status_t conn_status = jabber_get_connection_status(); + + if (conn_status != JABBER_CONNECTED) { + cons_show("You are not currently connected."); + return TRUE; + } + + win_type_t win_type = ui_current_win_type(); + if (win_type != WIN_MUC) { + cons_show("Command '/room' does not apply to this window."); + return TRUE; + } + + if ((g_strcmp0(args[0], "accept") != 0) && + (g_strcmp0(args[0], "destroy") != 0) && + (g_strcmp0(args[0], "config") != 0)) { cons_show("Usage: %s", help.usage); return TRUE; } @@ -1817,26 +2079,42 @@ cmd_room(gchar **args, struct cmd_help_t help) char *room = ui_current_recipient(); ProfWin *window = wins_get_by_recipient(room); int num = wins_get_num(window); + int ui_index = num; if (ui_index == 10) { ui_index = 0; } - gboolean requires_config = muc_requires_config(room); - if (!requires_config) { - win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", "Current room does not require configuration."); - return TRUE; + + if (g_strcmp0(args[0], "accept") == 0) { + gboolean requires_config = muc_requires_config(room); + if (!requires_config) { + win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", "Current room does not require configuration."); + return TRUE; + } else { + iq_confirm_instant_room(room); + muc_set_requires_config(room, FALSE); + win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", "Room unlocked."); + cons_show("Room unlocked: %s (%d)", room, ui_index); + return TRUE; + } } - if (g_strcmp0(args[1], "accept") == 0) { - iq_confirm_instant_room(room); - muc_set_requires_config(room, FALSE); - win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", "Room unlocked."); - cons_show("Room unlocked: %s (%d)", room, ui_index); + if (g_strcmp0(args[0], "destroy") == 0) { + iq_destroy_instant_room(room); return TRUE; } - if (g_strcmp0(args[1], "cancel") == 0) { - iq_destroy_instant_room(room); + if (g_strcmp0(args[0], "config") == 0) { + GString *win_title = g_string_new(room); + g_string_append(win_title, " config"); + ProfWin *window = wins_get_by_recipient(win_title->str); + g_string_free(win_title, TRUE); + if (window != NULL) { + num = wins_get_num(window); + ui_switch_win(num); + } else { + iq_request_room_config_form(room); + } return TRUE; } @@ -2238,6 +2516,17 @@ cmd_close(gchar **args, struct cmd_help_t help) return TRUE; } + // check for unsaved form + if (ui_win_has_unsaved_form(index)) { + ProfWin *window = wins_get_current(); + if (wins_is_current(window)) { + ui_current_print_line("You have unsaved changes, use /form submit or /form cancel"); + } else { + cons_show("Cannot close form window with unsaved changes, use /form submit or /form cancel"); + } + return TRUE; + } + // handle leaving rooms, or chat if (conn_status == JABBER_CONNECTED) { ui_close_connected_win(index); diff --git a/src/command/commands.h b/src/command/commands.h index 7eddc127..528c78aa 100644 --- a/src/command/commands.h +++ b/src/command/commands.h @@ -125,5 +125,6 @@ gboolean cmd_xa(gchar **args, struct cmd_help_t help); gboolean cmd_alias(gchar **args, struct cmd_help_t help); gboolean cmd_xmlconsole(gchar **args, struct cmd_help_t help); gboolean cmd_ping(gchar **args, struct cmd_help_t help); +gboolean cmd_form(gchar **args, struct cmd_help_t help); #endif diff --git a/src/main.c b/src/main.c index 898cdaf3..e4643d2e 100644 --- a/src/main.c +++ b/src/main.c @@ -63,6 +63,7 @@ _init_modules(void) message_init_module(); presence_init_module(); roster_init_module(); + form_init_module(); ui_init_module(); console_init_module(); diff --git a/src/server_events.c b/src/server_events.c index 408e4e3e..4e48aecb 100644 --- a/src/server_events.c +++ b/src/server_events.c @@ -464,6 +464,36 @@ handle_room_destroy(const char * const room) } void +handle_room_configure(const char * const room, DataForm *form) +{ + ui_handle_room_configuration(room, form); +} + +void +handle_room_config_submit_result(void) +{ + ui_handle_room_config_submit_result(); +} + +void +handle_room_configuration_form_error(const char * const room, const char * const message) +{ + if (room != NULL) { + if (message != NULL) { + cons_show_error("Room config error for %s: %s.", room, message); + } else { + cons_show_error("Room config error for %s.", room); + } + } else { + if (message != NULL) { + cons_show_error("Room config error: %s.", message); + } else { + cons_show_error("Room config error."); + } + } +} + +void handle_room_roster_complete(const char * const room) { if (muc_room_is_autojoin(room)) { diff --git a/src/server_events.h b/src/server_events.h index aaad9923..06e851f0 100644 --- a/src/server_events.h +++ b/src/server_events.h @@ -96,5 +96,8 @@ void handle_presence_error(const char *from, const char * const type, void handle_xmpp_stanza(const char * const msg); void handle_ping_result(const char * const from, int millis); void handle_ping_error_result(const char * const from, const char * const error); +void handle_room_configure(const char * const room, DataForm *form); +void handle_room_configuration_form_error(const char * const from, const char * const message); +void handle_room_config_submit_result(void); #endif diff --git a/src/tools/autocomplete.c b/src/tools/autocomplete.c index 3361c10b..efff6ee0 100644 --- a/src/tools/autocomplete.c +++ b/src/tools/autocomplete.c @@ -62,10 +62,12 @@ autocomplete_new(void) void autocomplete_clear(Autocomplete ac) { - g_slist_free_full(ac->items, free); - ac->items = NULL; + if (ac != NULL) { + g_slist_free_full(ac->items, free); + ac->items = NULL; - autocomplete_reset(ac); + autocomplete_reset(ac); + } } void @@ -335,4 +337,4 @@ _search_from(Autocomplete ac, GSList *curr, gboolean quote) } return NULL; -} \ No newline at end of file +} diff --git a/src/ui/core.c b/src/ui/core.c index 83e5c1a2..53d3a2ad 100644 --- a/src/ui/core.c +++ b/src/ui/core.c @@ -633,7 +633,7 @@ _ui_close_all_wins(void) while (curr != NULL) { int num = GPOINTER_TO_INT(curr->data); - if (num != 1) { + if ((num != 1) && (!ui_win_has_unsaved_form(num))) { if (conn_status == JABBER_CONNECTED) { ui_close_connected_win(num); } @@ -660,7 +660,7 @@ _ui_close_read_wins(void) while (curr != NULL) { int num = GPOINTER_TO_INT(curr->data); - if ((num != 1) && (ui_win_unread(num) == 0)) { + if ((num != 1) && (ui_win_unread(num) == 0) && (!ui_win_has_unsaved_form(num))) { if (conn_status == JABBER_CONNECTED) { ui_close_connected_win(num); } @@ -676,6 +676,20 @@ _ui_close_read_wins(void) return count; } +static gboolean +_ui_win_has_unsaved_form(int num) +{ + ProfWin *window = wins_get_by_num(num); + + if (window->type != WIN_MUC_CONFIG) { + return FALSE; + } + if (window->form == NULL) { + return FALSE; + } + return window->form->modified; +} + GString * _get_recipient_string(ProfWin *window) { @@ -1602,7 +1616,8 @@ _ui_room_requires_config(const char * const room_jid) ui_index = 0; } - win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", "Room requires configuration, use '/room config accept' or '/room config cancel'"); + win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", + "Room requires configuration, use '/room config accept' or '/room config destroy'"); // currently in groupchat window if (wins_is_current(window)) { @@ -1871,6 +1886,296 @@ _ui_draw_term_title(void) } static void +_ui_handle_form_field(ProfWin *window, char *tag, FormField *field) +{ + win_save_vprint(window, '-', NULL, NO_EOL, COLOUR_AWAY, "", "[%s] ", tag); + win_save_vprint(window, '-', NULL, NO_EOL | NO_DATE, 0, "", "%s", field->label); + if (field->required) { + win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", " (required): "); + } else { + win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", ": "); + } + + GSList *values = field->values; + GSList *curr_value = values; + + switch (field->type_t) { + case FIELD_HIDDEN: + break; + case FIELD_TEXT_SINGLE: + if (curr_value != NULL) { + char *value = curr_value->data; + if (value != NULL) { + if (g_strcmp0(field->var, "muc#roomconfig_roomsecret") == 0) { + win_save_print(window, '-', NULL, NO_DATE | NO_EOL, COLOUR_ONLINE, "", "[hidden]"); + } else { + win_save_print(window, '-', NULL, NO_DATE | NO_EOL, COLOUR_ONLINE, "", value); + } + } + } + win_save_newline(window); + break; + case FIELD_TEXT_PRIVATE: + if (curr_value != NULL) { + char *value = curr_value->data; + if (value != NULL) { + win_save_print(window, '-', NULL, NO_DATE | NO_EOL, COLOUR_ONLINE, "", "[hidden]"); + } + } + win_save_newline(window); + break; + case FIELD_TEXT_MULTI: + win_save_newline(window); + int index = 1; + while (curr_value != NULL) { + char *value = curr_value->data; + GString *val_tag = g_string_new(""); + g_string_printf(val_tag, "val%d", index++); + win_save_vprint(window, '-', NULL, 0, COLOUR_ONLINE, "", " [%s] %s", val_tag->str, value); + g_string_free(val_tag, TRUE); + curr_value = g_slist_next(curr_value); + } + break; + case FIELD_BOOLEAN: + if (curr_value == NULL) { + win_save_print(window, '-', NULL, NO_DATE, COLOUR_OFFLINE, "", "FALSE"); + } else { + char *value = curr_value->data; + if (value == NULL) { + win_save_print(window, '-', NULL, NO_DATE, COLOUR_OFFLINE, "", "FALSE"); + } else { + if (g_strcmp0(value, "0") == 0) { + win_save_print(window, '-', NULL, NO_DATE, COLOUR_OFFLINE, "", "FALSE"); + } else { + win_save_print(window, '-', NULL, NO_DATE, COLOUR_ONLINE, "", "TRUE"); + } + } + } + break; + case FIELD_LIST_SINGLE: + if (curr_value != NULL) { + win_save_newline(window); + char *value = curr_value->data; + GSList *options = field->options; + GSList *curr_option = options; + while (curr_option != NULL) { + FormOption *option = curr_option->data; + if (g_strcmp0(option->value, value) == 0) { + win_save_vprint(window, '-', NULL, 0, COLOUR_ONLINE, "", " [%s] %s", option->value, option->label); + } else { + win_save_vprint(window, '-', NULL, 0, COLOUR_OFFLINE, "", " [%s] %s", option->value, option->label); + } + curr_option = g_slist_next(curr_option); + } + } + break; + case FIELD_LIST_MULTI: + if (curr_value != NULL) { + win_save_newline(window); + GSList *options = field->options; + GSList *curr_option = options; + while (curr_option != NULL) { + FormOption *option = curr_option->data; + if (g_slist_find_custom(curr_value, option->value, (GCompareFunc)g_strcmp0) != NULL) { + win_save_vprint(window, '-', NULL, 0, COLOUR_ONLINE, "", " [%s] %s", option->value, option->label); + } else { + win_save_vprint(window, '-', NULL, 0, COLOUR_OFFLINE, "", " [%s] %s", option->value, option->label); + } + curr_option = g_slist_next(curr_option); + } + } + break; + case FIELD_JID_SINGLE: + if (curr_value != NULL) { + char *value = curr_value->data; + if (value != NULL) { + win_save_print(window, '-', NULL, NO_DATE | NO_EOL, COLOUR_ONLINE, "", value); + } + } + win_save_newline(window); + break; + case FIELD_JID_MULTI: + win_save_newline(window); + while (curr_value != NULL) { + char *value = curr_value->data; + win_save_vprint(window, '-', NULL, 0, COLOUR_ONLINE, "", " %s", value); + curr_value = g_slist_next(curr_value); + } + break; + case FIELD_FIXED: + if (curr_value != NULL) { + char *value = curr_value->data; + if (value != NULL) { + win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", value); + } + } + win_save_newline(window); + break; + default: + break; + } +} + +static void +_ui_show_form(ProfWin *window, const char * const room, DataForm *form) +{ + if (form->title != NULL) { + win_save_print(window, '-', NULL, 0, 0, "", form->title); + } else { + win_save_vprint(window, '-', NULL, 0, 0, "", "Configuration for room %s.", room); + } + win_save_print(window, '-', NULL, 0, 0, "", ""); + + ui_show_form_help(window, form); + + GSList *fields = form->fields; + GSList *curr_field = fields; + while (curr_field != NULL) { + FormField *field = curr_field->data; + + if (g_strcmp0(field->type, "hidden") != 0) { + char *tag = g_hash_table_lookup(form->var_to_tag, field->var); + _ui_handle_form_field(window, tag, field); + } + + curr_field = g_slist_next(curr_field); + } + + win_save_println(window, ""); +} + +static void +_ui_show_form_field(ProfWin *window, DataForm *form, char *tag) +{ + FormField *field = form_get_field_by_tag(form, tag); + _ui_handle_form_field(window, tag, field); + win_save_println(window, ""); +} + +static void +_ui_handle_room_configuration(const char * const room, DataForm *form) +{ + GString *title = g_string_new(room); + g_string_append(title, " config"); + ProfWin *window = wins_new(title->str, WIN_MUC_CONFIG); + g_string_free(title, TRUE); + + window->form = form; + + int num = wins_get_num(window); + ui_switch_win(num); + + ui_show_form(window, room, form); +} + +static void +_ui_handle_room_config_submit_result(void) +{ + cons_show("GOT ROOM CONFIG SUBMIT RESULT!!!!"); +} + +static void +_ui_show_form_field_help(ProfWin *window, DataForm *form, char *tag) +{ + FormField *field = form_get_field_by_tag(form, tag); + if (field != NULL) { + win_save_print(window, '-', NULL, NO_EOL, 0, "", field->label); + if (field->required) { + win_save_print(window, '-', NULL, NO_DATE, 0, "", " (Required):"); + } else { + win_save_print(window, '-', NULL, NO_DATE, 0, "", ":"); + } + if (field->description != NULL) { + win_save_vprint(window, '-', NULL, 0, 0, "", " Description : %s", field->description); + } + win_save_vprint(window, '-', NULL, 0, 0, "", " Type : %s", field->type); + + int num_values = 0; + GSList *curr_option = NULL; + FormOption *option = NULL; + + switch (field->type_t) { + case FIELD_TEXT_SINGLE: + case FIELD_TEXT_PRIVATE: + win_save_vprint(window, '-', NULL, 0, 0, "", " Set : /form set %s <value>", tag); + win_save_print(window, '-', NULL, 0, 0, "", " Where : <value> is any text"); + break; + case FIELD_TEXT_MULTI: + num_values = form_get_value_count(form, tag); + win_save_vprint(window, '-', NULL, 0, 0, "", " Add : /form add %s <value>", tag); + win_save_print(window, '-', NULL, 0, 0, "", " Where : <value> is any text"); + if (num_values > 0) { + win_save_vprint(window, '-', NULL, 0, 0, "", " Remove : /form remove %s <value>", tag); + win_save_vprint(window, '-', NULL, 0, 0, "", " Where : <value> between 'val1' and 'val%d'", num_values); + } + break; + case FIELD_BOOLEAN: + win_save_vprint(window, '-', NULL, 0, 0, "", " Set : /form set %s <value>", tag); + win_save_print(window, '-', NULL, 0, 0, "", " Where : <value> is either 'on' or 'off'"); + break; + case FIELD_LIST_SINGLE: + win_save_vprint(window, '-', NULL, 0, 0, "", " Set : /form set %s <value>", tag); + win_save_print(window, '-', NULL, 0, 0, "", " Where : <value> is one of"); + curr_option = field->options; + while (curr_option != NULL) { + option = curr_option->data; + win_save_vprint(window, '-', NULL, 0, 0, "", " %s", option->value); + curr_option = g_slist_next(curr_option); + } + break; + case FIELD_LIST_MULTI: + win_save_vprint(window, '-', NULL, 0, 0, "", " Add : /form add %s <value>", tag); + win_save_vprint(window, '-', NULL, 0, 0, "", " Remove : /form remove %s <value>", tag); + win_save_print(window, '-', NULL, 0, 0, "", " Where : <value> is one of"); + curr_option = field->options; + while (curr_option != NULL) { + option = curr_option->data; + win_save_vprint(window, '-', NULL, 0, 0, "", " %s", option->value); + curr_option = g_slist_next(curr_option); + } + break; + case FIELD_JID_SINGLE: + win_save_vprint(window, '-', NULL, 0, 0, "", " Set : /form set %s <value>", tag); + win_save_print(window, '-', NULL, 0, 0, "", " Where : <value> is a valid Jabber ID"); + break; + case FIELD_JID_MULTI: + win_save_vprint(window, '-', NULL, 0, 0, "", " Add : /form set %s <value>", tag); + win_save_vprint(window, '-', NULL, 0, 0, "", " Remove : /form set %s <value>", tag); + win_save_print(window, '-', NULL, 0, 0, "", " Where : <value> is a valid Jabber ID"); + break; + case FIELD_FIXED: + case FIELD_UNKNOWN: + case FIELD_HIDDEN: + default: + break; + } + } else { + win_save_vprint(window, '-', NULL, 0, 0, "", "No such field %s", tag); + } +} + +static void +_ui_show_form_help(ProfWin *window, DataForm *form) +{ + if (form->instructions != NULL) { + win_save_print(window, '-', NULL, 0, 0, "", "Instructions:"); + win_save_print(window, '-', NULL, 0, 0, "", form->instructions); + win_save_print(window, '-', NULL, 0, 0, "", ""); + } +} + +static void +_ui_show_lines(ProfWin *window, const gchar** lines) +{ + if (lines != NULL) { + int i; + for (i = 0; lines[i] != NULL; i++) { + win_save_print(window, '-', NULL, 0, 0, "", lines[i]); + } + } +} + +static void _win_handle_switch(const wint_t * const ch) { if (*ch == KEY_F(1)) { @@ -2107,4 +2412,12 @@ ui_init_module(void) ui_update = _ui_update; ui_room_requires_config = _ui_room_requires_config; ui_room_destroyed = _ui_room_destroyed; + ui_handle_room_configuration = _ui_handle_room_configuration; + ui_handle_room_config_submit_result = _ui_handle_room_config_submit_result; + ui_win_has_unsaved_form = _ui_win_has_unsaved_form; + ui_show_form = _ui_show_form; + ui_show_form_field = _ui_show_form_field; + ui_show_form_help = _ui_show_form_help; + ui_show_form_field_help = _ui_show_form_field_help; + ui_show_lines = _ui_show_lines; } diff --git a/src/ui/statusbar.c b/src/ui/statusbar.c index 2d9ea533..0f3267b3 100644 --- a/src/ui/statusbar.c +++ b/src/ui/statusbar.c @@ -103,14 +103,7 @@ create_status_bar(void) void status_bar_update_virtual(void) { - GDateTime *now_time = g_date_time_new_now_local(); - GTimeSpan elapsed = g_date_time_difference(now_time, last_time); - - if (elapsed >= TIME_CHECK) { - _status_bar_draw(); - } - - g_date_time_unref(now_time); + _status_bar_draw(); } void diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c index ad374786..811ac2c2 100644 --- a/src/ui/titlebar.c +++ b/src/ui/titlebar.c @@ -86,10 +86,10 @@ title_bar_update_virtual(void) g_timer_destroy(typing_elapsed); typing_elapsed = NULL; - _title_bar_draw(); } } } + _title_bar_draw(); } void @@ -241,6 +241,14 @@ _title_bar_draw(void) } #endif + // show indicator for unsaved forms + ProfWin *current = wins_get_current(); + if ((current != NULL ) && (current->type == WIN_MUC_CONFIG)) { + if ((current->form != NULL) && (current->form->modified)) { + wprintw(win, " *"); + } + } + // show contact typing if (typing) { wprintw(win, " (typing...)"); diff --git a/src/ui/ui.h b/src/ui/ui.h index 7e8967c0..52a93521 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -160,6 +160,13 @@ void (*ui_handle_recipient_error)(const char * const recipient, const char * con void (*ui_handle_error)(const char * const err_msg); void (*ui_clear_win_title)(void); void (*ui_handle_room_join_error)(const char * const room, const char * const err); +void (*ui_handle_room_configuration)(const char * const room, DataForm *form); +void (*ui_handle_room_config_submit_result)(void); +void (*ui_show_form)(ProfWin *window, const char * const room, DataForm *form); +void (*ui_show_form_field)(ProfWin *window, DataForm *form, char *tag); +void (*ui_show_form_help)(ProfWin *window, DataForm *form); +void (*ui_show_form_field_help)(ProfWin *window, DataForm *form, char *tag); +void (*ui_show_lines)(ProfWin *window, const gchar** lines); // contact status functions void (*ui_status_room)(const char * const contact); @@ -199,6 +206,8 @@ void (*ui_create_xmlconsole_win)(void); gboolean (*ui_xmlconsole_exists)(void); void (*ui_open_xmlconsole_win)(void); +gboolean (*ui_win_has_unsaved_form)(int num); + // console window actions void (*cons_show)(const char * const msg, ...); void (*cons_about)(void); diff --git a/src/ui/window.c b/src/ui/window.c index 6a1240e5..21591bae 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -68,6 +68,7 @@ win_create(const char * const title, int cols, win_type_t type) new_win->type = type; new_win->is_otr = FALSE; new_win->is_trusted = FALSE; + new_win->form = NULL; scrollok(new_win->win, TRUE); return new_win; @@ -79,6 +80,7 @@ win_free(ProfWin* window) buffer_free(window->buffer); delwin(window->win); free(window->from); + form_destroy(window->form); free(window); } diff --git a/src/ui/window.h b/src/ui/window.h index ed0c64b4..8150fac9 100644 --- a/src/ui/window.h +++ b/src/ui/window.h @@ -45,6 +45,7 @@ #include "contact.h" #include "ui/buffer.h" +#include "xmpp/xmpp.h" #define NO_ME 1 #define NO_DATE 2 @@ -58,6 +59,7 @@ typedef enum { WIN_CONSOLE, WIN_CHAT, WIN_MUC, + WIN_MUC_CONFIG, WIN_PRIVATE, WIN_DUCK, WIN_XML @@ -74,6 +76,7 @@ typedef struct prof_win_t { int paged; int unread; int history_shown; + DataForm *form; } ProfWin; ProfWin* win_create(const char * const title, int cols, win_type_t type); diff --git a/src/ui/windows.c b/src/ui/windows.c index 3d835a0e..ff7fc3e2 100644 --- a/src/ui/windows.c +++ b/src/ui/windows.c @@ -81,7 +81,11 @@ wins_get_console(void) ProfWin * wins_get_current(void) { - return g_hash_table_lookup(windows, GINT_TO_POINTER(current)); + if (windows != NULL) { + return g_hash_table_lookup(windows, GINT_TO_POINTER(current)); + } else { + return NULL; + } } GList * @@ -385,7 +389,11 @@ wins_get_prune_recipients(void) while (curr != NULL) { ProfWin *window = curr->data; - if (window->unread == 0 && window->type != WIN_MUC && window->type != WIN_CONSOLE) { + if (window->unread == 0 && + window->type != WIN_MUC && + window->type != WIN_MUC_CONFIG && + window->type != WIN_XML && + window->type != WIN_CONSOLE) { result = g_slist_append(result, window->from); } curr = g_list_next(curr); @@ -539,6 +547,7 @@ wins_create_summary(void) GString *chat_string; GString *priv_string; GString *muc_string; + GString *muc_config_string; GString *duck_string; GString *xml_string; @@ -606,6 +615,14 @@ wins_create_summary(void) break; + case WIN_MUC_CONFIG: + muc_config_string = g_string_new(""); + g_string_printf(muc_config_string, "%d: %s", ui_index, window->from); + result = g_slist_append(result, strdup(muc_config_string->str)); + g_string_free(muc_config_string, TRUE); + + break; + case WIN_DUCK: duck_string = g_string_new(""); g_string_printf(duck_string, "%d: DuckDuckGo search", ui_index); diff --git a/src/xmpp/capabilities.c b/src/xmpp/capabilities.c index 2fb9f065..606bd64b 100644 --- a/src/xmpp/capabilities.c +++ b/src/xmpp/capabilities.c @@ -47,6 +47,7 @@ #include "common.h" #include "xmpp/xmpp.h" #include "xmpp/stanza.h" +#include "xmpp/form.h" static GHashTable *capabilities; @@ -137,7 +138,7 @@ caps_create_sha1_str(xmpp_stanza_t * const query) GSList *form_names = NULL; DataForm *form = NULL; FormField *field = NULL; - GHashTable *forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)stanza_destroy_form); + GHashTable *forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)form_destroy); GString *s = g_string_new(""); @@ -170,9 +171,10 @@ caps_create_sha1_str(xmpp_stanza_t * const query) features = g_slist_insert_sorted(features, g_strdup(feature_str), (GCompareFunc)strcmp); } else if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_X) == 0) { if (strcmp(xmpp_stanza_get_ns(child), STANZA_NS_DATA) == 0) { - form = stanza_create_form(child); - form_names = g_slist_insert_sorted(form_names, g_strdup(form->form_type), (GCompareFunc)strcmp); - g_hash_table_insert(forms, g_strdup(form->form_type), form); + form = form_create(child); + char *form_type = form_get_form_type_field(form); + form_names = g_slist_insert_sorted(form_names, g_strdup(form_type), (GCompareFunc)strcmp); + g_hash_table_insert(forms, g_strdup(form_type), form); } } child = xmpp_stanza_get_next(child); @@ -194,7 +196,8 @@ caps_create_sha1_str(xmpp_stanza_t * const query) curr = form_names; while (curr != NULL) { form = g_hash_table_lookup(forms, curr->data); - g_string_append(s, form->form_type); + char *form_type = form_get_form_type_field(form); + g_string_append(s, form_type); g_string_append(s, "<"); GSList *curr_field = form->fields; diff --git a/src/xmpp/form.c b/src/xmpp/form.c new file mode 100644 index 00000000..a7b9fe0a --- /dev/null +++ b/src/xmpp/form.c @@ -0,0 +1,628 @@ +/* + * form.c + * + * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#include <string.h> +#include <stdlib.h> + +#include <strophe.h> +#include <glib.h> + +#include "log.h" +#include "xmpp/xmpp.h" +#include "xmpp/stanza.h" +#include "xmpp/connection.h" + +static gboolean +_is_valid_form_element(xmpp_stanza_t *stanza) +{ + char *name = xmpp_stanza_get_name(stanza); + if (g_strcmp0(name, STANZA_NAME_X) != 0) { + log_error("Error parsing form, root element not <x/>."); + return FALSE; + } + + char *ns = xmpp_stanza_get_ns(stanza); + if (g_strcmp0(ns, STANZA_NS_DATA) != 0) { + log_error("Error parsing form, namespace not %s.", STANZA_NS_DATA); + return FALSE; + } + + char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); + if ((g_strcmp0(type, "form") != 0) && + (g_strcmp0(type, "submit") != 0) && + (g_strcmp0(type, "cancel") != 0) && + (g_strcmp0(type, "result") != 0)) { + log_error("Error parsing form, unknown type."); + return FALSE; + } + + return TRUE; +} + +static DataForm * +_form_new(void) +{ + DataForm *form = malloc(sizeof(DataForm)); + form->type = NULL; + form->title = NULL; + form->instructions = NULL; + form->fields = NULL; + form->var_to_tag = NULL; + form->tag_to_var = NULL; + form->tag_ac = NULL; + + return form; +} + +static FormField * +_field_new(void) +{ + FormField *field = malloc(sizeof(FormField)); + field->label = NULL; + field->type = NULL; + field->var = NULL; + field->description = NULL; + field->required = FALSE; + field->values = NULL; + field->options = NULL; + + return field; +} + +static char * +_get_property(xmpp_stanza_t * const stanza, const char * const property) +{ + char *result = NULL; + xmpp_ctx_t *ctx = connection_get_ctx(); + + xmpp_stanza_t *child = xmpp_stanza_get_child_by_name(stanza, property); + if (child != NULL) { + char *child_text = xmpp_stanza_get_text(child); + if (child_text != NULL) { + result = strdup(child_text); + xmpp_free(ctx, child_text); + } + } + + return result; +} + +static char * +_get_attr(xmpp_stanza_t * const stanza, const char * const attr) +{ + char *result = xmpp_stanza_get_attribute(stanza, attr); + if (result != NULL) { + return strdup(result); + } else { + return NULL; + } +} + +static gboolean +_is_required(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *child = xmpp_stanza_get_child_by_name(stanza, "required"); + if (child != NULL) { + return TRUE; + } else { + return FALSE; + } +} + +static form_field_type_t +_get_field_type(const char * const type) +{ + if (g_strcmp0(type, "hidden") == 0) { + return FIELD_HIDDEN; + } + if (g_strcmp0(type, "text-single") == 0) { + return FIELD_TEXT_SINGLE; + } + if (g_strcmp0(type, "text-private") == 0) { + return FIELD_TEXT_PRIVATE; + } + if (g_strcmp0(type, "text-multi") == 0) { + return FIELD_TEXT_MULTI; + } + if (g_strcmp0(type, "boolean") == 0) { + return FIELD_BOOLEAN; + } + if (g_strcmp0(type, "list-single") == 0) { + return FIELD_LIST_SINGLE; + } + if (g_strcmp0(type, "list-multi") == 0) { + return FIELD_LIST_MULTI; + } + if (g_strcmp0(type, "jid-single") == 0) { + return FIELD_JID_SINGLE; + } + if (g_strcmp0(type, "jid-multi") == 0) { + return FIELD_JID_MULTI; + } + if (g_strcmp0(type, "fixed") == 0) { + return FIELD_FIXED; + } + return FIELD_UNKNOWN; +} + +DataForm * +form_create(xmpp_stanza_t * const form_stanza) +{ + xmpp_ctx_t *ctx = connection_get_ctx(); + + if (!_is_valid_form_element(form_stanza)) { + return NULL; + } + + DataForm *form = _form_new(); + form->type = _get_attr(form_stanza, "type"); + form->title = _get_property(form_stanza, "title"); + form->instructions = _get_property(form_stanza, "instructions"); + form->var_to_tag = g_hash_table_new_full(g_str_hash, g_str_equal, free, free); + form->tag_to_var = g_hash_table_new_full(g_str_hash, g_str_equal, free, free); + form->tag_ac = autocomplete_new(); + form->modified = FALSE; + + int tag_num = 1; + + // get fields + xmpp_stanza_t *form_child = xmpp_stanza_get_children(form_stanza); + while (form_child != NULL) { + char *child_name = xmpp_stanza_get_name(form_child); + if (g_strcmp0(child_name, "field") == 0) { + xmpp_stanza_t *field_stanza = form_child; + + FormField *field = _field_new(); + field->label = _get_attr(field_stanza, "label"); + field->type = _get_attr(field_stanza, "type"); + field->type_t = _get_field_type(field->type); + + field->var = _get_attr(field_stanza, "var"); + + if (field->type_t != FIELD_HIDDEN) { + GString *tag = g_string_new(""); + g_string_printf(tag, "field%d", tag_num++); + g_hash_table_insert(form->var_to_tag, strdup(field->var), strdup(tag->str)); + g_hash_table_insert(form->tag_to_var, strdup(tag->str), strdup(field->var)); + autocomplete_add(form->tag_ac, tag->str); + g_string_free(tag, TRUE); + } + + field->description = _get_property(field_stanza, "desc"); + field->required = _is_required(field_stanza); + + // handle repeated field children + xmpp_stanza_t *field_child = xmpp_stanza_get_children(field_stanza); + while (field_child != NULL) { + child_name = xmpp_stanza_get_name(field_child); + + // handle values + if (g_strcmp0(child_name, "value") == 0) { + char *value = xmpp_stanza_get_text(field_child); + if (value != NULL) { + field->values = g_slist_append(field->values, strdup(value)); + xmpp_free(ctx, value); + } + + // handle options + } else if (g_strcmp0(child_name, "option") == 0) { + FormOption *option = malloc(sizeof(FormOption)); + option->label = _get_attr(field_child, "label"); + option->value = _get_property(field_child, "value"); + + field->options = g_slist_append(field->options, option); + } + + field_child = xmpp_stanza_get_next(field_child); + } + + form->fields = g_slist_append(form->fields, field); + } + + form_child = xmpp_stanza_get_next(form_child); + } + + return form; +} + +xmpp_stanza_t * +form_create_submission(DataForm *form) +{ + xmpp_ctx_t *ctx = connection_get_ctx(); + + xmpp_stanza_t *x = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(x, STANZA_NAME_X); + xmpp_stanza_set_ns(x, STANZA_NS_DATA); + xmpp_stanza_set_type(x, "submit"); + + GSList *curr_field = form->fields; + while (curr_field != NULL) { + FormField *field = curr_field->data; + + xmpp_stanza_t *field_stanza = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(field_stanza, "field"); + xmpp_stanza_set_attribute(field_stanza, "var", field->var); + + xmpp_stanza_t *value_stanza = NULL; + GSList *curr_value = NULL; + + switch (field->type_t) { + case FIELD_HIDDEN: + case FIELD_FIXED: + case FIELD_TEXT_SINGLE: + case FIELD_TEXT_PRIVATE: + case FIELD_BOOLEAN: + case FIELD_LIST_SINGLE: + case FIELD_JID_SINGLE: + value_stanza = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(value_stanza, "value"); + if (field->values != NULL) { + if (field->values->data != NULL) { + xmpp_stanza_t *text_stanza = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(text_stanza, field->values->data); + xmpp_stanza_add_child(value_stanza, text_stanza); + xmpp_stanza_release(text_stanza); + } + } + xmpp_stanza_add_child(field_stanza, value_stanza); + xmpp_stanza_release(value_stanza); + + break; + + case FIELD_TEXT_MULTI: + case FIELD_LIST_MULTI: + case FIELD_JID_MULTI: + curr_value = field->values; + while (curr_value != NULL) { + char *value = curr_value->data; + + value_stanza = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(value_stanza, "value"); + if (value != NULL) { + xmpp_stanza_t *text_stanza = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(text_stanza, value); + xmpp_stanza_add_child(value_stanza, text_stanza); + xmpp_stanza_release(text_stanza); + } + + xmpp_stanza_add_child(field_stanza, value_stanza); + xmpp_stanza_release(value_stanza); + + curr_value = g_slist_next(curr_value); + } + break; + default: + break; + } + + xmpp_stanza_add_child(x, field_stanza); + xmpp_stanza_release(field_stanza); + + curr_field = g_slist_next(curr_field); + } + + return x; +} + +static void +_free_option(FormOption *option) +{ + if (option != NULL) { + free(option->label); + free(option->value); + free(option); + } +} + +static void +_free_field(FormField *field) +{ + if (field != NULL) { + free(field->label); + free(field->type); + free(field->var); + free(field->description); + g_slist_free_full(field->values, free); + g_slist_free_full(field->options, (GDestroyNotify)_free_option); + free(field); + } +} + +static void +_form_destroy(DataForm *form) +{ + if (form != NULL) { + free(form->type); + free(form->title); + free(form->instructions); + g_slist_free_full(form->fields, (GDestroyNotify)_free_field); + g_hash_table_destroy(form->var_to_tag); + g_hash_table_destroy(form->tag_to_var); + autocomplete_free(form->tag_ac); + free(form); + } +} + +static char * +_form_get_form_type_field(DataForm *form) +{ + GSList *curr = form->fields; + while (curr != NULL) { + FormField *field = curr->data; + if (g_strcmp0(field->var, "FORM_TYPE") == 0) { + return field->values->data; + } + curr = g_slist_next(curr); + } + + return NULL; +} + +static gboolean +_form_tag_exists(DataForm *form, const char * const tag) +{ + GList *tags = g_hash_table_get_keys(form->tag_to_var); + GList *curr = tags; + while (curr != NULL) { + if (g_strcmp0(curr->data, tag) == 0) { + return TRUE; + } + curr = g_list_next(curr); + } + return FALSE; +} + +static form_field_type_t +_form_get_field_type(DataForm *form, const char * const tag) +{ + char *var = g_hash_table_lookup(form->tag_to_var, tag); + if (var != NULL) { + GSList *curr = form->fields; + while (curr != NULL) { + FormField *field = curr->data; + if (g_strcmp0(field->var, var) == 0) { + return field->type_t; + } + curr = g_slist_next(curr); + } + } + return FIELD_UNKNOWN; +} + +static void +_form_set_value(DataForm *form, const char * const tag, char *value) +{ + char *var = g_hash_table_lookup(form->tag_to_var, tag); + if (var != NULL) { + GSList *curr = form->fields; + while (curr != NULL) { + FormField *field = curr->data; + if (g_strcmp0(field->var, var) == 0) { + if (g_slist_length(field->values) == 0) { + field->values = g_slist_append(field->values, strdup(value)); + form->modified = TRUE; + return; + } else if (g_slist_length(field->values) == 1) { + free(field->values->data); + field->values->data = strdup(value); + form->modified = TRUE; + return; + } + } + curr = g_slist_next(curr); + } + } +} + +static void +_form_add_value(DataForm *form, const char * const tag, char *value) +{ + char *var = g_hash_table_lookup(form->tag_to_var, tag); + if (var != NULL) { + GSList *curr = form->fields; + while (curr != NULL) { + FormField *field = curr->data; + if (g_strcmp0(field->var, var) == 0) { + field->values = g_slist_append(field->values, strdup(value)); + form->modified = TRUE; + return; + } + curr = g_slist_next(curr); + } + } +} + +static gboolean +_form_add_unique_value(DataForm *form, const char * const tag, char *value) +{ + char *var = g_hash_table_lookup(form->tag_to_var, tag); + if (var != NULL) { + GSList *curr = form->fields; + while (curr != NULL) { + FormField *field = curr->data; + if (g_strcmp0(field->var, var) == 0) { + GSList *curr_value = field->values; + while (curr_value != NULL) { + if (g_strcmp0(curr_value->data, value) == 0) { + return FALSE; + } + curr_value = g_slist_next(curr_value); + } + + field->values = g_slist_append(field->values, strdup(value)); + form->modified = TRUE; + return TRUE; + } + curr = g_slist_next(curr); + } + } + + return FALSE; +} + +static gboolean +_form_remove_value(DataForm *form, const char * const tag, char *value) +{ + char *var = g_hash_table_lookup(form->tag_to_var, tag); + if (var != NULL) { + GSList *curr = form->fields; + while (curr != NULL) { + FormField *field = curr->data; + if (g_strcmp0(field->var, var) == 0) { + GSList *found = g_slist_find_custom(field->values, value, (GCompareFunc)g_strcmp0); + if (found != NULL) { + free(found->data); + found->data = NULL; + field->values = g_slist_delete_link(field->values, found); + form->modified = TRUE; + return TRUE; + } else { + return FALSE; + } + } + curr = g_slist_next(curr); + } + } + + return FALSE; +} + +static gboolean +_form_remove_text_multi_value(DataForm *form, const char * const tag, int index) +{ + index--; + char *var = g_hash_table_lookup(form->tag_to_var, tag); + if (var != NULL) { + GSList *curr = form->fields; + while (curr != NULL) { + FormField *field = curr->data; + if (g_strcmp0(field->var, var) == 0) { + GSList *item = g_slist_nth(field->values, index); + if (item != NULL) { + free(item->data); + item->data = NULL; + field->values = g_slist_delete_link(field->values, item); + form->modified = TRUE; + return TRUE; + } else { + return FALSE; + } + } + curr = g_slist_next(curr); + } + } + + return FALSE; +} + +static int +_form_get_value_count(DataForm *form, const char * const tag) +{ + char *var = g_hash_table_lookup(form->tag_to_var, tag); + if (var != NULL) { + GSList *curr = form->fields; + while (curr != NULL) { + FormField *field = curr->data; + if (g_strcmp0(field->var, var) == 0) { + if ((g_slist_length(field->values) == 1) && (field->values->data == NULL)) { + return 0; + } else { + return g_slist_length(field->values); + } + } + curr = g_slist_next(curr); + } + } + + return 0; +} + +static gboolean +_form_field_contains_option(DataForm *form, const char * const tag, char *value) +{ + char *var = g_hash_table_lookup(form->tag_to_var, tag); + if (var != NULL) { + GSList *curr = form->fields; + while (curr != NULL) { + FormField *field = curr->data; + if (g_strcmp0(field->var, var) == 0) { + GSList *curr_option = field->options; + while (curr_option != NULL) { + FormOption *option = curr_option->data; + if (g_strcmp0(option->value, value) == 0) { + return TRUE; + } + curr_option = g_slist_next(curr_option); + } + } + curr = g_slist_next(curr); + } + } + + return FALSE; +} + +static FormField * +_form_get_field_by_tag(DataForm *form, const char * const tag) +{ + char *var = g_hash_table_lookup(form->tag_to_var, tag); + if (var != NULL) { + GSList *curr = form->fields; + while (curr != NULL) { + FormField *field = curr->data; + if (g_strcmp0(field->var, var) == 0) { + return field; + } + curr = g_slist_next(curr); + } + } + return NULL; +} + +void +form_init_module(void) +{ + form_destroy = _form_destroy; + form_get_form_type_field = _form_get_form_type_field; + form_get_field_type = _form_get_field_type; + form_set_value = _form_set_value; + form_add_unique_value = _form_add_unique_value; + form_add_value = _form_add_value; + form_remove_value = _form_remove_value; + form_remove_text_multi_value = _form_remove_text_multi_value; + form_field_contains_option = _form_field_contains_option; + form_tag_exists = _form_tag_exists; + form_get_value_count = _form_get_value_count; + form_get_field_by_tag = _form_get_field_by_tag; +} diff --git a/src/xmpp/form.h b/src/xmpp/form.h new file mode 100644 index 00000000..816f5df0 --- /dev/null +++ b/src/xmpp/form.h @@ -0,0 +1,43 @@ +/* + * form.h + * + * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#ifndef FORM_H +#define FROM_H + +#include "xmpp/xmpp.h" + +DataForm* form_create(xmpp_stanza_t * const stanza); +xmpp_stanza_t* form_create_submission(DataForm *form); + +#endif diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c index 8dc86080..62dfd64a 100644 --- a/src/xmpp/iq.c +++ b/src/xmpp/iq.c @@ -52,6 +52,7 @@ #include "xmpp/capabilities.h" #include "xmpp/connection.h" #include "xmpp/stanza.h" +#include "xmpp/form.h" #include "roster_list.h" #include "xmpp/xmpp.h" @@ -75,6 +76,10 @@ static int _disco_items_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata); static int _destroy_room_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata); +static int _room_config_handler(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); +static int _room_config_submit_handler(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, void * const userdata); static int _manual_pong_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza, void * const userdata); static int _ping_timed_handler(xmpp_conn_t * const conn, @@ -187,6 +192,44 @@ _iq_destroy_instant_room(const char * const room_jid) } static void +_iq_request_room_config_form(const char * const room_jid) +{ + xmpp_conn_t * const conn = connection_get_conn(); + xmpp_ctx_t * const ctx = connection_get_ctx(); + xmpp_stanza_t *iq = stanza_create_room_config_request_iq(ctx, room_jid); + + char *id = xmpp_stanza_get_id(iq); + xmpp_id_handler_add(conn, _room_config_handler, id, NULL); + + xmpp_send(conn, iq); + xmpp_stanza_release(iq); +} + +static void +_iq_submit_room_config(const char * const room, DataForm *form) +{ + xmpp_conn_t * const conn = connection_get_conn(); + xmpp_ctx_t * const ctx = connection_get_ctx(); + xmpp_stanza_t *iq = stanza_create_room_config_submit_iq(ctx, room, form); + + char *id = xmpp_stanza_get_id(iq); + xmpp_id_handler_add(conn, _room_config_submit_handler, id, NULL); + + xmpp_send(conn, iq); + xmpp_stanza_release(iq); +} + +static void +_iq_room_config_cancel(const char * const room_jid) +{ + xmpp_conn_t * const conn = connection_get_conn(); + xmpp_ctx_t * const ctx = connection_get_ctx(); + xmpp_stanza_t *iq = stanza_create_room_config_cancel_iq(ctx, room_jid); + xmpp_send(conn, iq); + xmpp_stanza_release(iq); +} + +static void _iq_send_ping(const char * const target) { xmpp_conn_t * const conn = connection_get_conn(); @@ -563,6 +606,78 @@ _destroy_room_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const sta return 0; } +static int +_room_config_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID); + const char *type = xmpp_stanza_get_type(stanza); + const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + + if (id != NULL) { + log_debug("IQ room config handler fired, id: %s.", id); + } else { + log_debug("IQ room config handler fired."); + } + + // handle error responses + if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) { + char *error_message = stanza_get_error_message(stanza); + handle_room_configuration_form_error(from, error_message); + free(error_message); + return 0; + } + + if (from == NULL) { + log_error("No from attribute for IQ config request result"); + handle_room_configuration_form_error(from, "No from attribute for room cofig response."); + return 0; + } + + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + if (query == NULL) { + log_error("No query element found parsing room config response"); + handle_room_configuration_form_error(from, "No query element found parsing room config response"); + return 0; + } + + xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(query, STANZA_NS_DATA); + if (x == NULL) { + log_error("No x element found with %s namespace parsing room config response", STANZA_NS_DATA); + handle_room_configuration_form_error(from, "No form data element found parsing room config response"); + return 0; + } + + char *form_type = xmpp_stanza_get_attribute(x, STANZA_ATTR_TYPE); + if (g_strcmp0(form_type, "form") != 0) { + log_error("x element not of type 'form' parsing room config response"); + handle_room_configuration_form_error(from, "Form not of type 'form' parsing room config response."); + return 0; + } + + DataForm *form = form_create(x); + handle_room_configure(from, form); + + return 0; +} + +static int +_room_config_submit_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID); + + if (id != NULL) { + log_debug("IQ room config handler fired, id: %s.", id); + } else { + log_debug("IQ room config handler fired."); + } + + handle_room_config_submit_result(); + + return 0; +} + static void _identity_destroy(DiscoIdentity *identity) { @@ -709,10 +824,11 @@ _disco_info_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanz xmpp_stanza_t *softwareinfo = xmpp_stanza_get_child_by_ns(query, STANZA_NS_DATA); if (softwareinfo != NULL) { - DataForm *form = stanza_create_form(softwareinfo); + DataForm *form = form_create(softwareinfo); FormField *formField = NULL; - if (g_strcmp0(form->form_type, STANZA_DATAFORM_SOFTWARE) == 0) { + char *form_type = form_get_form_type_field(form); + if (g_strcmp0(form_type, STANZA_DATAFORM_SOFTWARE) == 0) { GSList *field = form->fields; while (field != NULL) { formField = field->data; @@ -731,7 +847,7 @@ _disco_info_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanz } } - stanza_destroy_form(form); + form_destroy(form); } xmpp_stanza_t *child = xmpp_stanza_get_children(query); @@ -812,4 +928,7 @@ iq_init_module(void) iq_confirm_instant_room = _iq_confirm_instant_room; iq_destroy_instant_room = _iq_destroy_instant_room; iq_send_ping = _iq_send_ping; + iq_request_room_config_form = _iq_request_room_config_form; + iq_room_config_cancel = _iq_room_config_cancel; + iq_submit_room_config = _iq_submit_room_config; } diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c index 654ba121..118bffb7 100644 --- a/src/xmpp/stanza.c +++ b/src/xmpp/stanza.c @@ -43,11 +43,10 @@ #include "xmpp/connection.h" #include "xmpp/stanza.h" #include "xmpp/capabilities.h" +#include "xmpp/form.h" #include "muc.h" -static int _field_compare(FormField *f1, FormField *f2); - #if 0 xmpp_stanza_t * stanza_create_bookmarks_pubsub_request(xmpp_ctx_t *ctx) @@ -430,7 +429,7 @@ stanza_create_instant_room_request_iq(xmpp_ctx_t *ctx, const char * const room_j xmpp_stanza_set_name(iq, STANZA_NAME_IQ); xmpp_stanza_set_type(iq, STANZA_TYPE_SET); xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, room_jid); - char *id = create_unique_id("leave"); + char *id = create_unique_id("room"); xmpp_stanza_set_id(iq, id); free(id); @@ -459,7 +458,7 @@ stanza_create_instant_room_destroy_iq(xmpp_ctx_t *ctx, const char * const room_j xmpp_stanza_set_name(iq, STANZA_NAME_IQ); xmpp_stanza_set_type(iq, STANZA_TYPE_SET); xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, room_jid); - char *id = create_unique_id("leave"); + char *id = create_unique_id("room"); xmpp_stanza_set_id(iq, id); free(id); @@ -480,6 +479,56 @@ stanza_create_instant_room_destroy_iq(xmpp_ctx_t *ctx, const char * const room_j } xmpp_stanza_t * +stanza_create_room_config_request_iq(xmpp_ctx_t *ctx, const char * const room_jid) +{ + xmpp_stanza_t *iq = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(iq, STANZA_NAME_IQ); + xmpp_stanza_set_type(iq, STANZA_TYPE_GET); + xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, room_jid); + char *id = create_unique_id("room"); + xmpp_stanza_set_id(iq, id); + free(id); + + xmpp_stanza_t *query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, STANZA_NAME_QUERY); + xmpp_stanza_set_ns(query, STANZA_NS_MUC_OWNER); + + xmpp_stanza_add_child(iq, query); + xmpp_stanza_release(query); + + return iq; +} + +xmpp_stanza_t * +stanza_create_room_config_cancel_iq(xmpp_ctx_t *ctx, const char * const room_jid) +{ + xmpp_stanza_t *iq = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(iq, STANZA_NAME_IQ); + xmpp_stanza_set_type(iq, STANZA_TYPE_SET); + xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, room_jid); + char *id = create_unique_id("room"); + xmpp_stanza_set_id(iq, id); + free(id); + + xmpp_stanza_t *query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, STANZA_NAME_QUERY); + xmpp_stanza_set_ns(query, STANZA_NS_MUC_OWNER); + + xmpp_stanza_t *x = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(x, STANZA_NAME_X); + xmpp_stanza_set_type(x, "cancel"); + xmpp_stanza_set_ns(x, STANZA_NS_DATA); + + xmpp_stanza_add_child(query, x); + xmpp_stanza_release(x); + + xmpp_stanza_add_child(iq, query); + xmpp_stanza_release(query); + + return iq; +} + +xmpp_stanza_t * stanza_create_presence(xmpp_ctx_t * const ctx) { xmpp_stanza_t *presence = xmpp_stanza_new(ctx); @@ -568,6 +617,31 @@ stanza_create_disco_items_iq(xmpp_ctx_t *ctx, const char * const id, return iq; } +xmpp_stanza_t * +stanza_create_room_config_submit_iq(xmpp_ctx_t *ctx, const char * const room, DataForm *form) +{ + xmpp_stanza_t *iq = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(iq, STANZA_NAME_IQ); + xmpp_stanza_set_type(iq, STANZA_TYPE_SET); + xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, room); + char *id = create_unique_id("roomconf_submit"); + xmpp_stanza_set_id(iq, id); + free(id); + + xmpp_stanza_t *query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, STANZA_NAME_QUERY); + xmpp_stanza_set_ns(query, STANZA_NS_MUC_OWNER); + + xmpp_stanza_t *x = form_create_submission(form); + xmpp_stanza_add_child(query, x); + xmpp_stanza_release(x); + + xmpp_stanza_add_child(iq, query); + xmpp_stanza_release(query); + + return iq; +} + gboolean stanza_contains_chat_state(xmpp_stanza_t *stanza) { @@ -1023,79 +1097,6 @@ stanza_get_error_message(xmpp_stanza_t *stanza) return strdup("unknown"); } -DataForm * -stanza_create_form(xmpp_stanza_t * const stanza) -{ - DataForm *result = NULL; - xmpp_ctx_t *ctx = connection_get_ctx(); - - xmpp_stanza_t *child = xmpp_stanza_get_children(stanza); - - if (child != NULL) { - result = malloc(sizeof(DataForm)); - result->form_type = NULL; - result->fields = NULL; - } - - //handle fields - while (child != NULL) { - char *var = xmpp_stanza_get_attribute(child, "var"); - - // handle FORM_TYPE - if (g_strcmp0(var, "FORM_TYPE") == 0) { - xmpp_stanza_t *value = xmpp_stanza_get_child_by_name(child, "value"); - char *value_text = xmpp_stanza_get_text(value); - result->form_type = strdup(value_text); - xmpp_free(ctx, value_text); - - // handle regular fields - } else { - FormField *field = malloc(sizeof(FormField)); - field->var = strdup(var); - field->values = NULL; - xmpp_stanza_t *value = xmpp_stanza_get_children(child); - - // handle values - while (value != NULL) { - char *text = xmpp_stanza_get_text(value); - if (text != NULL) { - field->values = g_slist_insert_sorted(field->values, strdup(text), (GCompareFunc)strcmp); - xmpp_free(ctx, text); - } - value = xmpp_stanza_get_next(value); - } - - result->fields = g_slist_insert_sorted(result->fields, field, (GCompareFunc)_field_compare); - } - - child = xmpp_stanza_get_next(child); - } - - return result; -} - -void -stanza_destroy_form(DataForm *form) -{ - if (form != NULL) { - if (form->fields != NULL) { - GSList *curr_field = form->fields; - while (curr_field != NULL) { - FormField *field = curr_field->data; - free(field->var); - if ((field->values) != NULL) { - g_slist_free_full(field->values, free); - } - curr_field = curr_field->next; - } - g_slist_free_full(form->fields, free); - } - - free(form->form_type); - free(form); - } -} - void stanza_attach_priority(xmpp_ctx_t * const ctx, xmpp_stanza_t * const presence, const int pri) @@ -1199,9 +1200,3 @@ stanza_get_presence_string_from_type(resource_presence_t presence_type) return NULL; } } - -static int -_field_compare(FormField *f1, FormField *f2) -{ - return strcmp(f1->var, f2->var); -} diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h index 3d925787..155044f2 100644 --- a/src/xmpp/stanza.h +++ b/src/xmpp/stanza.h @@ -36,6 +36,7 @@ #define XMPP_STANZA_H #include <strophe.h> +#include <xmpp/xmpp.h> #define STANZA_NAME_ACTIVE "active" #define STANZA_NAME_INACTIVE "inactive" @@ -154,16 +155,6 @@ #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo" -typedef struct form_field_t { - char *var; - GSList *values; -} FormField; - -typedef struct data_form_t { - char *form_type; - GSList *fields; -} DataForm; - xmpp_stanza_t* stanza_create_bookmarks_storage_request(xmpp_ctx_t *ctx); xmpp_stanza_t* stanza_create_chat_state(xmpp_ctx_t *ctx, @@ -207,6 +198,12 @@ xmpp_stanza_t* stanza_create_instant_room_request_iq(xmpp_ctx_t *ctx, const char * const room_jid); xmpp_stanza_t* stanza_create_instant_room_destroy_iq(xmpp_ctx_t *ctx, const char * const room_jid); +xmpp_stanza_t* stanza_create_room_config_request_iq(xmpp_ctx_t *ctx, + const char * const room_jid); +xmpp_stanza_t* stanza_create_room_config_cancel_iq(xmpp_ctx_t *ctx, + const char * const room_jid); +xmpp_stanza_t* stanza_create_room_config_submit_iq(xmpp_ctx_t *ctx, + const char * const room, DataForm *form); int stanza_get_idle_time(xmpp_stanza_t * const stanza); char * stanza_get_caps_str(xmpp_stanza_t * const stanza); diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h index 78703230..85704640 100644 --- a/src/xmpp/xmpp.h +++ b/src/xmpp/xmpp.h @@ -40,6 +40,7 @@ #include "config/accounts.h" #include "contact.h" #include "jid.h" +#include "tools/autocomplete.h" #define JABBER_PRIORITY_MIN -128 #define JABBER_PRIORITY_MAX 127 @@ -86,6 +87,47 @@ typedef struct disco_identity_t { char *category; } DiscoIdentity; +typedef enum { + FIELD_HIDDEN, + FIELD_TEXT_SINGLE, + FIELD_TEXT_PRIVATE, + FIELD_TEXT_MULTI, + FIELD_BOOLEAN, + FIELD_LIST_SINGLE, + FIELD_LIST_MULTI, + FIELD_JID_SINGLE, + FIELD_JID_MULTI, + FIELD_FIXED, + FIELD_UNKNOWN +} form_field_type_t; + +typedef struct form_option_t { + char *label; + char *value; +} FormOption; + +typedef struct form_field_t { + char *label; + char *type; + form_field_type_t type_t; + char *var; + char *description; + gboolean required; + GSList *values; + GSList *options; +} FormField; + +typedef struct data_form_t { + char *type; + char *title; + char *instructions; + GSList *fields; + GHashTable *var_to_tag; + GHashTable *tag_to_var; + Autocomplete tag_ac; + gboolean modified; +} DataForm; + void jabber_init_module(void); void bookmark_init_module(void); void capabilities_init_module(void); @@ -93,6 +135,7 @@ void iq_init_module(void); void message_init_module(void); void presence_init_module(void); void roster_init_module(void); +void form_init_module(void); // connection functions void (*jabber_init)(const int disable_tls); @@ -141,6 +184,9 @@ void (*iq_disco_items_request)(gchar *jid); void (*iq_set_autoping)(int seconds); void (*iq_confirm_instant_room)(const char * const room_jid); void (*iq_destroy_instant_room)(const char * const room_jid); +void (*iq_request_room_config_form)(const char * const room_jid); +void (*iq_submit_room_config)(const char * const room, DataForm *form); +void (*iq_room_config_cancel)(const char * const room_jid); void (*iq_send_ping)(const char * const target); // caps functions @@ -161,4 +207,17 @@ void (*roster_send_remove_from_group)(const char * const group, PContact contact void (*roster_send_add_new)(const char * const barejid, const char * const name); void (*roster_send_remove)(const char * const barejid); +void (*form_destroy)(DataForm *form); +char * (*form_get_form_type_field)(DataForm *form); +void (*form_set_value)(DataForm *form, const char * const tag, char *value); +gboolean (*form_add_unique_value)(DataForm *form, const char * const tag, char *value); +void (*form_add_value)(DataForm *form, const char * const tag, char *value); +gboolean (*form_remove_value)(DataForm *form, const char * const tag, char *value); +gboolean (*form_remove_text_multi_value)(DataForm *form, const char * const tag, int index); +gboolean (*form_tag_exists)(DataForm *form, const char * const tag); +form_field_type_t (*form_get_field_type)(DataForm *form, const char * const tag); +gboolean (*form_field_contains_option)(DataForm *form, const char * const tag, char *value); +int (*form_get_value_count)(DataForm *form, const char * const tag); +FormField* (*form_get_field_by_tag)(DataForm *form, const char * const tag); + #endif |