diff options
Diffstat (limited to 'src')
61 files changed, 2954 insertions, 634 deletions
diff --git a/src/common.h b/src/common.h index a7eaa7cf..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 12 +#define IRSSI_ABI_VERSION 15 #define DEFAULT_SERVER_ADD_PORT 6667 #define DEFAULT_SERVER_ADD_TLS_PORT 6697 diff --git a/src/core/channels-setup.c b/src/core/channels-setup.c index 4966d77d..8002646d 100644 --- a/src/core/channels-setup.c +++ b/src/core/channels-setup.c @@ -37,9 +37,14 @@ static int compare_channel_setup (CONFIG_NODE *node, CHANNEL_SETUP_REC *channel) name = config_node_get_str(node, "name", NULL); chatnet = config_node_get_str(node, "chatnet", NULL); - if (g_strcmp0(name, channel->name) != 0 || - g_strcmp0(chatnet, channel->chatnet) != 0) + if (name == NULL || chatnet == NULL) { + return 0; + } + + if (g_ascii_strcasecmp(name, channel->name) != 0 || + g_ascii_strcasecmp(chatnet, channel->chatnet) != 0) { return 1; + } return 0; } diff --git a/src/core/levels.c b/src/core/levels.c index e623c4de..eb7efcf7 100644 --- a/src/core/levels.c +++ b/src/core/levels.c @@ -21,6 +21,7 @@ #include "module.h" #include "levels.h" +/* the order of these levels must match the bits in levels.h */ static const char *levels[] = { "CRAP", "MSGS", @@ -44,9 +45,6 @@ static const char *levels[] = { "CLIENTCRAP", "CLIENTERRORS", "HILIGHTS", - - "NOHILIGHT", - "NO_ACT", NULL }; @@ -63,6 +61,9 @@ int level_get(const char *level) if (g_ascii_strcasecmp(level, "NO_ACT") == 0) return MSGLEVEL_NO_ACT; + if (g_ascii_strcasecmp(level, "HIDDEN") == 0) + return MSGLEVEL_HIDDEN; + len = strlen(level); if (len == 0) return 0; @@ -138,17 +139,13 @@ char *bits2level(int bits) str = g_string_new(NULL); - if (bits & MSGLEVEL_NEVER) { + if (bits & MSGLEVEL_NEVER) g_string_append(str, "NEVER "); - bits &= ~MSGLEVEL_NEVER; - } - if (bits & MSGLEVEL_NO_ACT) { + if (bits & MSGLEVEL_NO_ACT) g_string_append(str, "NO_ACT "); - bits &= ~MSGLEVEL_NO_ACT; - } - if (bits == MSGLEVEL_ALL) { + if ((bits & MSGLEVEL_ALL) == MSGLEVEL_ALL) { g_string_append(str, "ALL "); } else { for (n = 0; levels[n] != NULL; n++) { @@ -156,6 +153,10 @@ char *bits2level(int bits) g_string_append_printf(str, "%s ", levels[n]); } } + + if (bits & MSGLEVEL_HIDDEN) + g_string_append(str, "HIDDEN "); + if (str->len > 0) g_string_truncate(str, str->len-1); diff --git a/src/core/levels.h b/src/core/levels.h index 9f7e588f..b0ebafba 100644 --- a/src/core/levels.h +++ b/src/core/levels.h @@ -36,7 +36,9 @@ enum { MSGLEVEL_NOHILIGHT = 0x1000000, /* Don't highlight this message */ MSGLEVEL_NO_ACT = 0x2000000, /* Don't trigger channel activity */ MSGLEVEL_NEVER = 0x4000000, /* never ignore / never log */ - MSGLEVEL_LASTLOG = 0x8000000 /* never ignore / never log */ + MSGLEVEL_LASTLOG = 0x8000000, /* used for /lastlog */ + + MSGLEVEL_HIDDEN = 0x10000000 /* Hidden from view */ }; int level_get(const char *level); diff --git a/src/core/misc.c b/src/core/misc.c index e589b8c5..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) { @@ -703,8 +716,11 @@ int expand_escape(const char **data) *data += 2; return strtol(digit, NULL, 16); case 'c': - /* control character (\cA = ^A) */ - (*data)++; + /* check for end of string */ + if ((*data)[1] == '\0') + return 0; + /* control character (\cA = ^A) */ + (*data)++; return i_toupper(**data) - 64; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': 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/core/servers-setup.c b/src/core/servers-setup.c index 9492c58c..2a92a367 100644 --- a/src/core/servers-setup.c +++ b/src/core/servers-setup.c @@ -474,10 +474,15 @@ static int compare_server_setup (CONFIG_NODE *node, SERVER_SETUP_REC *server) chatnet = config_node_get_str(node, "chatnet", NULL); port = config_node_get_int(node, "port", 0); - if (g_strcmp0(address, server->address) != 0 || - g_strcmp0(chatnet, server->chatnet) != 0 || - port != server->port) + if (address == NULL || chatnet == NULL) { + return 0; + } + + if (g_ascii_strcasecmp(address, server->address) != 0 || + g_ascii_strcasecmp(chatnet, server->chatnet) != 0 || + port != server->port) { return 1; + } return 0; } diff --git a/src/core/special-vars.c b/src/core/special-vars.c index aaf8da8f..33d9cd55 100644 --- a/src/core/special-vars.c +++ b/src/core/special-vars.c @@ -33,6 +33,8 @@ #define isarg(c) \ (i_isdigit(c) || (c) == '*' || (c) == '~' || (c) == '-') +#define ALIGN_MAX 222488 + static SPECIAL_HISTORY_FUNC history_func = NULL; static char *get_argument(char **cmd, char **arglist) @@ -300,6 +302,10 @@ static int get_alignment_args(char **data, int *align, int *flags, char *pad) if (!parse_uint(str, &endptr, 10, &align_)) { return FALSE; } + /* alignment larger than supported */ + if (align_ > ALIGN_MAX) { + return FALSE; + } str = endptr; *align = align_; @@ -337,11 +343,14 @@ char *get_alignment(const char *text, int align, int flags, char pad) /* add pad characters */ if (flags & ALIGN_PAD) { - while (string_width(str->str, policy) < align) { + int pad_len = align - string_width(str->str, policy); + if (pad_len > 0) { + char *pad_full = g_strnfill(pad_len, pad); if (flags & ALIGN_RIGHT) - g_string_prepend_c(str, pad); + g_string_prepend(str, pad_full); else - g_string_append_c(str, pad); + g_string_append(str, pad_full); + g_free(pad_full); } } @@ -384,6 +393,7 @@ char *parse_special(char **cmd, SERVER_REC *server, void *item, } nest_free = FALSE; nest_value = NULL; +#if 0 /* this code is disabled due to security issues until it is fixed */ if (**cmd == '(' && (*cmd)[1] != '\0') { /* subvariable */ int toplevel = nested_orig_cmd == NULL; @@ -412,6 +422,9 @@ char *parse_special(char **cmd, SERVER_REC *server, void *item, if (toplevel) nested_orig_cmd = NULL; } +#else + if (nested_orig_cmd) nested_orig_cmd = NULL; +#endif if (**cmd != '{') brackets = FALSE; diff --git a/src/fe-common/core/chat-completion.c b/src/fe-common/core/chat-completion.c index 97cd0565..d610008f 100644 --- a/src/fe-common/core/chat-completion.c +++ b/src/fe-common/core/chat-completion.c @@ -173,6 +173,7 @@ static void sig_message_public(SERVER_REC *server, const char *msg, { CHANNEL_REC *channel; int own; + g_return_if_fail(nick != NULL); channel = channel_find(server, target); if (channel != NULL) { @@ -185,6 +186,7 @@ static void sig_message_join(SERVER_REC *server, const char *channel, const char *nick, const char *address) { CHANNEL_REC *chanrec; + g_return_if_fail(nick != NULL); chanrec = channel_find(server, channel); if (chanrec != NULL) @@ -639,6 +641,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 +746,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/core/completion.c b/src/fe-common/core/completion.c index e78fe7d5..fd452e5c 100644 --- a/src/fe-common/core/completion.c +++ b/src/fe-common/core/completion.c @@ -187,12 +187,18 @@ char *word_complete(WINDOW_REC *window, const char *line, int *pos, int erase, i char *old; old = linestart; - linestart = *linestart == '\0' ? - g_strdup(word) : - g_strdup_printf("%s%c%s", - /* do not accidentally duplicate the word separator */ - line == wordstart - 1 ? "" : linestart, - old_wordstart[-1], word); + /* we want to move word into linestart */ + if (*linestart == '\0') { + linestart = g_strdup(word); + } else { + GString *str = g_string_new(linestart); + if (old_wordstart[-1] != str->str[str->len - 1]) { + /* do not accidentally duplicate the word separator */ + g_string_append_c(str, old_wordstart[-1]); + } + g_string_append(str, word); + linestart = g_string_free(str, FALSE); + } g_free(old); g_free(word); diff --git a/src/fe-common/core/fe-common-core.c b/src/fe-common/core/fe-common-core.c index d0d36490..ef5d2113 100644 --- a/src/fe-common/core/fe-common-core.c +++ b/src/fe-common/core/fe-common-core.c @@ -470,26 +470,49 @@ void fe_common_core_finish_init(void) gboolean strarray_find_dest(char **array, const TEXT_DEST_REC *dest) { + WI_ITEM_REC *item; + int server_tag_len, channel_type, query_type; + char **tmp; + + channel_type = module_get_uniq_id_str("WINDOW ITEM TYPE", "CHANNEL"); + query_type = module_get_uniq_id_str("WINDOW ITEM TYPE", "QUERY"); + g_return_val_if_fail(array != NULL, FALSE); + g_return_val_if_fail(dest != NULL, FALSE); + g_return_val_if_fail(dest->window != NULL, FALSE); + g_return_val_if_fail(dest->target != NULL, FALSE); - if (strarray_find(array, "*") != -1) - return TRUE; + item = window_item_find_window(dest->window, dest->server, dest->target); + if (item == NULL) { + return FALSE; + } - if (strarray_find(array, dest->target) != -1) - return TRUE; + server_tag_len = dest->server_tag != NULL ? strlen(dest->server_tag) : 0; + for (tmp = array; *tmp != NULL; tmp++) { + char *str = *tmp; + if (*str == '\0') { + continue; + } - if (dest->server_tag != NULL) { - char *tagtarget = g_strdup_printf("%s/%s", dest->server_tag, "*"); - int ret = strarray_find(array, tagtarget); - g_free(tagtarget); - if (ret != -1) - return TRUE; + if (server_tag_len && + g_ascii_strncasecmp(str, dest->server_tag, server_tag_len) == 0 && + str[server_tag_len] == '/') { + str += server_tag_len + 1; + } - tagtarget = g_strdup_printf("%s/%s", dest->server_tag, dest->target); - ret = strarray_find(array, tagtarget); - g_free(tagtarget); - if (ret != -1) + if (g_strcmp0(str, "") == 0 || g_strcmp0(str, "::all") == 0) { return TRUE; + } else if (g_ascii_strcasecmp(str, dest->target) == 0) { + return TRUE; + } else if (item->type == query_type && + g_strcmp0(str, dest->target[0] == '=' ? "::dccqueries" : + "::queries") == 0) { + return TRUE; + } else if (item->type == channel_type && + g_strcmp0(str, "::channels") == 0) { + return TRUE; + } } + return FALSE; } diff --git a/src/fe-common/core/fe-core-commands.c b/src/fe-common/core/fe-core-commands.c index fb98cc25..222543d5 100644 --- a/src/fe-common/core/fe-core-commands.c +++ b/src/fe-common/core/fe-core-commands.c @@ -114,7 +114,7 @@ static void cmd_version(char *data) } } -/* SYNTAX: CAT <file> */ +/* SYNTAX: CAT <file> [<seek position>] */ static void cmd_cat(const char *data) { char *fname, *fposstr; diff --git a/src/fe-common/core/fe-exec.c b/src/fe-common/core/fe-exec.c index 36990866..c1739d39 100644 --- a/src/fe-common/core/fe-exec.c +++ b/src/fe-common/core/fe-exec.c @@ -613,7 +613,7 @@ static void sig_exec_input(PROCESS_REC *rec, const char *text) str = g_strconcat(rec->target_nick ? "-nick " : rec->target_channel ? "-channel " : "", - rec->target, " ", text, NULL); + rec->target, " ", *text == '\0' ? " " : text, NULL); signal_emit(rec->notice ? "command notice" : "command msg", 3, str, server, item); g_free(str); diff --git a/src/fe-common/core/fe-windows.h b/src/fe-common/core/fe-windows.h index 32d6cfcd..aaa773c8 100644 --- a/src/fe-common/core/fe-windows.h +++ b/src/fe-common/core/fe-windows.h @@ -11,6 +11,14 @@ enum { DATA_LEVEL_HILIGHT }; +enum { + MAIN_WINDOW_TYPE_NONE = -1, + MAIN_WINDOW_TYPE_DEFAULT = 0, + MAIN_WINDOW_TYPE_HIDDEN = 1, + MAIN_WINDOW_TYPE_SPLIT = 2, + MAIN_WINDOW_TYPE_RSPLIT = 3 +}; + typedef struct { char *servertag; char *name; diff --git a/src/fe-common/core/formats.c b/src/fe-common/core/formats.c index 37db6f7c..4c819c2d 100644 --- a/src/fe-common/core/formats.c +++ b/src/fe-common/core/formats.c @@ -480,27 +480,30 @@ int format_real_length(const char *str, int len) start = str; tmp = g_string_new(NULL); - while (*str != '\0' && len > 0) { + while (*str != '\0') { + oldstr = str; if (*str == '%' && str[1] != '\0') { str++; if (*str != '%') { adv = format_expand_styles(tmp, &str, NULL); - str += adv; - if (adv) - continue; - } - - /* %% or unknown %code, written as-is */ - if (*str != '%') { - if (--len == 0) - break; + if (adv) { + str += adv; + continue; + } + /* discount for unknown % */ + if (--len < 0) { + str = oldstr; + break; + } + oldstr = str; } } - oldstr = str; len -= string_advance(&str, utf8); - if (len < 0) + if (len < 0) { str = oldstr; + break; + } } g_string_free(tmp, TRUE); diff --git a/src/fe-common/core/keyboard.c b/src/fe-common/core/keyboard.c index 6f7907eb..c3df5ed7 100644 --- a/src/fe-common/core/keyboard.c +++ b/src/fe-common/core/keyboard.c @@ -156,6 +156,7 @@ static void keyconfig_save(const char *id, const char *key, const char *data) static void keyconfig_clear(const char *key) { CONFIG_NODE *node; + KEY_REC *rec; g_return_if_fail(key != NULL); @@ -165,6 +166,11 @@ static void keyconfig_clear(const char *key) iconfig_node_remove(iconfig_node_traverse("(keyboard", FALSE), node); } + if ((rec = g_hash_table_lookup(default_keys, key)) != NULL) { + node = iconfig_node_traverse("(keyboard", TRUE); + node = iconfig_node_section(node, NULL, NODE_TYPE_BLOCK); + iconfig_node_set_str(node, "key", key); + } } KEYINFO_REC *key_info_find(const char *id) @@ -569,13 +575,38 @@ void key_configure_remove(const char *key) g_return_if_fail(key != NULL); + keyconfig_clear(key); + rec = g_hash_table_lookup(keys, key); if (rec == NULL) return; - keyconfig_clear(key); key_configure_destroy(rec); } +/* Reset key to default */ +void key_configure_reset(const char *key) +{ + KEY_REC *rec; + CONFIG_NODE *node; + + g_return_if_fail(key != NULL); + + node = key_config_find(key); + if (node != NULL) { + iconfig_node_remove(iconfig_node_traverse("(keyboard", FALSE), node); + } + + if ((rec = g_hash_table_lookup(default_keys, key)) != NULL) { + key_configure_create(rec->info->id, rec->key, rec->data); + } else { + rec = g_hash_table_lookup(keys, key); + if (rec == NULL) + return; + + key_configure_destroy(rec); + } +} + static int key_emit_signal(KEYBOARD_REC *keyboard, KEY_REC *key) { int consumed; @@ -739,7 +770,9 @@ static void cmd_show_keys(const char *searchkey, int full) for (key = rec->keys; key != NULL; key = key->next) { KEY_REC *rec = key->data; - if ((len == 0 || g_ascii_strncasecmp(rec->key, searchkey, len) == 0) && + if ((len == 0 || + (full ? strncmp(rec->key, searchkey, len) == 0 : + g_ascii_strncasecmp(rec->key, searchkey, len) == 0)) && (!full || rec->key[len] == '\0')) { printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_LIST, rec->key, rec->info->id, rec->data == NULL ? "" : rec->data); @@ -750,7 +783,7 @@ static void cmd_show_keys(const char *searchkey, int full) printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_FOOTER); } -/* SYNTAX: BIND [-list] [-delete] [<key> [<command> [<data>]]] */ +/* SYNTAX: BIND [-list] [-delete | -reset] [<key> [<command> [<data>]]] */ static void cmd_bind(const char *data) { GHashTable *optlist; @@ -780,6 +813,12 @@ static void cmd_bind(const char *data) key_configure_remove(key); cmd_params_free(free_arg); return; + } else if (*key != '\0' && g_hash_table_lookup(optlist, "reset")) { + /* reset key */ + key_configure_reset(key); + cmd_show_keys(key, TRUE); + cmd_params_free(free_arg); + return; } if (*id == '\0') { @@ -878,8 +917,13 @@ static void key_config_read(CONFIG_NODE *node) id = config_node_get_str(node, "id", NULL); data = config_node_get_str(node, "data", NULL); - if (key != NULL && id != NULL) + if (key != NULL && id != NULL) { key_configure_create(id, key, data); + } else if (key != NULL && id == NULL && data == NULL) { + KEY_REC *rec = g_hash_table_lookup(keys, key); + if (rec != NULL) + key_configure_destroy(rec); + } } static void read_keyboard_config(void) @@ -938,7 +982,7 @@ void keyboard_init(void) signal_add("complete command bind", (SIGNAL_FUNC) sig_complete_bind); command_bind("bind", NULL, (SIGNAL_FUNC) cmd_bind); - command_set_options("bind", "delete list"); + command_set_options("bind", "delete reset list"); } void keyboard_deinit(void) diff --git a/src/fe-common/core/themes.c b/src/fe-common/core/themes.c index cb1cce8f..e9818be9 100644 --- a/src/fe-common/core/themes.c +++ b/src/fe-common/core/themes.c @@ -130,7 +130,7 @@ static char *theme_replace_expand(THEME_REC *theme, int index, abstract = rec->data; abstract = theme_format_expand_data(theme, (const char **) &abstract, default_fg, default_bg, - last_fg, last_bg, flags); + last_fg, last_bg, (flags | EXPAND_FLAG_IGNORE_REPLACES)); ret = parse_special_string(abstract, NULL, NULL, data, NULL, PARSE_FLAG_ONLY_ARGS); g_free(abstract); @@ -382,7 +382,8 @@ char *theme_format_expand_get(THEME_REC *theme, const char **format) } else { theme_format_append_next(theme, str, format, reset, reset, - &dummy, &dummy, 0); + &dummy, &dummy, + EXPAND_FLAG_IGNORE_REPLACES); continue; } @@ -400,16 +401,19 @@ char *theme_format_expand_get(THEME_REC *theme, const char **format) return ret; } +static char *theme_format_expand_data_rec(THEME_REC *theme, const char **format, + theme_rm_col default_fg, theme_rm_col default_bg, + theme_rm_col *save_last_fg, theme_rm_col *save_last_bg, + int flags, GTree *block_list); + /* expand a single {abstract ...data... } */ -static char *theme_format_expand_abstract(THEME_REC *theme, - const char **formatp, - theme_rm_col *last_fg, - theme_rm_col *last_bg, - int flags) +static char *theme_format_expand_abstract(THEME_REC *theme, const char **formatp, + theme_rm_col *last_fg, theme_rm_col *last_bg, int flags, + GTree *block_list) { GString *str; const char *p, *format; - char *abstract, *data, *ret; + char *abstract, *data, *ret, *blocking; theme_rm_col default_fg, default_bg; int len; @@ -439,12 +443,22 @@ static char *theme_format_expand_abstract(THEME_REC *theme, } *formatp = format+len; + if (block_list == NULL) { + block_list = g_tree_new_full((GCompareDataFunc) g_strcmp0, NULL, g_free, NULL); + } else { + g_tree_ref(block_list); + } + /* get the abstract data */ data = g_hash_table_lookup(theme->abstracts, abstract); - g_free(abstract); - if (data == NULL) { + if (data == NULL || g_tree_lookup(block_list, abstract) != NULL) { /* unknown abstract, just display the data */ data = "$0-"; + g_free(abstract); + blocking = NULL; + } else { + blocking = abstract; + g_tree_insert(block_list, blocking, blocking); } abstract = g_strdup(data); @@ -473,7 +487,7 @@ static char *theme_format_expand_abstract(THEME_REC *theme, str = g_string_new(NULL); p = ret; while (*p != '\0') { - if (*p == '\\') { + if (*p == '\\' && p[1] != '\0') { int chr; p++; chr = expand_escape(&p); @@ -488,18 +502,20 @@ static char *theme_format_expand_abstract(THEME_REC *theme, /* abstract may itself contain abstracts or replaces */ p = abstract; - ret = theme_format_expand_data(theme, &p, default_fg, default_bg, - last_fg, last_bg, - flags | EXPAND_FLAG_LASTCOLOR_ARG); + ret = theme_format_expand_data_rec(theme, &p, default_fg, default_bg, last_fg, last_bg, + flags | EXPAND_FLAG_LASTCOLOR_ARG, block_list); g_free(abstract); + if (blocking != NULL) { + g_tree_remove(block_list, blocking); + } + g_tree_unref(block_list); return ret; } -/* expand the data part in {abstract data} */ -char *theme_format_expand_data(THEME_REC *theme, const char **format, - theme_rm_col default_fg, theme_rm_col default_bg, - theme_rm_col *save_last_fg, theme_rm_col *save_last_bg, - int flags) +static char *theme_format_expand_data_rec(THEME_REC *theme, const char **format, + theme_rm_col default_fg, theme_rm_col default_bg, + theme_rm_col *save_last_fg, theme_rm_col *save_last_bg, + int flags, GTree *block_list) { GString *str; char *ret, *abstract; @@ -545,9 +561,8 @@ char *theme_format_expand_data(THEME_REC *theme, const char **format, break; /* error */ /* get a single {...} */ - abstract = theme_format_expand_abstract(theme, format, - &last_fg, &last_bg, - recurse_flags); + abstract = theme_format_expand_abstract(theme, format, &last_fg, &last_bg, + recurse_flags, block_list); if (abstract != NULL) { g_string_append(str, abstract); g_free(abstract); @@ -565,6 +580,15 @@ char *theme_format_expand_data(THEME_REC *theme, const char **format, return ret; } +/* expand the data part in {abstract data} */ +char *theme_format_expand_data(THEME_REC *theme, const char **format, theme_rm_col default_fg, + theme_rm_col default_bg, theme_rm_col *save_last_fg, + theme_rm_col *save_last_bg, int flags) +{ + return theme_format_expand_data_rec(theme, format, default_fg, default_bg, save_last_bg, + save_last_bg, flags, NULL); +} + #define IS_OLD_FORMAT(code, last_fg, last_bg) \ (((code) == 'n' && (last_fg) == 'n' && (last_bg) == 'n') || \ ((code) != 'n' && ((code) == (last_fg) || (code) == (last_bg)))) diff --git a/src/fe-common/core/window-commands.c b/src/fe-common/core/window-commands.c index 57c81ac2..a81c0180 100644 --- a/src/fe-common/core/window-commands.c +++ b/src/fe-common/core/window-commands.c @@ -169,7 +169,7 @@ static void cmd_window(const char *data, void *server, WI_ITEM_REC *item) command_runsub("window", data, server, item); } -/* SYNTAX: WINDOW NEW [HIDDEN|SPLIT] */ +/* SYNTAX: WINDOW NEW [HIDDEN|SPLIT|RSPLIT] */ static void cmd_window_new(const char *data, void *server, WI_ITEM_REC *item) { WINDOW_REC *window; @@ -177,8 +177,9 @@ static void cmd_window_new(const char *data, void *server, WI_ITEM_REC *item) g_return_if_fail(data != NULL); - type = (g_ascii_strncasecmp(data, "hid", 3) == 0 || g_ascii_strcasecmp(data, "tab") == 0) ? 1 : - (g_ascii_strcasecmp(data, "split") == 0 ? 2 : 0); + type = (g_ascii_strncasecmp(data, "hid", 3) == 0 || g_ascii_strcasecmp(data, "tab") == 0) ? MAIN_WINDOW_TYPE_HIDDEN : + g_ascii_strcasecmp(data, "split") == 0 ? MAIN_WINDOW_TYPE_SPLIT : + g_ascii_strncasecmp(data, "rs", 2) == 0 ? MAIN_WINDOW_TYPE_RSPLIT : MAIN_WINDOW_TYPE_DEFAULT; signal_emit("gui window create override", 1, GINT_TO_POINTER(type)); window = window_create(NULL, FALSE); diff --git a/src/fe-common/core/window-items.c b/src/fe-common/core/window-items.c index bd6ae5e9..8eab7124 100644 --- a/src/fe-common/core/window-items.c +++ b/src/fe-common/core/window-items.c @@ -314,7 +314,7 @@ void window_item_create(WI_ITEM_REC *item, int automatic) /* create new window to use */ if (settings_get_bool("autocreate_split_windows")) { signal_emit("gui window create override", 1, - GINT_TO_POINTER(0)); + GINT_TO_POINTER(MAIN_WINDOW_TYPE_SPLIT)); } window = window_create(item, automatic); } else { diff --git a/src/fe-common/irc/fe-irc-commands.c b/src/fe-common/irc/fe-irc-commands.c index 11a911d2..7a6b17a6 100644 --- a/src/fe-common/irc/fe-irc-commands.c +++ b/src/fe-common/irc/fe-irc-commands.c @@ -345,16 +345,18 @@ static void cmd_ts(const char *data) } typedef struct { - IRC_SERVER_REC *server; + char *server_tag; char *nick; } OPER_PASS_REC; static void cmd_oper_got_pass(const char *password, OPER_PASS_REC *rec) { - if (*password != '\0') - irc_send_cmdv(rec->server, "OPER %s %s", rec->nick, password); + SERVER_REC *server_rec = server_find_tag(rec->server_tag); + if (*password != '\0' && IS_IRC_SERVER(server_rec)) + irc_send_cmdv((IRC_SERVER_REC *) server_rec, "OPER %s %s", rec->nick, password); g_free(rec->nick); - g_free(rec); + g_free(rec->server_tag); + g_free(rec); } static void cmd_oper(const char *data, IRC_SERVER_REC *server) @@ -374,7 +376,7 @@ static void cmd_oper(const char *data, IRC_SERVER_REC *server) OPER_PASS_REC *rec; rec = g_new(OPER_PASS_REC, 1); - rec->server = server; + rec->server_tag = g_strdup(server->tag); rec->nick = g_strdup(*nick != '\0' ? nick : server->nick); format = format_get_text(MODULE_NAME, NULL, server, NULL, diff --git a/src/fe-common/irc/fe-netjoin.c b/src/fe-common/irc/fe-netjoin.c index 8272093f..9ea633b4 100644 --- a/src/fe-common/irc/fe-netjoin.c +++ b/src/fe-common/irc/fe-netjoin.c @@ -253,15 +253,17 @@ static void sig_print_starting(TEXT_DEST_REC *dest) if (!IS_IRC_SERVER(dest->server)) return; - if (!(dest->level & MSGLEVEL_PUBLIC)) - return; - - if (!server_ischannel(dest->server, dest->target)) - return; - rec = netjoin_find_server(IRC_SERVER(dest->server)); - if (rec != NULL && rec->netjoins != NULL) - print_netjoins(rec, dest->target); + if (rec != NULL && rec->netjoins != NULL) { + /* if netjoins exists, the server rec should be + still valid. otherwise, calling server->ischannel + may not be safe. */ + if (dest->target != NULL && + !server_ischannel((SERVER_REC *) rec->server, dest->target)) + return; + + print_netjoins(rec, 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 4c69dd10..258d0d57 100644 --- a/src/fe-common/irc/fe-netsplit.c +++ b/src/fe-common/irc/fe-netsplit.c @@ -199,7 +199,7 @@ static void temp_split_chan_free(TEMP_SPLIT_CHAN_REC *rec) g_free(rec); } -static void print_splits(IRC_SERVER_REC *server, const char *channel) +static void print_splits(IRC_SERVER_REC *server, const char *filter_channel) { TEMP_SPLIT_REC temp; GSList *servers; @@ -218,7 +218,7 @@ static void print_splits(IRC_SERVER_REC *server, const char *channel) g_hash_table_foreach(server->splits, (GHFunc) get_server_splits, &temp); - print_server_splits(server, &temp, channel); + print_server_splits(server, &temp, filter_channel); g_slist_foreach(temp.channels, (GFunc) temp_split_chan_free, NULL); @@ -255,15 +255,16 @@ static void sig_print_starting(TEXT_DEST_REC *dest) if (!IS_IRC_SERVER(dest->server)) return; - if (!(dest->level & MSGLEVEL_PUBLIC)) - return; - - if (!server_ischannel(dest->server, dest->target)) - return; - rec = IRC_SERVER(dest->server); - if (rec->split_servers != NULL) - print_splits(rec, dest->target); + if (rec->split_servers != NULL) { + /* if split_servers exists, the server rec should be + still valid. otherwise, calling server->ischannel + may not be safe. */ + if (dest->target != NULL && !server_ischannel((SERVER_REC *) rec, dest->target)) + return; + + print_splits(rec, NULL); + } } static int sig_check_splits(void) diff --git a/src/fe-fuzz/Makefile.am b/src/fe-fuzz/Makefile.am index c11b3dbb..40abd5ba 100644 --- a/src/fe-fuzz/Makefile.am +++ b/src/fe-fuzz/Makefile.am @@ -1,3 +1,5 @@ +SUBDIRS = irc fe-common + bin_PROGRAMS = irssi-fuzz # Force link with CXX for libfuzzer support diff --git a/src/fe-fuzz/fe-common/Makefile.am b/src/fe-fuzz/fe-common/Makefile.am new file mode 100644 index 00000000..52770885 --- /dev/null +++ b/src/fe-fuzz/fe-common/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = core diff --git a/src/fe-fuzz/fe-common/core/Makefile.am b/src/fe-fuzz/fe-common/core/Makefile.am new file mode 100644 index 00000000..4fe5937c --- /dev/null +++ b/src/fe-fuzz/fe-common/core/Makefile.am @@ -0,0 +1,46 @@ +bin_PROGRAMS = theme-load-fuzz + +# Force link with CXX for libfuzzer support +CCLD=$(CXX) $(CXXFLAGS) + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core/ \ + -I$(top_srcdir)/src/irc/core/ \ + -I$(top_srcdir)/src/fe-common/core/ \ + $(GLIB_CFLAGS) + +AM_DEPENDENCIES = \ + ../../../core/libcore.a \ + ../../../lib-config/libirssi_config.a \ + ../../../irc/libirc.a \ + ../../../irc/core/libirc_core.a \ + ../../../irc/dcc/libirc_dcc.a \ + ../../../irc/flood/libirc_flood.a \ + ../../../irc/notifylist/libirc_notifylist.a \ + ../../../fe-common/core/libfe_common_core.a \ + ../../../fe-common/irc/libfe_common_irc.a \ + ../../../fe-common/irc/dcc/libfe_irc_dcc.a \ + ../../../fe-common/irc/notifylist/libfe_irc_notifylist.a + +LDADD = \ + ../../../irc/libirc.a \ + ../../../irc/core/libirc_core.a \ + ../../../irc/dcc/libirc_dcc.a \ + ../../../irc/flood/libirc_flood.a \ + ../../../irc/notifylist/libirc_notifylist.a \ + ../../../fe-common/core/libfe_common_core.a \ + ../../../fe-common/irc/libfe_common_irc.a \ + ../../../fe-common/irc/dcc/libfe_irc_dcc.a \ + ../../../fe-common/irc/notifylist/libfe_irc_notifylist.a \ + ../../../core/libcore.a \ + ../../../lib-config/libirssi_config.a \ + @PROG_LIBS@ \ + $(FUZZER_LIBS) + +theme_load_fuzz_SOURCES = \ + theme-load.c \ + $(top_srcdir)/src/fe-text/module-formats.c + +noinst_HEADERS = \ + $(top_srcdir)/src/fe-text/module-formats.h diff --git a/src/fe-fuzz/fe-common/core/theme-load.c b/src/fe-fuzz/fe-common/core/theme-load.c new file mode 100644 index 00000000..14df74c6 --- /dev/null +++ b/src/fe-fuzz/fe-common/core/theme-load.c @@ -0,0 +1,66 @@ +/* + theme-load.c : irssi + + Copyright (C) 2018 Joseph Bisch + + This program 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 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include "modules-load.h" +#include "levels.h" +#include "../fe-text/module-formats.h" // need to explicitly grab from fe-text +#include "themes.h" +#include "core.h" +#include "fe-common-core.h" +#include "args.h" +#include "printtext.h" +#include "irc.h" +#include "themes.h" + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +int LLVMFuzzerInitialize(int *argc, char ***argv) { + core_register_options(); + fe_common_core_register_options(); + char *irssi_argv[] = {*argv[0], "--home", "/tmp/irssi", NULL}; + int irssi_argc = sizeof(irssi_argv) / sizeof(char *) - 1; + args_execute(irssi_argc, irssi_argv); + core_preinit((*argv)[0]); + core_init(); + fe_common_core_init(); + theme_register(gui_text_formats); + module_register("core", "fe-fuzz"); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + gchar *copy = g_strndup((const gchar *)data, size); + + FILE *fp = fopen("/tmp/irssi/fuzz.theme", "wb"); + if (fp) { + fwrite(copy, strlen(copy), 1, fp); + fclose(fp); + } + + THEME_REC *theme = theme_load("fuzz"); + theme_destroy(theme); + + g_free(copy); + return 0; +} diff --git a/src/fe-fuzz/irc/Makefile.am b/src/fe-fuzz/irc/Makefile.am new file mode 100644 index 00000000..52770885 --- /dev/null +++ b/src/fe-fuzz/irc/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = core diff --git a/src/fe-fuzz/irc/core/Makefile.am b/src/fe-fuzz/irc/core/Makefile.am new file mode 100644 index 00000000..fa614abb --- /dev/null +++ b/src/fe-fuzz/irc/core/Makefile.am @@ -0,0 +1,46 @@ +bin_PROGRAMS = event-get-params-fuzz + +# Force link with CXX for libfuzzer support +CCLD=$(CXX) $(CXXFLAGS) + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core/ \ + -I$(top_srcdir)/src/irc/core/ \ + -I$(top_srcdir)/src/fe-common/core/ \ + $(GLIB_CFLAGS) + +AM_DEPENDENCIES = \ + ../../../core/libcore.a \ + ../../../lib-config/libirssi_config.a \ + ../../../irc/libirc.a \ + ../../../irc/core/libirc_core.a \ + ../../../irc/dcc/libirc_dcc.a \ + ../../../irc/flood/libirc_flood.a \ + ../../../irc/notifylist/libirc_notifylist.a \ + ../../../fe-common/core/libfe_common_core.a \ + ../../../fe-common/irc/libfe_common_irc.a \ + ../../../fe-common/irc/dcc/libfe_irc_dcc.a \ + ../../../fe-common/irc/notifylist/libfe_irc_notifylist a + +LDADD = \ + ../../../irc/libirc.a \ + ../../../irc/core/libirc_core.a \ + ../../../irc/dcc/libirc_dcc.a \ + ../../../irc/flood/libirc_flood.a \ + ../../../irc/notifylist/libirc_notifylist.a \ + ../../../fe-common/core/libfe_common_core.a \ + ../../../fe-common/irc/libfe_common_irc.a \ + ../../../fe-common/irc/dcc/libfe_irc_dcc.a \ + ../../../fe-common/irc/notifylist/libfe_irc_notifylist.a \ + ../../../core/libcore.a \ + ../../../lib-config/libirssi_config.a \ + @PROG_LIBS@ \ + $(FUZZER_LIBS) + +event_get_params_fuzz_SOURCES = \ + event-get-params.c \ + $(top_srcdir)/src/fe-text/module-formats.c + +noinst_HEADERS = \ + $(top_srcdir)/src/fe-text/module-formats.h diff --git a/src/fe-fuzz/irc/core/event-get-params.c b/src/fe-fuzz/irc/core/event-get-params.c new file mode 100644 index 00000000..c50b6205 --- /dev/null +++ b/src/fe-fuzz/irc/core/event-get-params.c @@ -0,0 +1,82 @@ +/* + event-get-params.c : irssi + + Copyright (C) 2017 Joseph Bisch + + This program 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 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include "modules-load.h" +#include "levels.h" +#include "../fe-text/module-formats.h" // need to explicitly grab from fe-text +#include "themes.h" +#include "core.h" +#include "fe-common-core.h" +#include "args.h" +#include "printtext.h" +#include "irc.h" + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +int LLVMFuzzerInitialize(int *argc, char ***argv) { + core_register_options(); + fe_common_core_register_options(); + /* no args */ + args_execute(0, NULL); + core_preinit((*argv)[0]); + core_init(); + fe_common_core_init(); + theme_register(gui_text_formats); + module_register("core", "fe-fuzz"); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size < 1) { + return 0; + } + uint8_t count = *data; + gchar *copy = g_strndup((const gchar *)data+1, size-1); + + char *output0; + char *output1; + char *output2; + char *output3; + char *params; + if (count % 8 == 0) { + params = event_get_params(copy, 1 | PARAM_FLAG_GETREST, &output0); + } else if (count % 8 == 1) { + params = event_get_params(copy, 2 | PARAM_FLAG_GETREST, &output0, &output1); + } else if (count % 8 == 2) { + params = event_get_params(copy, 3 | PARAM_FLAG_GETREST, &output0, &output1, &output2); + } else if (count % 8 == 3) { + params = event_get_params(copy, 4 | PARAM_FLAG_GETREST, &output0, &output1, &output2, &output3); + } else if (count % 8 == 4) { + params = event_get_params(copy, 1, &output0); + } else if (count % 8 == 5) { + params = event_get_params(copy, 2, &output0, &output1); + } else if (count % 8 == 6) { + params = event_get_params(copy, 3, &output0, &output1, &output2); + } else { + params = event_get_params(copy, 4, &output0, &output1, &output2, &output3); + } + g_free(params); + g_free(copy); + return 0; +} 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/gui-printtext.c b/src/fe-text/gui-printtext.c index a07451fa..c52f9ced 100644 --- a/src/fe-text/gui-printtext.c +++ b/src/fe-text/gui-printtext.c @@ -24,6 +24,7 @@ #include "formats.h" #include "printtext.h" +#include "themes.h" #include "term.h" #include "gui-printtext.h" @@ -138,6 +139,39 @@ void gui_printtext_after(TEXT_DEST_REC *dest, LINE_REC *prev, const char *str) gui_printtext_after_time(dest, prev, str, 0); } +void gui_printtext_window_border(int x, int y) +{ + char *v0, *v1; + int len; + if (current_theme != NULL) { + v1 = theme_format_expand(current_theme, "{window_border} "); + len = format_real_length(v1, 1); + v1[len] = '\0'; + } + else { + v1 = g_strdup(" "); + } + + if (*v1 == '\0') { + g_free(v1); + v1 = g_strdup(" "); + } + + if (clrtoeol_info->color != NULL) { + char *color = g_strdup(clrtoeol_info->color); + len = format_real_length(color, 0); + color[len] = '\0'; + v0 = g_strconcat(color, v1, NULL); + g_free(color); + g_free(v1); + } else { + v0 = v1; + } + + gui_printtext(x, y, v0); + g_free(v0); +} + static void remove_old_lines(TEXT_BUFFER_VIEW_REC *view) { LINE_REC *line; @@ -236,8 +270,13 @@ static void sig_gui_print_text(WINDOW_REC *window, void *fgcolor, term_set_color2(root_window, attr, fg, bg); term_move(root_window, next_xpos, next_ypos); - if (flags & GUI_PRINT_FLAG_CLRTOEOL) - term_clrtoeol(root_window); + if (flags & GUI_PRINT_FLAG_CLRTOEOL) { + if (clrtoeol_info->window != NULL) { + term_window_clrtoeol_abs(clrtoeol_info->window, next_ypos); + } else { + term_clrtoeol(root_window); + } + } next_xpos += term_addstr(root_window, str); return; } diff --git a/src/fe-text/gui-printtext.h b/src/fe-text/gui-printtext.h index d2671497..64595bbe 100644 --- a/src/fe-text/gui-printtext.h +++ b/src/fe-text/gui-printtext.h @@ -20,5 +20,6 @@ void gui_printtext(int xpos, int ypos, const char *str); void gui_printtext_internal(int xpos, int ypos, const char *str); void gui_printtext_after(TEXT_DEST_REC *dest, LINE_REC *prev, const char *str); void gui_printtext_after_time(TEXT_DEST_REC *dest, LINE_REC *prev, const char *str, time_t time); +void gui_printtext_window_border(int xpos, int ypos); #endif diff --git a/src/fe-text/gui-readline.c b/src/fe-text/gui-readline.c index b3a78396..88e827a7 100644 --- a/src/fe-text/gui-readline.c +++ b/src/fe-text/gui-readline.c @@ -1174,6 +1174,7 @@ void gui_readline_init(void) key_bind("key", NULL, "^H", "backspace", (SIGNAL_FUNC) key_combo); key_bind("key", NULL, "^?", "backspace", (SIGNAL_FUNC) key_combo); key_bind("key", NULL, "^I", "tab", (SIGNAL_FUNC) key_combo); + key_bind("key", NULL, "meta2-Z", "stab", (SIGNAL_FUNC) key_combo); /* meta */ key_bind("key", NULL, "^[", "meta", (SIGNAL_FUNC) key_combo); @@ -1278,7 +1279,7 @@ void gui_readline_init(void) /* line transmitting */ key_bind("send_line", "Execute the input line", "return", NULL, (SIGNAL_FUNC) key_send_line); - key_bind("word_completion_backward", "", NULL, NULL, (SIGNAL_FUNC) key_word_completion_backward); + key_bind("word_completion_backward", "Choose previous completion suggestion", "stab", NULL, (SIGNAL_FUNC) key_word_completion_backward); key_bind("word_completion", "Complete the current word", "tab", NULL, (SIGNAL_FUNC) key_word_completion); key_bind("erase_completion", "Remove the completion added by word_completion", "meta-k", NULL, (SIGNAL_FUNC) key_erase_completion); key_bind("check_replaces", "Check word replaces", NULL, NULL, (SIGNAL_FUNC) key_check_replaces); diff --git a/src/fe-text/gui-windows.c b/src/fe-text/gui-windows.c index c63c495c..f761e390 100644 --- a/src/fe-text/gui-windows.c +++ b/src/fe-text/gui-windows.c @@ -23,6 +23,7 @@ #include "misc.h" #include "settings.h" #include "special-vars.h" +#include "levels.h" #include "term.h" #include "gui-entry.h" @@ -50,6 +51,7 @@ static GUI_WINDOW_REC *gui_window_init(WINDOW_REC *window, !settings_get_bool("indent_always"), get_default_indent_func()); textbuffer_view_set_break_wide(gui->view, settings_get_bool("break_wide")); + textbuffer_view_set_hidden_level(gui->view, MSGLEVEL_HIDDEN); if (parent->active == window) textbuffer_view_set_window(gui->view, parent->screen_win); return gui; @@ -73,17 +75,18 @@ static void gui_window_created(WINDOW_REC *window, void *automatic) g_return_if_fail(window != NULL); - new_parent = window_create_override == 0 || - window_create_override == 2 || + new_parent = window_create_override == MAIN_WINDOW_TYPE_DEFAULT || + window_create_override == MAIN_WINDOW_TYPE_SPLIT || + window_create_override == MAIN_WINDOW_TYPE_RSPLIT || active_win == NULL || WINDOW_GUI(active_win) == NULL; - parent = !new_parent ? WINDOW_MAIN(active_win) : mainwindow_create(); + parent = !new_parent ? WINDOW_MAIN(active_win) : mainwindow_create(window_create_override == MAIN_WINDOW_TYPE_RSPLIT); if (parent == NULL) { /* not enough space for new window, but we really can't abort creation of the window anymore, so create hidden window instead. */ parent = WINDOW_MAIN(active_win); } - window_create_override = -1; + window_create_override = MAIN_WINDOW_TYPE_NONE; if (parent->active == NULL) parent->active = window; window->gui_data = gui_window_init(window, parent); @@ -281,13 +284,14 @@ static void read_settings(void) void gui_windows_init(void) { - settings_add_bool("lookandfeel", "autostick_split_windows", TRUE); + settings_add_bool("lookandfeel", "autostick_split_windows", FALSE); + settings_add_bool("lookandfeel", "autounstick_windows", TRUE); settings_add_int("lookandfeel", "indent", 10); settings_add_bool("lookandfeel", "indent_always", FALSE); settings_add_bool("lookandfeel", "break_wide", FALSE); settings_add_bool("lookandfeel", "scroll", TRUE); - window_create_override = -1; + window_create_override = MAIN_WINDOW_TYPE_NONE; read_settings(); signal_add("gui window create override", (SIGNAL_FUNC) sig_window_create_override); diff --git a/src/fe-text/irssi.c b/src/fe-text/irssi.c index ef443670..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(); @@ -207,6 +208,8 @@ static void textui_finish_init(void) fe_settings_set_print("nick"); if (user_settings_changed & USER_SETTINGS_HOSTNAME) fe_settings_set_print("hostname"); + + term_environment_check(); } static void textui_deinit(void) @@ -228,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-layout.c b/src/fe-text/mainwindows-layout.c index fae02539..17c6647b 100644 --- a/src/fe-text/mainwindows-layout.c +++ b/src/fe-text/mainwindows-layout.c @@ -23,6 +23,7 @@ #include "misc.h" #include "lib-config/iconfig.h" #include "settings.h" +#include "levels.h" #include "mainwindows.h" #include "gui-windows.h" @@ -41,6 +42,12 @@ static void sig_layout_window_save(WINDOW_REC *window, CONFIG_NODE *node) iconfig_node_set_int(node, "parent", active->refnum); } + if (gui->view->hidden_level != MSGLEVEL_HIDDEN) { + char *level = bits2level(gui->view->hidden_level); + iconfig_node_set_str(node, "hidelevel", level); + g_free(level); + } + if (gui->use_scroll) iconfig_node_set_bool(node, "scroll", gui->scroll); } @@ -48,9 +55,9 @@ static void sig_layout_window_save(WINDOW_REC *window, CONFIG_NODE *node) static void sig_layout_window_restore(WINDOW_REC *window, CONFIG_NODE *node) { WINDOW_REC *parent; - GUI_WINDOW_REC *gui; + GUI_WINDOW_REC *gui; - gui = WINDOW_GUI(window); + gui = WINDOW_GUI(window); parent = window_find_refnum(config_node_get_int(node, "parent", -1)); if (parent != NULL) @@ -58,10 +65,13 @@ static void sig_layout_window_restore(WINDOW_REC *window, CONFIG_NODE *node) if (config_node_get_bool(node, "sticky", FALSE)) gui_window_set_sticky(window); + + textbuffer_view_set_hidden_level(gui->view, level2bits(config_node_get_str(node, "hidelevel", "HIDDEN"), NULL)); + if (config_node_get_str(node, "scroll", NULL) != NULL) { gui->use_scroll = TRUE; gui->scroll = config_node_get_bool(node, "scroll", TRUE); - textbuffer_view_set_scroll(gui->view, gui->scroll); + textbuffer_view_set_scroll(gui->view, gui->scroll); } } @@ -74,6 +84,8 @@ static void main_window_save(MAIN_WINDOW_REC *window, CONFIG_NODE *node) iconfig_node_set_int(node, "first_line", window->first_line); iconfig_node_set_int(node, "lines", window->height); + iconfig_node_set_int(node, "first_column", window->first_column); + iconfig_node_set_int(node, "columns", window->width); } static void sig_layout_save(void) @@ -88,8 +100,16 @@ static void sig_layout_save(void) static int window_node_cmp(CONFIG_NODE *n1, CONFIG_NODE *n2) { - return config_node_get_int(n1, "first_line", 0) > - config_node_get_int(n2, "first_line", 0) ? -1 : 1; + return (config_node_get_int(n1, "first_line", 0) == + config_node_get_int(n2, "first_line", 0) + && + config_node_get_int(n1, "first_column", 0) > + config_node_get_int(n2, "first_column", 0) + ) || + config_node_get_int(n1, "first_line", 0) > + config_node_get_int(n2, "first_line", 0) + ? -1 + : 1; } /* Returns list of mainwindow nodes sorted by first_line @@ -108,14 +128,45 @@ static GSList *get_sorted_windows_config(CONFIG_NODE *node) return output; } +static GSList *get_windows_config_filter_line(GSList *in) +{ + GSList *tmp, *output; + + output = NULL; + for (tmp = in; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *node = tmp->data; + if (config_node_get_int(node, "first_column", 0) == 0) + output = g_slist_append(output, node); + } + + return output; +} + +static GSList *get_windows_config_filter_column(GSList *in, int first_line, int last_line) +{ + GSList *tmp, *output; + + output = NULL; + for (tmp = in; tmp != NULL; tmp = tmp->next) { + int l1, l2; + CONFIG_NODE *node = tmp->data; + l1 = config_node_get_int(node, "first_line", -1); + l2 = l1 + config_node_get_int(node, "lines", 0) - 1; + if (l1 >= first_line && l2 <= last_line) + output = g_slist_prepend(output, node); + } + + return output; +} + static void sig_layout_restore(void) { - MAIN_WINDOW_REC *lower_window; - WINDOW_REC *window; + MAIN_WINDOW_REC *lower_window; + WINDOW_REC *window, *first; CONFIG_NODE *node; - GSList *tmp, *sorted_config; - int avail_height, height, *heights; - int i, lower_size, windows_count, diff; + GSList *tmp, *sorted_config, *lines_config; + int avail_height, height, *heights, *widths, max_wins_line; + int i, lower_size, lines_count, columns_count, diff; node = iconfig_node_traverse("mainwindows", FALSE); if (node == NULL) return; @@ -123,51 +174,56 @@ static void sig_layout_restore(void) sorted_config = get_sorted_windows_config(node); if (sorted_config == NULL) return; - windows_count = g_slist_length(sorted_config); + lines_config = get_windows_config_filter_line(sorted_config); + lines_count = g_slist_length(lines_config); - /* calculate the saved terminal height */ + /* calculate the saved terminal height */ avail_height = term_height - screen_reserved_top - screen_reserved_bottom; height = 0; - heights = g_new0(int, windows_count); - for (i = 0, tmp = sorted_config; tmp != NULL; tmp = tmp->next, i++) { + heights = g_new0(int, lines_count); + for (i = 0, tmp = lines_config; tmp != NULL; tmp = tmp->next, i++) { CONFIG_NODE *node = tmp->data; - heights[i] = config_node_get_int(node, "lines", 0); + heights[i] = config_node_get_int(node, "lines", 0); height += heights[i]; } + max_wins_line = (term_width + 1) / (NEW_WINDOW_WIDTH + 1); + if (max_wins_line < 1) + max_wins_line = 1; + if (avail_height <= (WINDOW_MIN_SIZE*2)+1) { /* we can fit only one window to screen - give it all the height we can */ - windows_count = 1; - heights[0] = avail_height; + lines_count = 1; + heights[0] = avail_height; } else if (height != avail_height) { /* Terminal's height is different from the saved one. Resize the windows so they fit to screen. */ while (height > avail_height && - windows_count*(WINDOW_MIN_SIZE+1) > avail_height) { + lines_count*(WINDOW_MIN_SIZE+1) > avail_height) { /* all windows can't fit into screen, remove the lowest ones */ - windows_count--; + lines_count--; } - /* try to keep the windows' size about the same in percents */ - for (i = 0; i < windows_count; i++) { + /* try to keep the windows' size about the same in percents */ + for (i = 0; i < lines_count; i++) { int size = avail_height*heights[i]/height; if (size < WINDOW_MIN_SIZE+1) - size = WINDOW_MIN_SIZE+1; + size = WINDOW_MIN_SIZE+1; heights[i] = size; } /* give/remove the last bits */ - height = 0; - for (i = 0; i < windows_count; i++) - height += heights[i]; + height = 0; + for (i = 0; i < lines_count; i++) + height += heights[i]; diff = height < avail_height ? 1 : -1; for (i = 0; height != avail_height; i++) { - if (i == windows_count) + if (i == lines_count) i = 0; if (heights[i] > WINDOW_MIN_SIZE+1) { @@ -178,25 +234,59 @@ static void sig_layout_restore(void) } /* create all the visible windows with correct size */ - lower_window = NULL; lower_size = 0; - for (i = 0, tmp = sorted_config; i < windows_count; tmp = tmp->next, i++) { + lower_window = NULL; lower_size = 0; first = NULL; + for (i = 0, tmp = lines_config; i < lines_count; tmp = tmp->next, i++) { + GSList *tmp2, *columns_config, *line; + int j, l1, l2; CONFIG_NODE *node = tmp->data; if (node->key == NULL) continue; - /* create a new window + mainwindow */ - signal_emit("gui window create override", 1, - GINT_TO_POINTER(0)); + l1 = config_node_get_int(node, "first_line", -1); + l2 = l1 + config_node_get_int(node, "lines", 0) - 1; + columns_config = get_windows_config_filter_column(sorted_config, l1, l2); + + window = NULL; columns_count = 0; + widths = g_new0(int, max_wins_line); + for (j = 0, tmp2 = columns_config; j < max_wins_line && tmp2 != NULL; tmp2 = tmp2->next, j++) { + int width; + WINDOW_REC *new_win; + CONFIG_NODE *node2 = tmp2->data; + if (node2->key == NULL) continue; + + /* create a new window + mainwindow */ + signal_emit("gui window create override", 1, + GINT_TO_POINTER(window == NULL ? MAIN_WINDOW_TYPE_SPLIT : MAIN_WINDOW_TYPE_RSPLIT)); - window = window_create(NULL, TRUE); - window_set_refnum(window, atoi(node->key)); + new_win = window_create(NULL, TRUE); + + window_set_refnum(new_win, atoi(node2->key)); + width = config_node_get_int(node2, "columns", NEW_WINDOW_WIDTH); + widths[j] = width; + columns_count += width + (window == NULL ? 0 : 1); + + if (window == NULL) + window = new_win; + if (first == NULL) + first = new_win; + + window_set_active(new_win); + active_mainwin = WINDOW_MAIN(new_win); + } + if (window == NULL) + continue; + line = g_slist_reverse(mainwindows_get_line(WINDOW_MAIN(window))); + for (j = g_slist_length(line), tmp2 = line; tmp2 != NULL; tmp2 = tmp2->next, j--) { + int width = MAX(NEW_WINDOW_WIDTH, widths[j-1] * term_width / columns_count); + MAIN_WINDOW_REC *rec = tmp2->data; + mainwindow_set_rsize(rec, width); + } + g_slist_free(line); + g_free(widths); if (lower_size > 0) mainwindow_set_size(lower_window, lower_size, FALSE); - window_set_active(window); - active_mainwin = WINDOW_MAIN(window); - - lower_window = WINDOW_MAIN(window); + lower_window = WINDOW_MAIN(window); lower_size = heights[i]; if (lower_size < WINDOW_MIN_SIZE+1) lower_size = WINDOW_MIN_SIZE+1; @@ -206,6 +296,11 @@ static void sig_layout_restore(void) if (lower_size > 0) mainwindow_set_size(lower_window, lower_size, FALSE); + + if (first != NULL) { + window_set_active(first); + active_mainwin = WINDOW_MAIN(first); + } } static void sig_layout_reset(void) diff --git a/src/fe-text/mainwindows.c b/src/fe-text/mainwindows.c index 5f58c25f..83a3e0cc 100644 --- a/src/fe-text/mainwindows.c +++ b/src/fe-text/mainwindows.c @@ -34,24 +34,27 @@ GSList *mainwindows; MAIN_WINDOW_REC *active_mainwin; +MAIN_WINDOW_BORDER_REC *clrtoeol_info; int screen_reserved_top, screen_reserved_bottom; -static int old_screen_width, old_screen_height; +int screen_reserved_left, screen_reserved_right; +static int screen_width, screen_height; #define mainwindow_create_screen(window) \ - term_window_create(0, \ + term_window_create((window)->first_column + (window)->statusbar_columns_left, \ (window)->first_line + (window)->statusbar_lines_top, \ - (window)->width, \ + (window)->width - (window)->statusbar_columns, \ (window)->height - (window)->statusbar_lines) #define mainwindow_set_screen_size(window) \ - term_window_move((window)->screen_win, 0, \ + term_window_move((window)->screen_win, \ + (window)->first_column + (window)->statusbar_columns_left, \ (window)->first_line + (window)->statusbar_lines_top, \ - (window)->width, \ + (window)->width - (window)->statusbar_columns, \ (window)->height - (window)->statusbar_lines); -static MAIN_WINDOW_REC *find_window_with_room(void) +static MAIN_WINDOW_REC *find_window_with_room() { MAIN_WINDOW_REC *biggest_rec; GSList *tmp; @@ -71,14 +74,34 @@ static MAIN_WINDOW_REC *find_window_with_room(void) return biggest_rec; } +static MAIN_WINDOW_REC *find_window_with_room_right(void) +{ + MAIN_WINDOW_REC *biggest_rec; + GSList *tmp; + int space, biggest; + + biggest = 0; biggest_rec = NULL; + for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) { + MAIN_WINDOW_REC *rec = tmp->data; + + space = MAIN_WINDOW_TEXT_WIDTH(rec); + if (space >= 2 * NEW_WINDOW_WIDTH && space > biggest) { + biggest = space; + biggest_rec = rec; + } + } + + return biggest_rec; +} + #define window_size_equals(window, mainwin) \ - ((window)->width == (mainwin)->width && \ + ((window)->width == MAIN_WINDOW_TEXT_WIDTH(mainwin) && \ (window)->height == MAIN_WINDOW_TEXT_HEIGHT(mainwin)) static void mainwindow_resize_windows(MAIN_WINDOW_REC *window) { GSList *tmp; - int resized; + int resized; mainwindow_set_screen_size(window); @@ -89,24 +112,31 @@ static void mainwindow_resize_windows(MAIN_WINDOW_REC *window) if (rec->gui_data != NULL && WINDOW_GUI(rec)->parent == window && !window_size_equals(rec, window)) { - resized = TRUE; - gui_window_resize(rec, window->width, + resized = TRUE; + gui_window_resize(rec, MAIN_WINDOW_TEXT_WIDTH(window), MAIN_WINDOW_TEXT_HEIGHT(window)); } } - if (resized) + if (resized) signal_emit("mainwindow resized", 1, window); } static void mainwindow_resize(MAIN_WINDOW_REC *window, int xdiff, int ydiff) { + int height, width; if (quitting || (xdiff == 0 && ydiff == 0)) - return; + return; - window->width += xdiff; + height = window->height + ydiff; + width = window->width + xdiff; + window->width = window->last_column-window->first_column+1; window->height = window->last_line-window->first_line+1; - window->size_dirty = TRUE; + if (height != window->height || width != window->width) { + g_warning("Resizing window %p W:%d expected:%d H:%d expected:%d", + window, window->width, width, window->height, height); + } + window->size_dirty = TRUE; } static GSList *get_sticky_windows_sorted(MAIN_WINDOW_REC *mainwin) @@ -178,14 +208,13 @@ void mainwindows_recreate(void) } } -MAIN_WINDOW_REC *mainwindow_create(void) +MAIN_WINDOW_REC *mainwindow_create(int right) { MAIN_WINDOW_REC *rec, *parent; int space; rec = g_new0(MAIN_WINDOW_REC, 1); rec->dirty = TRUE; - rec->width = term_width; if (mainwindows == NULL) { active_mainwin = rec; @@ -193,21 +222,53 @@ MAIN_WINDOW_REC *mainwindow_create(void) rec->first_line = screen_reserved_top; rec->last_line = term_height-1 - screen_reserved_bottom; rec->height = rec->last_line-rec->first_line+1; + rec->first_column = screen_reserved_left; + rec->last_column = screen_width-1 - screen_reserved_right; + rec->width = rec->last_column-rec->first_column+1; } else { parent = WINDOW_MAIN(active_win); - if (MAIN_WINDOW_TEXT_HEIGHT(parent) < - WINDOW_MIN_SIZE+NEW_WINDOW_SIZE) - parent = find_window_with_room(); - if (parent == NULL) - return NULL; /* not enough space */ - - space = parent->height / 2; - rec->first_line = parent->first_line; - rec->last_line = rec->first_line + space; - rec->height = rec->last_line-rec->first_line+1; - parent->first_line += space+1; - mainwindow_resize(parent, 0, -space-1); + if (!right) { + GSList *tmp, *line; + if (MAIN_WINDOW_TEXT_HEIGHT(parent) < + WINDOW_MIN_SIZE+NEW_WINDOW_SIZE) + parent = find_window_with_room(); + if (parent == NULL) + return NULL; /* not enough space */ + + space = parent->height / 2; + rec->first_line = parent->first_line; + rec->last_line = rec->first_line + space; + rec->height = rec->last_line-rec->first_line+1; + rec->first_column = screen_reserved_left; + rec->last_column = screen_width-1 - screen_reserved_right; + rec->width = rec->last_column-rec->first_column+1; + + line = mainwindows_get_line(parent); + for (tmp = line; tmp != NULL; tmp = tmp->next) { + MAIN_WINDOW_REC *rec = tmp->data; + rec->first_line += space+1; + mainwindow_resize(rec, 0, -space-1); + } + g_slist_free(line); + } else { + if (MAIN_WINDOW_TEXT_WIDTH(parent) < + 2* NEW_WINDOW_WIDTH) + parent = find_window_with_room_right(); + if (parent == NULL) + return NULL; /* not enough space */ + + space = parent->width / 2; + rec->first_line = parent->first_line; + rec->last_line = parent->last_line; + rec->height = parent->height; + rec->first_column = parent->last_column - space + 1; + rec->last_column = parent->last_column; + rec->width = rec->last_column-rec->first_column+1; + + parent->last_column -= space+1; + mainwindow_resize(parent, -space-1, 0); + } } rec->screen_win = mainwindow_create_screen(rec); @@ -218,16 +279,22 @@ MAIN_WINDOW_REC *mainwindow_create(void) return rec; } -static MAIN_WINDOW_REC *mainwindows_find_lower(int line) +static MAIN_WINDOW_REC *mainwindows_find_lower(MAIN_WINDOW_REC *window) { + int last_line; MAIN_WINDOW_REC *best; GSList *tmp; + if (window != NULL) + last_line = window->last_line; + else + last_line = -1; + best = NULL; for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) { MAIN_WINDOW_REC *rec = tmp->data; - if (rec->first_line > line && + if (rec->first_line > last_line && (best == NULL || rec->first_line < best->first_line)) best = rec; } @@ -235,16 +302,64 @@ static MAIN_WINDOW_REC *mainwindows_find_lower(int line) return best; } -static MAIN_WINDOW_REC *mainwindows_find_upper(int line) +static MAIN_WINDOW_REC *mainwindows_find_right(MAIN_WINDOW_REC *window, int find_first) { + int first_line, last_line, last_column; MAIN_WINDOW_REC *best; GSList *tmp; + if (window != NULL) { + first_line = window->first_line; + last_line = window->last_line; + last_column = window->last_column; + } else { + first_line = last_line = last_column = -1; + } + + if (find_first) + last_column = -1; + best = NULL; for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) { MAIN_WINDOW_REC *rec = tmp->data; - if (rec->last_line < line && + if (rec->first_line >= first_line && + rec->last_line <= last_line && + rec->first_column > last_column && + (best == NULL || rec->first_column < best->first_column)) + best = rec; + } + + return best; +} + +static MAIN_WINDOW_REC *mainwindows_find_lower_right(MAIN_WINDOW_REC *window) +{ + MAIN_WINDOW_REC *best; + + best = mainwindows_find_right(window, FALSE); + if (best == NULL) + best = mainwindows_find_lower(window); + + return best; +} + +static MAIN_WINDOW_REC *mainwindows_find_upper(MAIN_WINDOW_REC *window) +{ + int first_line; + MAIN_WINDOW_REC *best; + GSList *tmp; + + if (window != NULL) + first_line = window->first_line; + else + first_line = screen_height; + + best = NULL; + for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) { + MAIN_WINDOW_REC *rec = tmp->data; + + if (rec->last_line < first_line && (best == NULL || rec->last_line > best->last_line)) best = rec; } @@ -252,27 +367,142 @@ static MAIN_WINDOW_REC *mainwindows_find_upper(int line) return best; } -static void mainwindows_add_space(int first_line, int last_line) +static MAIN_WINDOW_REC *mainwindows_find_left(MAIN_WINDOW_REC *window, int find_last) +{ + int first_line, last_line, first_column; + MAIN_WINDOW_REC *best; + GSList *tmp; + + if (window != NULL) { + first_line = window->first_line; + last_line = window->last_line; + first_column = window->first_column; + } else { + first_line = last_line = screen_height; + first_column = screen_width; + } + + if (find_last) + first_column = screen_width; + + best = NULL; + for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) { + MAIN_WINDOW_REC *rec = tmp->data; + + if (rec->first_line >= first_line && + rec->last_line <= last_line && + rec->last_column < first_column && + (best == NULL || rec->last_column > best->last_column)) + best = rec; + } + + return best; +} + +static MAIN_WINDOW_REC *mainwindows_find_upper_left(MAIN_WINDOW_REC *window) +{ + MAIN_WINDOW_REC *best; + + best = mainwindows_find_left(window, FALSE); + if (best == NULL) + best = mainwindows_find_upper(window); + + return best; +} + +static MAIN_WINDOW_REC *mainwindows_find_left_upper(MAIN_WINDOW_REC *window) +{ + MAIN_WINDOW_REC *best; + + best = mainwindows_find_left(window, FALSE); + if (best == NULL) + best = mainwindows_find_left(mainwindows_find_upper(window), TRUE); + + return best; +} + +GSList *mainwindows_get_line(MAIN_WINDOW_REC *rec) +{ + MAIN_WINDOW_REC *win; + GSList *list; + + list = NULL; + + for (win = mainwindows_find_left(rec, FALSE); + win != NULL; + win = mainwindows_find_left(win, FALSE)) { + list = g_slist_append(list, win); + } + + if (rec != NULL) + list = g_slist_append(list, rec); + + for (win = mainwindows_find_right(rec, FALSE); + win != NULL; + win = mainwindows_find_right(win, FALSE)) { + list = g_slist_append(list, win); + } + + return list; +} + +/* add back the space which was occupied by destroyed mainwindow first_line .. last_line */ +static void mainwindows_add_space(MAIN_WINDOW_REC *destroy_win) { MAIN_WINDOW_REC *rec; - int size; + int size, rsize; + + if (destroy_win->last_line < destroy_win->first_line) + return; - if (last_line < first_line) + if (destroy_win->last_column < destroy_win->first_column) return; - size = last_line-first_line+1; + rsize = destroy_win->last_column-destroy_win->first_column+1; + rec = mainwindows_find_left(destroy_win, FALSE); + if (rec != NULL) { + rec->last_column = destroy_win->last_column; + mainwindow_resize(rec, rsize+1, 0); + return; + } - rec = mainwindows_find_lower(last_line); + rec = mainwindows_find_right(destroy_win, FALSE); if (rec != NULL) { - rec->first_line = first_line; - mainwindow_resize(rec, 0, size); + rec->first_column = destroy_win->first_column; + mainwindow_resize(rec, rsize+1, 0); return; } - rec = mainwindows_find_upper(first_line); + size = destroy_win->last_line-destroy_win->first_line+1; + + rec = mainwindows_find_lower(destroy_win); if (rec != NULL) { - rec->last_line = last_line; - mainwindow_resize(rec, 0, size); + GSList *tmp, *list; + list = mainwindows_get_line(rec); + + for (tmp = list; tmp != NULL; tmp = tmp->next) { + MAIN_WINDOW_REC *rec = tmp->data; + rec->first_line = destroy_win->first_line; + mainwindow_resize(rec, 0, size); + } + + g_slist_free(list); + return; + } + + rec = mainwindows_find_upper(destroy_win); + if (rec != NULL) { + GSList *tmp, *list; + list = mainwindows_get_line(rec); + + for (tmp = list; tmp != NULL; tmp = tmp->next) { + MAIN_WINDOW_REC *rec = tmp->data; + rec->last_line = destroy_win->last_line; + mainwindow_resize(rec, 0, size); + } + + g_slist_free(list); + return; } } @@ -302,8 +532,7 @@ void mainwindow_destroy(MAIN_WINDOW_REC *window) if (mainwindows != NULL) { gui_windows_remove_parent(window); if (!quitting) { - mainwindows_add_space(window->first_line, - window->last_line); + mainwindows_add_space(window); mainwindows_redraw(); } } @@ -313,6 +542,14 @@ void mainwindow_destroy(MAIN_WINDOW_REC *window) if (active_mainwin == window) active_mainwin = NULL; } +void mainwindow_destroy_half(MAIN_WINDOW_REC *window) +{ + int really_quitting = quitting; + quitting = TRUE; + mainwindow_destroy(window); + quitting = really_quitting; +} + void mainwindows_redraw(void) { GSList *tmp; @@ -327,12 +564,20 @@ void mainwindows_redraw(void) static int mainwindows_compare(MAIN_WINDOW_REC *w1, MAIN_WINDOW_REC *w2) { - return w1->first_line < w2->first_line ? -1 : 1; + return w1->first_line < w2->first_line ? -1 + : w1->first_line > w2->first_line ? 1 + : w1->first_column < w2->first_column ? -1 + : w1->first_column > w2->first_column ? 1 + : 0; } static int mainwindows_compare_reverse(MAIN_WINDOW_REC *w1, MAIN_WINDOW_REC *w2) { - return w1->first_line < w2->first_line ? 1 : -1; + return w1->first_line < w2->first_line ? 1 + : w1->first_line > w2->first_line ? -1 + : w1->first_column < w2->first_column ? 1 + : w1->first_column > w2->first_column ? -1 + : 0; } GSList *mainwindows_get_sorted(int reverse) @@ -348,123 +593,235 @@ GSList *mainwindows_get_sorted(int reverse) return list; } -static void mainwindows_resize_smaller(int xdiff, int ydiff) +static void mainwindows_resize_smaller(int ydiff) { - MAIN_WINDOW_REC *rec; + MAIN_WINDOW_REC *rec; GSList *sorted, *tmp; - int space; + int space; - sorted = mainwindows_get_sorted(TRUE); + sorted = NULL; + for (rec = mainwindows_find_lower(NULL); + rec != NULL; + rec = mainwindows_find_lower(rec)) { + sorted = g_slist_prepend(sorted, rec); + } if (sorted == NULL) return; for (;;) { + int skip_active = FALSE; space = 0; - for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) { + /* for each line of windows, calculate the space that can be reduced still */ + for (tmp = sorted; tmp != NULL; tmp = tmp->next) { + int min; + GSList *line, *ltmp; rec = tmp->data; - space += MAIN_WINDOW_TEXT_HEIGHT(rec)-WINDOW_MIN_SIZE; + line = mainwindows_get_line(rec); + min = screen_height - ydiff; + for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) { + int lmin; + MAIN_WINDOW_REC *win = ltmp->data; + if (win == active_mainwin && tmp == sorted) + skip_active = TRUE; + + lmin = MAIN_WINDOW_TEXT_HEIGHT(win)-WINDOW_MIN_SIZE; + if (lmin < min) + min = lmin; + } + g_slist_free(line); + space += min; } if (space >= -ydiff) break; rec = sorted->data; - if (rec == active_mainwin && sorted->next != NULL) + if (skip_active && sorted->next != NULL) rec = sorted->next->data; sorted = g_slist_remove(sorted, rec); if (sorted != NULL) { /* terminal is too small - destroy the uppest window and try again */ - mainwindow_destroy(rec); + GSList *line, *ltmp; + line = mainwindows_get_line(rec); + for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) { + MAIN_WINDOW_REC *win = ltmp->data; + mainwindow_destroy(win); + } + g_slist_free(line); } else { - /* only one window in screen.. just force the resize */ - rec->last_line += ydiff; - mainwindow_resize(rec, xdiff, ydiff); - return; + /* only one line of window in screen.. just force the resize */ + GSList *line, *ltmp; + line = mainwindows_get_line(rec); + for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) { + MAIN_WINDOW_REC *win = ltmp->data; + win->last_line += ydiff; + mainwindow_resize(win, 0, ydiff); + } + g_slist_free(line); + return; } } /* resize windows that have space */ for (tmp = sorted; tmp != NULL && ydiff < 0; tmp = tmp->next) { - rec = tmp->data; + int min; + GSList *line, *ltmp; - space = MAIN_WINDOW_TEXT_HEIGHT(rec)-WINDOW_MIN_SIZE; - if (space == 0) { - mainwindow_resize(rec, xdiff, 0); - - rec->first_line += ydiff; - rec->last_line += ydiff; - signal_emit("mainwindow moved", 1, rec); - continue; + rec = tmp->data; + line = mainwindows_get_line(rec); + min = screen_height - ydiff; + for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) { + int lmin; + MAIN_WINDOW_REC *win = ltmp->data; + lmin = MAIN_WINDOW_TEXT_HEIGHT(win)-WINDOW_MIN_SIZE; + if (lmin < min) + min = lmin; } + space = min; - if (space > -ydiff) space = -ydiff; - rec->last_line += ydiff; - ydiff += space; - rec->first_line += ydiff; - - mainwindow_resize(rec, xdiff, -space); - } + if (space == 0) { + /* move the line */ + for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) { + MAIN_WINDOW_REC *win = ltmp->data; + mainwindow_resize(win, 0, 0); + win->size_dirty = TRUE; + win->first_line += ydiff; + win->last_line += ydiff; + signal_emit("mainwindow moved", 1, win); + } + } else { + if (space > -ydiff) space = -ydiff; + for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) { + MAIN_WINDOW_REC *win = ltmp->data; + win->last_line += ydiff; + win->first_line += ydiff + space; - if (xdiff != 0) { - while (tmp != NULL) { - mainwindow_resize(tmp->data, xdiff, 0); - tmp = tmp->next; + mainwindow_resize(win, 0, -space); + } + ydiff += space; } + g_slist_free(line); } g_slist_free(sorted); } -static void mainwindows_resize_bigger(int xdiff, int ydiff) +static void mainwindows_rresize_line(int xdiff, MAIN_WINDOW_REC *win) { - GSList *sorted, *tmp; + int windows, i, extra_width, next_column, shrunk; + int *widths; + GSList *line, *tmp; - sorted = mainwindows_get_sorted(FALSE); - for (tmp = sorted; tmp != NULL; tmp = tmp->next) { - MAIN_WINDOW_REC *rec = tmp->data; + line = mainwindows_get_line(win); + windows = g_slist_length(line); + widths = g_new0(int, windows); - if (ydiff == 0 || tmp->next != NULL) { - mainwindow_resize(rec, xdiff, 0); - continue; + extra_width = screen_width - windows + 1; + for (tmp = line, i = 0; tmp != NULL; tmp = tmp->next, i++) { + MAIN_WINDOW_REC *rec = tmp->data; + widths[i] = (MAIN_WINDOW_TEXT_WIDTH(rec) * (screen_width - windows + 1)) / (screen_width - xdiff - windows + 1); + extra_width -= widths[i] + rec->statusbar_columns; + } + shrunk = FALSE; + for (i = windows; extra_width < 0; i = i > 1 ? i - 1 : windows) { + if (widths[i-1] > NEW_WINDOW_WIDTH || (i == 1 && !shrunk)) { + widths[i-1]--; + extra_width++; + shrunk = i == 1; } - - /* lowest window - give all the extra space for it */ - rec->last_line += ydiff; - mainwindow_resize(rec, xdiff, ydiff); } - g_slist_free(sorted); -} -static void mainwindows_resize_horiz(int xdiff) -{ - GSList *tmp; + next_column = 0; - for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) { - MAIN_WINDOW_REC *rec = tmp->data; +#define extra ( (i >= screen_width % windows && i < extra_width + (screen_width % windows)) \ + || i + windows < extra_width + (screen_width % windows) ? 1 : 0 ) - mainwindow_resize(rec, xdiff, 0); + for (tmp = line, i = 0; tmp != NULL; tmp = tmp->next, i++) { + MAIN_WINDOW_REC *rec = tmp->data; + rec->first_column = next_column; + rec->last_column = rec->first_column + widths[i] + rec->statusbar_columns + extra - 1; + next_column = rec->last_column + 2; + mainwindow_resize(rec, widths[i] + rec->statusbar_columns + extra - rec->width, 0); + rec->size_dirty = TRUE; } +#undef extra + + g_free(widths); + g_slist_free(line); } void mainwindows_resize(int width, int height) { int xdiff, ydiff; - xdiff = width-old_screen_width; - ydiff = height-old_screen_height; - old_screen_width = width; - old_screen_height = height; + xdiff = width-screen_width; + ydiff = height-screen_height; + screen_width = width; + screen_height = height; + + if (ydiff > 0) { + /* algorithm: enlarge bottom window */ + MAIN_WINDOW_REC *rec; + GSList *line, *tmp; + line = mainwindows_get_line(mainwindows_find_upper(NULL)); + for (tmp = line; tmp != NULL; tmp = tmp->next) { + rec = tmp->data; + rec->last_line += ydiff; + mainwindow_resize(rec, 0, ydiff); + } + g_slist_free(line); + } - if (ydiff < 0) - mainwindows_resize_smaller(xdiff, ydiff); - else if (ydiff > 0) - mainwindows_resize_bigger(xdiff, ydiff); - else if (xdiff != 0) - mainwindows_resize_horiz(xdiff); + if (xdiff > 0) { + /* algorithm: distribute new space on each line */ + MAIN_WINDOW_REC *win; - signal_emit("terminal resized", 0); + for (win = mainwindows_find_lower(NULL); + win != NULL; + win = mainwindows_find_lower(win)) { + mainwindows_rresize_line(xdiff, win); + } + } + + if (xdiff < 0) { + /* algorithm: shrink each window, + destroy windows on the right if no room */ + MAIN_WINDOW_REC *win; + + for (win = mainwindows_find_lower(NULL); + win != NULL; + win = mainwindows_find_lower(win)) { + int max_windows, i, last_column; + GSList *line, *tmp; + + line = mainwindows_get_line(win); + max_windows = (screen_width + 1) / (NEW_WINDOW_WIDTH + 1); + if (max_windows < 1) + max_windows = 1; + last_column = screen_width - 1; + for (tmp = line, i = 0; tmp != NULL; tmp = tmp->next, i++) { + MAIN_WINDOW_REC *rec = tmp->data; + if (i >= max_windows) + mainwindow_destroy_half(rec); + else + last_column = rec->last_column; + } + win = line->data; + g_slist_free(line); + + mainwindows_rresize_line(screen_width - last_column + 1, win); + } + } + + if (ydiff < 0) { + /* algorithm: shrink windows starting from bottom, + destroy windows starting from top if no room */ + mainwindows_resize_smaller(ydiff); + } + + signal_emit("terminal resized", 0); irssi_redraw(); } @@ -474,31 +831,37 @@ int mainwindows_reserve_lines(int top, int bottom) MAIN_WINDOW_REC *window; int ret; - ret = -1; + ret = -1; if (top != 0) { + GSList *list, *tmp; g_return_val_if_fail(top > 0 || screen_reserved_top > top, -1); ret = screen_reserved_top; screen_reserved_top += top; - window = mainwindows_find_lower(-1); - if (window != NULL) { + list = mainwindows_get_line(mainwindows_find_lower(NULL)); + for (tmp = list; tmp != NULL; tmp = tmp->next) { + window = tmp->data; window->first_line += top; mainwindow_resize(window, 0, -top); } + g_slist_free(list); } if (bottom != 0) { + GSList *list, *tmp; g_return_val_if_fail(bottom > 0 || screen_reserved_bottom > bottom, -1); ret = screen_reserved_bottom; screen_reserved_bottom += bottom; - window = mainwindows_find_upper(term_height); - if (window != NULL) { + list = mainwindows_get_line(mainwindows_find_upper(NULL)); + for (tmp = list; tmp != NULL; tmp = tmp->next) { + window = tmp->data; window->last_line -= bottom; mainwindow_resize(window, 0, -bottom); } + g_slist_free(list); } return ret; @@ -528,47 +891,113 @@ int mainwindow_set_statusbar_lines(MAIN_WINDOW_REC *window, return ret; } -static void mainwindows_resize_two(MAIN_WINDOW_REC *grow_win, - MAIN_WINDOW_REC *shrink_win, int count) +static void mainwindows_resize_two(GSList *grow_list, + GSList *shrink_list, int count) { - irssi_set_dirty(); + GSList *tmp; + MAIN_WINDOW_REC *win; - mainwindow_resize(grow_win, 0, count); - mainwindow_resize(shrink_win, 0, -count); - grow_win->dirty = TRUE; - shrink_win->dirty = TRUE; + irssi_set_dirty(); + + for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + mainwindow_resize(win, 0, -count); + win->dirty = TRUE; + } + for (tmp = grow_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + mainwindow_resize(win, 0, count); + win->dirty = TRUE; + } } static int try_shrink_lower(MAIN_WINDOW_REC *window, int count) { MAIN_WINDOW_REC *shrink_win; - shrink_win = mainwindows_find_lower(window->last_line); - if (shrink_win != NULL && - MAIN_WINDOW_TEXT_HEIGHT(shrink_win)-count >= WINDOW_MIN_SIZE) { - window->last_line += count; - shrink_win->first_line += count; - mainwindows_resize_two(window, shrink_win, count); - return TRUE; + g_return_val_if_fail(count >= 0, FALSE); + + shrink_win = mainwindows_find_lower(window); + if (shrink_win != NULL) { + int ok; + GSList *shrink_list, *tmp; + MAIN_WINDOW_REC *win; + + ok = TRUE; + shrink_list = mainwindows_get_line(shrink_win); + + for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + if (MAIN_WINDOW_TEXT_HEIGHT(win)-count < WINDOW_MIN_SIZE) { + ok = FALSE; + break; + } + } + if (ok) { + GSList *grow_list; + grow_list = mainwindows_get_line(window); + + for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + win->first_line += count; + } + for (tmp = grow_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + win->last_line += count; + } + + mainwindows_resize_two(grow_list, shrink_list, count); + g_slist_free(grow_list); + } + + g_slist_free(shrink_list); + return ok; } - return FALSE; + return FALSE; } static int try_shrink_upper(MAIN_WINDOW_REC *window, int count) { MAIN_WINDOW_REC *shrink_win; - shrink_win = mainwindows_find_upper(window->first_line); - if (shrink_win != NULL && - MAIN_WINDOW_TEXT_HEIGHT(shrink_win)-count >= WINDOW_MIN_SIZE) { - window->first_line -= count; - shrink_win->last_line -= count; - mainwindows_resize_two(window, shrink_win, count); - return TRUE; + g_return_val_if_fail(count >= 0, FALSE); + + shrink_win = mainwindows_find_upper(window); + if (shrink_win != NULL) { + int ok; + GSList *shrink_list, *tmp; + MAIN_WINDOW_REC *win; + + ok = TRUE; + shrink_list = mainwindows_get_line(shrink_win); + + for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + if (MAIN_WINDOW_TEXT_HEIGHT(win)-count < WINDOW_MIN_SIZE) { + ok = FALSE; + break; + } + } + if (ok) { + GSList *grow_list; + grow_list = mainwindows_get_line(window); + for (tmp = grow_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + win->first_line -= count; + } + for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + win->last_line -= count; + } + mainwindows_resize_two(grow_list, shrink_list, count); + g_slist_free(grow_list); + } + g_slist_free(shrink_list); + return ok; } - return FALSE; + return FALSE; } static int mainwindow_grow(MAIN_WINDOW_REC *window, int count, @@ -588,32 +1017,58 @@ static int try_grow_lower(MAIN_WINDOW_REC *window, int count) { MAIN_WINDOW_REC *grow_win; - grow_win = mainwindows_find_lower(window->last_line); + grow_win = mainwindows_find_lower(window); if (grow_win != NULL) { - window->last_line -= count; - grow_win->first_line -= count; - mainwindows_resize_two(grow_win, window, count); + MAIN_WINDOW_REC *win; + GSList *grow_list, *shrink_list, *tmp; + grow_list = mainwindows_get_line(grow_win); + shrink_list = mainwindows_get_line(window); + for (tmp = grow_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + win->first_line -= count; + } + for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + win->last_line -= count; + } + mainwindows_resize_two(grow_list, shrink_list, count); + g_slist_free(shrink_list); + g_slist_free(grow_list); } - return grow_win != NULL; + return grow_win != NULL; } static int try_grow_upper(MAIN_WINDOW_REC *window, int count) { MAIN_WINDOW_REC *grow_win; - grow_win = mainwindows_find_upper(window->first_line); + grow_win = mainwindows_find_upper(window); if (grow_win != NULL) { - window->first_line += count; - grow_win->last_line += count; - mainwindows_resize_two(grow_win, window, count); + MAIN_WINDOW_REC *win; + GSList *grow_list, *shrink_list, *tmp; + grow_list = mainwindows_get_line(grow_win); + shrink_list = mainwindows_get_line(window); + for (tmp = grow_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + win->last_line += count; + } + for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) { + win = tmp->data; + win->first_line += count; + } + mainwindows_resize_two(grow_list, shrink_list, count); + g_slist_free(shrink_list); + g_slist_free(grow_list); } - return grow_win != NULL; + return grow_win != NULL; } 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; @@ -627,6 +1082,115 @@ static int mainwindow_shrink(MAIN_WINDOW_REC *window, int count, int resize_lowe return TRUE; } +static void mainwindows_rresize_two(MAIN_WINDOW_REC *grow_win, + MAIN_WINDOW_REC *shrink_win, int count) +{ + irssi_set_dirty(); + + mainwindow_resize(grow_win, count, 0); + mainwindow_resize(shrink_win, -count, 0); + grow_win->dirty = TRUE; + shrink_win->dirty = TRUE; +} + +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) { + return FALSE; + } + + shrink_win->first_column += count; + window->last_column += count; + + mainwindows_rresize_two(window, shrink_win, count); + return TRUE; + } + + return FALSE; +} + +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) { + return FALSE; + } + window->first_column -= count; + shrink_win->last_column -= count; + + mainwindows_rresize_two(window, shrink_win, count); + return TRUE; + } + + return FALSE; +} + +static int mainwindow_rgrow(MAIN_WINDOW_REC *window, int count) +{ + if (!try_rshrink_right(window, count)) { + if (!try_rshrink_left(window, count)) + return FALSE; + } + + return TRUE; +} + +static int try_rgrow_right(MAIN_WINDOW_REC *window, int count) +{ + MAIN_WINDOW_REC *grow_win; + + grow_win = mainwindows_find_right(window, FALSE); + if (grow_win != NULL) { + grow_win->first_column -= count; + window->last_column -= count; + mainwindows_rresize_two(grow_win, window, count); + return TRUE; + } + + return FALSE; +} + +static int try_rgrow_left(MAIN_WINDOW_REC *window, int count) +{ + MAIN_WINDOW_REC *grow_win; + + grow_win = mainwindows_find_left(window, FALSE); + if (grow_win != NULL) { + grow_win->last_column += count; + window->first_column += count; + mainwindows_rresize_two(grow_win, window, count); + return TRUE; + } + + return FALSE; +} + +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; + + if (!try_rgrow_right(window, count)) { + if (!try_rgrow_left(window, count)) + return FALSE; + } + + return TRUE; +} + /* Change the window height - the height includes the lines needed for statusbars. If resize_lower is TRUE, the lower window is first tried to be resized instead of upper window. */ @@ -639,6 +1203,15 @@ void mainwindow_set_size(MAIN_WINDOW_REC *window, int height, int resize_lower) mainwindow_grow(window, height, resize_lower); } +void mainwindow_set_rsize(MAIN_WINDOW_REC *window, int width) +{ + width -= window->width; + if (width < 0) + mainwindow_rshrink(window, -width); + else + mainwindow_rgrow(window, width); +} + void mainwindows_redraw_dirty(void) { GSList *tmp; @@ -653,6 +1226,23 @@ void mainwindows_redraw_dirty(void) if (rec->dirty) { rec->dirty = FALSE; gui_window_redraw(rec->active); + } else if (WINDOW_GUI(rec->active)->view->dirty) { + gui_window_redraw(rec->active); + } + } +} + +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); } } } @@ -660,16 +1250,11 @@ void mainwindows_redraw_dirty(void) /* 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>] */ @@ -678,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; @@ -695,47 +1278,60 @@ 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 */ static void cmd_window_balance(void) { - GSList *sorted, *tmp; + GSList *sorted, *stmp, *line, *ltmp; int avail_size, unit_size, bigger_units; int windows, last_line, old_size; + MAIN_WINDOW_REC *win; windows = g_slist_length(mainwindows); if (windows == 1) return; + sorted = NULL; + windows = 0; + for (win = mainwindows_find_lower(NULL); + win != NULL; + win = mainwindows_find_lower(win)) { + windows++; + sorted = g_slist_append(sorted, win); + } + avail_size = term_height - screen_reserved_top-screen_reserved_bottom; unit_size = avail_size/windows; bigger_units = avail_size%windows; - sorted = mainwindows_get_sorted(FALSE); - last_line = screen_reserved_top; - for (tmp = sorted; tmp != NULL; tmp = tmp->next) { - MAIN_WINDOW_REC *rec = tmp->data; + last_line = screen_reserved_top; + for (stmp = sorted; stmp != NULL; stmp = stmp->next) { + win = stmp->data; + line = mainwindows_get_line(win); - old_size = rec->height; - rec->first_line = last_line; - rec->last_line = rec->first_line + unit_size-1; + for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) { + MAIN_WINDOW_REC *rec = ltmp->data; + old_size = rec->height; + rec->first_line = last_line; + rec->last_line = rec->first_line + unit_size-1; - if (bigger_units > 0) { - rec->last_line++; - bigger_units--; - } + if (bigger_units > 0) { + rec->last_line++; + } - rec->height = rec->last_line-rec->first_line+1; - last_line = rec->last_line+1; + rec->height = rec->last_line-rec->first_line+1; + + mainwindow_resize(rec, 0, rec->height-old_size); + } + if (line != NULL && bigger_units > 0) { + bigger_units--; + } + last_line = win->last_line+1; - mainwindow_resize(rec, 0, rec->height-old_size); + g_slist_free(line); } g_slist_free(sorted); @@ -769,29 +1365,30 @@ static void cmd_window_hide(const char *data) return; if (WINDOW_MAIN(window)->sticky_windows) { - printformat_window(active_win, MSGLEVEL_CLIENTERROR, - TXT_CANT_HIDE_STICKY_WINDOWS); - return; + if (!settings_get_bool("autounstick_windows")) { + printformat_window(active_win, MSGLEVEL_CLIENTERROR, + TXT_CANT_HIDE_STICKY_WINDOWS); + return; + } } mainwindow_destroy(WINDOW_MAIN(window)); if (active_mainwin == NULL) { active_mainwin = WINDOW_MAIN(active_win); - window_set_active(active_mainwin->active); + window_set_active(active_mainwin->active); } } -/* SYNTAX: WINDOW SHOW <number>|<name> */ -static void cmd_window_show(const char *data) +static void _cmd_window_show_opt(const char *data, int right) { - MAIN_WINDOW_REC *parent; + MAIN_WINDOW_REC *parent; WINDOW_REC *window; if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); if (is_numeric(data, '\0')) { - window = window_find_refnum(atoi(data)); + window = window_find_refnum(atoi(data)); if (window == NULL) { printformat_window(active_win, MSGLEVEL_CLIENTERROR, TXT_REFNUM_NOT_FOUND, data); @@ -804,30 +1401,161 @@ static void cmd_window_show(const char *data) return; if (WINDOW_GUI(window)->sticky) { - printformat_window(active_win, MSGLEVEL_CLIENTERROR, - TXT_CANT_SHOW_STICKY_WINDOWS); - return; + if (!settings_get_bool("autounstick_windows")) { + printformat_window(active_win, MSGLEVEL_CLIENTERROR, + TXT_CANT_SHOW_STICKY_WINDOWS); + return; + } + } + + parent = mainwindow_create(right); + if (parent == NULL) { + printformat_window(active_win, MSGLEVEL_CLIENTERROR, TXT_WINDOW_TOO_SMALL); + return; } - parent = mainwindow_create(); parent->active = window; - gui_window_reparent(window, parent); + gui_window_reparent(window, parent); if (settings_get_bool("autostick_split_windows")) - gui_window_set_sticky(window); + gui_window_set_sticky(window); active_mainwin = NULL; window_set_active(window); } +/* SYNTAX: WINDOW SHOW <number>|<name> */ +static void cmd_window_show(const char *data) +{ + _cmd_window_show_opt(data, FALSE); +} + +/* SYNTAX: WINDOW RSHOW <number>|<name> */ +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); + + window_rgrow_int(count); +} + +/* SYNTAX: WINDOW RSHRINK [<lines>] */ +static void cmd_window_rshrink(const char *data) +{ + int count; + + count = *data == '\0' ? 1 : atoi(data); + if (count < -INT_MAX) count = -INT_MAX; + + window_rgrow_int(-count); +} + +/* SYNTAX: WINDOW RSIZE <columns> */ +static void cmd_window_rsize(const char *data) +{ + int rsize; + + if (!is_numeric(data, 0)) return; + rsize = atoi(data); + + rsize -= MAIN_WINDOW_TEXT_WIDTH(WINDOW_MAIN(active_win)); + + window_rgrow_int(rsize); +} + +/* SYNTAX: WINDOW RBALANCE */ +static void cmd_window_rbalance(void) +{ + GSList *line, *ltmp; + int avail_width, unit_width, bigger_units; + int windows, last_column, old_width; + MAIN_WINDOW_REC *win; + + line = mainwindows_get_line(WINDOW_MAIN(active_win)); + windows = g_slist_length(line); + if (windows == 1) { + g_slist_free(line); + return; + } + + avail_width = term_width - screen_reserved_left-screen_reserved_right - windows + 1; + unit_width = avail_width/windows; + bigger_units = avail_width%windows; + + last_column = screen_reserved_left; + for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) { + win = ltmp->data; + old_width = win->width; + win->first_column = last_column; + win->last_column = win->first_column + unit_width-1; + + if (bigger_units > 0) { + win->last_column++; + bigger_units--; + } + + mainwindow_resize(win, win->last_column - win->first_column + 1 - old_width, 0); + last_column = win->last_column+2; + } + g_slist_free(line); + + mainwindows_redraw(); +} + /* SYNTAX: WINDOW UP */ static void cmd_window_up(void) { MAIN_WINDOW_REC *rec; - rec = mainwindows_find_upper(active_mainwin->first_line); + rec = mainwindows_find_left_upper(active_mainwin); if (rec == NULL) - rec = mainwindows_find_upper(term_height); + rec = mainwindows_find_left_upper(NULL); + if (rec != NULL) + window_set_active(rec->active); +} + +/* SYNTAX: WINDOW DUP */ +static void cmd_window_dup(void) +{ + MAIN_WINDOW_REC *rec; + + rec = mainwindows_find_upper(active_mainwin); + if (rec == NULL) + rec = mainwindows_find_upper(NULL); + if (rec != NULL) + window_set_active(rec->active); +} + +/* SYNTAX: WINDOW DLEFT */ +static void cmd_window_dleft(void) +{ + MAIN_WINDOW_REC *rec; + + rec = mainwindows_find_left(active_mainwin, FALSE); + if (rec == NULL) + rec = mainwindows_find_left(active_mainwin, TRUE); if (rec != NULL) window_set_active(rec->active); } @@ -837,9 +1565,33 @@ static void cmd_window_down(void) { MAIN_WINDOW_REC *rec; - rec = mainwindows_find_lower(active_mainwin->last_line); + rec = mainwindows_find_lower_right(active_mainwin); + if (rec == NULL) + rec = mainwindows_find_lower_right(NULL); + if (rec != NULL) + window_set_active(rec->active); +} + +/* SYNTAX: WINDOW DDOWN */ +static void cmd_window_ddown(void) +{ + MAIN_WINDOW_REC *rec; + + rec = mainwindows_find_lower(active_mainwin); if (rec == NULL) - rec = mainwindows_find_lower(-1); + rec = mainwindows_find_lower(NULL); + if (rec != NULL) + window_set_active(rec->active); +} + +/* SYNTAX: WINDOW DRIGHT */ +static void cmd_window_dright(void) +{ + MAIN_WINDOW_REC *rec; + + rec = mainwindows_find_right(active_mainwin, FALSE); + if (rec == NULL) + rec = mainwindows_find_right(active_mainwin, TRUE); if (rec != NULL) window_set_active(rec->active); } @@ -997,13 +1749,37 @@ static void cmd_window_move_right(void) window_set_refnum(active_win, refnum); } +/* SYNTAX: WINDOW MOVE DLEFT */ +static void cmd_window_move_dleft(void) +{ + MAIN_WINDOW_REC *rec; + + rec = mainwindows_find_left(active_mainwin, FALSE); + if (rec == NULL) + rec = mainwindows_find_left(active_mainwin, TRUE); + if (rec != NULL) + window_reparent(active_win, rec); +} + +/* SYNTAX: WINDOW MOVE DRIGHT */ +static void cmd_window_move_dright(void) +{ + MAIN_WINDOW_REC *rec; + + rec = mainwindows_find_right(active_mainwin, FALSE); + if (rec == NULL) + rec = mainwindows_find_right(active_mainwin, TRUE); + if (rec != NULL) + window_reparent(active_win, rec); +} + /* SYNTAX: WINDOW MOVE UP */ static void cmd_window_move_up(void) { MAIN_WINDOW_REC *rec; - rec = mainwindows_find_upper(active_mainwin->first_line); - if (rec != NULL) + rec = mainwindows_find_upper_left(active_mainwin); + if (rec != NULL) window_reparent(active_win, rec); } @@ -1012,7 +1788,7 @@ static void cmd_window_move_down(void) { MAIN_WINDOW_REC *rec; - rec = mainwindows_find_lower(active_mainwin->last_line); + rec = mainwindows_find_lower_right(active_mainwin); if (rec != NULL) window_reparent(active_win, rec); } @@ -1058,12 +1834,14 @@ static void sig_window_print_info(WINDOW_REC *win) void mainwindows_init(void) { - old_screen_width = term_width; - old_screen_height = term_height; + screen_width = term_width; + screen_height = term_height; mainwindows = NULL; active_mainwin = NULL; + clrtoeol_info = g_new0(MAIN_WINDOW_BORDER_REC, 1); screen_reserved_top = screen_reserved_bottom = 0; + screen_reserved_left = screen_reserved_right = 0; command_bind("window grow", NULL, (SIGNAL_FUNC) cmd_window_grow); command_bind("window shrink", NULL, (SIGNAL_FUNC) cmd_window_shrink); @@ -1075,18 +1853,30 @@ void mainwindows_init(void) command_bind("window down", NULL, (SIGNAL_FUNC) cmd_window_down); command_bind("window left", NULL, (SIGNAL_FUNC) cmd_window_left); command_bind("window right", NULL, (SIGNAL_FUNC) cmd_window_right); + command_bind("window dup", NULL, (SIGNAL_FUNC) cmd_window_dup); + command_bind("window ddown", NULL, (SIGNAL_FUNC) cmd_window_ddown); + command_bind("window dleft", NULL, (SIGNAL_FUNC) cmd_window_dleft); + command_bind("window dright", NULL, (SIGNAL_FUNC) cmd_window_dright); command_bind("window stick", NULL, (SIGNAL_FUNC) cmd_window_stick); command_bind("window move left", NULL, (SIGNAL_FUNC) cmd_window_move_left); command_bind("window move right", NULL, (SIGNAL_FUNC) cmd_window_move_right); command_bind("window move up", NULL, (SIGNAL_FUNC) cmd_window_move_up); command_bind("window move down", NULL, (SIGNAL_FUNC) cmd_window_move_down); - signal_add("window print info", (SIGNAL_FUNC) sig_window_print_info); + command_bind("window move dleft", NULL, (SIGNAL_FUNC) cmd_window_move_dleft); + command_bind("window move dright", NULL, (SIGNAL_FUNC) cmd_window_move_dright); + command_bind("window rgrow", NULL, (SIGNAL_FUNC) cmd_window_rgrow); + command_bind("window rshrink", NULL, (SIGNAL_FUNC) cmd_window_rshrink); + command_bind("window rsize", NULL, (SIGNAL_FUNC) cmd_window_rsize); + command_bind("window rbalance", NULL, (SIGNAL_FUNC) cmd_window_rbalance); + command_bind("window rshow", NULL, (SIGNAL_FUNC) cmd_window_rshow); + signal_add("window print info", (SIGNAL_FUNC) sig_window_print_info); } void mainwindows_deinit(void) { while (mainwindows != NULL) mainwindow_destroy(mainwindows->data); + g_free(clrtoeol_info); command_unbind("window grow", (SIGNAL_FUNC) cmd_window_grow); command_unbind("window shrink", (SIGNAL_FUNC) cmd_window_shrink); @@ -1098,10 +1888,21 @@ void mainwindows_deinit(void) command_unbind("window down", (SIGNAL_FUNC) cmd_window_down); command_unbind("window left", (SIGNAL_FUNC) cmd_window_left); command_unbind("window right", (SIGNAL_FUNC) cmd_window_right); + command_unbind("window dup", (SIGNAL_FUNC) cmd_window_dup); + command_unbind("window ddown", (SIGNAL_FUNC) cmd_window_ddown); + command_unbind("window dleft", (SIGNAL_FUNC) cmd_window_dleft); + command_unbind("window dright", (SIGNAL_FUNC) cmd_window_dright); command_unbind("window stick", (SIGNAL_FUNC) cmd_window_stick); command_unbind("window move left", (SIGNAL_FUNC) cmd_window_move_left); command_unbind("window move right", (SIGNAL_FUNC) cmd_window_move_right); command_unbind("window move up", (SIGNAL_FUNC) cmd_window_move_up); command_unbind("window move down", (SIGNAL_FUNC) cmd_window_move_down); - signal_remove("window print info", (SIGNAL_FUNC) sig_window_print_info); + command_unbind("window move dleft", (SIGNAL_FUNC) cmd_window_move_dleft); + command_unbind("window move dright", (SIGNAL_FUNC) cmd_window_move_dright); + command_unbind("window rgrow", (SIGNAL_FUNC) cmd_window_rgrow); + command_unbind("window rshrink", (SIGNAL_FUNC) cmd_window_rshrink); + command_unbind("window rsize", (SIGNAL_FUNC) cmd_window_rsize); + command_unbind("window rbalance", (SIGNAL_FUNC) cmd_window_rbalance); + command_unbind("window rshow", (SIGNAL_FUNC) cmd_window_rshow); + signal_remove("window print info", (SIGNAL_FUNC) sig_window_print_info); } diff --git a/src/fe-text/mainwindows.h b/src/fe-text/mainwindows.h index 1bca333d..414275bf 100644 --- a/src/fe-text/mainwindows.h +++ b/src/fe-text/mainwindows.h @@ -5,36 +5,48 @@ #include "term.h" #define WINDOW_MIN_SIZE 2 +#define NEW_WINDOW_WIDTH 10 #define MAIN_WINDOW_TEXT_HEIGHT(window) \ ((window)->height-(window)->statusbar_lines) +#define MAIN_WINDOW_TEXT_WIDTH(window) \ + ((window)->width-(window)->statusbar_columns) + typedef struct { WINDOW_REC *active; TERM_WINDOW *screen_win; - int sticky_windows; /* number of sticky windows */ + int sticky_windows; /* number of sticky windows */ int first_line, last_line; /* first/last line used by this window (0..x) (includes statusbars) */ + int first_column, last_column; /* first/last column used by this window (0..x) (includes statusbars) */ int width, height; /* width/height of the window (includes statusbars) */ GSList *statusbars; - int statusbar_lines_top; - int statusbar_lines_bottom; + int statusbar_lines_top, statusbar_lines_bottom; int statusbar_lines; /* top+bottom */ + int statusbar_columns_left, statusbar_columns_right; + int statusbar_columns; /* left+right */ unsigned int dirty:1; /* This window needs a redraw */ unsigned int size_dirty:1; /* We'll need to resize the window, but haven't got around doing it just yet. */ } MAIN_WINDOW_REC; +typedef struct { + char *color; + TERM_WINDOW *window; +} MAIN_WINDOW_BORDER_REC; + extern GSList *mainwindows; extern MAIN_WINDOW_REC *active_mainwin; +extern MAIN_WINDOW_BORDER_REC *clrtoeol_info; extern int screen_reserved_top, screen_reserved_bottom; void mainwindows_init(void); void mainwindows_deinit(void); -MAIN_WINDOW_REC *mainwindow_create(void); +MAIN_WINDOW_REC *mainwindow_create(int); void mainwindow_destroy(MAIN_WINDOW_REC *window); void mainwindows_redraw(void); @@ -45,6 +57,7 @@ void mainwindows_recreate(void); to be resized instead of upper window. */ void mainwindow_set_size(MAIN_WINDOW_REC *window, int height, int resize_lower); +void mainwindow_set_rsize(MAIN_WINDOW_REC *window, int width); void mainwindows_resize(int width, int height); void mainwindow_change_active(MAIN_WINDOW_REC *mainwin, @@ -56,5 +69,6 @@ int mainwindow_set_statusbar_lines(MAIN_WINDOW_REC *window, void mainwindows_redraw_dirty(void); GSList *mainwindows_get_sorted(int reverse); +GSList *mainwindows_get_line(MAIN_WINDOW_REC *rec); #endif diff --git a/src/fe-text/module-formats.c b/src/fe-text/module-formats.c index c4606197..b234f46c 100644 --- a/src/fe-text/module-formats.c +++ b/src/fe-text/module-formats.c @@ -41,8 +41,8 @@ FORMAT_REC gui_text_formats[] = { "refnum_not_found", "Window number $0 not found", 1, { 0 } }, { "window_too_small", "Not enough room to resize this window", 0 }, { "cant_hide_last", "You can't hide the last window", 0 }, - { "cant_hide_sticky_windows", "You can't hide sticky windows (use /WINDOW STICK OFF)", 0 }, - { "cant_show_sticky_windows", "You can't show sticky windows (use /WINDOW STICK OFF)", 0 }, + { "cant_hide_sticky_windows", "You can't hide sticky windows (use /SET autounstick_windows ON)", 0 }, + { "cant_show_sticky_windows", "You can't show sticky windows (use /SET autounstick_windows ON)", 0 }, { "window_not_sticky", "Window is not sticky", 0 }, { "window_set_sticky", "Window set sticky", 0 }, { "window_unset_sticky", "Window is not sticky anymore", 0 }, @@ -50,6 +50,7 @@ FORMAT_REC gui_text_formats[] = { "window_info_scroll", "%#Scroll : $0", 1, { 0 } }, { "window_scroll", "Window scroll mode is now $0", 1, { 0 } }, { "window_scroll_unknown", "Unknown scroll mode $0, must be ON, OFF or DEFAULT", 1, { 0 } }, + { "window_hidelevel", "Window hidden level is now $0", 1, { 0 } }, /* ---- */ { NULL, "Statusbars", 0 }, @@ -86,7 +87,7 @@ FORMAT_REC gui_text_formats[] = "|_ _|_ _ _____(_)%:" " | || '_(_-<_-< |%:" "|___|_| /__/__/_|%:" - "Irssi v$J - http://www.irssi.org", 0 }, + "Irssi v$J - https://irssi.org", 0 }, { "welcome_firsttime", "- - - - - - - - - - - - - - - - - - - - - - - - - - - -\n" "Hi there! If this is your first time using Irssi, you%:" diff --git a/src/fe-text/module-formats.h b/src/fe-text/module-formats.h index 05e9438d..b753238b 100644 --- a/src/fe-text/module-formats.h +++ b/src/fe-text/module-formats.h @@ -26,6 +26,7 @@ enum { TXT_WINDOW_INFO_SCROLL, TXT_WINDOW_SCROLL, TXT_WINDOW_SCROLL_UNKNOWN, + TXT_WINDOW_HIDELEVEL, TXT_FILL_3, diff --git a/src/fe-text/statusbar-items.c b/src/fe-text/statusbar-items.c index c7d6bcfb..5740a40b 100644 --- a/src/fe-text/statusbar-items.c +++ b/src/fe-text/statusbar-items.c @@ -418,7 +418,7 @@ static void item_input(SBAR_ITEM_REC *item, int get_size_only) rec = g_hash_table_lookup(input_entries, item->bar->config->name); if (rec == NULL) { - rec = gui_entry_create(item->xpos, item->bar->real_ypos, + rec = gui_entry_create(ITEM_WINDOW_REAL_XPOS(item), item->bar->real_ypos, item->size, term_type == TERM_TYPE_UTF8); gui_entry_set_active(rec); g_hash_table_insert(input_entries, @@ -426,12 +426,21 @@ static void item_input(SBAR_ITEM_REC *item, int get_size_only) } if (get_size_only) { - item->min_size = 2+term_width/10; - item->max_size = term_width; - return; + int max_width; + WINDOW_REC *window; + + window = item->bar->parent_window != NULL + ? item->bar->parent_window->active + : NULL; + + max_width = window != NULL ? window->width : term_width; + + item->min_size = 2+max_width/10; + item->max_size = max_width; + return; } - gui_entry_move(rec, item->xpos, item->bar->real_ypos, + gui_entry_move(rec, ITEM_WINDOW_REAL_XPOS(item), item->bar->real_ypos, item->size); gui_entry_redraw(rec); /* FIXME: this is only necessary with ^L.. */ } diff --git a/src/fe-text/statusbar.c b/src/fe-text/statusbar.c index f0dff828..40837eea 100644 --- a/src/fe-text/statusbar.c +++ b/src/fe-text/statusbar.c @@ -242,17 +242,24 @@ static void statusbar_resize_items(STATUSBAR_REC *bar, int max_width) static void statusbar_calc_item_positions(STATUSBAR_REC *bar) { - WINDOW_REC *old_active_win; + WINDOW_REC *window; + WINDOW_REC *old_active_win; GSList *tmp, *right_items; int xpos, rxpos; + int max_width; old_active_win = active_win; - if (bar->parent_window != NULL) + if (bar->parent_window != NULL) active_win = bar->parent_window->active; - statusbar_resize_items(bar, term_width); + window = bar->parent_window != NULL + ? bar->parent_window->active + : NULL; + + max_width = window != NULL ? window->width : term_width; + statusbar_resize_items(bar, max_width); - /* left-aligned items */ + /* left-aligned items */ xpos = 0; for (tmp = bar->items; tmp != NULL; tmp = tmp->next) { SBAR_ITEM_REC *rec = tmp->data; @@ -260,11 +267,11 @@ static void statusbar_calc_item_positions(STATUSBAR_REC *bar) if (!rec->config->right_alignment && (rec->size > 0 || rec->current_size > 0)) { if (SBAR_ITEM_REDRAW_NEEDED(bar, rec, xpos)) { - /* redraw the item */ + /* redraw the item */ rec->dirty = TRUE; if (bar->dirty_xpos == -1 || xpos < bar->dirty_xpos) { - irssi_set_dirty(); + irssi_set_dirty(); bar->dirty = TRUE; bar->dirty_xpos = xpos; } @@ -277,12 +284,12 @@ static void statusbar_calc_item_positions(STATUSBAR_REC *bar) /* right-aligned items - first copy them to a new list backwards, easier to draw them in right order */ - right_items = NULL; + right_items = NULL; for (tmp = bar->items; tmp != NULL; tmp = tmp->next) { SBAR_ITEM_REC *rec = tmp->data; if (rec->config->right_alignment) { - if (rec->size > 0) + if (rec->size > 0) right_items = g_slist_prepend(right_items, rec); else if (rec->current_size > 0 && (bar->dirty_xpos == -1 || @@ -291,12 +298,12 @@ static void statusbar_calc_item_positions(STATUSBAR_REC *bar) to begin from the item's old xpos */ irssi_set_dirty(); bar->dirty = TRUE; - bar->dirty_xpos = rec->xpos; + bar->dirty_xpos = rec->xpos; } } } - rxpos = term_width; + rxpos = max_width; for (tmp = right_items; tmp != NULL; tmp = tmp->next) { SBAR_ITEM_REC *rec = tmp->data; @@ -312,7 +319,7 @@ static void statusbar_calc_item_positions(STATUSBAR_REC *bar) rec->xpos = rxpos; } } - g_slist_free(right_items); + g_slist_free(right_items); active_win = old_active_win; } @@ -451,8 +458,13 @@ static void mainwindow_recalc_ypos(MAIN_WINDOW_REC *window, int placement) static void sig_mainwindow_resized(MAIN_WINDOW_REC *window) { - mainwindow_recalc_ypos(window, STATUSBAR_TOP); - mainwindow_recalc_ypos(window, STATUSBAR_BOTTOM); + GSList *tmp; + mainwindow_recalc_ypos(window, STATUSBAR_TOP); + mainwindow_recalc_ypos(window, STATUSBAR_BOTTOM); + for (tmp = window->statusbars; tmp != NULL; tmp = tmp->next) { + STATUSBAR_REC *bar = tmp->data; + statusbar_redraw(bar, TRUE); + } } STATUSBAR_REC *statusbar_create(STATUSBAR_GROUP_REC *group, @@ -728,7 +740,7 @@ void statusbar_item_default_handler(SBAR_ITEM_REC *item, int get_size_only, g_string_append_c(out, ' '); } - gui_printtext(item->xpos, item->bar->real_ypos, out->str); + gui_printtext(ITEM_WINDOW_REAL_XPOS(item), item->bar->real_ypos, out->str); g_string_free(out, TRUE); } g_free(tmpstr); @@ -960,20 +972,42 @@ void statusbar_item_destroy(SBAR_ITEM_REC *item) g_free(item); } +static MAIN_WINDOW_BORDER_REC *set_border_info(STATUSBAR_REC *bar) +{ + MAIN_WINDOW_BORDER_REC *orig_border, *new_border; + orig_border = clrtoeol_info; + new_border = g_new0(MAIN_WINDOW_BORDER_REC, 1); + new_border->window = bar->parent_window != NULL ? bar->parent_window->screen_win : NULL; + new_border->color = bar->color; + clrtoeol_info = new_border; + return orig_border; +} + +static void restore_border_info(MAIN_WINDOW_BORDER_REC *border_info) +{ + MAIN_WINDOW_BORDER_REC *old_border; + old_border = clrtoeol_info; + clrtoeol_info = border_info; + g_free(old_border); +} + static void statusbar_redraw_needed_items(STATUSBAR_REC *bar) { - WINDOW_REC *old_active_win; + WINDOW_REC *old_active_win; GSList *tmp; char *str; old_active_win = active_win; - if (bar->parent_window != NULL) + if (bar->parent_window != NULL) active_win = bar->parent_window->active; if (bar->dirty_xpos >= 0) { + MAIN_WINDOW_BORDER_REC *orig_border; + orig_border = set_border_info(bar); str = g_strconcat(bar->color, "%>", NULL); - gui_printtext(bar->dirty_xpos, bar->real_ypos, str); + gui_printtext(BAR_WINDOW_REAL_DIRTY_XPOS(bar), bar->real_ypos, str); g_free(str); + restore_border_info(orig_border); } for (tmp = bar->items; tmp != NULL; tmp = tmp->next) { @@ -982,13 +1016,13 @@ static void statusbar_redraw_needed_items(STATUSBAR_REC *bar) if (rec->dirty || (bar->dirty_xpos != -1 && rec->xpos >= bar->dirty_xpos)) { - rec->current_size = rec->size; + rec->current_size = rec->size; rec->func(rec, FALSE); rec->dirty = FALSE; } } - active_win = old_active_win; + active_win = old_active_win; } void statusbar_redraw_dirty(void) diff --git a/src/fe-text/statusbar.h b/src/fe-text/statusbar.h index 309294b0..b0048cc4 100644 --- a/src/fe-text/statusbar.h +++ b/src/fe-text/statusbar.h @@ -23,6 +23,12 @@ typedef struct SBAR_ITEM_REC SBAR_ITEM_REC; #define STATUSBAR_VISIBLE_ACTIVE 2 #define STATUSBAR_VISIBLE_INACTIVE 3 +#define ITEM_WINDOW_REAL_XPOS(item) ( ( (item)->bar->parent_window != NULL ? \ + (item)->bar->parent_window->first_column + (item)->bar->parent_window->statusbar_columns_left : 0 ) + (item)->xpos ) + +#define BAR_WINDOW_REAL_DIRTY_XPOS(bar) ( ( (bar)->parent_window != NULL ? \ + (bar)->parent_window->first_column + (bar)->parent_window->statusbar_columns_left : 0 ) + (bar)->dirty_xpos ) + typedef struct { char *name; GSList *config_bars; diff --git a/src/fe-text/term-terminfo.c b/src/fe-text/term-terminfo.c index 6645cfb0..68947f0c 100644 --- a/src/fe-text/term-terminfo.c +++ b/src/fe-text/term-terminfo.c @@ -23,6 +23,7 @@ #include "term.h" #include "terminfo-core.h" #include "fe-windows.h" +#include "gui-printtext.h" #include "utf8.h" #include <signal.h> @@ -284,10 +285,10 @@ void term_window_clear(TERM_WINDOW *window) { int y; - terminfo_set_normal(); - if (window->y == 0 && window->height == term_height) { - term_clear(); - } else { + terminfo_set_normal(); + if (window->y == 0 && window->height == term_height && window->width == term_width) { + term_clear(); + } else { for (y = 0; y < window->height; y++) { term_move(window, 0, y); term_clrtoeol(window); @@ -452,14 +453,14 @@ void term_set_color(TERM_WINDOW *window, int col) void term_move(TERM_WINDOW *window, int x, int y) { if (x >= 0 && y >= 0) { - vcmove = TRUE; - vcx = x+window->x; - vcy = y+window->y; - - if (vcx >= term_width) - vcx = term_width-1; - if (vcy >= term_height) - vcy = term_height-1; + vcmove = TRUE; + vcx = x+window->x; + vcy = y+window->y; + + if (vcx >= term_width) + vcx = term_width-1; + if (vcy >= term_height) + vcy = term_height-1; } } @@ -552,7 +553,7 @@ int term_addstr(TERM_WINDOW *window, const char *str) while (*ptr != '\0') { tmp = g_utf8_get_char_validated(ptr, -1); /* On utf8 error, treat as single byte and try to - continue interpretting rest of string as utf8 */ + continue interpreting rest of string as utf8 */ if (tmp == (gunichar)-1 || tmp == (gunichar)-2) { len++; ptr++; @@ -574,21 +575,52 @@ int term_addstr(TERM_WINDOW *window, const char *str) void term_clrtoeol(TERM_WINDOW *window) { - /* clrtoeol() doesn't necessarily understand colors */ - if (last_fg == -1 && last_bg == -1 && - (last_attrs & (ATTR_UNDERLINE|ATTR_REVERSE|ATTR_ITALIC)) == 0) { - if (!term_lines_empty[vcy]) { - if (vcmove) term_move_real(); - terminfo_clrtoeol(); - if (vcx == 0) term_lines_empty[vcy] = TRUE; - } - } else if (vcx < term_width) { - /* we'll need to fill the line ourself. */ + if (vcx < window->x) { + /* we just wrapped outside of the split, warp the cursor back into the window */ + vcx += window->x; + vcmove = TRUE; + } + if (window->x + window->width < term_width) { + /* we need to fill a vertical split */ if (vcmove) term_move_real(); - terminfo_repeat(' ', term_width-vcx); + terminfo_repeat(' ', window->x + window->width - vcx + 1); terminfo_move(vcx, vcy); - term_lines_empty[vcy] = FALSE; + term_lines_empty[vcy] = FALSE; + } else { + /* clrtoeol() doesn't necessarily understand colors */ + if (last_fg == -1 && last_bg == -1 && + (last_attrs & (ATTR_UNDERLINE|ATTR_REVERSE|ATTR_ITALIC)) == 0) { + if (!term_lines_empty[vcy]) { + if (vcmove) term_move_real(); + terminfo_clrtoeol(); + if (vcx == 0) term_lines_empty[vcy] = TRUE; + } + } else if (vcx < term_width) { + /* we'll need to fill the line ourself. */ + if (vcmove) term_move_real(); + terminfo_repeat(' ', term_width-vcx); + terminfo_move(vcx, vcy); + term_lines_empty[vcy] = FALSE; + } + } +} + +void term_window_clrtoeol(TERM_WINDOW* window, int ypos) +{ + if (ypos >= 0 && window->y + ypos != vcy) { + /* the line is already full */ + return; } + term_clrtoeol(window); + if (window->x + window->width < term_width) { + gui_printtext_window_border(window->x + window->width, window->y + ypos); + term_set_color(window, ATTR_RESET); + } +} + +void term_window_clrtoeol_abs(TERM_WINDOW* window, int ypos) +{ + term_window_clrtoeol(window, ypos - window->y); } void term_move_cursor(int x, int y) @@ -733,3 +765,31 @@ void term_gets(GArray *buffer, int *line_count) } } } + +static const char* term_env_warning = + "You seem to be running Irssi inside %2$s, but the TERM environment variable " + "is set to '%1$s', which can cause display glitches.\n" + "Consider changing TERM to '%2$s' or '%2$s-256color' instead."; + +void term_environment_check(void) +{ + const char *term, *sty, *tmux, *multiplexer; + + term = g_getenv("TERM"); + sty = g_getenv("STY"); + tmux = g_getenv("TMUX"); + + multiplexer = (sty && *sty) ? "screen" : + (tmux && *tmux) ? "tmux" : NULL; + + if (!multiplexer) { + return; + } + + if (term && (g_str_has_prefix(term, "screen") || + g_str_has_prefix(term, "tmux"))) { + return; + } + + g_warning(term_env_warning, term, multiplexer); +} diff --git a/src/fe-text/term.h b/src/fe-text/term.h index 0c7847f6..f25154c2 100644 --- a/src/fe-text/term.h +++ b/src/fe-text/term.h @@ -85,6 +85,8 @@ void term_addch(TERM_WINDOW *window, char chr); void term_add_unichar(TERM_WINDOW *window, unichar chr); int term_addstr(TERM_WINDOW *window, const char *str); void term_clrtoeol(TERM_WINDOW *window); +void term_window_clrtoeol(TERM_WINDOW* window, int ypos); +void term_window_clrtoeol_abs(TERM_WINDOW* window, int ypos_abs); void term_move_cursor(int x, int y); @@ -105,4 +107,6 @@ void term_gets(GArray *buffer, int *line_count); void term_common_init(void); void term_common_deinit(void); +void term_environment_check(void); + #endif diff --git a/src/fe-text/textbuffer-commands.c b/src/fe-text/textbuffer-commands.c index 648862e7..97d897f3 100644 --- a/src/fe-text/textbuffer-commands.c +++ b/src/fe-text/textbuffer-commands.c @@ -89,6 +89,25 @@ static void cmd_window_scroll(const char *data) gui->scroll : settings_get_bool("scroll")); } +/* SYNTAX: WINDOW HIDELEVEL [<level>] */ +static void cmd_window_hidelevel(const char *data) +{ + GUI_WINDOW_REC *gui; + char *level; + + g_return_if_fail(data != NULL); + + gui = WINDOW_GUI(active_win); + textbuffer_view_set_hidden_level(gui->view, + combine_level(gui->view->hidden_level, data)); + textbuffer_view_redraw(gui->view); + level = gui->view->hidden_level == 0 ? g_strdup("NONE") : + bits2level(gui->view->hidden_level); + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_WINDOW_HIDELEVEL, level); + g_free(level); +} + static void cmd_scrollback(const char *data, SERVER_REC *server, WI_ITEM_REC *item) { @@ -358,6 +377,7 @@ void textbuffer_commands_init(void) { command_bind("clear", NULL, (SIGNAL_FUNC) cmd_clear); command_bind("window scroll", NULL, (SIGNAL_FUNC) cmd_window_scroll); + command_bind("window hidelevel", NULL, (SIGNAL_FUNC) cmd_window_hidelevel); command_bind("scrollback", NULL, (SIGNAL_FUNC) cmd_scrollback); command_bind("scrollback clear", NULL, (SIGNAL_FUNC) cmd_scrollback_clear); command_bind("scrollback levelclear", NULL, (SIGNAL_FUNC) cmd_scrollback_levelclear); @@ -377,6 +397,7 @@ void textbuffer_commands_deinit(void) { command_unbind("clear", (SIGNAL_FUNC) cmd_clear); command_unbind("window scroll", (SIGNAL_FUNC) cmd_window_scroll); + command_unbind("window hidelevel", (SIGNAL_FUNC) cmd_window_hidelevel); command_unbind("scrollback", (SIGNAL_FUNC) cmd_scrollback); command_unbind("scrollback clear", (SIGNAL_FUNC) cmd_scrollback_clear); command_unbind("scrollback levelclear", (SIGNAL_FUNC) cmd_scrollback_levelclear); diff --git a/src/fe-text/textbuffer-view.c b/src/fe-text/textbuffer-view.c index cb066f5e..99625ecc 100644 --- a/src/fe-text/textbuffer-view.c +++ b/src/fe-text/textbuffer-view.c @@ -41,9 +41,15 @@ static GSList *views; #define view_is_bottom(view) \ ((view)->ypos >= -1 && (view)->ypos < (view)->height) -#define view_get_linecount(view, line) \ +#define view_get_linecount_hidden(view, line) \ textbuffer_view_get_line_cache(view, line)->count +#define view_line_is_hidden(view, line) \ + (((line)->info.level & (view)->hidden_level) != 0) + +#define view_get_linecount(view, line) \ + (view_line_is_hidden(view, line) ? 0 : view_get_linecount_hidden(view, line)) + static GSList *textbuffer_get_views(TEXT_BUFFER_REC *buffer) { GSList *tmp, *list; @@ -388,9 +394,9 @@ static void view_reset_cache(TEXT_BUFFER_VIEW_REC *view) static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, int subline, int ypos, int max) { - INDENT_FUNC indent_func; + INDENT_FUNC indent_func; LINE_CACHE_REC *cache; - const unsigned char *text, *end, *text_newline; + const unsigned char *text, *end, *text_newline; unsigned char *tmp; unichar chr; int xpos, color, drawcount, first, need_move, need_clrtoeol, char_width; @@ -399,54 +405,54 @@ static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, #endif if (view->dirty) /* don't bother drawing anything - redraw is coming */ - return 0; + return 0; cache = textbuffer_view_get_line_cache(view, line); if (subline >= cache->count) - return 0; + return 0; - color = ATTR_RESET; - need_move = TRUE; need_clrtoeol = FALSE; + color = ATTR_RESET; + need_move = TRUE; need_clrtoeol = FALSE; xpos = drawcount = 0; first = TRUE; text_newline = text = subline == 0 ? line->text : cache->lines[subline-1].start; for (;;) { if (text == text_newline) { - if (need_clrtoeol && xpos < term_width) { + if (need_clrtoeol && xpos < view->width + (view->width == term_width ? 0 : 1)) { term_set_color(view->window, ATTR_RESET); - term_clrtoeol(view->window); + term_window_clrtoeol(view->window, ypos); } if (first) first = FALSE; else { ypos++; - if (--max == 0) + if (--max == 0) break; } if (subline > 0) { - /* continuing previous line - indent it */ + /* continuing previous line - indent it */ indent_func = cache->lines[subline-1].indent_func; if (indent_func == NULL) xpos = cache->lines[subline-1].indent; - color = cache->lines[subline-1].color; + color = cache->lines[subline-1].color; #ifdef TERM_TRUECOLOR - fg24 = cache->lines[subline-1].fg24; - bg24 = cache->lines[subline-1].bg24; + fg24 = cache->lines[subline-1].fg24; + bg24 = cache->lines[subline-1].bg24; #endif } else { indent_func = NULL; } if (xpos == 0 && indent_func == NULL) - need_clrtoeol = TRUE; + need_clrtoeol = TRUE; else { /* line was indented - need to clear the - indented area first */ + indented area first */ term_set_color(view->window, ATTR_RESET); term_move(view->window, 0, ypos); - term_clrtoeol(view->window); + term_window_clrtoeol(view->window, ypos); if (indent_func != NULL) xpos = indent_func(view, line, ypos); @@ -463,9 +469,17 @@ static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, } else { /* get the beginning of the next subline */ text_newline = cache->lines[subline].start; - need_move = !cache->lines[subline].continues; + if (view->width == term_width) { + /* ensure that links / long words are not broken */ + need_move = !cache->lines[subline].continues; + } else { + /* we cannot use the need_move + optimisation unless the split spans + the whole width */ + need_move = TRUE; + } } - drawcount++; + drawcount++; subline++; } @@ -473,10 +487,10 @@ static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, /* command */ text++; if (*text == LINE_CMD_EOL) - break; + break; if (*text == LINE_CMD_CONTINUE) { - /* jump to next block */ + /* jump to next block */ memcpy(&tmp, text+1, sizeof(unsigned char *)); text = tmp; continue; @@ -511,13 +525,13 @@ static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, } xpos += char_width; - if (xpos <= term_width) { + if (xpos <= view->width) { if (unichar_isprint(chr)) { if (view->utf8) - term_add_unichar(view->window, chr); + term_add_unichar(view->window, chr); else - for (; text < end; text++) - term_addch(view->window, *text); + for (; text < end; text++) + term_addch(view->window, *text); } else { /* low-ascii */ term_set_color(view->window, ATTR_RESET|ATTR_REVERSE); @@ -528,12 +542,12 @@ static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, text = end; } - if (need_clrtoeol && xpos < term_width) { + if (need_clrtoeol && xpos < view->width + (view->width == term_width ? 0 : 1)) { term_set_color(view->window, ATTR_RESET); - term_clrtoeol(view->window); + term_window_clrtoeol(view->window, ypos); } - return drawcount; + return drawcount; } /* Recalculate view's bottom line information - try to keep the @@ -552,6 +566,9 @@ static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view) total = 0; line = textbuffer_line_last(view->buffer); for (; line != NULL; line = line->prev) { + if (view_line_is_hidden(view, line)) + continue; + linecount = view_get_linecount(view, line); if (line == view->bottom_startline) { /* keep the old one, make sure that subline is ok */ @@ -614,6 +631,8 @@ TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer, view->subline = view->bottom_subline; view->bottom = TRUE; + view->hidden_level = 0; + textbuffer_view_init_ypos(view); view->bookmarks = g_hash_table_new((GHashFunc) g_str_hash, @@ -726,8 +745,10 @@ static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, return; while (line != NULL && lines > 0) { - linecount = view_line_draw(view, line, subline, ypos, lines); - ypos += linecount; lines -= linecount; + if (!view_line_is_hidden(view, line)) { + linecount = view_line_draw(view, line, subline, ypos, lines); + ypos += linecount; lines -= linecount; + } subline = 0; line = line->next; @@ -738,7 +759,7 @@ static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, term_set_color(view->window, ATTR_RESET); while (lines > 0) { term_move(view->window, 0, ypos); - term_clrtoeol(view->window); + term_window_clrtoeol(view->window, ypos); ypos++; lines--; } } @@ -768,58 +789,63 @@ static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines) view_draw(view, line, subline, maxline, lines, TRUE); } -/* Returns number of lines actually scrolled */ +/* lines: this pointer is scrolled by scrollcount screen lines + subline: this pointer contains the subline position + scrollcount: the number of lines to scroll down (negative: up) + draw_nonclean: whether to redraw the screen now + + Returns number of lines actually scrolled */ static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **lines, int *subline, int scrollcount, int draw_nonclean) { int linecount, realcount, scroll_visible; if (*lines == NULL) - return 0; + return 0; /* scroll down */ scroll_visible = lines == &view->startline; realcount = -*subline; scrollcount += *subline; - *subline = 0; + *subline = 0; while (scrollcount > 0) { linecount = view_get_linecount(view, *lines); if ((scroll_visible && *lines == view->bottom_startline) && (scrollcount >= view->bottom_subline)) { *subline = view->bottom_subline; - realcount += view->bottom_subline; - scrollcount = 0; - break; + realcount += view->bottom_subline; + scrollcount = 0; + break; } - realcount += linecount; + realcount += linecount; scrollcount -= linecount; if (scrollcount < 0) { - realcount += scrollcount; + realcount += scrollcount; *subline = linecount+scrollcount; - scrollcount = 0; - break; + scrollcount = 0; + break; } if ((*lines)->next == NULL) break; - *lines = (*lines)->next; + *lines = (*lines)->next; } - /* scroll up */ + /* scroll up */ while (scrollcount < 0 && (*lines)->prev != NULL) { *lines = (*lines)->prev; linecount = view_get_linecount(view, *lines); - realcount -= linecount; + realcount -= linecount; scrollcount += linecount; if (scrollcount > 0) { - realcount += scrollcount; + realcount += scrollcount; *subline = scrollcount; - break; + break; } } @@ -827,19 +853,28 @@ static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **lines, if (realcount <= -view->height || realcount >= view->height) { /* scrolled more than screenful, redraw the whole view */ - textbuffer_view_redraw(view); + textbuffer_view_redraw(view); } else { - term_set_color(view->window, ATTR_RESET); - term_window_scroll(view->window, realcount); + if (view->width == term_width) { + /* we can try to use vt100 scroll regions */ + term_set_color(view->window, ATTR_RESET); + term_window_scroll(view->window, realcount); - if (draw_nonclean) { - if (realcount < 0) - view_draw_top(view, -realcount, TRUE); - else - view_draw_bottom(view, realcount); - } + if (draw_nonclean) { + if (realcount < 0) + view_draw_top(view, -realcount, TRUE); + else + view_draw_bottom(view, realcount); + } - term_refresh(view->window); + term_refresh(view->window); + } else { + /* do not bother with vt400 scroll + rectangles for now, redraw the + whole view */ + view->dirty = TRUE; + irssi_set_dirty(); + } } } @@ -1027,7 +1062,7 @@ static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) view->bottom = view_is_bottom(view); } - if (view->window != NULL) { + if (view->window != NULL && !view_line_is_hidden(view, line)) { ypos = view->ypos+1 - view_get_linecount(view, line); if (ypos >= 0) subline = 0; @@ -1042,7 +1077,7 @@ static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) } } - if (view->window != NULL) + if (view->window != NULL && !view_line_is_hidden(view, line)) term_refresh(view->window); } @@ -1122,6 +1157,12 @@ static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view, return height < view->height ? height : view->height; } +/* line: line to remove + linecount: linecount of that line, to be offset when the line was in/below view + + scroll the window maintaining the startline while removing line + if startline is removed, make the previous line the new startline +*/ static void view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, int linecount) { @@ -1318,6 +1359,37 @@ LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view, return g_hash_table_lookup(view->bookmarks, name); } +void textbuffer_view_set_hidden_level(TEXT_BUFFER_VIEW_REC *view, int level) +{ + g_return_if_fail(view != NULL); + + if (view->hidden_level != level) { + if (view->empty_linecount > 0 && view->startline != NULL) { + int old_height, new_height; + LINE_REC *hidden_start; + + hidden_start = view->startline; + while (hidden_start->prev != NULL && view_line_is_hidden(view, hidden_start->prev)) { + hidden_start = hidden_start->prev; + } + + old_height = view_get_lines_height(view, hidden_start, view->subline, NULL); + view->hidden_level = level; + new_height = view_get_lines_height(view, hidden_start, view->subline, NULL); + + view->empty_linecount -= new_height - old_height; + + if (view->empty_linecount < 0) + view->empty_linecount = 0; + else if (view->empty_linecount > view->height) + view->empty_linecount = view->height; + } else { + view->hidden_level = level; + } + textbuffer_view_resize(view, view->width, view->height); + } +} + /* Specify window where the changes in view should be drawn, NULL disables it. */ void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view, diff --git a/src/fe-text/textbuffer-view.h b/src/fe-text/textbuffer-view.h index 5e7a9d0a..a670df2b 100644 --- a/src/fe-text/textbuffer-view.h +++ b/src/fe-text/textbuffer-view.h @@ -49,41 +49,51 @@ typedef struct { struct _TEXT_BUFFER_VIEW_REC { TEXT_BUFFER_REC *buffer; - GSList *siblings; /* other views that use the same buffer */ + /* other views that use the same buffer */ + GSList *siblings; TERM_WINDOW *window; int width, height; int default_indent; INDENT_FUNC default_indent_func; - unsigned int longword_noindent:1; - unsigned int scroll:1; /* scroll down automatically when at bottom */ - unsigned int utf8:1; /* use UTF8 in this view */ - unsigned int break_wide:1; /* Break wide chars in this view */ TEXT_BUFFER_CACHE_REC *cache; - int ypos; /* cursor position - visible area is 0..height-1 */ + /* cursor position - visible area is 0..height-1 */ + int ypos; - LINE_REC *startline; /* line at the top of the screen */ - int subline; /* number of "real lines" to skip from `startline' */ + /* line at the top of the screen */ + LINE_REC *startline; + /* number of "real lines" to skip from `startline' */ + int subline; /* marks the bottom of the text buffer */ LINE_REC *bottom_startline; int bottom_subline; + /* Bookmarks to the lines in the buffer - removed automatically + when the line gets removed from buffer */ + GHashTable *bookmarks; + + /* these levels should be hidden */ + int hidden_level; /* how many empty lines are in screen. a screenful when started or used /CLEAR */ int empty_linecount; + + unsigned int longword_noindent:1; + /* scroll down automatically when at bottom */ + unsigned int scroll:1; + /* use UTF8 in this view */ + unsigned int utf8:1; + /* Break wide chars in this view */ + unsigned int break_wide:1; /* window is at the bottom of the text buffer */ unsigned int bottom:1; /* if !bottom - new text has been printed since we were at bottom */ unsigned int more_text:1; /* Window needs a redraw */ unsigned int dirty:1; - - /* Bookmarks to the lines in the buffer - removed automatically - when the line gets removed from buffer */ - GHashTable *bookmarks; }; /* Create new view. */ @@ -143,6 +153,8 @@ void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view, /* Return the line for bookmark */ LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view, const char *name); +/* Set hidden level for view */ +void textbuffer_view_set_hidden_level(TEXT_BUFFER_VIEW_REC *view, int level); /* Specify window where the changes in view should be drawn, NULL disables it. */ diff --git a/src/fe-text/textbuffer.c b/src/fe-text/textbuffer.c index a920fab2..01cdd118 100644 --- a/src/fe-text/textbuffer.c +++ b/src/fe-text/textbuffer.c @@ -230,6 +230,7 @@ LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer) return buffer->cur_line; } +/* returns TRUE if `search' comes on or after `line' in the buffer */ int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search) { while (line != NULL) { diff --git a/src/fe-text/textbuffer.h b/src/fe-text/textbuffer.h index 303789a3..2aa22f1a 100644 --- a/src/fe-text/textbuffer.h +++ b/src/fe-text/textbuffer.h @@ -65,10 +65,10 @@ typedef struct { LINE_REC *cur_line; TEXT_CHUNK_REC *cur_text; - unsigned int last_eol:1; int last_fg; int last_bg; int last_flags; + unsigned int last_eol:1; } TEXT_BUFFER_REC; /* Create new buffer */ diff --git a/src/irc/core/channel-events.c b/src/irc/core/channel-events.c index b0bddab2..46bbd5fa 100644 --- a/src/irc/core/channel-events.c +++ b/src/irc/core/channel-events.c @@ -138,7 +138,13 @@ static void channel_change_topic(IRC_SERVER_REC *server, const char *channel, g_free_not_null(chanrec->topic_by); chanrec->topic_by = g_strdup(setby); - chanrec->topic_time = settime; + if (chanrec->topic_by == NULL) { + /* ensure invariant topic_time > 0 <=> topic_by != NULL. + this could be triggered by a topic command without sender */ + chanrec->topic_time = 0; + } else { + chanrec->topic_time = settime; + } signal_emit("channel topic changed", 1, chanrec); } diff --git a/src/irc/core/irc-cap.c b/src/irc/core/irc-cap.c index 5464e493..1a60d99b 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,131 @@ 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_lookup_extended(server->cap_supported, key, NULL, NULL)) { + /* 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); + } + g_hash_table_insert(server->cap_supported, key, val); } - 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 +211,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 +227,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 +235,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-nicklist.c b/src/irc/core/irc-nicklist.c index 1cb1f3e9..3e16db80 100644 --- a/src/irc/core/irc-nicklist.c +++ b/src/irc/core/irc-nicklist.c @@ -323,8 +323,9 @@ static void event_nick_invalid(IRC_SERVER_REC *server, const char *data) static void event_nick_in_use(IRC_SERVER_REC *server, const char *data) { - char *str, *cmd; + char *str, *cmd, *params, *nick; int n; + gboolean try_alternate_nick; g_return_if_fail(data != NULL); @@ -332,11 +333,21 @@ static void event_nick_in_use(IRC_SERVER_REC *server, const char *data) /* Already connected, no need to handle this anymore. */ return; } + + try_alternate_nick = g_ascii_strcasecmp(server->nick, server->connrec->nick) == 0 && + server->connrec->alternate_nick != NULL && + g_ascii_strcasecmp(server->connrec->alternate_nick, server->nick) != 0; + + params = event_get_params(data, 2, NULL, &nick); + if (g_ascii_strcasecmp(server->nick, nick) != 0) { + /* the server uses a nick different from the one we send */ + g_free(server->nick); + server->nick = g_strdup(nick); + } + g_free(params); /* nick already in use - need to change it .. */ - if (g_ascii_strcasecmp(server->nick, server->connrec->nick) == 0 && - server->connrec->alternate_nick != NULL && - g_ascii_strcasecmp(server->connrec->alternate_nick, server->nick) != 0) { + if (try_alternate_nick) { /* first try, so try the alternative nick.. */ g_free(server->nick); server->nick = g_strdup(server->connrec->alternate_nick); diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c index 5076a688..02d971dc 100644 --- a/src/irc/core/irc-servers.c +++ b/src/irc/core/irc-servers.c @@ -443,8 +443,10 @@ static void sig_destroyed(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/irc/core/sasl.c b/src/irc/core/sasl.c index 2b589579..c5aa2caa 100644 --- a/src/irc/core/sasl.c +++ b/src/irc/core/sasl.c @@ -55,10 +55,21 @@ static gboolean sasl_timeout(IRC_SERVER_REC *server) return FALSE; } +static void sasl_timeout_stop(IRC_SERVER_REC *server) +{ + /* Stop any pending timeout, if any */ + if (server->sasl_timeout != 0) { + g_source_remove(server->sasl_timeout); + server->sasl_timeout = 0; + } +} + static void sasl_start(IRC_SERVER_REC *server, const char *data, const char *from) { IRC_SERVER_CONNECT_REC *conn; + sasl_timeout_stop(server); + conn = server->connrec; switch (conn->sasl_mechanism) { @@ -77,11 +88,6 @@ static void sasl_fail(IRC_SERVER_REC *server, const char *data, const char *from { char *params, *error; - /* Stop any pending timeout, if any */ - if (server->sasl_timeout != 0) { - g_source_remove(server->sasl_timeout); - server->sasl_timeout = 0; - } params = event_get_params(data, 2, NULL, &error); @@ -97,10 +103,7 @@ static void sasl_fail(IRC_SERVER_REC *server, const char *data, const char *from static void sasl_already(IRC_SERVER_REC *server, const char *data, const char *from) { - if (server->sasl_timeout != 0) { - g_source_remove(server->sasl_timeout); - server->sasl_timeout = 0; - } + sasl_timeout_stop(server); server->sasl_success = TRUE; @@ -112,10 +115,7 @@ static void sasl_already(IRC_SERVER_REC *server, const char *data, const char *f static void sasl_success(IRC_SERVER_REC *server, const char *data, const char *from) { - if (server->sasl_timeout != 0) { - g_source_remove(server->sasl_timeout); - server->sasl_timeout = 0; - } + sasl_timeout_stop(server); server->sasl_success = TRUE; @@ -265,7 +265,7 @@ static void sasl_step_fail(IRC_SERVER_REC *server) irc_send_cmd_now(server, "AUTHENTICATE *"); cap_finish_negotiation(server); - server->sasl_timeout = 0; + sasl_timeout_stop(server); signal_emit("server sasl failure", 2, server, "The server sent an invalid payload"); } @@ -274,11 +274,7 @@ static void sasl_step(IRC_SERVER_REC *server, const char *data, const char *from { GString *req = NULL; - /* Stop the timer */ - if (server->sasl_timeout != 0) { - g_source_remove(server->sasl_timeout); - server->sasl_timeout = 0; - } + sasl_timeout_stop(server); if (!sasl_reassemble_incoming(server, data, &req)) { sasl_step_fail(server); @@ -302,10 +298,7 @@ static void sasl_disconnected(IRC_SERVER_REC *server) return; } - if (server->sasl_timeout != 0) { - g_source_remove(server->sasl_timeout); - server->sasl_timeout = 0; - } + sasl_timeout_stop(server); } void sasl_init(void) diff --git a/src/irc/dcc/dcc-get.c b/src/irc/dcc/dcc-get.c index cecbb076..ee8b9a49 100644 --- a/src/irc/dcc/dcc-get.c +++ b/src/irc/dcc/dcc-get.c @@ -237,8 +237,12 @@ void sig_dccget_connected(GET_DCC_REC *dcc) if (temphandle == -1) ret = -1; - else - ret = fchmod(temphandle, dcc_file_create_mode); + else { + if (fchmod(temphandle, dcc_file_create_mode) != 0) + g_warning("fchmod(3) failed: %s", strerror(errno)); + /* proceed even if chmod fails */ + ret = 0; + } close(temphandle); @@ -249,7 +253,7 @@ void sig_dccget_connected(GET_DCC_REC *dcc) /* Linux */ (errno == EPERM || /* FUSE */ - errno == ENOSYS || + errno == ENOSYS || errno == EACCES || /* BSD */ errno == EOPNOTSUPP)) { /* hard links aren't supported - some people 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 5d2c8a7f..e2f162a0 100644 --- a/src/perl/textui/TextUI.xs +++ b/src/perl/textui/TextUI.xs @@ -40,6 +40,7 @@ static void perl_text_buffer_view_fill_hash(HV *hv, TEXT_BUFFER_VIEW_REC *view) (void) hv_store(hv, "startline", 9, plain_bless(view->startline, "Irssi::TextUI::Line"), 0); (void) hv_store(hv, "subline", 7, newSViv(view->subline), 0); + (void) hv_store(hv, "hidden_level", 12, newSViv(view->hidden_level), 0); (void) hv_store(hv, "bottom_startline", 16, plain_bless(view->bottom_startline, "Irssi::TextUI::Line"), 0); (void) hv_store(hv, "bottom_subline", 14, newSViv(view->bottom_subline), 0); @@ -123,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: |