diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/common.h | 2 | ||||
-rw-r--r-- | src/core/misc.c | 13 | ||||
-rw-r--r-- | src/core/misc.h | 3 | ||||
-rw-r--r-- | src/core/network-openssl.c | 20 | ||||
-rw-r--r-- | src/fe-common/core/chat-completion.c | 55 | ||||
-rw-r--r-- | src/fe-common/irc/fe-netjoin.c | 16 | ||||
-rw-r--r-- | src/fe-common/irc/fe-netsplit.c | 15 | ||||
-rw-r--r-- | src/fe-text/gui-entry.c | 596 | ||||
-rw-r--r-- | src/fe-text/gui-entry.h | 11 | ||||
-rw-r--r-- | src/fe-text/irssi.c | 2 | ||||
-rw-r--r-- | src/fe-text/mainwindows.c | 91 | ||||
-rw-r--r-- | src/irc/core/irc-cap.c | 175 | ||||
-rw-r--r-- | src/irc/core/irc-servers.c | 6 | ||||
-rw-r--r-- | src/irc/core/irc-servers.h | 3 | ||||
-rw-r--r-- | src/perl/irc/Irc.xs | 17 | ||||
-rw-r--r-- | src/perl/textui/TextUI.xs | 68 |
16 files changed, 926 insertions, 167 deletions
diff --git a/src/common.h b/src/common.h index ba5557e6..746aad4e 100644 --- a/src/common.h +++ b/src/common.h @@ -6,7 +6,7 @@ #define IRSSI_GLOBAL_CONFIG "irssi.conf" /* config file name in /etc/ */ #define IRSSI_HOME_CONFIG "config" /* config file name in ~/.irssi/ */ -#define IRSSI_ABI_VERSION 13 +#define IRSSI_ABI_VERSION 15 #define DEFAULT_SERVER_ADD_PORT 6667 #define DEFAULT_SERVER_ADD_TLS_PORT 6697 diff --git a/src/core/misc.c b/src/core/misc.c index 4e9f4bbe..27741220 100644 --- a/src/core/misc.c +++ b/src/core/misc.c @@ -218,6 +218,19 @@ GSList *gslist_remove_string (GSList *list, const char *str) return list; } +GSList *gslist_delete_string (GSList *list, const char *str, GDestroyNotify free_func) +{ + GSList *l; + + l = g_slist_find_custom(list, str, (GCompareFunc) g_strcmp0); + if (l != NULL) { + free_func(l->data); + return g_slist_delete_link(list, l); + } + + return list; +} + /* `list' contains pointer to structure with a char* to string. */ char *gslistptr_to_string(GSList *list, int offset, const char *delimiter) { diff --git a/src/core/misc.h b/src/core/misc.h index 375744db..a46a1432 100644 --- a/src/core/misc.h +++ b/src/core/misc.h @@ -21,7 +21,8 @@ GSList *gslist_find_string(GSList *list, const char *key); GSList *gslist_find_icase_string(GSList *list, const char *key); GList *glist_find_string(GList *list, const char *key); GList *glist_find_icase_string(GList *list, const char *key); -GSList *gslist_remove_string (GSList *list, const char *str); +GSList *gslist_remove_string (GSList *list, const char *str) G_GNUC_DEPRECATED; +GSList *gslist_delete_string (GSList *list, const char *str, GDestroyNotify free_func); void gslist_free_full (GSList *list, GDestroyNotify free_func); diff --git a/src/core/network-openssl.c b/src/core/network-openssl.c index c7ce4b43..9fddf073 100644 --- a/src/core/network-openssl.c +++ b/src/core/network-openssl.c @@ -46,6 +46,7 @@ #endif /* OpenSSL 1.1.0 also introduced some useful additions to the api */ +#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined (LIBRESSL_VERSION_NUMBER) static int X509_STORE_up_ref(X509_STORE *vfy) { @@ -57,6 +58,7 @@ static int X509_STORE_up_ref(X509_STORE *vfy) return (n > 1) ? 1 : 0; } #endif +#endif /* ssl i/o channel object */ typedef struct @@ -72,7 +74,10 @@ typedef struct } GIOSSLChannel; static int ssl_inited = FALSE; +/* https://github.com/irssi/irssi/issues/820 */ +#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) static X509_STORE *store = NULL; +#endif static void irssi_ssl_free(GIOChannel *handle) { @@ -379,7 +384,9 @@ static GIOFuncs irssi_ssl_channel_funcs = { gboolean irssi_ssl_init(void) { +#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) int success; +#endif #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER) if (!OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, NULL)) { @@ -391,6 +398,8 @@ gboolean irssi_ssl_init(void) SSL_load_error_strings(); OpenSSL_add_all_algorithms(); #endif + +#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) store = X509_STORE_new(); if (store == NULL) { g_error("Could not initialize OpenSSL: X509_STORE_new() failed"); @@ -404,6 +413,7 @@ gboolean irssi_ssl_init(void) store = NULL; /* Don't return an error; the user might have their own cafile/capath. */ } +#endif ssl_inited = TRUE; @@ -522,13 +532,21 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, int port, SERVER_ g_free(scafile); g_free(scapath); verify = TRUE; - } else if (store != NULL) { + } +#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) + else if (store != NULL) { /* Make sure to increment the refcount every time the store is * used, that's essential not to get it free'd by OpenSSL when * the SSL_CTX is destroyed. */ X509_STORE_up_ref(store); SSL_CTX_set_cert_store(ctx, store); } +#else + else { + if (!SSL_CTX_set_default_verify_paths(ctx)) + g_warning("Could not load default certificates"); + } +#endif if(!(ssl = SSL_new(ctx))) { diff --git a/src/fe-common/core/chat-completion.c b/src/fe-common/core/chat-completion.c index 97cd0565..7ecdd4a2 100644 --- a/src/fe-common/core/chat-completion.c +++ b/src/fe-common/core/chat-completion.c @@ -639,6 +639,59 @@ static void complete_window_nicks(GList **list, WINDOW_REC *window, } } +/* Checks if a line is only nicks from autocompletion. + This lets us insert colons only at the beginning of a list + of nicks */ +static int only_nicks(const char *linestart) +{ + int i = 1; + char prev; + + // at the beginning of the line + if (*linestart == '\0') { + return TRUE; + } + + /* completion_char being a whole string introduces loads of edge cases + and can't be handled generally. Skip this case; we handled the + "beginning of line" case already */ + if (completion_char[1] != '\0') + return FALSE; + + /* This would make the completion char get inserted everywhere otherwise */ + if (*completion_char == ' ') + return FALSE; + + /* First ensure that the line is of the format "foo: bar: baz" + we check this by ensuring each space is preceded by a colon or + another space */ + while (linestart[i] != '\0') { + if (linestart[i] == ' ') { + prev = linestart[i - 1]; + if (prev != *completion_char && prev != ' ') + return FALSE; + } + i += 1; + } + + /* There's an edge case here, if we're completing something + like `foo: bar ba<tab>`, then the `linestart` line will end + at "bar", and we'll miss the space. Ensure that the end + of the line is a colon followed by an optional series of spaces */ + i -= 1; + while (i >= 0) { + if (linestart[i] == ' ') { + i--; + continue; + } else if (linestart[i] == *completion_char) { + return TRUE; + } else { + break; + } + } + return FALSE; +} + static void sig_complete_word(GList **list, WINDOW_REC *window, const char *word, const char *linestart, int *want_space) @@ -691,7 +744,7 @@ static void sig_complete_word(GList **list, WINDOW_REC *window, } else if (channel != NULL) { /* nick completion .. we could also be completing a nick after /MSG from nicks in channel */ - const char *suffix = *linestart != '\0' ? NULL : completion_char; + const char *suffix = only_nicks(linestart) ? completion_char : NULL; complete_window_nicks(list, window, word, suffix); } else if (window->level & MSGLEVEL_MSGS) { /* msgs window, complete /MSG nicks */ diff --git a/src/fe-common/irc/fe-netjoin.c b/src/fe-common/irc/fe-netjoin.c index bc39b27c..a7a2e4fe 100644 --- a/src/fe-common/irc/fe-netjoin.c +++ b/src/fe-common/irc/fe-netjoin.c @@ -245,20 +245,18 @@ static void print_netjoins(NETJOIN_SERVER_REC *server, const char *filter_channe message before it. */ static void sig_print_starting(TEXT_DEST_REC *dest) { - NETJOIN_SERVER_REC *rec; + GSList *tmp, *next; if (printing_joins) return; - if (!IS_IRC_SERVER(dest->server)) - return; - - if (!server_ischannel(dest->server, dest->target)) - return; + for (tmp = joinservers; tmp != NULL; tmp = next) { + NETJOIN_SERVER_REC *server = tmp->data; - rec = netjoin_find_server(IRC_SERVER(dest->server)); - if (rec != NULL && rec->netjoins != NULL) - print_netjoins(rec, NULL); + next = tmp->next; + if (server->netjoins != NULL) + print_netjoins(server, NULL); + } } static int sig_check_netjoins(void) diff --git a/src/fe-common/irc/fe-netsplit.c b/src/fe-common/irc/fe-netsplit.c index ac3330e5..edd3fc34 100644 --- a/src/fe-common/irc/fe-netsplit.c +++ b/src/fe-common/irc/fe-netsplit.c @@ -247,20 +247,17 @@ static int check_server_splits(IRC_SERVER_REC *server) message before it. */ static void sig_print_starting(TEXT_DEST_REC *dest) { - IRC_SERVER_REC *rec; + GSList *tmp; if (printing_splits) return; - if (!IS_IRC_SERVER(dest->server)) - return; - - if (!server_ischannel(dest->server, dest->target)) - return; + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *rec = tmp->data; - rec = IRC_SERVER(dest->server); - if (rec->split_servers != NULL) - print_splits(rec, NULL); + if (IS_IRC_SERVER(rec) && rec->split_servers != NULL) + print_splits(rec, NULL); + } } static int sig_check_splits(void) diff --git a/src/fe-text/gui-entry.c b/src/fe-text/gui-entry.c index e91fcfb3..52a39969 100644 --- a/src/fe-text/gui-entry.c +++ b/src/fe-text/gui-entry.c @@ -65,6 +65,10 @@ static void entry_text_grow(GUI_ENTRY_REC *entry, int grow_size) entry->text_alloc = nearest_power(entry->text_alloc+grow_size); entry->text = g_realloc(entry->text, sizeof(unichar) * entry->text_alloc); + + if (entry->uses_extents) + entry->extents = g_realloc(entry->extents, + sizeof(char *) * entry->text_alloc); } GUI_ENTRY_REC *gui_entry_create(int xpos, int ypos, int width, int utf8) @@ -74,14 +78,30 @@ GUI_ENTRY_REC *gui_entry_create(int xpos, int ypos, int width, int utf8) rec = g_new0(GUI_ENTRY_REC, 1); rec->xpos = xpos; rec->ypos = ypos; - rec->width = width; - rec->text_alloc = 1024; + rec->width = width; + rec->text_alloc = 1024; rec->text = g_new(unichar, rec->text_alloc); - rec->text[0] = '\0'; - rec->utf8 = utf8; + rec->extents = NULL; + rec->text[0] = '\0'; + rec->utf8 = utf8; return rec; } +static void destroy_extents(GUI_ENTRY_REC *entry) +{ + if (entry->uses_extents) { + int i; + for (i = 0; i < entry->text_alloc; i++) { + if (entry->extents[i] != NULL) { + g_free(entry->extents[i]); + } + } + } + g_free(entry->extents); + entry->extents = NULL; + entry->uses_extents = FALSE; +} + void gui_entry_destroy(GUI_ENTRY_REC *entry) { GSList *tmp; @@ -100,9 +120,10 @@ void gui_entry_destroy(GUI_ENTRY_REC *entry) } g_slist_free(entry->kill_ring); - g_free(entry->text); + destroy_extents(entry); + g_free(entry->text); g_free(entry->prompt); - g_free(entry); + g_free(entry); } /* big5 functions */ @@ -164,15 +185,36 @@ void big5_to_unichars(const char *str, unichar *out) *out = '\0'; } +/* Return screen length of plain string */ +static int scrlen_str(const char *str, int utf8) +{ + int len = 0; + char *stripped; + g_return_val_if_fail(str != NULL, 0); + + stripped = strip_codes(str); + len = string_width(stripped, utf8 ? TREAT_STRING_AS_UTF8 : TREAT_STRING_AS_BYTES); + g_free(stripped); + return len; +} + /* ----------------------------- */ -static int pos2scrpos(GUI_ENTRY_REC *entry, int pos) +static int pos2scrpos(GUI_ENTRY_REC *entry, int pos, int cursor) { int i; int xpos = 0; + if (!cursor && pos <= 0) + return 0; + + if (entry->uses_extents && entry->extents[0] != NULL) { + xpos += scrlen_str(entry->extents[0], entry->utf8); + } + for (i = 0; i < pos; i++) { unichar c = entry->text[i]; + const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL; if (term_type == TERM_TYPE_BIG5) xpos += big5_width(c); @@ -180,16 +222,26 @@ static int pos2scrpos(GUI_ENTRY_REC *entry, int pos) xpos += unichar_isprint(c) ? mk_wcwidth(c) : 1; else xpos++; + + if (extent != NULL) { + xpos += scrlen_str(extent, entry->utf8); + } + } return xpos; } static int scrpos2pos(GUI_ENTRY_REC *entry, int pos) { - int i, width, xpos; + int i, width, xpos = 0; - for (i = 0, xpos = 0; i < entry->text_len; i++) { + if (entry->uses_extents && entry->extents[0] != NULL) { + xpos += scrlen_str(entry->extents[0], entry->utf8); + } + + for (i = 0; i < entry->text_len && xpos < pos; i++) { unichar c = entry->text[i]; + const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL; if (term_type == TERM_TYPE_BIG5) width = big5_width(c); @@ -198,15 +250,13 @@ static int scrpos2pos(GUI_ENTRY_REC *entry, int pos) else width = 1; - if (xpos + width > pos) - break; xpos += width; - } - if (xpos == pos) - return i; - else - return i-1; + if (extent != NULL) { + xpos += scrlen_str(extent, entry->utf8); + } + } + return i; } /* Fixes the cursor position in screen */ @@ -215,19 +265,19 @@ static void gui_entry_fix_cursor(GUI_ENTRY_REC *entry) int old_scrstart; /* assume prompt len == prompt scrlen */ - int start = pos2scrpos(entry, entry->scrstart); - int now = pos2scrpos(entry, entry->pos); + int start = pos2scrpos(entry, entry->scrstart, FALSE); + int now = pos2scrpos(entry, entry->pos, TRUE); old_scrstart = entry->scrstart; - if (now-start < entry->width - 2 - entry->promptlen && now-start > 0) + if (now-start < entry->width - 2 - entry->promptlen && now-start > 0) { entry->scrpos = now-start; - else if (now < entry->width - 1 - entry->promptlen) { + } else if (now < entry->width - 1 - entry->promptlen) { entry->scrstart = 0; entry->scrpos = now; } else { entry->scrstart = scrpos2pos(entry, now-(entry->width - entry->promptlen)*2/3); - start = pos2scrpos(entry, entry->scrstart); + start = pos2scrpos(entry, entry->scrstart, FALSE); entry->scrpos = now - start; } @@ -235,59 +285,140 @@ static void gui_entry_fix_cursor(GUI_ENTRY_REC *entry) entry->redraw_needed_from = 0; } +static char *text_effects_only(const char *p) +{ + GString *str; + + str = g_string_sized_new(strlen(p)); + for (; *p != '\0'; p++) { + if (*p == 4 && p[1] != '\0') { + if (p[1] >= FORMAT_STYLE_SPECIAL) { + g_string_append_len(str, p, 2); + p++; + continue; + } + + /* irssi color */ + if (p[2] != '\0') { +#ifdef TERM_TRUECOLOR + if (p[1] == FORMAT_COLOR_24) { + if (p[3] == '\0') p += 2; + else if (p[4] == '\0') p += 3; + else if (p[5] == '\0') p += 4; + else { + g_string_append_len(str, p, 6); + p += 5; + } + } else { +#endif /* TERM_TRUECOLOR */ + g_string_append_len(str, p, 3); + p += 2; +#ifdef TERM_TRUECOLOR + } +#endif /* TERM_TRUECOLOR */ + continue; + } + } + } + + return g_string_free(str, FALSE); +} + static void gui_entry_draw_from(GUI_ENTRY_REC *entry, int pos) { - int i; - int xpos, end_xpos; + int i, start; + int start_xpos, xpos, new_xpos, end_xpos; + char *tmp; + GString *str; + + start = entry->scrstart + pos; - xpos = entry->xpos + entry->promptlen + - pos2scrpos(entry, pos + entry->scrstart) - - pos2scrpos(entry, entry->scrstart); + start_xpos = xpos = entry->xpos + entry->promptlen + + pos2scrpos(entry, start, FALSE) - + pos2scrpos(entry, entry->scrstart, FALSE); end_xpos = entry->xpos + entry->width; if (xpos > end_xpos) return; + str = g_string_sized_new(entry->text_alloc); + term_set_color(root_window, ATTR_RESET); - term_move(root_window, xpos, entry->ypos); + /* term_move(root_window, xpos, entry->ypos); */ + + if (entry->uses_extents && entry->extents[0] != NULL) { + g_string_append(str, entry->extents[0]); + } + for (i = 0; i < start && i < entry->text_len; i++) { + const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL; + if (extent != NULL) { + g_string_append(str, extent); + } + } + if (i == 0) { + xpos += scrlen_str(str->str, entry->utf8); + } else { + tmp = text_effects_only(str->str); + g_string_assign(str, tmp); + g_free(tmp); + } - for (i = entry->scrstart + pos; i < entry->text_len; i++) { + for (; i < entry->text_len; i++) { unichar c = entry->text[i]; + const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL; + new_xpos = xpos; if (entry->hidden) - xpos++; + new_xpos++; else if (term_type == TERM_TYPE_BIG5) - xpos += big5_width(c); + new_xpos += big5_width(c); else if (entry->utf8) - xpos += unichar_isprint(c) ? mk_wcwidth(c) : 1; + new_xpos += unichar_isprint(c) ? mk_wcwidth(c) : 1; else - xpos++; + new_xpos++; - if (xpos > end_xpos) + if (new_xpos > end_xpos) break; if (entry->hidden) - term_addch(root_window, ' '); + g_string_append_c(str, ' '); else if (unichar_isprint(c)) - term_add_unichar(root_window, c); + g_string_append_unichar(str, c); else { - term_set_color(root_window, ATTR_RESET|ATTR_REVERSE); - term_addch(root_window, (c & 127)+'A'-1); - term_set_color(root_window, ATTR_RESET); + g_string_append_c(str, 4); + g_string_append_c(str, FORMAT_STYLE_REVERSE); + g_string_append_c(str, (c & 127)+'A'-1); + g_string_append_c(str, 4); + g_string_append_c(str, FORMAT_STYLE_REVERSE); + } + xpos = new_xpos; + + if (extent != NULL) { + new_xpos += scrlen_str(extent, entry->utf8); + + if (new_xpos > end_xpos) + break; + + g_string_append(str, extent); + xpos = new_xpos; } } /* clear the rest of the input line */ if (xpos < end_xpos) { - if (end_xpos == term_width) - term_clrtoeol(root_window); - else { + if (end_xpos == term_width) { + g_string_append_c(str, 4); + g_string_append_c(str, FORMAT_STYLE_CLRTOEOL); + } else { while (xpos < end_xpos) { - term_addch(root_window, ' '); + g_string_append_c(str, ' '); xpos++; } } } + + gui_printtext_internal(start_xpos, entry->ypos, str->str); + g_string_free(str, TRUE); } static void gui_entry_draw(GUI_ENTRY_REC *entry) @@ -359,19 +490,6 @@ void gui_entry_set_active(GUI_ENTRY_REC *entry) } } -/* Return screen length of plain string */ -static int scrlen_str(const char *str) -{ - int len = 0; - char *stripped; - g_return_val_if_fail(str != NULL, 0); - - stripped = strip_codes(str); - len = string_width(stripped, -1); - g_free(stripped); - return len; -} - void gui_entry_set_prompt(GUI_ENTRY_REC *entry, const char *str) { int oldlen; @@ -382,7 +500,7 @@ void gui_entry_set_prompt(GUI_ENTRY_REC *entry, const char *str) if (str != NULL) { g_free_not_null(entry->prompt); entry->prompt = g_strdup(str); - entry->promptlen = scrlen_str(str); + entry->promptlen = scrlen_str(str, entry->utf8); } if (entry->prompt != NULL) @@ -416,6 +534,7 @@ void gui_entry_set_text(GUI_ENTRY_REC *entry, const char *str) entry->text_len = 0; entry->pos = 0; entry->text[0] = '\0'; + destroy_extents(entry); gui_entry_insert_text(entry, str); } @@ -488,6 +607,15 @@ void gui_entry_insert_text(GUI_ENTRY_REC *entry, const char *str) g_memmove(entry->text + entry->pos + len, entry->text + entry->pos, (entry->text_len-entry->pos + 1) * sizeof(unichar)); + /* make space for the color */ + if (entry->uses_extents) { + g_memmove(entry->extents + entry->pos + len + 1, entry->extents + entry->pos + 1, + (entry->text_len-entry->pos) * sizeof(char *)); + for (i = 0; i < len; i++) { + entry->extents[entry->pos + i + 1] = NULL; + } + } + if (!entry->utf8) { if (term_type == TERM_TYPE_BIG5) { chr = entry->text[entry->pos + len]; @@ -514,7 +642,7 @@ void gui_entry_insert_text(GUI_ENTRY_REC *entry, const char *str) void gui_entry_insert_char(GUI_ENTRY_REC *entry, unichar chr) { - g_return_if_fail(entry != NULL); + g_return_if_fail(entry != NULL); if (chr == 0 || chr == 13 || chr == 10) return; /* never insert NUL, CR or LF characters */ @@ -522,7 +650,7 @@ void gui_entry_insert_char(GUI_ENTRY_REC *entry, unichar chr) if (entry->utf8 && entry->pos == 0 && mk_wcwidth(chr) == 0) return; - gui_entry_redraw_from(entry, entry->pos); + gui_entry_redraw_from(entry, entry->pos); entry_text_grow(entry, 1); @@ -530,9 +658,15 @@ void gui_entry_insert_char(GUI_ENTRY_REC *entry, unichar chr) g_memmove(entry->text + entry->pos + 1, entry->text + entry->pos, (entry->text_len-entry->pos + 1) * sizeof(unichar)); + if (entry->uses_extents) { + g_memmove(entry->extents + entry->pos + 1 + 1, entry->extents + entry->pos + 1, + (entry->text_len-entry->pos) * sizeof(char *)); + entry->extents[entry->pos + 1] = NULL; + } + entry->text[entry->pos] = chr; entry->text_len++; - entry->pos++; + entry->pos++; gui_entry_fix_cursor(entry); gui_entry_draw(entry); @@ -631,7 +765,7 @@ static GUI_ENTRY_CUTBUFFER_REC *get_cutbuffer_rec(GUI_ENTRY_REC *entry, CUTBUFFE void gui_entry_erase(GUI_ENTRY_REC *entry, int size, CUTBUFFER_UPDATE_OP update_cutbuffer) { - size_t w = 0; + size_t i, w = 0; g_return_if_fail(entry != NULL); @@ -700,6 +834,23 @@ void gui_entry_erase(GUI_ENTRY_REC *entry, int size, CUTBUFFER_UPDATE_OP update_ g_memmove(entry->text + entry->pos - size, entry->text + entry->pos, (entry->text_len-entry->pos+1) * sizeof(unichar)); + if (entry->uses_extents) { + for (i = entry->pos - size; i < entry->pos; i++) { + if (entry->extents[i+1] != NULL) { + g_free(entry->extents[i+1]); + } + } + g_memmove(entry->extents + entry->pos - size + 1, entry->extents + entry->pos + 1, + (entry->text_len - entry->pos) * sizeof(void *)); /* no null terminator here */ + for (i = 0; i < size; i++) { + entry->extents[entry->text_len - i] = NULL; + } + if (entry->text_len == size && entry->extents[0] != NULL) { + g_free(entry->extents[0]); + entry->extents[0] = NULL; + } + } + entry->pos -= size; entry->text_len -= size; @@ -719,11 +870,28 @@ void gui_entry_erase_cell(GUI_ENTRY_REC *entry) mk_wcwidth(entry->text[entry->pos+size]) == 0) size++; g_memmove(entry->text + entry->pos, entry->text + entry->pos + size, - (entry->text_len-entry->pos-size+1) * sizeof(unichar)); + (entry->text_len-entry->pos-size+1) * sizeof(unichar)); + + if (entry->uses_extents) { + int i; + for (i = 0; i < size; i++) { + g_free(entry->extents[entry->pos + i + 1]); + } + g_memmove(entry->extents + entry->pos + 1, entry->extents + entry->pos + size + 1, + (entry->text_len-entry->pos-size) * sizeof(char *)); + for (i = 0; i < size; i++) { + entry->extents[entry->text_len - i] = NULL; + } + if (entry->text_len == size && entry->extents[0] != NULL) { + g_free(entry->extents[0]); + entry->extents[0] = NULL; + } + } entry->text_len -= size; gui_entry_redraw_from(entry, entry->pos); + gui_entry_fix_cursor(entry); gui_entry_draw(entry); } @@ -782,6 +950,7 @@ void gui_entry_erase_next_word(GUI_ENTRY_REC *entry, int to_space, CUTBUFFER_UPD void gui_entry_transpose_chars(GUI_ENTRY_REC *entry) { unichar chr; + char *extent; if (entry->pos == 0 || entry->text_len < 2) return; @@ -794,6 +963,12 @@ void gui_entry_transpose_chars(GUI_ENTRY_REC *entry) entry->text[entry->pos] = entry->text[entry->pos-1]; entry->text[entry->pos-1] = chr; + if (entry->uses_extents) { + extent = entry->extents[entry->pos+1]; + entry->extents[entry->pos+1] = entry->extents[entry->pos]; + entry->extents[entry->pos] = extent; + } + entry->pos++; gui_entry_redraw_from(entry, entry->pos-2); @@ -830,31 +1005,60 @@ void gui_entry_transpose_words(GUI_ENTRY_REC *entry) /* do wordswap if any found */ if (spos1 < epos1 && epos1 < spos2 && spos2 < epos2) { unichar *first, *sep, *second; + char **first_extent, **sep_extent, **second_extent; int i; first = (unichar *) g_malloc( (epos1 - spos1) * sizeof(unichar) ); sep = (unichar *) g_malloc( (spos2 - epos1) * sizeof(unichar) ); second = (unichar *) g_malloc( (epos2 - spos2) * sizeof(unichar) ); - for (i = spos1; i < epos1; i++) + first_extent = (char **) g_malloc( (epos1 - spos1) * sizeof(char *) ); + sep_extent = (char **) g_malloc( (spos2 - epos1) * sizeof(char *) ); + second_extent = (char **) g_malloc( (epos2 - spos2) * sizeof(char *) ); + + for (i = spos1; i < epos1; i++) { first[i-spos1] = entry->text[i]; - for (i = epos1; i < spos2; i++) + if (entry->uses_extents) + first_extent[i-spos1] = entry->extents[i+1]; + } + for (i = epos1; i < spos2; i++) { sep[i-epos1] = entry->text[i]; - for (i = spos2; i < epos2; i++) + if (entry->uses_extents) + sep_extent[i-epos1] = entry->extents[i+1]; + } + for (i = spos2; i < epos2; i++) { second[i-spos2] = entry->text[i]; + if (entry->uses_extents) + second_extent[i-spos2] = entry->extents[i+1]; + } entry->pos = spos1; - for (i = 0; i < epos2-spos2; i++) - entry->text[entry->pos++] = second[i]; - for (i = 0; i < spos2-epos1; i++) - entry->text[entry->pos++] = sep[i]; - for (i = 0; i < epos1-spos1; i++) - entry->text[entry->pos++] = first[i]; + for (i = 0; i < epos2-spos2; i++) { + entry->text[entry->pos] = second[i]; + if (entry->uses_extents) + entry->extents[entry->pos+1] = second_extent[i]; + entry->pos++; + } + for (i = 0; i < spos2-epos1; i++) { + entry->text[entry->pos] = sep[i]; + if (entry->uses_extents) + entry->extents[entry->pos+1] = sep_extent[i]; + entry->pos++; + } + for (i = 0; i < epos1-spos1; i++) { + entry->text[entry->pos] = first[i]; + if (entry->uses_extents) + entry->extents[entry->pos+1] = first_extent[i]; + entry->pos++; + } g_free(first); g_free(sep); g_free(second); + g_free(first_extent); + g_free(sep_extent); + g_free(second_extent); } gui_entry_redraw_from(entry, spos1); @@ -938,11 +1142,17 @@ void gui_entry_set_pos(GUI_ENTRY_REC *entry, int pos) void gui_entry_set_text_and_pos_bytes(GUI_ENTRY_REC *entry, const char *str, int pos_bytes) { - int pos; + int pos, extents_alloc; + char **extents; const char *ptr; g_return_if_fail(entry != NULL); + extents = entry->extents; + extents_alloc = entry->text_alloc; + entry->extents = NULL; + entry->uses_extents = FALSE; + gui_entry_set_text(entry, str); if (entry->utf8) { @@ -953,6 +1163,19 @@ void gui_entry_set_text_and_pos_bytes(GUI_ENTRY_REC *entry, const char *str, int else pos = pos_bytes; + if (extents != NULL) { + entry->uses_extents = TRUE; + entry->extents = extents; + if (extents_alloc < entry->text_alloc) { + int i; + entry->extents = g_realloc(entry->extents, + sizeof(char *) * entry->text_alloc); + for (i = extents_alloc; i < entry->text_alloc; i++) { + entry->extents[i] = NULL; + } + } + } + gui_entry_redraw_from(entry, 0); gui_entry_set_pos(entry, pos); } @@ -1042,3 +1265,234 @@ void gui_entry_redraw(GUI_ENTRY_REC *entry) gui_entry_fix_cursor(entry); gui_entry_draw(entry); } + +static void gui_entry_alloc_extents(GUI_ENTRY_REC *entry) +{ + entry->uses_extents = TRUE; + entry->extents = g_new0(char *, entry->text_alloc); +} + +void gui_entry_set_extent(GUI_ENTRY_REC *entry, int pos, const char *text) +{ + int update = FALSE; + + g_return_if_fail(entry != NULL); + + if (pos < 0 || pos > entry->text_len) + return; + + if (text == NULL) + return; + + if (!entry->uses_extents) { + gui_entry_alloc_extents(entry); + } + + if (g_strcmp0(entry->extents[pos], text) != 0) { + g_free(entry->extents[pos]); + if (*text == '\0') { + entry->extents[pos] = NULL; + } else { + entry->extents[pos] = g_strdup(text); + } + update = TRUE; + } + + if (update) { + gui_entry_redraw_from(entry, pos - 1); + gui_entry_fix_cursor(entry); + gui_entry_draw(entry); + } +} + +void gui_entry_set_extents(GUI_ENTRY_REC *entry, int pos, int len, const char *left, const char *right) +{ + int end, update = FALSE; + + g_return_if_fail(entry != NULL); + + if (pos < 0 || len < 0 || pos > entry->text_len) + return; + + end = pos + len; + + if (end > entry->text_len) + end = entry->text_len; + + if (!entry->uses_extents) { + gui_entry_alloc_extents(entry); + } + + if (g_strcmp0(entry->extents[pos], left) != 0) { + g_free(entry->extents[pos]); + if (*left == '\0') { + entry->extents[pos] = NULL; + } else { + entry->extents[pos] = g_strdup(left); + } + update = TRUE; + } + + if (pos != end && g_strcmp0(entry->extents[end], right) != 0) { + g_free(entry->extents[end]); + if (*right == '\0') { + entry->extents[end] = NULL; + } else { + entry->extents[end] = g_strdup(right); + } + update = TRUE; + } + + if (update) { + gui_entry_redraw_from(entry, pos - 1); + gui_entry_fix_cursor(entry); + gui_entry_draw(entry); + } +} + +void gui_entry_clear_extents(GUI_ENTRY_REC *entry, int pos, int len) +{ + int i, end, update = FALSE; + + g_return_if_fail(entry != NULL); + + if (pos < 0 || len < 0 || pos > entry->text_len) + return; + + end = pos + len; + + if (end > entry->text_len) + end = entry->text_len; + + if (!entry->uses_extents) { + return; + } + + for (i = pos; i <= end; i++) { + if (entry->extents[i] != NULL) { + g_free(entry->extents[i]); + entry->extents[i] = NULL; + update = TRUE; + } + } + + if (update) { + gui_entry_redraw_from(entry, pos); + gui_entry_fix_cursor(entry); + gui_entry_draw(entry); + } +} + +char *gui_entry_get_extent(GUI_ENTRY_REC *entry, int pos) +{ + g_return_val_if_fail(entry != NULL, NULL); + + if (!entry->uses_extents) + return NULL; + + if (pos < 0 || pos >= entry->text_len) + return NULL; + + return entry->extents[pos]; +} + +#define POS_FLAG "%|" +GSList *gui_entry_get_text_and_extents(GUI_ENTRY_REC *entry) +{ + GSList *list = NULL; + GString *str; + int i; + + g_return_val_if_fail(entry != NULL, NULL); + + if (entry->uses_extents && entry->extents[0] != NULL) { + if (entry->pos == 0) { + list = g_slist_prepend(list, g_strconcat(entry->extents[0], POS_FLAG, NULL)); + } else { + list = g_slist_prepend(list, g_strdup(entry->extents[0])); + } + } else { + if (entry->pos == 0) { + list = g_slist_prepend(list, g_strdup(POS_FLAG)); + } else { + list = g_slist_prepend(list, NULL); + } + } + + str = g_string_sized_new(entry->text_alloc); + for (i = 0; i < entry->text_len; i++) { + if (entry->utf8) { + g_string_append_unichar(str, entry->text[i]); + } else if (term_type == TERM_TYPE_BIG5) { + if(entry->text[i] > 0xff) + g_string_append_c(str, (entry->text[i] >> 8) & 0xff); + g_string_append_c(str, entry->text[i] & 0xff); + } else { + g_string_append_c(str, entry->text[i]); + } + if (entry->pos == i+1 || (entry->uses_extents && entry->extents[i+1] != NULL)) { + list = g_slist_prepend(list, g_strdup(str->str)); + g_string_truncate(str, 0); + if (entry->uses_extents && entry->extents[i+1] != NULL) { + if (entry->pos == i+1) { + list = g_slist_prepend(list, g_strconcat(entry->extents[i+1], POS_FLAG, NULL)); + } else { + list = g_slist_prepend(list, g_strdup(entry->extents[i+1])); + } + } else if (entry->pos == i+1) { + list = g_slist_prepend(list, g_strdup(POS_FLAG)); + } + } + } + if (str->len > 0) { + list = g_slist_prepend(list, g_strdup(str->str)); + } + list = g_slist_reverse(list); + g_string_free(str, TRUE); + + return list; +} + +void gui_entry_set_text_and_extents(GUI_ENTRY_REC *entry, GSList *list) +{ + GSList *tmp; + int pos = -1; + int is_extent = 1; + + gui_entry_set_text(entry, ""); + for (tmp = list, is_extent = TRUE; tmp != NULL; tmp = tmp->next, is_extent ^= 1) { + if (is_extent) { + char *extent; + int len; + + if (tmp->data == NULL) + continue; + + extent = g_strdup(tmp->data); + len = strlen(extent); + if (len >= strlen(POS_FLAG) && g_strcmp0(&extent[len-strlen(POS_FLAG)], POS_FLAG) == 0) { + char *tmp; + tmp = extent; + extent = g_strndup(tmp, len - strlen(POS_FLAG)); + g_free(tmp); + pos = entry->pos; + } + + if (strlen(extent) > 0) { + gui_entry_set_extent(entry, entry->pos, extent); + } + g_free(extent); + } else { + gui_entry_insert_text(entry, tmp->data); + } + } + gui_entry_set_pos(entry, pos); +} + +void gui_entry_init(void) +{ +} + +void gui_entry_deinit(void) +{ +} diff --git a/src/fe-text/gui-entry.h b/src/fe-text/gui-entry.h index 000c5f03..dff860d3 100644 --- a/src/fe-text/gui-entry.h +++ b/src/fe-text/gui-entry.h @@ -9,6 +9,7 @@ typedef struct { typedef struct { int text_len, text_alloc; /* as shorts, not chars */ unichar *text; + char **extents; GSList *kill_ring; @@ -26,6 +27,7 @@ typedef struct { unsigned int previous_append_next_kill:1; unsigned int append_next_kill:1; unsigned int yank_preceded:1; + unsigned int uses_extents:1; } GUI_ENTRY_REC; typedef enum { @@ -77,5 +79,14 @@ void gui_entry_move_words(GUI_ENTRY_REC *entry, int count, int to_space); void gui_entry_redraw(GUI_ENTRY_REC *entry); +void gui_entry_set_extent(GUI_ENTRY_REC *entry, int pos, const char *text); +void gui_entry_set_extents(GUI_ENTRY_REC *entry, int pos, int len, const char *left, const char *right); +void gui_entry_clear_extents(GUI_ENTRY_REC *entry, int pos, int len); +char *gui_entry_get_extent(GUI_ENTRY_REC *entry, int pos); +GSList *gui_entry_get_text_and_extents(GUI_ENTRY_REC *entry); +void gui_entry_set_text_and_extents(GUI_ENTRY_REC *entry, GSList *list); + +void gui_entry_init(void); +void gui_entry_deinit(void); #endif diff --git a/src/fe-text/irssi.c b/src/fe-text/irssi.c index 0288e4f1..f30ce4b8 100644 --- a/src/fe-text/irssi.c +++ b/src/fe-text/irssi.c @@ -165,6 +165,7 @@ static void textui_finish_init(void) gui_expandos_init(); gui_printtext_init(); gui_readline_init(); + gui_entry_init(); lastlog_init(); mainwindows_init(); mainwindow_activity_init(); @@ -230,6 +231,7 @@ static void textui_deinit(void) lastlog_deinit(); statusbar_deinit(); + gui_entry_deinit(); gui_printtext_deinit(); gui_readline_deinit(); gui_windows_deinit(); diff --git a/src/fe-text/mainwindows.c b/src/fe-text/mainwindows.c index 5f24674f..83a3e0cc 100644 --- a/src/fe-text/mainwindows.c +++ b/src/fe-text/mainwindows.c @@ -915,6 +915,8 @@ static int try_shrink_lower(MAIN_WINDOW_REC *window, int count) { MAIN_WINDOW_REC *shrink_win; + g_return_val_if_fail(count >= 0, FALSE); + shrink_win = mainwindows_find_lower(window); if (shrink_win != NULL) { int ok; @@ -959,6 +961,8 @@ static int try_shrink_upper(MAIN_WINDOW_REC *window, int count) { MAIN_WINDOW_REC *shrink_win; + g_return_val_if_fail(count >= 0, FALSE); + shrink_win = mainwindows_find_upper(window); if (shrink_win != NULL) { int ok; @@ -1063,6 +1067,8 @@ static int try_grow_upper(MAIN_WINDOW_REC *window, int count) static int mainwindow_shrink(MAIN_WINDOW_REC *window, int count, int resize_lower) { + g_return_val_if_fail(count >= 0, FALSE); + if (MAIN_WINDOW_TEXT_HEIGHT(window)-count < WINDOW_MIN_SIZE) return FALSE; @@ -1091,6 +1097,8 @@ static int try_rshrink_right(MAIN_WINDOW_REC *window, int count) { MAIN_WINDOW_REC *shrink_win; + g_return_val_if_fail(count >= 0, FALSE); + shrink_win = mainwindows_find_right(window, FALSE); if (shrink_win != NULL) { if (MAIN_WINDOW_TEXT_WIDTH(shrink_win)-count < NEW_WINDOW_WIDTH) { @@ -1111,6 +1119,8 @@ static int try_rshrink_left(MAIN_WINDOW_REC *window, int count) { MAIN_WINDOW_REC *shrink_win; + g_return_val_if_fail(count >= 0, FALSE); + shrink_win = mainwindows_find_left(window, FALSE); if (shrink_win != NULL) { if (MAIN_WINDOW_TEXT_WIDTH(shrink_win)-count < NEW_WINDOW_WIDTH) { @@ -1168,6 +1178,8 @@ static int try_rgrow_left(MAIN_WINDOW_REC *window, int count) static int mainwindow_rshrink(MAIN_WINDOW_REC *window, int count) { + g_return_val_if_fail(count >= 0, FALSE); + if (MAIN_WINDOW_TEXT_WIDTH(window)-count < NEW_WINDOW_WIDTH) return FALSE; @@ -1220,19 +1232,29 @@ void mainwindows_redraw_dirty(void) } } +static void mainwindow_grow_int(int count) +{ + if (count == 0) { + return; + } else if (count < 0) { + if (!mainwindow_shrink(WINDOW_MAIN(active_win), -count, FALSE)) { + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, TXT_WINDOW_TOO_SMALL); + } + } else { + if (!mainwindow_grow(WINDOW_MAIN(active_win), count, FALSE)) { + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, TXT_WINDOW_TOO_SMALL); + } + } +} + /* SYNTAX: WINDOW GROW [<lines>] */ static void cmd_window_grow(const char *data) { - MAIN_WINDOW_REC *window; int count; count = *data == '\0' ? 1 : atoi(data); - window = WINDOW_MAIN(active_win); - if (!mainwindow_grow(window, count, FALSE)) { - printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, - TXT_WINDOW_TOO_SMALL); - } + mainwindow_grow_int(count); } /* SYNTAX: WINDOW SHRINK [<lines>] */ @@ -1241,16 +1263,14 @@ static void cmd_window_shrink(const char *data) int count; count = *data == '\0' ? 1 : atoi(data); - if (!mainwindow_shrink(WINDOW_MAIN(active_win), count, FALSE)) { - printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, - TXT_WINDOW_TOO_SMALL); - } + if (count < -INT_MAX) count = -INT_MAX; + + mainwindow_grow_int(-count); } /* SYNTAX: WINDOW SIZE <lines> */ static void cmd_window_size(const char *data) { - char sizestr[MAX_INT_STRLEN]; int size; if (!is_numeric(data, 0)) return; @@ -1258,13 +1278,9 @@ static void cmd_window_size(const char *data) size -= WINDOW_MAIN(active_win)->height - WINDOW_MAIN(active_win)->statusbar_lines; - if (size == 0) return; + if (size < -INT_MAX) size = -INT_MAX; - ltoa(sizestr, size < 0 ? -size : size); - if (size < 0) - cmd_window_shrink(sizestr); - else - cmd_window_grow(sizestr); + mainwindow_grow_int(size); } /* SYNTAX: WINDOW BALANCE */ @@ -1393,6 +1409,11 @@ static void _cmd_window_show_opt(const char *data, int right) } parent = mainwindow_create(right); + if (parent == NULL) { + printformat_window(active_win, MSGLEVEL_CLIENTERROR, TXT_WINDOW_TOO_SMALL); + return; + } + parent->active = window; gui_window_reparent(window, parent); @@ -1415,16 +1436,29 @@ static void cmd_window_rshow(const char *data) _cmd_window_show_opt(data, TRUE); } +static void window_rgrow_int(int count) +{ + if (count == 0) { + return; + } else if (count < 0) { + if (!mainwindow_rshrink(WINDOW_MAIN(active_win), -count)) { + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, TXT_WINDOW_TOO_SMALL); + } + } else { + if (!mainwindow_rgrow(WINDOW_MAIN(active_win), count)) { + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, TXT_WINDOW_TOO_SMALL); + } + } +} + /* SYNTAX: WINDOW RGROW [<columns>] */ static void cmd_window_rgrow(const char *data) { int count; count = *data == '\0' ? 1 : atoi(data); - if (!mainwindow_rgrow(WINDOW_MAIN(active_win), count)) { - printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, - TXT_WINDOW_TOO_SMALL); - } + + window_rgrow_int(count); } /* SYNTAX: WINDOW RSHRINK [<lines>] */ @@ -1433,29 +1467,22 @@ static void cmd_window_rshrink(const char *data) int count; count = *data == '\0' ? 1 : atoi(data); - if (!mainwindow_rshrink(WINDOW_MAIN(active_win), count)) { - printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, - TXT_WINDOW_TOO_SMALL); - } + if (count < -INT_MAX) count = -INT_MAX; + + window_rgrow_int(-count); } /* SYNTAX: WINDOW RSIZE <columns> */ static void cmd_window_rsize(const char *data) { - char rsizestr[MAX_INT_STRLEN]; int rsize; if (!is_numeric(data, 0)) return; rsize = atoi(data); rsize -= MAIN_WINDOW_TEXT_WIDTH(WINDOW_MAIN(active_win)); - if (rsize == 0) return; - ltoa(rsizestr, rsize < 0 ? -rsize : rsize); - if (rsize < 0) - cmd_window_rshrink(rsizestr); - else - cmd_window_rgrow(rsizestr); + window_rgrow_int(rsize); } /* SYNTAX: WINDOW RBALANCE */ diff --git a/src/irc/core/irc-cap.c b/src/irc/core/irc-cap.c index 5464e493..dd720841 100644 --- a/src/irc/core/irc-cap.c +++ b/src/irc/core/irc-cap.c @@ -36,7 +36,7 @@ int cap_toggle (IRC_SERVER_REC *server, char *cap, int enable) return TRUE; } else if (!enable && gslist_find_string(server->cap_queue, cap)) { - server->cap_queue = gslist_remove_string(server->cap_queue, cap); + server->cap_queue = gslist_delete_string(server->cap_queue, cap, g_free); return TRUE; } @@ -45,7 +45,7 @@ int cap_toggle (IRC_SERVER_REC *server, char *cap, int enable) if (enable && !gslist_find_string(server->cap_active, cap)) { /* Make sure the required cap is supported by the server */ - if (!gslist_find_string(server->cap_supported, cap)) + if (!g_hash_table_lookup_extended(server->cap_supported, cap, NULL, NULL)) return FALSE; irc_send_cmdv(server, "CAP REQ %s", cap); @@ -79,61 +79,130 @@ static void cap_emit_signal (IRC_SERVER_REC *server, char *cmd, char *args) g_free(signal_name); } +static gboolean parse_cap_name(char *name, char **key, char **val) +{ + const char *eq; + + g_return_val_if_fail(name != NULL, FALSE); + g_return_val_if_fail(name[0] != '\0', FALSE); + + eq = strchr(name, '='); + /* KEY only value */ + if (eq == NULL) { + *key = g_strdup(name); + *val = NULL; + /* Some values are in a KEY=VALUE form, parse them */ + } else { + *key = g_strndup(name, (gsize)(eq - name)); + *val = g_strdup(eq + 1); + } + + return TRUE; +} + static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *address) { GSList *tmp; GString *cmd; - char *params, *evt, *list, **caps; - int i, caps_length, disable, avail_caps; + char *params, *evt, *list, *star, **caps; + int i, caps_length, disable, avail_caps, multiline; - params = event_get_params(args, 3, NULL, &evt, &list); + params = event_get_params(args, 4, NULL, &evt, &star, &list); if (params == NULL) return; + /* Multiline responses have an additional parameter and we have to do + * this stupid dance to parse them */ + if (!g_ascii_strcasecmp(evt, "LS") && !strcmp(star, "*")) { + multiline = TRUE; + } + /* This branch covers the '*' parameter isn't present, adjust the + * parameter pointer to compensate for this */ + else if (list[0] == '\0') { + multiline = FALSE; + list = star; + } + /* Malformed request, terminate the negotiation */ + else { + cap_finish_negotiation(server); + g_warn_if_reached(); + return; + } + + /* The table is created only when needed */ + if (server->cap_supported == NULL) { + server->cap_supported = g_hash_table_new_full(g_str_hash, + g_str_equal, + g_free, g_free); + } + /* Strip the trailing whitespaces before splitting the string, some servers send responses with * superfluous whitespaces that g_strsplit the interprets as tokens */ caps = g_strsplit(g_strchomp(list), " ", -1); caps_length = g_strv_length(caps); - if (!g_strcmp0(evt, "LS")) { + if (!g_ascii_strcasecmp(evt, "LS")) { + if (!server->cap_in_multiline) { + /* Throw away everything and start from scratch */ + g_hash_table_remove_all(server->cap_supported); + } + + server->cap_in_multiline = multiline; + /* Create a list of the supported caps */ - for (i = 0; i < caps_length; i++) - server->cap_supported = g_slist_prepend(server->cap_supported, g_strdup(caps[i])); + for (i = 0; i < caps_length; i++) { + char *key, *val; - /* Request the required caps, if any */ - if (server->cap_queue == NULL) { - cap_finish_negotiation(server); + if (!parse_cap_name(caps[i], &key, &val)) { + g_warning("Invalid CAP %s key/value pair", evt); + continue; + } + + if (!g_hash_table_insert(server->cap_supported, key, val)) { + /* The specification doesn't say anything about + * duplicated values, let's just warn the user */ + g_warning("The server sent the %s capability twice", key); + } } - else { - cmd = g_string_new("CAP REQ :"); - avail_caps = 0; + /* A multiline response is always terminated by a normal one, + * wait until we receive that one to require any CAP */ + if (multiline == FALSE) { + /* No CAP has been requested */ + if (server->cap_queue == NULL) { + cap_finish_negotiation(server); + } + else { + cmd = g_string_new("CAP REQ :"); + + avail_caps = 0; - /* Check whether the cap is supported by the server */ - for (tmp = server->cap_queue; tmp != NULL; tmp = tmp->next) { - if (gslist_find_string(server->cap_supported, tmp->data)) { - if (avail_caps > 0) - g_string_append_c(cmd, ' '); - g_string_append(cmd, tmp->data); + /* Check whether the cap is supported by the server */ + for (tmp = server->cap_queue; tmp != NULL; tmp = tmp->next) { + if (g_hash_table_lookup_extended(server->cap_supported, tmp->data, NULL, NULL)) { + if (avail_caps > 0) + g_string_append_c(cmd, ' '); + g_string_append(cmd, tmp->data); - avail_caps++; + avail_caps++; + } } - } - /* Clear the queue here */ - gslist_free_full(server->cap_queue, (GDestroyNotify) g_free); - server->cap_queue = NULL; + /* Clear the queue here */ + gslist_free_full(server->cap_queue, (GDestroyNotify) g_free); + server->cap_queue = NULL; - /* If the server doesn't support any cap we requested close the negotiation here */ - if (avail_caps > 0) - irc_send_cmd_now(server, cmd->str); - else - cap_finish_negotiation(server); + /* If the server doesn't support any cap we requested close the negotiation here */ + if (avail_caps > 0) + irc_send_cmd_now(server, cmd->str); + else + cap_finish_negotiation(server); - g_string_free(cmd, TRUE); + g_string_free(cmd, TRUE); + } } } - else if (!g_strcmp0(evt, "ACK")) { + else if (!g_ascii_strcasecmp(evt, "ACK")) { int got_sasl = FALSE; /* Emit a signal for every ack'd cap */ @@ -141,11 +210,11 @@ static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *add disable = (*caps[i] == '-'); if (disable) - server->cap_active = gslist_remove_string(server->cap_active, caps[i] + 1); + server->cap_active = gslist_delete_string(server->cap_active, caps[i] + 1, g_free); else server->cap_active = g_slist_prepend(server->cap_active, g_strdup(caps[i])); - if (!g_strcmp0(caps[i], "sasl")) + if (!strcmp(caps[i], "sasl")) got_sasl = TRUE; cap_emit_signal(server, "ack", caps[i]); @@ -157,7 +226,7 @@ static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *add if (got_sasl == FALSE) cap_finish_negotiation(server); } - else if (!g_strcmp0(evt, "NAK")) { + else if (!g_ascii_strcasecmp(evt, "NAK")) { g_warning("The server answered with a NAK to our CAP request, this should not happen"); /* A NAK'd request means that a required cap can't be enabled or disabled, don't update the @@ -165,6 +234,42 @@ static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *add for (i = 0; i < caps_length; i++) cap_emit_signal(server, "nak", caps[i]); } + else if (!g_ascii_strcasecmp(evt, "NEW")) { + for (i = 0; i < caps_length; i++) { + char *key, *val; + + if (!parse_cap_name(caps[i], &key, &val)) { + g_warning("Invalid CAP %s key/value pair", evt); + continue; + } + + g_hash_table_insert(server->cap_supported, key, val); + cap_emit_signal(server, "new", key); + } + } + else if (!g_ascii_strcasecmp(evt, "DEL")) { + for (i = 0; i < caps_length; i++) { + char *key, *val; + + if (!parse_cap_name(caps[i], &key, &val)) { + g_warning("Invalid CAP %s key/value pair", evt); + continue; + } + + g_hash_table_remove(server->cap_supported, key); + cap_emit_signal(server, "delete", key); + /* The server removed this CAP, remove it from the list + * of the active ones if we had requested it */ + server->cap_active = gslist_delete_string(server->cap_active, key, g_free); + /* We don't transfer the ownership of those two + * variables this time, just free them when we're done. */ + g_free(key); + g_free(val); + } + } + else { + g_warning("Unhandled CAP subcommand %s", evt); + } g_strfreev(caps); g_free(params); diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c index 4eaab712..e154d17f 100644 --- a/src/irc/core/irc-servers.c +++ b/src/irc/core/irc-servers.c @@ -443,8 +443,10 @@ static void sig_disconnected(IRC_SERVER_REC *server) gslist_free_full(server->cap_active, (GDestroyNotify) g_free); server->cap_active = NULL; - gslist_free_full(server->cap_supported, (GDestroyNotify) g_free); - server->cap_supported = NULL; + if (server->cap_supported) { + g_hash_table_destroy(server->cap_supported); + server->cap_supported = NULL; + } gslist_free_full(server->cap_queue, (GDestroyNotify) g_free); server->cap_queue = NULL; diff --git a/src/irc/core/irc-servers.h b/src/irc/core/irc-servers.h index 09f3f81d..1374e846 100644 --- a/src/irc/core/irc-servers.h +++ b/src/irc/core/irc-servers.h @@ -68,6 +68,7 @@ struct _IRC_SERVER_REC { unsigned int motd_got:1; /* We've received MOTD */ unsigned int isupport_sent:1; /* Server has sent us an isupport reply */ unsigned int cap_complete:1; /* We've done the initial CAP negotiation */ + unsigned int cap_in_multiline:1; /* We're waiting for the multiline response to end */ unsigned int sasl_success:1; /* Did we authenticate successfully ? */ int max_kicks_in_cmd; /* max. number of people to kick with one /KICK command */ @@ -75,7 +76,7 @@ struct _IRC_SERVER_REC { int max_whois_in_cmd; /* max. number of nicks in one /WHOIS command */ int max_msgs_in_cmd; /* max. number of targets in one /MSG */ - GSList *cap_supported; /* A list of caps supported by the server */ + GHashTable *cap_supported; /* A list of caps supported by the server */ GSList *cap_active; /* A list of caps active for this session */ GSList *cap_queue; /* A list of caps to request on connection */ diff --git a/src/perl/irc/Irc.xs b/src/perl/irc/Irc.xs index 41690010..3bf81f9a 100644 --- a/src/perl/irc/Irc.xs +++ b/src/perl/irc/Irc.xs @@ -12,7 +12,10 @@ static void perl_irc_connect_fill_hash(HV *hv, IRC_SERVER_CONNECT_REC *conn) static void perl_irc_server_fill_hash(HV *hv, IRC_SERVER_REC *server) { AV *av; + HV *hv_; GSList *tmp; + GHashTableIter iter; + gpointer key_, val_; perl_irc_connect_fill_hash(hv, server->connrec); perl_server_fill_hash(hv, (SERVER_REC *) server); @@ -34,10 +37,16 @@ static void perl_irc_server_fill_hash(HV *hv, IRC_SERVER_REC *server) (void) hv_store(hv, "cap_complete", 12, newSViv(server->cap_complete), 0); (void) hv_store(hv, "sasl_success", 12, newSViv(server->sasl_success), 0); - av = newAV(); - for (tmp = server->cap_supported; tmp != NULL; tmp = tmp->next) - av_push(av, new_pv(tmp->data)); - (void) hv_store(hv, "cap_supported", 13, newRV_noinc((SV*)av), 0); + if (server->cap_supported != NULL) { + hv_ = newHV(); + g_hash_table_iter_init(&iter, server->cap_supported); + while (g_hash_table_iter_next(&iter, &key_, &val_)) { + char *key = (char *)key_; + char *val = (char *)val_; + hv_store(hv_, key, strlen(key), new_pv(val), 0); + } + (void) hv_store(hv, "cap_supported", 13, newRV_noinc((SV*)hv_), 0); + } av = newAV(); for (tmp = server->cap_active; tmp != NULL; tmp = tmp->next) diff --git a/src/perl/textui/TextUI.xs b/src/perl/textui/TextUI.xs index 12732e3f..e2f162a0 100644 --- a/src/perl/textui/TextUI.xs +++ b/src/perl/textui/TextUI.xs @@ -124,6 +124,74 @@ gui_input_set(str) CODE: gui_entry_set_text(active_entry, str); +void +gui_input_set_extent(pos, text) + int pos + char *text +PREINIT: + char *tt; +CODE: + tt = text != NULL ? format_string_expand(text, NULL) : NULL; + gui_entry_set_extent(active_entry, pos, tt); + g_free(tt); + +void +gui_input_set_extents(pos, len, left, right) + int pos + int len + char *left + char *right +PREINIT: + char *tl; + char *tr; +CODE: + tl = left != NULL ? format_string_expand(left, NULL) : NULL; + tr = right != NULL ? format_string_expand(right, NULL) : NULL; + gui_entry_set_extents(active_entry, pos, len, tl, tr); + g_free(tl); + g_free(tr); + +void +gui_input_clear_extents(pos, len = 0) + int pos + int len +CODE: + gui_entry_clear_extents(active_entry, pos, len); + +void +gui_input_get_extent(pos) + int pos +PREINIT: + char *ret; +PPCODE: + ret = gui_entry_get_extent(active_entry, pos); + XPUSHs(sv_2mortal(new_pv(ret))); + g_free(ret); + +void +gui_input_get_text_and_extents() +PREINIT: + GSList *ret, *tmp; +PPCODE: + ret = gui_entry_get_text_and_extents(active_entry); + for (tmp = ret; tmp != NULL; tmp = tmp->next) { + XPUSHs(sv_2mortal(new_pv(tmp->data))); + } + g_slist_free_full(ret, g_free); + +void +gui_input_set_text_and_extents(...) +PREINIT: + GSList *list; + int i; +PPCODE: + list = NULL; + for (i = items; i > 0; i--) { + list = g_slist_prepend(list, SvPV_nolen(ST(i-1))); + } + gui_entry_set_text_and_extents(active_entry, list); + g_slist_free(list); + int gui_input_get_pos() CODE: |