diff options
Diffstat (limited to 'src')
45 files changed, 1096 insertions, 507 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 78856692..6cb77148 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,3 +12,7 @@ gtk_fe = fe-gtk endif SUBDIRS = pixmaps common $(gtk_fe) $(text_fe) + +if WITH_TM +SUBDIRS += htm +endif diff --git a/src/common/dcc.c b/src/common/dcc.c index 1137c444..5f4b1190 100644 --- a/src/common/dcc.c +++ b/src/common/dcc.c @@ -227,6 +227,15 @@ is_dcc (struct DCC *dcc) return FALSE; } +gboolean +is_dcc_completed (struct DCC *dcc) +{ + if (dcc != NULL) + return (dcc->dccstat == STAT_FAILED || dcc->dccstat == STAT_DONE || dcc->dccstat == STAT_ABORTED); + + return FALSE; +} + /* this is called from hexchat.c:hexchat_misc_checks() every 1 second. */ void diff --git a/src/common/dcc.h b/src/common/dcc.h index e3163c8a..acb87f34 100644 --- a/src/common/dcc.h +++ b/src/common/dcc.h @@ -117,6 +117,7 @@ struct dccstat_info extern struct dccstat_info dccstat[]; gboolean is_dcc (struct DCC *dcc); +gboolean is_dcc_completed (struct DCC *dcc); void dcc_abort (session *sess, struct DCC *dcc); void dcc_get (struct DCC *dcc); int dcc_resume (struct DCC *dcc); diff --git a/src/common/fe.h b/src/common/fe.h index 22db38df..91bf0a5b 100644 --- a/src/common/fe.h +++ b/src/common/fe.h @@ -125,11 +125,12 @@ void fe_get_str (char *prompt, char *def, void *callback, void *ud); void fe_get_int (char *prompt, int def, void *callback, void *ud); #define FRF_WRITE 1 /* save file */ #define FRF_MULTIPLE 2 /* multi-select */ -#define FRF_ADDFOLDER 4 /* add ~/.config/hexchat to favourites */ +#define FRF_RECENTLYUSED 4 /* let gtk decide start dir instead of our config */ #define FRF_CHOOSEFOLDER 8 /* choosing a folder only */ #define FRF_FILTERISINITIAL 16 /* filter is initial directory */ #define FRF_NOASKOVERWRITE 32 /* don't ask to overwrite existing files */ #define FRF_EXTENSIONS 64 /* specify file extensions to be displayed */ +#define FRF_MIMETYPES 128 /* specify file mimetypes to be displayed */ void fe_get_file (const char *title, char *initial, void (*callback) (void *userdata, char *file), void *userdata, int flags); diff --git a/src/common/hexchat.h b/src/common/hexchat.h index 76d6c3c3..68da144d 100644 --- a/src/common/hexchat.h +++ b/src/common/hexchat.h @@ -461,6 +461,12 @@ struct msproxy_state_t unsigned char seq_sent; /* seq number of last packet sent. */ }; +/* SASL Mechanisms */ +#define MECH_PLAIN 0 +#define MECH_BLOWFISH 1 +#define MECH_AES 2 +#define MECH_EXTERNAL 3 + typedef struct server { /* server control operations (in server*.c) */ @@ -598,9 +604,13 @@ typedef struct server unsigned int have_sasl:1; /* SASL capability */ unsigned int have_except:1; /* ban exemptions +e */ unsigned int have_invite:1; /* invite exemptions +I */ + unsigned int have_cert:1; /* have loaded a cert */ unsigned int using_cp1255:1; /* encoding is CP1255/WINDOWS-1255? */ unsigned int using_irc:1; /* encoding is "IRC" (CP1252/UTF-8 hybrid)? */ unsigned int use_who:1; /* whether to use WHO command to get dcc_ip */ + unsigned int sasl_mech; /* mechanism for sasl auth */ + unsigned int sent_saslauth:1; /* have sent AUTHENICATE yet */ + unsigned int sent_capend:1; /* have sent CAP END yet */ #ifdef USE_OPENSSL unsigned int use_ssl:1; /* is server SSL capable? */ unsigned int accept_invalid_cert:1;/* ignore result of server's cert. verify */ diff --git a/src/common/inbound.c b/src/common/inbound.c index f57207d1..fca5f071 100644 --- a/src/common/inbound.c +++ b/src/common/inbound.c @@ -392,6 +392,8 @@ inbound_action (session *sess, char *chan, char *from, char *ip, char *text, user->lasttalk = time (0); if (user->account) id = TRUE; + if (user->me) + fromme = TRUE; } inbound_make_idtext (serv, idtext, sizeof (idtext), id); @@ -459,6 +461,8 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from, id = TRUE; nickchar[0] = user->prefix[0]; user->lasttalk = time (0); + if (user->me) + fromme = TRUE; } if (fromme) @@ -1450,7 +1454,12 @@ nowindow: static int inbound_exec_eom_cmd (char *str, void *sess) { - handle_command (sess, (str[0] == '/') ? str + 1 : str, TRUE); + char *cmd; + + cmd = command_insert_vars ((session*)sess, (str[0] == '/') ? str + 1 : str); + handle_command ((session*)sess, cmd, TRUE); + g_free (cmd); + return 1; } @@ -1557,8 +1566,6 @@ void inbound_cap_ack (server *serv, char *nick, char *extensions, const message_tags_data *tags_data) { - char *pass; /* buffer for SASL password */ - EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPACK, serv->server_session, nick, extensions, NULL, NULL, 0, tags_data->timestamp); @@ -1594,20 +1601,25 @@ inbound_cap_ack (server *serv, char *nick, char *extensions, if (strstr (extensions, "sasl") != NULL) { - char *user; - serv->have_sasl = TRUE; + serv->sent_saslauth = FALSE; - user = (((ircnet *)serv->network)->user) - ? (((ircnet *)serv->network)->user) : prefs.hex_irc_user_name; - - EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLAUTH, serv->server_session, user, NULL, - NULL, NULL, 0, tags_data->timestamp); +#ifdef USE_OPENSSL + if (serv->loginmethod == LOGIN_SASLEXTERNAL) + { + serv->sasl_mech = MECH_EXTERNAL; + tcp_send_len (serv, "AUTHENTICATE EXTERNAL\r\n", 23); + } + else + { + /* default to most secure, it will fallback if not supported */ + serv->sasl_mech = MECH_AES; + tcp_send_len (serv, "AUTHENTICATE DH-AES\r\n", 21); + } +#else + serv->sasl_mech = MECH_PLAIN; tcp_send_len (serv, "AUTHENTICATE PLAIN\r\n", 20); - - pass = encode_sasl_pass (user, serv->password); - tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass); - free (pass); +#endif } } @@ -1678,9 +1690,9 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str, } /* if the SASL password is set AND auth mode is set to SASL, request SASL auth */ - if (serv->loginmethod == LOGIN_SASL - && strcmp (extension, "sasl") == 0 - && strlen (serv->password) != 0) + if (!strcmp (extension, "sasl") + && ((serv->loginmethod == LOGIN_SASL && strlen (serv->password) != 0) + || (serv->loginmethod == LOGIN_SASLEXTERNAL && serv->have_cert))) { strcat (buffer, "sasl "); want_cap = 1; @@ -1701,6 +1713,7 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str, if (!want_sasl) { /* if we use SASL, CAP END is dealt via raw numerics */ + serv->sent_capend = TRUE; tcp_send_len (serv, "CAP END\r\n", 9); } } @@ -1708,6 +1721,7 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str, void inbound_cap_nak (server *serv, const message_tags_data *tags_data) { + serv->sent_capend = TRUE; tcp_send_len (serv, "CAP END\r\n", 9); } @@ -1718,3 +1732,69 @@ inbound_cap_list (server *serv, char *nick, char *extensions, EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPACK, serv->server_session, nick, extensions, NULL, NULL, 0, tags_data->timestamp); } + +static const char *sasl_mechanisms[] = +{ + "PLAIN", + "DH-BLOWFISH", + "DH-AES", + "EXTERNAL" +}; + +void +inbound_sasl_authenticate (server *serv, char *data) +{ + char *user, *pass = NULL; + const char *mech = sasl_mechanisms[serv->sasl_mech]; + + user = (((ircnet*)serv->network)->user) + ? (((ircnet*)serv->network)->user) : prefs.hex_irc_user_name; + + switch (serv->sasl_mech) + { + case MECH_PLAIN: + pass = encode_sasl_pass_plain (user, serv->password); + break; +#ifdef USE_OPENSSL + case MECH_BLOWFISH: + pass = encode_sasl_pass_blowfish (user, serv->password, data); + break; + case MECH_AES: + pass = encode_sasl_pass_aes (user, serv->password, data); + break; + case MECH_EXTERNAL: + pass = g_strdup ("+"); + break; +#endif + } + + if (pass == NULL) + { + /* something went wrong abort */ + serv->sent_saslauth = TRUE; /* prevent trying PLAIN */ + tcp_sendf (serv, "AUTHENTICATE *\r\n"); + return; + } + + serv->sent_saslauth = TRUE; + tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass); + g_free (pass); + + + EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLAUTH, serv->server_session, user, (char*)mech, + NULL, NULL, 0, 0); +} + +int +inbound_sasl_error (server *serv) +{ + /* If server sent 904 before we sent password, + * mech not support so fallback to next mech */ + if (!serv->sent_saslauth && serv->sasl_mech != MECH_EXTERNAL && serv->sasl_mech != MECH_PLAIN) + { + serv->sasl_mech -= 1; + tcp_sendf (serv, "AUTHENTICATE %s\r\n", sasl_mechanisms[serv->sasl_mech]); + return 1; + } + return 0; +} diff --git a/src/common/inbound.h b/src/common/inbound.h index cbb04890..40eeb8f6 100644 --- a/src/common/inbound.h +++ b/src/common/inbound.h @@ -95,6 +95,8 @@ void inbound_cap_ls (server *serv, char *nick, char *extensions, void inbound_cap_nak (server *serv, const message_tags_data *tags_data); void inbound_cap_list (server *serv, char *nick, char *extensions, const message_tags_data *tags_data); +void inbound_sasl_authenticate (server *serv, char *data); +int inbound_sasl_error (server *serv); void do_dns (session *sess, char *nick, char *host, const message_tags_data *tags_data); gboolean alert_match_word (char *word, char *masks); diff --git a/src/common/modes.c b/src/common/modes.c index 420f3e8c..416805c3 100644 --- a/src/common/modes.c +++ b/src/common/modes.c @@ -848,7 +848,7 @@ inbound_005 (server * serv, char *word[], const message_tags_data *tags_data) serv->p_cmp = (void *)g_ascii_strcasecmp; } else if (strncmp (word[w], "CHARSET=", 8) == 0) { - if (g_ascii_strcasecmp (word[w] + 8, "UTF-8") == 0) + if (g_ascii_strncasecmp (word[w] + 8, "UTF-8", 5) == 0) { server_set_encoding (serv, "UTF-8"); } diff --git a/src/common/notify.c b/src/common/notify.c index 944d826c..f1439140 100644 --- a/src/common/notify.c +++ b/src/common/notify.c @@ -284,6 +284,70 @@ notify_set_online (server * serv, char *nick, notify_announce_online (serv, servnot, nick, tags_data); } +/* monitor can send lists for numeric 730/731 */ + +void +notify_set_offline_list (server * serv, char *users, int quiet, + const message_tags_data *tags_data) +{ + struct notify_per_server *servnot; + char nick[NICKLEN]; + char *token, *chr; + int pos; + + token = strtok (users, ","); + while (token != NULL) + { + chr = strchr (token, '!'); + if (!chr) + goto end; + + pos = chr - token; + if (pos + 1 >= sizeof(nick)) + goto end; + + memset (nick, 0, sizeof(nick)); + strncpy (nick, token, pos); + + servnot = notify_find (serv, nick); + if (servnot) + notify_announce_offline (serv, servnot, nick, quiet, tags_data); +end: + token = strtok (NULL, ","); + } +} + +void +notify_set_online_list (server * serv, char *users, + const message_tags_data *tags_data) +{ + struct notify_per_server *servnot; + char nick[NICKLEN]; + char *token, *chr; + int pos; + + token = strtok (users, ","); + while (token != NULL) + { + chr = strchr (token, '!'); + if (!chr) + goto end; + + pos = chr - token; + if (pos + 1 >= sizeof(nick)) + goto end; + + memset (nick, 0, sizeof(nick)); + strncpy (nick, token, pos); + + servnot = notify_find (serv, nick); + if (servnot) + notify_announce_online (serv, servnot, nick, tags_data); +end: + token = strtok (NULL, ","); + } +} + static void notify_watch (server * serv, char *nick, int add) { diff --git a/src/common/notify.h b/src/common/notify.h index 4a6ffb35..5bf43410 100644 --- a/src/common/notify.h +++ b/src/common/notify.h @@ -47,6 +47,11 @@ void notify_set_online (server * serv, char *nick, const message_tags_data *tags_data); void notify_set_offline (server * serv, char *nick, int quiet, const message_tags_data *tags_data); +/* the MONITOR stuff */ +void notify_set_online_list (server * serv, char *users, + const message_tags_data *tags_data); +void notify_set_offline_list (server * serv, char *users, int quiet, + const message_tags_data *tags_data); void notify_send_watches (server * serv); /* the general stuff */ diff --git a/src/common/outbound.c b/src/common/outbound.c index 9ddacb75..a35ba429 100644 --- a/src/common/outbound.c +++ b/src/common/outbound.c @@ -487,19 +487,19 @@ create_mask (session * sess, char *mask, char *mode, char *typestr, int deop) switch (type) { case 0: - snprintf (buf, sizeof (buf), "%s%s *!*@%s.*", mode, p2, domain); + snprintf (buf, sizeof (buf), "%s %s *!*@%s.*", mode, p2, domain); break; case 1: - snprintf (buf, sizeof (buf), "%s%s *!*@%s", mode, p2, fullhost); + snprintf (buf, sizeof (buf), "%s %s *!*@%s", mode, p2, fullhost); break; case 2: - snprintf (buf, sizeof (buf), "%s%s *!%s@%s.*", mode, p2, username, domain); + snprintf (buf, sizeof (buf), "%s %s *!%s@%s.*", mode, p2, username, domain); break; case 3: - snprintf (buf, sizeof (buf), "%s%s *!%s@%s", mode, p2, username, fullhost); + snprintf (buf, sizeof (buf), "%s %s *!%s@%s", mode, p2, username, fullhost); break; } } else @@ -507,19 +507,19 @@ create_mask (session * sess, char *mask, char *mode, char *typestr, int deop) switch (type) { case 0: - snprintf (buf, sizeof (buf), "%s%s *!*@*%s", mode, p2, domain); + snprintf (buf, sizeof (buf), "%s %s *!*@*%s", mode, p2, domain); break; case 1: - snprintf (buf, sizeof (buf), "%s%s *!*@%s", mode, p2, fullhost); + snprintf (buf, sizeof (buf), "%s %s *!*@%s", mode, p2, fullhost); break; case 2: - snprintf (buf, sizeof (buf), "%s%s *!%s@*%s", mode, p2, username, domain); + snprintf (buf, sizeof (buf), "%s %s *!%s@*%s", mode, p2, username, domain); break; case 3: - snprintf (buf, sizeof (buf), "%s%s *!%s@%s", mode, p2, username, fullhost); + snprintf (buf, sizeof (buf), "%s %s *!%s@%s", mode, p2, username, fullhost); break; } } @@ -3498,6 +3498,39 @@ cmd_unload (struct session *sess, char *tbuf, char *word[], char *word_eol[]) return FALSE; } +static int +cmd_reload (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ +#ifdef USE_PLUGIN + int len, by_file = FALSE; + + len = strlen (word[2]); +#ifdef WIN32 + if (len > 4 && g_ascii_strcasecmp (word[2] + len - 4, ".dll") == 0) +#else +#if defined(__hpux) + if (len > 3 && g_ascii_strcasecmp (word[2] + len - 3, ".sl") == 0) +#else + if (len > 3 && g_ascii_strcasecmp (word[2] + len - 3, ".so") == 0) +#endif +#endif + by_file = TRUE; + + switch (plugin_reload (sess, word[2], by_file)) + { + case 0: /* error */ + PrintText (sess, _("No such plugin found.\n")); + break; + case 1: /* success */ + return TRUE; + case 2: /* fake plugin, we know it exists but scripts should handle it. */ + return TRUE; + } +#endif + + return FALSE; +} + static server * find_server_from_hostname (char *hostname) { @@ -3782,8 +3815,8 @@ const struct commands xc_cmds[] = { N_("BAN <mask> [<bantype>], bans everyone matching the mask from the current channel. If they are already on the channel this doesn't kick them (needs chanop)")}, {"CHANOPT", cmd_chanopt, 0, 0, 1, N_("CHANOPT [-quiet] <variable> [<value>]")}, {"CHARSET", cmd_charset, 0, 0, 1, N_("CHARSET [<encoding>], get or set the encoding used for the current connection")}, - {"CLEAR", cmd_clear, 0, 0, 1, N_("CLEAR [ALL|HISTORY], Clears the current text window or command history")}, - {"CLOSE", cmd_close, 0, 0, 1, N_("CLOSE, Closes the current window/tab")}, + {"CLEAR", cmd_clear, 0, 0, 1, N_("CLEAR [ALL|HISTORY|[-]<amount>], Clears the current text window or command history")}, + {"CLOSE", cmd_close, 0, 0, 1, N_("CLOSE [-m], Closes the current window/tab or all queries")}, {"COUNTRY", cmd_country, 0, 0, 1, N_("COUNTRY [-s] <code|wildcard>, finds a country code, eg: au = australia")}, @@ -3883,7 +3916,7 @@ const struct commands xc_cmds[] = { {"MODE", cmd_mode, 1, 0, 1, 0}, {"MOP", cmd_mop, 1, 1, 1, N_("MOP, Mass op's all users in the current channel (needs chanop)")}, - {"MSG", cmd_msg, 0, 0, 1, N_("MSG <nick> <message>, sends a private message")}, + {"MSG", cmd_msg, 0, 0, 1, N_("MSG <nick> <message>, sends a private message, message \".\" to send to last nick or prefix with \"=\" for dcc chat")}, {"NAMES", cmd_names, 1, 0, 1, N_("NAMES, Lists the nicks on the current channel")}, @@ -3918,7 +3951,7 @@ const struct commands xc_cmds[] = { N_("RECONNECT [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")}, #endif {"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to HexChat, as if it was received from the IRC server")}, - + {"RELOAD", cmd_reload, 0, 0, 1, N_("RELOAD <name>, reloads a plugin or script")}, {"SAY", cmd_say, 0, 0, 1, N_("SAY <text>, sends the text to the object in the current window")}, {"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")}, @@ -4551,7 +4584,6 @@ handle_command (session *sess, char *cmd, int check_spch) char tbuf_static[TBUFSIZE]; char *pdibuf; char *tbuf; - char *cmd_vars; int len; int ret = TRUE; @@ -4563,9 +4595,7 @@ handle_command (session *sess, char *cmd, int check_spch) command_level++; /* anything below MUST DEC command_level before returning */ - cmd_vars = command_insert_vars (sess, cmd); - - len = strlen (cmd_vars); + len = strlen (cmd); if (len >= sizeof (pdibuf_static)) { pdibuf = malloc (len + 1); @@ -4585,7 +4615,7 @@ handle_command (session *sess, char *cmd, int check_spch) } /* split the text into words and word_eol */ - process_data_init (pdibuf, cmd_vars, word, word_eol, TRUE, TRUE); + process_data_init (pdibuf, cmd, word, word_eol, TRUE, TRUE); /* ensure an empty string at index 32 for cmd_deop etc */ /* (internal use only, plugins can still only read 1-31). */ @@ -4596,12 +4626,12 @@ handle_command (session *sess, char *cmd, int check_spch) /* redo it without quotes processing, for some commands like /JOIN */ if (int_cmd && !int_cmd->handle_quotes) { - process_data_init (pdibuf, cmd_vars, word, word_eol, FALSE, FALSE); + process_data_init (pdibuf, cmd, word, word_eol, FALSE, FALSE); } if (check_spch && prefs.hex_input_perc_color) { - check_special_chars (cmd_vars, prefs.hex_input_perc_ascii); + check_special_chars (cmd, prefs.hex_input_perc_ascii); } if (plugin_emit_command (sess, word[1], word, word_eol)) @@ -4668,7 +4698,7 @@ handle_command (session *sess, char *cmd, int check_spch) } else { - sess->server->p_raw (sess->server, cmd_vars); + sess->server->p_raw (sess->server, cmd); } } @@ -4685,8 +4715,6 @@ xit: free (tbuf); } - g_free (cmd_vars); - return ret; } diff --git a/src/common/outbound.h b/src/common/outbound.h index 7dc1ba97..81a98666 100644 --- a/src/common/outbound.h +++ b/src/common/outbound.h @@ -25,6 +25,7 @@ extern GSList *menu_list; int auto_insert (char *dest, int destlen, unsigned char *src, char *word[], char *word_eol[], char *a, char *c, char *d, char *e, char *h, char *n, char *s, char *u); +char *command_insert_vars (session *sess, char *cmd); int handle_command (session *sess, char *cmd, int check_spch); void process_data_init (char *buf, char *cmd, char *word[], char *word_eol[], gboolean handle_quotes, gboolean allow_escape_quotes); void handle_multiline (session *sess, char *cmd, int history, int nocommand); diff --git a/src/common/plugin.c b/src/common/plugin.c index 50157ea1..d186679a 100644 --- a/src/common/plugin.c +++ b/src/common/plugin.c @@ -512,6 +512,44 @@ plugin_auto_load (session *sess) g_free (sub_dir); } +int +plugin_reload (session *sess, char *name, int by_filename) +{ + GSList *list; + char *filename; + char *ret; + hexchat_plugin *pl; + + list = plugin_list; + while (list) + { + pl = list->data; + /* static-plugins (plugin-timer.c) have a NULL filename */ + if ((by_filename && pl->filename && g_ascii_strcasecmp (name, pl->filename) == 0) || + (by_filename && pl->filename && g_ascii_strcasecmp (name, file_part (pl->filename)) == 0) || + (!by_filename && g_ascii_strcasecmp (name, pl->name) == 0)) + { + /* statically linked plugins have a NULL filename */ + if (pl->filename != NULL && !pl->fake) + { + filename = g_strdup (pl->filename); + plugin_free (pl, TRUE, FALSE); + ret = plugin_load (sess, filename, NULL); + g_free (filename); + if (ret == NULL) + return 1; + else + return 0; + } + else + return 2; + } + list = list->next; + } + + return 0; +} + #endif static GSList * @@ -1343,7 +1381,7 @@ hexchat_list_fields (hexchat_plugin *ph, const char *name) }; static const char * const channels_fields[] = { - "schannel", "schantypes", "pcontext", "iflags", "iid", "ilag", "imaxmodes", + "schannel", "schannelkey", "schantypes", "pcontext", "iflags", "iid", "ilag", "imaxmodes", "snetwork", "snickmodes", "snickprefixes", "iqueue", "sserver", "itype", "iusers", NULL }; @@ -1438,6 +1476,8 @@ hexchat_list_str (hexchat_plugin *ph, hexchat_list *xlist, const char *name) { case 0x2c0b7d03: /* channel */ return ((session *)data)->channel; + case 0x8cea5e7c: /* channelkey */ + return ((session *)data)->channelkey; case 0x577e0867: /* chantypes */ return ((session *)data)->server->chantypes; case 0x38b735af: /* context */ diff --git a/src/common/plugin.h b/src/common/plugin.h index f75639e9..ee9da8c1 100644 --- a/src/common/plugin.h +++ b/src/common/plugin.h @@ -164,6 +164,7 @@ struct _hexchat_plugin #endif char *plugin_load (session *sess, char *filename, char *arg); +int plugin_reload (session *sess, char *name, int by_filename); void plugin_add (session *sess, char *filename, void *handle, void *init_func, void *deinit_func, char *arg, int fake); int plugin_kill (char *name, int by_filename); void plugin_kill_all (void); diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c index 128c0c85..250a2937 100644 --- a/src/common/proto-irc.c +++ b/src/common/proto-irc.c @@ -45,11 +45,11 @@ #include "url.h" #include "servlist.h" - static void irc_login (server *serv, char *user, char *realname) { tcp_sendf (serv, "CAP LS\r\n"); /* start with CAP LS as Charybdis sasl.txt suggests */ + serv->sent_capend = FALSE; /* track if we have finished */ if (serv->password[0] && serv->loginmethod == LOGIN_PASS) { @@ -465,8 +465,6 @@ process_numeric (session * sess, int n, server *serv = sess->server; /* show whois is the server tab */ session *whois_sess = serv->server_session; - - char *ex; /* unless this setting is on */ if (prefs.hex_irc_whois_front) @@ -678,6 +676,15 @@ process_numeric (session * sess, int n, handle_mode (serv, word, word_eol, "", TRUE, tags_data); break; + case 328: /* channel url */ + sess = find_channel (serv, word[4]); + if (sess) + { + EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANURL, sess, word[4], word[5] + 1, + NULL, NULL, 0, tags_data->timestamp); + } + break; + case 329: sess = find_channel (serv, word[4]); if (sess) @@ -933,17 +940,11 @@ process_numeric (session * sess, int n, break; case 730: /* RPL_MONONLINE */ - ex = strchr (word[4], '!'); /* only send the nick */ - if (ex) - ex[0] = 0; - notify_set_online (serv, word[4] + 1, tags_data); + notify_set_online_list (serv, word[4] + 1, tags_data); break; case 731: /* RPL_MONOFFLINE */ - ex = strchr (word[4], '!'); /* only send the nick */ - if (ex) - ex[0] = 0; - notify_set_offline (serv, word[4] + 1, FALSE, tags_data); + notify_set_offline_list (serv, word[4] + 1, FALSE, tags_data); break; case 900: /* successful SASL 'logged in as ' */ @@ -952,14 +953,20 @@ process_numeric (session * sess, int n, tags_data->timestamp); break; case 903: /* successful SASL auth */ - case 904: /* aborted SASL auth */ + case 904: /* failed SASL auth */ + if (inbound_sasl_error (serv)) + break; /* might retry */ case 905: /* failed SASL auth */ - case 906: /* registration completes before SASL auth */ + case 906: /* aborted */ case 907: /* attempting to re-auth after a successful auth */ EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLRESPONSE, serv->server_session, word[1], word[2], word[3], ++word_eol[4], 0, tags_data->timestamp); - tcp_send_len (serv, "CAP END\r\n", 9); + if (!serv->sent_capend) + { + serv->sent_capend = TRUE; + tcp_send_len (serv, "CAP END\r\n", 9); + } break; default: @@ -1144,7 +1151,6 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[], case WORDL('N','O','T','I'): { int id = FALSE; /* identified */ - char *response; text = word_eol[4]; if (*text == ':') @@ -1152,9 +1158,10 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[], text++; } +#ifdef USE_OPENSSL if (!strncmp (text, "CHALLENGE ", 10)) /* QuakeNet CHALLENGE upon our request */ { - response = challengeauth_response (((ircnet *)serv->network)->user ? ((ircnet *)serv->network)->user : prefs.hex_irc_user_name, serv->password, word[5]); + char *response = challengeauth_response (((ircnet *)serv->network)->user ? ((ircnet *)serv->network)->user : prefs.hex_irc_user_name, serv->password, word[5]); tcp_sendf (serv, "PRIVMSG %s :CHALLENGEAUTH %s %s %s\r\n", CHALLENGEAUTH_NICK, @@ -1165,6 +1172,7 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[], g_free (response); return; /* omit the CHALLENGE <hash> ALGOS message */ } +#endif if (serv->have_idmsg) { @@ -1320,8 +1328,9 @@ process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol tags_data->timestamp); return; } - if (!strncmp (buf, "AUTHENTICATE +", 14)) /* omit SASL "empty" responses */ + if (!strncmp (buf, "AUTHENTICATE", 12)) { + inbound_sasl_authenticate (sess->server, word_eol[2]); return; } diff --git a/src/common/server.c b/src/common/server.c index e59a7ee3..eea7ce08 100644 --- a/src/common/server.c +++ b/src/common/server.c @@ -1049,7 +1049,8 @@ server_cleanup (server * serv) #ifdef USE_OPENSSL if (serv->ssl) { - _SSL_close (serv->ssl); + SSL_shutdown (serv->ssl); + SSL_free (serv->ssl); serv->ssl = NULL; } #endif @@ -1705,18 +1706,25 @@ server_connect (server *serv, char *hostname, int port, int no_login) if (serv->use_ssl) { char *cert_file; + serv->have_cert = FALSE; /* first try network specific cert/key */ cert_file = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "certs" G_DIR_SEPARATOR_S "%s.pem", get_xdir (), server_get_network (serv, TRUE)); if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1) - SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM); + { + if (SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1) + serv->have_cert = TRUE; + } else { /* if that doesn't exist, try <config>/certs/client.pem */ cert_file = g_build_filename (get_xdir (), "certs", "client.pem", NULL); if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1) - SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM); + { + if (SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1) + serv->have_cert = TRUE; + } } g_free (cert_file); } diff --git a/src/common/servlist.c b/src/common/servlist.c index ceee3455..3ccd81a6 100644 --- a/src/common/servlist.c +++ b/src/common/servlist.c @@ -251,11 +251,13 @@ static const struct defaultserver def[] = {0, "irc.ggn.net"}, {0, "irc.vendetta.com"}, - {"freenode", 0, "#hexchat", 0, LOGIN_SASL}, + {"freenode", 0, 0, 0, LOGIN_SASL}, #ifdef USE_OPENSSL {0, "chat.freenode.net/+6697"}, #endif {0, "chat.freenode.net"}, + /* irc. points to chat. but many users and urls still reference it */ + {0, "irc.freenode.net"}, /* {"Freeworld", 0}, {0, "kabel.freeworld.nu"}, @@ -418,6 +420,9 @@ static const struct defaultserver def[] = /* {"NullusNet", 0}, {0, "irc.nullus.net"},*/ + + {"ObsidianIRC", 0}, + {0, "irc.obsidianirc.net"}, {"Oceanius", 0}, {0, "irc.oceanius.com"}, @@ -535,7 +540,7 @@ static const struct defaultserver def[] = {0, "eu.spidernet.org"}, {0, "irc.spidernet.org"},*/ - {"SpotChat", 0}, + {"SpotChat", 0, 0, 0, LOGIN_SASL}, #ifdef USE_OPENSSL {0, "irc.spotchat.org/+6697"}, #endif @@ -565,6 +570,9 @@ static const struct defaultserver def[] = #endif {0, "irc.swiftirc.net/6667"}, + {"TinyCrab", 0}, + {0, "irc.tinycrab.net"}, + /* {"TNI3", 0}, {0, "irc.tni3.com"},*/ diff --git a/src/common/servlist.h b/src/common/servlist.h index 45b6dad6..6d6f1bd3 100644 --- a/src/common/servlist.h +++ b/src/common/servlist.h @@ -79,6 +79,7 @@ extern GSList *network_list; #define LOGIN_PASS 7 #define LOGIN_CHALLENGEAUTH 8 #define LOGIN_CUSTOM 9 +#define LOGIN_SASLEXTERNAL 10 #define CHALLENGEAUTH_ALGO "HMAC-SHA-256" #define CHALLENGEAUTH_NICK "Q@CServe.quakenet.org" diff --git a/src/common/text.c b/src/common/text.c index b825faba..e0cdb5ee 100644 --- a/src/common/text.c +++ b/src/common/text.c @@ -1297,7 +1297,8 @@ static char * const pevt_generic_channel_help[] = { }; static char * const pevt_saslauth_help[] = { - N_("Username") + N_("Username"), + N_("Mechanism") }; static char * const pevt_saslresponse_help[] = { @@ -1359,6 +1360,11 @@ static char * const pevt_chanmodes_help[] = { N_("Modes string"), }; +static char * const pevt_chanurl_help[] = { + N_("Channel Name"), + N_("URL"), +}; + static char * const pevt_rawmodes_help[] = { N_("Nickname"), N_("Modes string"), diff --git a/src/common/textevents.in b/src/common/textevents.in index 1f86d90b..3631e363 100644 --- a/src/common/textevents.in +++ b/src/common/textevents.in @@ -202,6 +202,12 @@ pevt_chanunquiet_help %C22*%O$t%C26$1%O removes quiet on %C18$2%O 2 +Channel Url +XP_TE_CHANURL +pevt_chanurl_help +%C22*%O$tChannel %C22$1%O url: %C24$2 +2 + Channel Voice XP_TE_CHANVOICE pevt_chanvoice_help @@ -487,7 +493,7 @@ pevt_invited_help Join XP_TE_JOIN pevt_join_help -%C23*$t$1 ($3) has joined +%C23*$t$1 ($3%C23) has joined 3 Keyword @@ -619,13 +625,13 @@ n0 Part XP_TE_PART pevt_part_help -%C24*$t$1 ($2) has left +%C24*$t$1 ($2%C24) has left 3 Part with Reason XP_TE_PARTREASON pevt_partreason_help -%C24*$t$1 ($2) has left ($4) +%C24*$t$1 ($2%C24) has left ($4) 4 Ping Reply @@ -697,8 +703,8 @@ pevt_resolvinguser_help SASL Authenticating XP_TE_SASLAUTH pevt_saslauth_help -%C23*%O$tAuthenticating via SASL as %C18$1%O -1 +%C23*%O$tAuthenticating via SASL as %C18$1%O (%C24$2%O) +2 SASL Response XP_TE_SASLRESPONSE diff --git a/src/common/url.c b/src/common/url.c index 892441c8..57d8d88b 100644 --- a/src/common/url.c +++ b/src/common/url.c @@ -32,15 +32,22 @@ void *url_tree = NULL; GTree *url_btree = NULL; -static int do_an_re (const char *word, int *start, int *end, int *type); -static GRegex *re_url (void); -static GRegex *re_host (void); -static GRegex *re_host6 (void); -static GRegex *re_email (void); -static GRegex *re_nick (void); -static GRegex *re_channel (void); -static GRegex *re_path (void); - +static gboolean regex_match (const GRegex *re, const char *word, + int *start, int *end); +static const GRegex *re_url (void); +static const GRegex *re_host (void); +static const GRegex *re_host6 (void); +static const GRegex *re_email (void); +static const GRegex *re_nick (void); +static const GRegex *re_channel (void); +static const GRegex *re_path (void); +static gboolean match_nick (const char *word, int *start, int *end); +static gboolean match_channel (const char *word, int *start, int *end); +static gboolean match_email (const char *word, int *start, int *end); +static gboolean match_url (const char *word, int *start, int *end); +static gboolean match_host (const char *word, int *start, int *end); +static gboolean match_host6 (const char *word, int *start, int *end); +static gboolean match_path (const char *word, int *start, int *end); static int url_free (char *url, void *data) @@ -189,50 +196,111 @@ static int laststart = 0; static int lastend = 0; static int lasttype = 0; -static int -strchrs (char c, char *s) -{ - while (*s) - if (c == *s++) - return TRUE; - return FALSE; -} +#define NICKPRE "~+!@%&" +#define CHANPRE "#&!+" -#define NICKPRE "~+!@%%&" int url_check_word (const char *word) { + struct { + gboolean (*match) (const char *word, int *start, int *end); + int type; + } m[] = { + { match_url, WORD_URL }, + { match_email, WORD_EMAIL }, + { match_nick, WORD_NICK }, + { match_channel, WORD_CHANNEL }, + { match_host6, WORD_HOST6 }, + { match_host, WORD_HOST }, + { match_path, WORD_PATH }, + { NULL, 0} + }; + int i; + laststart = lastend = lasttype = 0; - if (do_an_re (word, &laststart, &lastend, &lasttype)) - { - switch (lasttype) + + for (i = 0; m[i].match; i++) + if (m[i].match (word, &laststart, &lastend)) { - char *str; - - case WORD_NICK: - if (strchrs (word[laststart], NICKPRE)) - laststart++; - str = g_strndup (&word[laststart], lastend - laststart); - if (!userlist_find (current_sess, str)) - lasttype = 0; - g_free (str); - return lasttype; - case WORD_EMAIL: - if (!isalnum (word[laststart])) - laststart++; - /* Fall through */ - case WORD_URL: - case WORD_HOST: - case WORD_HOST6: - case WORD_CHANNEL: - case WORD_PATH: - return lasttype; - default: - return 0; /* Should not occur */ + lasttype = m[i].type; + return lasttype; } + + return 0; +} + +static gboolean +match_nick (const char *word, int *start, int *end) +{ + const server *serv = current_sess->server; + const char *nick_prefixes = serv ? serv->nick_prefixes : NICKPRE; + char *str; + + if (!regex_match (re_nick (), word, start, end)) + return FALSE; + + /* ignore matches with prefixes that the server doesn't use */ + if (strchr (NICKPRE, word[*start]) + && !strchr (nick_prefixes, word[*start])) + return FALSE; + + /* nick prefix is not part of the matched word */ + if (strchr (nick_prefixes, word[*start])) + (*start)++; + + str = g_strndup (&word[*start], *end - *start); + + if (!userlist_find (current_sess, str)) + { + g_free (str); + return FALSE; } - else - return 0; + + g_free (str); + + return TRUE; +} + +static gboolean +match_channel (const char *word, int *start, int *end) +{ + const server *serv = current_sess->server; + const char *chan_prefixes = serv ? serv->chantypes : CHANPRE; + + if (!regex_match (re_channel (), word, start, end)) + return FALSE; + + return strchr (chan_prefixes, word[*start]) != NULL; +} + +static gboolean +match_email (const char *word, int *start, int *end) +{ + return regex_match (re_email (), word, start, end); +} + +static gboolean +match_url (const char *word, int *start, int *end) +{ + return regex_match (re_url (), word, start, end); +} + +static gboolean +match_host (const char *word, int *start, int *end) +{ + return regex_match (re_host (), word, start, end); +} + +static gboolean +match_host6 (const char *word, int *start, int *end) +{ + return regex_match (re_host6 (), word, start, end); +} + +static gboolean +match_path (const char *word, int *start, int *end) +{ + return regex_match (re_path (), word, start, end); } /* List of IRC commands for which contents (and thus possible URLs) @@ -307,46 +375,28 @@ url_last (int *lstart, int *lend) return lasttype; } -static int -do_an_re(const char *word, int *start, int *end, int *type) +static gboolean +regex_match (const GRegex *re, const char *word, int *start, int *end) { - typedef struct func_s { - GRegex *(*fn)(void); - int type; - } func_t; - func_t funcs[] = - { - { re_url, WORD_URL }, - { re_email, WORD_EMAIL }, - { re_channel, WORD_CHANNEL }, - { re_host6, WORD_HOST6 }, - { re_host, WORD_HOST }, - { re_path, WORD_PATH }, - { re_nick, WORD_NICK } - }; - GMatchInfo *gmi; - int k; - for (k = 0; k < sizeof funcs / sizeof (func_t); k++) + g_regex_match (re, word, 0, &gmi); + + if (!g_match_info_matches (gmi)) { - g_regex_match (funcs[k].fn(), word, 0, &gmi); - if (!g_match_info_matches (gmi)) - { - g_match_info_free (gmi); - continue; - } - while (g_match_info_matches (gmi)) - { - g_match_info_fetch_pos (gmi, 0, start, end); - g_match_info_next (gmi, NULL); - } g_match_info_free (gmi); - *type = funcs[k].type; - return TRUE; + return FALSE; } - - return FALSE; + + while (g_match_info_matches (gmi)) + { + g_match_info_fetch_pos (gmi, 0, start, end); + g_match_info_next (gmi, NULL); + } + + g_match_info_free (gmi); + + return TRUE; } /* Miscellaneous description --- */ @@ -376,7 +426,7 @@ make_re (char *grist) /* HOST description --- */ /* (see miscellaneous above) */ -static GRegex * +static const GRegex * re_host (void) { static GRegex *host_ret; @@ -384,7 +434,7 @@ re_host (void) if (host_ret) return host_ret; - grist = g_strdup_printf ( + grist = g_strdup ( "(" "(" HOST_URL PORT ")|(" HOST ")" ")" @@ -393,7 +443,7 @@ re_host (void) return host_ret; } -static GRegex * +static const GRegex * re_host6 (void) { static GRegex *host6_ret; @@ -401,7 +451,7 @@ re_host6 (void) if (host6_ret) return host6_ret; - grist = g_strdup_printf ( + grist = g_strdup ( "(" "(" IPV6ADDR ")|(" "\\[" IPV6ADDR "\\]" PORT ")" ")" @@ -489,7 +539,7 @@ struct { NULL, "", 0} }; -static GRegex * +static const GRegex * re_url (void) { static GRegex *url_ret = NULL; @@ -546,7 +596,7 @@ re_url (void) /* EMAIL description --- */ #define EMAIL "[a-z][-_a-z0-9]+@" "(" HOST_URL ")" -static GRegex * +static const GRegex * re_email (void) { static GRegex *email_ret; @@ -554,7 +604,7 @@ re_email (void) if (email_ret) return email_ret; - grist = g_strdup_printf ( + grist = g_strdup ( "(" EMAIL ")" @@ -577,12 +627,12 @@ re_email (void) /* Rationale is that do_an_re() above will anyway look up what */ /* we find, and that WORD_NICK is the last item in the array */ /* that do_an_re() runs through. */ -#define NICK0 "[" NICKPRE "]?[" NICKLET NICKDIG NICKSPE "]" +#define NICK0 "^[" NICKPRE "]?[" NICKLET NICKDIG NICKSPE "]" #endif #define NICK1 "[" NICKHYP NICKLET NICKDIG NICKSPE "]*" #define NICK NICK0 NICK1 -static GRegex * +static const GRegex * re_nick (void) { static GRegex *nick_ret; @@ -590,7 +640,7 @@ re_nick (void) if (nick_ret) return nick_ret; - grist = g_strdup_printf ( + grist = g_strdup ( "(" NICK ")" @@ -600,9 +650,9 @@ re_nick (void) } /* CHANNEL description --- */ -#define CHANNEL "#[^ \t\a,:]+" +#define CHANNEL "[" CHANPRE "][^ \t\a,]+(?:,[" CHANPRE "][^ \t\a,]+)*" -static GRegex * +static const GRegex * re_channel (void) { static GRegex *channel_ret; @@ -610,7 +660,7 @@ re_channel (void) if (channel_ret) return channel_ret; - grist = g_strdup_printf ( + grist = g_strdup ( "(" CHANNEL ")" @@ -628,7 +678,7 @@ re_channel (void) #define FS_PATH "^(/|\\./|\\.\\./).*" #endif -static GRegex * +static const GRegex * re_path (void) { static GRegex *path_ret; @@ -636,7 +686,7 @@ re_path (void) if (path_ret) return path_ret; - grist = g_strdup_printf ( + grist = g_strdup ( "(" FS_PATH ")" diff --git a/src/common/util.c b/src/common/util.c index 52464621..9771b1f6 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -58,6 +58,17 @@ #include <socks.h> #endif +/* SASL mechanisms */ +#ifdef USE_OPENSSL +#include <openssl/bn.h> +#include <openssl/rand.h> +#include <openssl/blowfish.h> +#include <openssl/aes.h> +#ifndef WIN32 +#include <netinet/in.h> +#endif +#endif + #ifndef HAVE_SNPRINTF #define snprintf g_snprintf #endif @@ -1929,7 +1940,7 @@ get_subdirs (const char *path) } char * -encode_sasl_pass (char *user, char *pass) +encode_sasl_pass_plain (char *user, char *pass) { int authlen; char *buffer; @@ -1944,6 +1955,230 @@ encode_sasl_pass (char *user, char *pass) return encoded; } +#ifdef USE_OPENSSL +/* Adapted from ZNC's SASL module */ + +static int +parse_dh (char *str, DH **dh_out, unsigned char **secret_out, int *keysize_out) +{ + DH *dh; + guchar *data, *decoded_data; + guchar *secret; + gsize data_len; + guint size; + guint16 size16; + BIGNUM *pubkey; + gint key_size; + + dh = DH_new(); + data = decoded_data = g_base64_decode (str, &data_len); + if (data_len < 2) + goto fail; + + /* prime number */ + memcpy (&size16, data, sizeof(size16)); + size = ntohs (size16); + data += 2; + data_len -= 2; + + if (size > data_len) + goto fail; + + dh->p = BN_bin2bn (data, size, NULL); + data += size; + + /* Generator */ + if (data_len < 2) + goto fail; + + memcpy (&size16, data, sizeof(size16)); + size = ntohs (size16); + data += 2; + data_len -= 2; + + if (size > data_len) + goto fail; + + dh->g = BN_bin2bn (data, size, NULL); + data += size; + + /* pub key */ + if (data_len < 2) + goto fail; + + memcpy (&size16, data, sizeof(size16)); + size = ntohs(size16); + data += 2; + data_len -= 2; + + pubkey = BN_bin2bn (data, size, NULL); + if (!(DH_generate_key (dh))) + goto fail; + + secret = (unsigned char*)malloc (DH_size(dh)); + key_size = DH_compute_key (secret, pubkey, dh); + if (key_size == -1) + goto fail; + + g_free (decoded_data); + + *dh_out = dh; + *secret_out = secret; + *keysize_out = key_size; + return 1; + +fail: + if (decoded_data) + g_free (decoded_data); + return 0; +} + +char * +encode_sasl_pass_blowfish (char *user, char *pass, char *data) +{ + DH *dh; + char *response, *ret; + unsigned char *secret; + unsigned char *encrypted_pass; + char *plain_pass; + BF_KEY key; + int key_size, length; + int pass_len = strlen (pass) + (8 - (strlen (pass) % 8)); + int user_len = strlen (user); + guint16 size16; + char *in_ptr, *out_ptr; + + if (!parse_dh (data, &dh, &secret, &key_size)) + return NULL; + BF_set_key (&key, key_size, secret); + + encrypted_pass = (guchar*)malloc (pass_len); + memset (encrypted_pass, 0, pass_len); + plain_pass = (char*)malloc (pass_len); + memset (plain_pass, 0, pass_len); + memcpy (plain_pass, pass, pass_len); + out_ptr = (char*)encrypted_pass; + in_ptr = (char*)plain_pass; + + for (length = pass_len; length; length -= 8, in_ptr += 8, out_ptr += 8) + BF_ecb_encrypt ((unsigned char*)in_ptr, (unsigned char*)out_ptr, &key, BF_ENCRYPT); + + /* Create response */ + length = 2 + BN_num_bytes (dh->pub_key) + pass_len + user_len + 1; + response = (char*)malloc (length); + out_ptr = response; + + /* our key */ + size16 = htons ((guint16)BN_num_bytes (dh->pub_key)); + memcpy (out_ptr, &size16, sizeof(size16)); + out_ptr += 2; + BN_bn2bin (dh->pub_key, (guchar*)out_ptr); + out_ptr += BN_num_bytes (dh->pub_key); + + /* username */ + memcpy (out_ptr, user, user_len + 1); + out_ptr += user_len + 1; + + /* pass */ + memcpy (out_ptr, encrypted_pass, pass_len); + + ret = g_base64_encode ((const guchar*)response, length); + + DH_free (dh); + free (plain_pass); + free (encrypted_pass); + free (secret); + free (response); + + return ret; +} + +char * +encode_sasl_pass_aes (char *user, char *pass, char *data) +{ + DH *dh; + AES_KEY key; + char *response = NULL; + char *out_ptr, *ret = NULL; + unsigned char *secret, *ptr; + unsigned char *encrypted_userpass, *plain_userpass; + int key_size, length; + guint16 size16; + unsigned char iv[16], iv_copy[16]; + int user_len = strlen (user) + 1; + int pass_len = strlen (pass) + 1; + int len = user_len + pass_len; + int padlen = 16 - (len % 16); + int userpass_len = len + padlen; + + if (!parse_dh (data, &dh, &secret, &key_size)) + return NULL; + + encrypted_userpass = (guchar*)malloc (userpass_len); + memset (encrypted_userpass, 0, userpass_len); + plain_userpass = (guchar*)malloc (userpass_len); + memset (plain_userpass, 0, userpass_len); + + /* create message */ + /* format of: <username>\0<password>\0<padding> */ + ptr = plain_userpass; + memcpy (ptr, user, user_len); + ptr += user_len; + memcpy (ptr, pass, pass_len); + ptr += pass_len; + if (padlen) + { + /* Padding */ + unsigned char randbytes[16]; + if (!RAND_bytes (randbytes, padlen)) + goto end; + + memcpy (ptr, randbytes, padlen); + } + + if (!RAND_bytes (iv, sizeof (iv))) + goto end; + + memcpy (iv_copy, iv, sizeof(iv)); + + /* Encrypt */ + AES_set_encrypt_key (secret, key_size * 8, &key); + AES_cbc_encrypt(plain_userpass, encrypted_userpass, userpass_len, &key, iv_copy, AES_ENCRYPT); + + /* Create response */ + /* format of: <size pubkey><pubkey><iv (always 16 bytes)><ciphertext> */ + length = 2 + key_size + sizeof(iv) + userpass_len; + response = (char*)malloc (length); + out_ptr = response; + + /* our key */ + size16 = htons ((guint16)key_size); + memcpy (out_ptr, &size16, sizeof(size16)); + out_ptr += 2; + BN_bn2bin (dh->pub_key, (guchar*)out_ptr); + out_ptr += key_size; + + /* iv */ + memcpy (out_ptr, iv, sizeof(iv)); + out_ptr += sizeof(iv); + + /* userpass */ + memcpy (out_ptr, encrypted_userpass, userpass_len); + + ret = g_base64_encode ((const guchar*)response, length); + +end: + DH_free (dh); + free (plain_userpass); + free (encrypted_userpass); + free (secret); + if (response) + free (response); + + return ret; +} +#endif + #ifdef WIN32 int find_font (const char *fontname) @@ -1975,6 +2210,7 @@ find_font (const char *fontname) } #endif +#ifdef USE_OPENSSL static char * str_sha256hash (char *string) { @@ -2050,3 +2286,4 @@ challengeauth_response (char *username, char *password, char *challenge) return (char *) digest; } +#endif diff --git a/src/common/util.h b/src/common/util.h index 0ebd89d4..6b8d359c 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -79,7 +79,9 @@ void canonalize_key (char *key); int portable_mode (); int unity_mode (); GSList *get_subdirs (const char *path); -char *encode_sasl_pass (char *user, char *pass); +char *encode_sasl_pass_plain (char *user, char *pass); +char *encode_sasl_pass_blowfish (char *user, char *pass, char *data); +char *encode_sasl_pass_aes (char *user, char *pass, char *data); char *challengeauth_response (char *username, char *password, char *challenge); #endif diff --git a/src/fe-gtk/Makefile.am b/src/fe-gtk/Makefile.am index 1890edbf..24098ace 100644 --- a/src/fe-gtk/Makefile.am +++ b/src/fe-gtk/Makefile.am @@ -7,7 +7,7 @@ AM_CPPFLAGS = $(GUI_CFLAGS) -DLOCALEDIR=\"$(localedir)\" hexchat_LDADD = ../common/libhexchatcommon.a $(GUI_LIBS) EXTRA_DIST = \ - about.h ascii.h banlist.h chanlist.h chanview.h chanview-tabs.c \ + ascii.h banlist.h chanlist.h chanview.h chanview-tabs.c \ chanview-tree.c custom-list.h editlist.h fe-gtk.h fkeys.h gtkutil.h joind.h \ maingui.h menu.h mmx_cmod.S mmx_cmod.h notifygui.h palette.h pixmaps.h \ plugin-tray.h plugingui.c plugingui.h rawlog.h sexy-iso-codes.h \ @@ -26,7 +26,7 @@ sexy_spell = \ sexy-iso-codes.c sexy-marshal.c sexy-spell-entry.c endif -hexchat_SOURCES = about.c ascii.c banlist.c chanlist.c chanview.c custom-list.c \ +hexchat_SOURCES = ascii.c banlist.c chanlist.c chanview.c custom-list.c \ dccgui.c editlist.c fe-gtk.c fkeys.c gtkutil.c ignoregui.c joind.c menu.c \ maingui.c $(mmx_cmod_S) notifygui.c palette.c pixmaps.c plugin-tray.c $(plugingui_c) \ rawlog.c servlistgui.c setup.c $(sexy_spell) textgui.c \ diff --git a/src/fe-gtk/about.c b/src/fe-gtk/about.c deleted file mode 100644 index c47fba4f..00000000 --- a/src/fe-gtk/about.c +++ /dev/null @@ -1,162 +0,0 @@ -/* X-Chat - * Copyright (C) 1998 Peter Zelezny. - * - * 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 St, Fifth Floor, Boston, MA 02110-1301, USA - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "fe-gtk.h" - -#ifdef USE_XLIB -#include <gdk/gdkx.h> -#endif - -#include "../common/hexchat.h" -#include "../common/util.h" -#include "../common/hexchatc.h" -#include "palette.h" -#include "pixmaps.h" -#include "gtkutil.h" -#include "about.h" - -static GtkWidget *about = 0; - -static int -about_close (void) -{ - about = 0; - return 0; -} - -void -menu_about (GtkWidget * wid, gpointer sess) -{ - GtkWidget *vbox; /* the main vertical box inside the about dialog */ - GtkWidget *hbox_main; /* horizontal box for containing text on the left and logo on the right */ - GtkWidget *vbox_logo; /* vertical box for our logo */ - GtkWidget *vbox_text; /* vertical box for text */ - GtkWidget *label_title; /* label for the main title */ - GtkWidget *label_subtitle; /* label for the subtitle */ - GtkWidget *label_info; /* for the informative text */ - GtkWidget *label_copyright; /* for copyright notices */ - GdkColor color; /* color buffer for our nice paintings */ - char buf[512]; /* text buffer for the labels */ - const char *locale = NULL; - extern GtkWindow *parent_window; /* maingui.c */ - - if (about) - { - gtk_window_present (GTK_WINDOW (about)); - return; - } - - /* general about dialog initialization */ - about = gtk_dialog_new (); - gtk_window_set_position (GTK_WINDOW (about), GTK_WIN_POS_CENTER_ON_PARENT); - gtk_window_set_resizable (GTK_WINDOW (about), FALSE); - gtk_window_set_title (GTK_WINDOW (about), _("About "DISPLAY_NAME)); - if (parent_window) - { - gtk_window_set_transient_for (GTK_WINDOW (about), parent_window); - } - g_signal_connect (G_OBJECT (about), "destroy", G_CALLBACK (about_close), 0); - vbox = GTK_DIALOG (about)->vbox; - - /* main horizontal box, text on the left, logo on the right */ - hbox_main = gtk_hbox_new (FALSE, 0); - gtk_container_add (GTK_CONTAINER (vbox), GTK_WIDGET (hbox_main)); - - /* textbox on the left */ - vbox_text = gtk_vbox_new (FALSE, 0); - gtk_box_pack_start (GTK_BOX (hbox_main), vbox_text, 0, 0, 5); - - /* label for title */ - snprintf (buf, sizeof (buf), "<span size=\"x-large\"><b>"DISPLAY_NAME"</b></span>"); - label_title = gtk_label_new (NULL); - gtk_misc_set_alignment (GTK_MISC (label_title), 0, 0); - gtk_box_pack_start (GTK_BOX (vbox_text), label_title, 0, 0, 10); - color.red = 0xd7d7; - color.green = 0x4343; - color.blue = 0x0404; - gtk_widget_modify_fg (label_title, GTK_STATE_NORMAL, &color); - gtk_label_set_markup (GTK_LABEL (label_title), buf); - - /* label for subtitle */ - snprintf (buf, sizeof (buf), "%s", _("<b>A multiplatform IRC Client</b>")); - label_subtitle = gtk_label_new (NULL); - gtk_misc_set_alignment (GTK_MISC (label_subtitle), 0, 0); - gtk_box_pack_start (GTK_BOX (vbox_text), label_subtitle, 0, 0, 10); - color.red = 0x5555; - color.green = 0x5555; - color.blue = 0x5555; - gtk_widget_modify_fg (label_subtitle, GTK_STATE_NORMAL, &color); - gtk_label_set_markup (GTK_LABEL (label_subtitle), buf); - - /* label for additional info */ - g_get_charset (&locale); - (snprintf) (buf, sizeof (buf), - "<b>Version:</b> "PACKAGE_VERSION"\n" - "<b>Compiled:</b> "__DATE__"\n" -#ifdef WIN32 - "<b>Portable Mode:</b> %s\n" - "<b>Build Type:</b> x%d\n" -#endif - "<b>OS:</b> %s\n" - "<b>Charset:</b> %s", -#ifdef WIN32 - (portable_mode () ? "Yes" : "No"), - get_cpu_arch (), -#endif - get_sys_str (0), - locale); - - label_info = gtk_label_new (NULL); - gtk_misc_set_alignment (GTK_MISC (label_info), 0, 0); - gtk_box_pack_start (GTK_BOX (vbox_text), label_info, 0, 0, 10); - gtk_label_set_selectable (GTK_LABEL (label_info), TRUE); - gtk_label_set_markup (GTK_LABEL (label_info), buf); - - /* label for copyright notices */ - snprintf (buf, sizeof (buf), "<small>\302\251 1998-2010 Peter \305\275elezn\303\275\n\302\251 2009-2013 Berke Viktor</small>"); - label_copyright = gtk_label_new (NULL); - gtk_misc_set_alignment (GTK_MISC (label_copyright), 0, 0); - gtk_box_pack_start (GTK_BOX (vbox_text), label_copyright, 0, 0, 10); - gtk_label_set_markup (GTK_LABEL (label_copyright), buf); - - /* imagebox on the right */ - vbox_logo = gtk_vbox_new (FALSE, 0); - gtk_box_pack_start (GTK_BOX (hbox_main), vbox_logo, 0, 0, 10); - - /* the actual image */ - wid = gtk_image_new_from_pixbuf (pix_hexchat); - gtk_box_pack_start (GTK_BOX (vbox_logo), wid, 0, 0, 10); - - /* our close button on the bottom right */ - wid = gtk_button_new_from_stock (GTK_STOCK_CLOSE); - GTK_WIDGET_SET_FLAGS (GTK_WIDGET (wid), GTK_CAN_DEFAULT); - gtk_box_pack_end (GTK_BOX (GTK_DIALOG (about)->action_area), wid, 0, 0, 0); - gtk_widget_grab_default (wid); - g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (gtkutil_destroy), about); - - /* pure white background for the whole about widget*/ - color.red = color.green = color.blue = 0xffff; - gtk_widget_modify_bg (about, GTK_STATE_NORMAL, &color); - - /* show off! */ - gtk_widget_show_all (about); -} diff --git a/src/fe-gtk/about.h b/src/fe-gtk/about.h deleted file mode 100644 index b4d5cb34..00000000 --- a/src/fe-gtk/about.h +++ /dev/null @@ -1,25 +0,0 @@ -/* HexChat - * Copyright (C) 1998-2010 Peter Zelezny. - * Copyright (C) 2009-2013 Berke Viktor. - * - * 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 St, Fifth Floor, Boston, MA 02110-1301, USA - */ - -#ifndef HEXCHAT_ABOUT_H -#define HEXCHAT_ABOUT_H - -void menu_about (GtkWidget * wid, gpointer sess); - -#endif diff --git a/src/fe-gtk/chanlist.c b/src/fe-gtk/chanlist.c index 0f0307b7..d0e0ea40 100644 --- a/src/fe-gtk/chanlist.c +++ b/src/fe-gtk/chanlist.c @@ -747,6 +747,7 @@ chanlist_opengui (server *serv, int do_refresh) serv->gui->chanlist_window = mg_create_generic_tab ("ChanList", tbuf, FALSE, TRUE, chanlist_closegui, serv, 640, 480, &vbox, serv); + gtkutil_destroy_on_esc (serv->gui->chanlist_window); gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); gtk_box_set_spacing (GTK_BOX (vbox), 12); diff --git a/src/fe-gtk/dccgui.c b/src/fe-gtk/dccgui.c index 24d3bcbf..a3c2619a 100644 --- a/src/fe-gtk/dccgui.c +++ b/src/fe-gtk/dccgui.c @@ -78,6 +78,7 @@ struct dccwindow GtkWidget *accept_button; GtkWidget *resume_button; GtkWidget *open_button; + GtkWidget *clear_button; /* clears aborted and completed requests */ GtkWidget *file_label; GtkWidget *address_label; @@ -380,6 +381,50 @@ dcc_append (struct DCC *dcc, GtkListStore *store, gboolean prepend) dcc_prepare_row_send (dcc, store, &iter, FALSE); } +/* Returns aborted and completed transfers. */ +static GSList * +dcc_get_completed (void) +{ + struct DCC *dcc; + GtkTreeIter iter; + GtkTreeModel *model; + GSList *completed = NULL; + + model = GTK_TREE_MODEL (dccfwin.store); + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + gtk_tree_model_get (model, &iter, COL_DCC, &dcc, -1); + if (is_dcc_completed (dcc)) + completed = g_slist_prepend (completed, dcc); + + } while (gtk_tree_model_iter_next (model, &iter)); + } + + return completed; +} + +static gboolean +dcc_completed_transfer_exists (void) +{ + gboolean exist; + GSList *comp_list; + + comp_list = dcc_get_completed (); + exist = comp_list != NULL; + + g_slist_free (comp_list); + return exist; +} + +static void +update_clear_button_sensitivity (void) +{ + gboolean sensitive = dcc_completed_transfer_exists (); + gtk_widget_set_sensitive (dccfwin.clear_button, sensitive); +} + static void dcc_fill_window (int flags) { @@ -426,6 +471,8 @@ dcc_fill_window (int flags) gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dccfwin.store), &iter); gtk_tree_selection_select_iter (dccfwin.sel, &iter); } + + update_clear_button_sensitivity (); } /* return list of selected DCCs */ @@ -511,6 +558,9 @@ abort_clicked (GtkWidget * wid, gpointer none) dcc_abort (dcc->serv->front_session, dcc); } g_slist_free (start); + + /* Enable the clear button if it wasn't already enabled */ + update_clear_button_sensitivity (); } static void @@ -530,6 +580,27 @@ accept_clicked (GtkWidget * wid, gpointer none) } static void +clear_completed (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + GSList *completed; + + /* Make a new list of only the completed items and abort each item. + * A new list is made because calling dcc_abort removes items from the original list, + * making it impossible to iterate over that list directly. + */ + for (completed = dcc_get_completed (); completed; completed = completed->next) + { + dcc = completed->data; + dcc_abort (dcc->serv->front_session, dcc); + } + + /* The data was freed by dcc_close */ + g_slist_free (completed); + update_clear_button_sensitivity (); +} + +static void browse_folder (char *dir) { #ifdef WIN32 @@ -812,6 +883,7 @@ fe_dcc_open_recv_win (int passive) dccfwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_clicked, 0, _("Abort")); dccfwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_clicked, 0, _("Accept")); dccfwin.resume_button = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, resume_clicked, 0, _("Resume")); + dccfwin.clear_button = gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, clear_completed, 0, _("Clear")); dccfwin.open_button = gtkutil_button (bbox, 0, 0, browse_dcc_folder, 0, _("Open Folder...")); gtk_widget_set_sensitive (dccfwin.accept_button, FALSE); gtk_widget_set_sensitive (dccfwin.resume_button, FALSE); @@ -1055,6 +1127,8 @@ fe_dcc_update (struct DCC *dcc) default: dcc_update_chat (dcc); } + + update_clear_button_sensitivity (); } void diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c index c8a80369..3469569e 100644 --- a/src/fe-gtk/fe-gtk.c +++ b/src/fe-gtk/fe-gtk.c @@ -220,26 +220,26 @@ fe_args (int argc, char *argv[]) if (arg_show_version) { + buffer = g_strdup_printf ("%s %s", PACKAGE_NAME, PACKAGE_VERSION); #ifdef WIN32 - buffer = g_strdup_printf (DISPLAY_NAME " " PACKAGE_VERSION "\n"); gtk_init (&argc, &argv); create_msg_dialog ("Version Information", buffer); - g_free (buffer); #else - printf (PACKAGE_NAME" "PACKAGE_VERSION"\n"); + puts (buffer); #endif + g_free (buffer); return 0; } if (arg_show_autoload) { - buffer = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "addons\n", get_xdir ()); + buffer = g_strdup_printf ("%s%caddons%c", get_xdir(), G_DIR_SEPARATOR, G_DIR_SEPARATOR); #ifdef WIN32 gtk_init (&argc, &argv); create_msg_dialog ("Plugin/Script Auto-load Directory", buffer); #else - printf (buffer); + puts (buffer); #endif g_free (buffer); @@ -248,12 +248,12 @@ fe_args (int argc, char *argv[]) if (arg_show_config) { - buffer = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "\n", get_xdir ()); + buffer = g_strdup_printf ("%s%c", get_xdir(), G_DIR_SEPARATOR); #ifdef WIN32 gtk_init (&argc, &argv); create_msg_dialog ("User Config Directory", buffer); #else - printf (buffer); + puts (buffer); #endif g_free (buffer); @@ -908,10 +908,15 @@ fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *) { /* warning, assuming fe_confirm is used by DCC only! */ struct DCC *dcc = ud; + char *filepath; if (dcc->file) - gtkutil_file_req (message, dcc_saveas_cb, ud, dcc->file, NULL, - FRF_WRITE|FRF_NOASKOVERWRITE); + { + filepath = g_build_filename (prefs.hex_dcc_dir, dcc->file, NULL); + gtkutil_file_req (message, dcc_saveas_cb, ud, filepath, NULL, + FRF_WRITE|FRF_NOASKOVERWRITE|FRF_FILTERISINITIAL); + g_free (filepath); + } } int diff --git a/src/fe-gtk/fe-gtk.vcxproj b/src/fe-gtk/fe-gtk.vcxproj index 405b5207..1bdc7fef 100644 --- a/src/fe-gtk/fe-gtk.vcxproj +++ b/src/fe-gtk/fe-gtk.vcxproj @@ -98,7 +98,6 @@ </Link> </ItemDefinitionGroup> <ItemGroup> - <ClInclude Include="about.h" /> <ClInclude Include="ascii.h" /> <ClInclude Include="banlist.h" /> <ClInclude Include="chanlist.h" /> @@ -129,7 +128,6 @@ <ClInclude Include="xtext.h" /> </ItemGroup> <ItemGroup> - <ClCompile Include="about.c" /> <ClCompile Include="ascii.c" /> <ClCompile Include="banlist.c" /> <ClCompile Include="chanlist.c" /> diff --git a/src/fe-gtk/fe-gtk.vcxproj.filters b/src/fe-gtk/fe-gtk.vcxproj.filters index 0f1f8203..424f7564 100644 --- a/src/fe-gtk/fe-gtk.vcxproj.filters +++ b/src/fe-gtk/fe-gtk.vcxproj.filters @@ -15,9 +15,6 @@ </Filter> </ItemGroup> <ItemGroup> - <ClInclude Include="about.h"> - <Filter>Header Files</Filter> - </ClInclude> <ClInclude Include="ascii.h"> <Filter>Header Files</Filter> </ClInclude> @@ -104,9 +101,6 @@ </ClInclude> </ItemGroup> <ItemGroup> - <ClCompile Include="about.c"> - <Filter>Source Files</Filter> - </ClCompile> <ClCompile Include="ascii.c"> <Filter>Source Files</Filter> </ClCompile> diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c index 8a9a13c2..ba9599e1 100644 --- a/src/fe-gtk/fkeys.c +++ b/src/fe-gtk/fkeys.c @@ -148,7 +148,7 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = { {key_action_insert, "Insert in Buffer", N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position")}, {key_action_scroll_page, "Scroll Page", - N_("The \002Scroll Page\002 command scrolls the text widget up or down one page or one line. Set Data 1 to either Up, Down, +1 or -1.")}, + N_("The \002Scroll Page\002 command scrolls the text widget up or down one page or one line. Set Data 1 to either Top, Bottom, Up, Down, +1 or -1.")}, {key_action_set_buffer, "Set Buffer", N_("The \002Set Buffer\002 command sets the entry where the key sequence was entered to the contents of Data 1")}, {key_action_history_up, "Last Command", @@ -395,6 +395,8 @@ key_load_defaults () "S\nNext\nChange Selected Nick\nD1!\nD2!\n\n"\ "S\nPrior\nChange Selected Nick\nD1:Up\nD2!\n\n"\ "None\nNext\nScroll Page\nD1:Down\nD2!\n\n"\ + "C\nHome\nScroll Page\nD1:Top\nD2!\n\n"\ + "C\nEnd\nScroll Page\nD1:Bottom\nD2!\n\n"\ "None\nPrior\nScroll Page\nD1:Up\nD2!\n\n"\ "S\nDown\nScroll Page\nD1:+1\nD2!\n\n"\ "S\nUp\nScroll Page\nD1:-1\nD2!\n\n"\ @@ -1279,13 +1281,19 @@ key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1, { int value, end; GtkAdjustment *adj; - enum scroll_type { PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN }; + enum scroll_type { PAGE_TOP, PAGE_BOTTOM, PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN }; int type = PAGE_DOWN; if (d1) { - if (!g_ascii_strcasecmp (d1, "up")) + if (!g_ascii_strcasecmp (d1, "top")) + type = PAGE_TOP; + else if (!g_ascii_strcasecmp (d1, "bottom")) + type = PAGE_BOTTOM; + else if (!g_ascii_strcasecmp (d1, "up")) type = PAGE_UP; + else if (!g_ascii_strcasecmp (d1, "down")) + type = PAGE_DOWN; else if (!g_ascii_strcasecmp (d1, "+1")) type = LINE_DOWN; else if (!g_ascii_strcasecmp (d1, "-1")) @@ -1300,21 +1308,29 @@ key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1, switch (type) { - case LINE_UP: - value = adj->value - 1.0; + case PAGE_TOP: + value = 0; break; - case LINE_DOWN: - value = adj->value + 1.0; + case PAGE_BOTTOM: + value = end; break; case PAGE_UP: value = adj->value - (adj->page_size - 1); break; - default: /* PAGE_DOWN */ + case PAGE_DOWN: value = adj->value + (adj->page_size - 1); break; + + case LINE_UP: + value = adj->value - 1.0; + break; + + case LINE_DOWN: + value = adj->value + 1.0; + break; } if (value < 0) diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c index 11206e02..985a2f78 100644 --- a/src/fe-gtk/gtkutil.c +++ b/src/fe-gtk/gtkutil.c @@ -51,7 +51,6 @@ extern void path_part (char *file, char *path, int pathlen); - struct file_req { GtkWidget *dialog; @@ -69,9 +68,6 @@ struct file_req #endif }; -static char last_dir[256] = ""; - - static void gtkutil_file_req_destroy (GtkWidget * wid, struct file_req *freq) { @@ -84,13 +80,14 @@ gtkutil_check_file (char *file, struct file_req *freq) { struct stat st; int axs = FALSE; + char temp[256]; - path_part (file, last_dir, sizeof (last_dir)); + path_part (file, temp, sizeof (temp)); /* check if the file is readable or writable */ if (freq->flags & FRF_WRITE) { - if (access (last_dir, W_OK) == 0) + if (access (temp, W_OK) == 0) axs = TRUE; } else { @@ -179,60 +176,6 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte char *token; char *tokenbuffer; -#if 0 /* native file dialogs */ -#ifdef WIN32 - if (!(flags & FRF_WRITE)) - { - freq = malloc (sizeof (struct file_req)); - freq->th = thread_new (); - freq->flags = 0; - freq->multiple = (flags & FRF_MULTIPLE); - freq->callback = callback; - freq->userdata = userdata; - freq->title = g_locale_from_utf8 (title, -1, 0, 0, 0); - if (!filter) - { - freq->filter = "All files\0*.*\0" - "Executables\0*.exe\0" - "ZIP files\0*.zip\0\0"; - } - else - { - freq->filter = filter; - } - - thread_start (freq->th, win32_thread, freq); - fe_input_add (freq->th->pipe_fd[0], FIA_FD|FIA_READ, win32_read_thread, freq); - - return; - - } - - else { - freq = malloc (sizeof (struct file_req)); - freq->th = thread_new (); - freq->flags = 0; - freq->multiple = (flags & FRF_MULTIPLE); - freq->callback = callback; - freq->userdata = userdata; - freq->title = g_locale_from_utf8 (title, -1, 0, 0, 0); - if (!filter) - { - freq->filter = "All files\0*.*\0\0"; - } - else - { - freq->filter = filter; - } - - thread_start (freq->th, win32_thread2, freq); - fe_input_add (freq->th->pipe_fd[0], FIA_FD|FIA_READ, win32_read_thread, freq); - - return; - } -#endif -#endif - if (flags & FRF_WRITE) { dialog = gtk_file_chooser_dialog_new (title, NULL, @@ -240,13 +183,6 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); - if (filter && filter[0]) /* filter becomes initial name when saving */ - { - char temp[1024]; - path_part (filter, temp, sizeof (temp)); - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), temp); - gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), file_part (filter)); - } if (!(flags & FRF_NOASKOVERWRITE)) gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); @@ -257,41 +193,28 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); - if (flags & FRF_MULTIPLE) - gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE); - if (last_dir[0]) - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), last_dir); - if (flags & FRF_ADDFOLDER) - gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), - get_xdir (), NULL); - if (flags & FRF_CHOOSEFOLDER) - { - gtk_file_chooser_set_action (GTK_FILE_CHOOSER (dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter); - } - else + + if (filter && filter[0] && (flags & FRF_FILTERISINITIAL)) { - if (filter && (flags & FRF_FILTERISINITIAL)) - { - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter); - } - /* With DCC, we can't rely on filter as initial folder since filter already contains - * the filename upon DCC RECV. Thus we have no better option than to check for the message - * which will be the title of the window. For DCC it always contains the "offering" word. - * This method is really ugly but it works so we'll stick with it for now. - */ - else if (strstr (title, "offering") != NULL) + if (flags & FRF_WRITE) { - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), prefs.hex_dcc_dir); + char temp[1024]; + path_part (filter, temp, sizeof (temp)); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), temp); + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), file_part (filter)); } - /* by default, open the config folder */ else - { - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), get_xdir ()); - } + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter); } + else if (!(flags & FRF_RECENTLYUSED)) + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), get_xdir ()); - if (flags & FRF_EXTENSIONS && extensions != NULL) + if (flags & FRF_MULTIPLE) + gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE); + if (flags & FRF_CHOOSEFOLDER) + gtk_file_chooser_set_action (GTK_FILE_CHOOSER (dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + + if ((flags & FRF_EXTENSIONS || flags & FRF_MIMETYPES) && extensions != NULL) { filefilter = gtk_file_filter_new (); tokenbuffer = g_strdup (extensions); @@ -299,7 +222,10 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte while (token != NULL) { - gtk_file_filter_add_pattern (filefilter, token); + if (flags & FRF_EXTENSIONS) + gtk_file_filter_add_pattern (filefilter, token); + else + gtk_file_filter_add_mime_type (filefilter, token); token = strtok (NULL, ";"); } @@ -307,6 +233,8 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filefilter); } + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), get_xdir (), NULL); + freq = malloc (sizeof (struct file_req)); freq->dialog = dialog; freq->flags = flags; @@ -678,6 +606,7 @@ gtkutil_window_new (char *title, char *role, int width, int height, int flags) { gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_DIALOG); gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent_window)); + gtk_window_set_destroy_with_parent (GTK_WINDOW (win), TRUE); } return win; diff --git a/src/fe-gtk/gtkutil.h b/src/fe-gtk/gtkutil.h index e3f59a29..2ecbe3c2 100644 --- a/src/fe-gtk/gtkutil.h +++ b/src/fe-gtk/gtkutil.h @@ -21,16 +21,10 @@ #define HEXCHAT_GTKUTIL_H #include <gtk/gtk.h> +#include "../common/fe.h" typedef void (*filereqcallback) (void *, char *file); -#define FRF_WRITE 1 -#define FRF_MULTIPLE 2 -#define FRF_ADDFOLDER 4 -#define FRF_CHOOSEFOLDER 8 -#define FRF_FILTERISINITIAL 16 -#define FRF_NOASKOVERWRITE 32 - void gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter, char *extensions, int flags); void gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad); void gtkutil_destroy_on_esc (GtkWidget *win); diff --git a/src/fe-gtk/joind.c b/src/fe-gtk/joind.c index b58d662a..55d62e3b 100644 --- a/src/fe-gtk/joind.c +++ b/src/fe-gtk/joind.c @@ -34,6 +34,7 @@ #include "../common/hexchat.h" #include "../common/hexchatc.h" #include "../common/server.h" +#include "../common/servlist.h" #include "../common/fe.h" #include "fe-gtk.h" #include "chanlist.h" @@ -241,6 +242,13 @@ joind_show_dialog (server *serv) G_CALLBACK (joind_radio2_cb), serv); g_signal_connect (G_OBJECT (okbutton1), "clicked", G_CALLBACK (joind_ok_cb), serv); + + if (serv->network) + if (g_ascii_strcasecmp(((ircnet*)serv->network)->name, "freenode") == 0) + { + gtk_entry_set_text (GTK_ENTRY (entry1), "#hexchat"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(radiobutton2), TRUE); + } gtk_widget_grab_focus (okbutton1); gtk_widget_show_all (dialog1); diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 91bc9d6f..fd0ee6f1 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -2106,8 +2106,8 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box) gui->flag_k = mg_create_flagbutton (_("Keyword"), box, "K"); gui->key_entry = gtk_entry_new (); gtk_widget_set_name (gui->key_entry, "hexchat-inputbox"); - gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 16); - gtk_widget_set_size_request (gui->key_entry, 30, -1); + gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 23); + gtk_widget_set_size_request (gui->key_entry, 115, -1); gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0); g_signal_connect (G_OBJECT (gui->key_entry), "activate", G_CALLBACK (mg_key_entry_cb), NULL); @@ -2310,6 +2310,8 @@ mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even) menu_nickmenu (sess, even, word, FALSE); break; case WORD_CHANNEL: + word[end] = 0; + word += start; menu_chanmenu (sess, even, word); break; case WORD_EMAIL: diff --git a/src/fe-gtk/menu.c b/src/fe-gtk/menu.c index dd3223a1..8d87f84e 100644 --- a/src/fe-gtk/menu.c +++ b/src/fe-gtk/menu.c @@ -43,7 +43,6 @@ #include "../common/notify.h" #include "../common/util.h" #include "xtext.h" -#include "about.h" #include "ascii.h" #include "banlist.h" #include "chanlist.h" @@ -1621,6 +1620,62 @@ menu_metres_both (GtkWidget *item, gpointer none) } } +static void +about_dialog_close (GtkDialog *dialog, int response, gpointer data) +{ + gtk_widget_destroy (GTK_WIDGET(dialog)); +} + +static gboolean +about_dialog_openurl (GtkAboutDialog *dialog, char *uri, gpointer data) +{ + fe_open_url (uri); + return TRUE; +} + +static void +menu_about (GtkWidget *wid, gpointer sess) +{ + GtkAboutDialog *dialog = GTK_ABOUT_DIALOG(gtk_about_dialog_new()); + char comment[512]; + char *license = "This program is free software; you can redistribute it and/or modify\n" \ + "it under the terms of the GNU General Public License as published by\n" \ + "the Free Software Foundation; version 2.\n\n" \ + "This program is distributed in the hope that it will be useful,\n" \ + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \ + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" \ + "GNU General Public License for more details.\n\n" \ + "You should have received a copy of the GNU General Public License\n" \ + "along with this program. If not, see <http://www.gnu.org/licenses/>"; + + g_snprintf (comment, sizeof(comment), "Compiled: "__DATE__"\n" +#ifdef WIN32 + "Portable Mode: %s\n" + "Build Type: x%d\n" +#endif + "OS: %s", +#ifdef WIN32 + (portable_mode () ? "Yes" : "No"), + get_cpu_arch (), +#endif + get_sys_str (0)); + + gtk_about_dialog_set_name (dialog, DISPLAY_NAME); + gtk_about_dialog_set_version (dialog, PACKAGE_VERSION); + gtk_about_dialog_set_license (dialog, license); /* gtk3 can use GTK_LICENSE_GPL_2_0 */ + gtk_about_dialog_set_website (dialog, "http://hexchat.github.io"); + gtk_about_dialog_set_website_label (dialog, "Website"); + gtk_about_dialog_set_logo (dialog, pix_hexchat); + gtk_about_dialog_set_copyright (dialog, "\302\251 1998-2010 Peter \305\275elezn\303\275\n\302\251 2009-2013 Berke Viktor"); + gtk_about_dialog_set_comments (dialog, comment); + + gtk_window_set_transient_for (GTK_WINDOW(dialog), GTK_WINDOW(parent_window)); + g_signal_connect (G_OBJECT(dialog), "response", G_CALLBACK(about_dialog_close), NULL); + g_signal_connect (G_OBJECT(dialog), "activate-link", G_CALLBACK(about_dialog_openurl), NULL); + + gtk_widget_show_all (GTK_WIDGET(dialog)); +} + static struct mymenu mymenu[] = { {N_("He_xChat"), 0, 0, M_NEWMENU, 0, 0, 1}, {N_("Network Li_st..."), menu_open_server_list, (char *)&pix_book, M_MENUPIX, 0, 0, 1, GDK_s}, diff --git a/src/fe-gtk/notifygui.c b/src/fe-gtk/notifygui.c index 872fb7ed..c0282ade 100644 --- a/src/fe-gtk/notifygui.c +++ b/src/fe-gtk/notifygui.c @@ -60,6 +60,7 @@ static void notify_closegui (void) { notify_window = 0; + notify_save (); } /* Need this to be able to set the foreground colour property of a row diff --git a/src/fe-gtk/plugingui.c b/src/fe-gtk/plugingui.c index c7193837..c6d90e2f 100644 --- a/src/fe-gtk/plugingui.c +++ b/src/fe-gtk/plugingui.c @@ -72,6 +72,31 @@ plugingui_treeview_new (GtkWidget *box) return view; } +static char * +plugingui_getfilename (GtkTreeView *view) +{ + GtkTreeModel *model; + GtkTreeSelection *sel; + GtkTreeIter iter; + GValue file; + char *str; + + memset (&file, 0, sizeof (file)); + + sel = gtk_tree_view_get_selection (view); + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gtk_tree_model_get_value (model, &iter, FILE_COLUMN, &file); + + str = g_value_dup_string (&file); + g_value_unset (&file); + + return str; + } + + return NULL; +} + static void plugingui_close (GtkWidget * wid, gpointer a) { @@ -137,9 +162,9 @@ plugingui_load (void) gtkutil_file_req (_("Select a Plugin or Script to load"), plugingui_load_cb, current_sess, #ifdef WIN32 - sub_dir, "*.dll;*.lua;*.pl;*.py;*.tcl;*.js", FRF_ADDFOLDER|FRF_FILTERISINITIAL|FRF_EXTENSIONS); + sub_dir, "*.dll;*.lua;*.pl;*.py;*.tcl;*.js", FRF_FILTERISINITIAL|FRF_EXTENSIONS); #else - sub_dir, "*.so;*.lua;*.pl;*.py;*.tcl;*.js", FRF_ADDFOLDER|FRF_FILTERISINITIAL|FRF_EXTENSIONS); + sub_dir, "*.so;*.lua;*.pl;*.py;*.tcl;*.js", FRF_FILTERISINITIAL|FRF_EXTENSIONS); #endif g_free (sub_dir); @@ -193,6 +218,25 @@ plugingui_unload (GtkWidget * wid, gpointer unused) g_free (file); } +static void +plugingui_reloadbutton_cb (GtkWidget *wid, GtkTreeView *view) +{ + char *file = plugingui_getfilename(view); + + if (file) + { + char *buf = malloc (strlen (file) + 9); + + if (strchr (file, ' ')) + sprintf (buf, "RELOAD \"%s\"", file); + else + sprintf (buf, "RELOAD %s", file); + handle_command (current_sess, buf, FALSE); + free (buf); + g_free (file); + } +} + void plugingui_open (void) { @@ -223,7 +267,10 @@ plugingui_open (void) plugingui_loadbutton_cb, NULL, _("_Load...")); gtkutil_button (hbox, GTK_STOCK_DELETE, NULL, - plugingui_unload, NULL, _("_UnLoad")); + plugingui_unload, NULL, _("_Unload")); + + gtkutil_button (hbox, GTK_STOCK_REFRESH, NULL, + plugingui_reloadbutton_cb, view, _("_Reload")); fe_pluginlist_update (); diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c index 94cb209f..ef5b57cd 100644 --- a/src/fe-gtk/servlistgui.c +++ b/src/fe-gtk/servlistgui.c @@ -120,10 +120,15 @@ static int login_types_conf[] = { LOGIN_DEFAULT, /* default entry - we don't use this but it makes indexing consistent with login_types[] so it's nice */ LOGIN_SASL, +#ifdef USE_OPENSSL + LOGIN_SASLEXTERNAL, +#endif LOGIN_PASS, LOGIN_MSG_NICKSERV, LOGIN_NICKSERV, +#ifdef USE_OPENSSL LOGIN_CHALLENGEAUTH, +#endif LOGIN_CUSTOM #if 0 LOGIN_NS, @@ -136,10 +141,15 @@ static const char *login_types[]= { "Default", "SASL (username + password)", +#ifdef USE_OPENSSL + "SASL EXTERNAL (cert)", +#endif "Server Password (/PASS password)", "NickServ (/MSG NickServ + password)", "NickServ (/NICKSERV + password)", +#ifdef USE_OPENSSL "Challenge Auth (username + password)", +#endif "Custom... (connect commands)", #if 0 "NickServ (/NS + password)", @@ -1513,6 +1523,12 @@ servlist_logintypecombo_cb (GtkComboBox *cb, gpointer *userdata) { gtk_notebook_set_current_page (GTK_NOTEBOOK (userdata), 2); /* FIXME avoid hardcoding? */ } + + /* EXTERNAL uses a cert, not a pass */ + if (login_types_conf[index] == LOGIN_SASLEXTERNAL) + gtk_widget_set_sensitive (edit_entry_pass, FALSE); + else + gtk_widget_set_sensitive (edit_entry_pass, TRUE); } @@ -1816,6 +1832,8 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) edit_entry_pass = servlist_create_entry (table3, _("Password:"), 11, net->pass, 0, _("Password used for login. If in doubt, leave blank.")); gtk_entry_set_visibility (GTK_ENTRY (edit_entry_pass), FALSE); + if (selected_net && selected_net->logintype == LOGIN_SASLEXTERNAL) + gtk_widget_set_sensitive (edit_entry_pass, FALSE); label34 = gtk_label_new (_("Character set:")); gtk_table_attach (GTK_TABLE (table3), label34, 0, 1, 12, 13, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), SERVLIST_X_PADDING, SERVLIST_Y_PADDING); diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 2d46b95f..8b3a43e5 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -426,7 +426,11 @@ static const setting alert_settings[] = #ifdef WIN32 {ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play the \"Instant Message Notification\" system sound upon the selected events"), (void *)beeplist, 0}, #else +#ifdef USE_LIBCANBERRA {ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play \"message-new-instant\" from the freedesktop.org sound theme upon the selected events"), (void *)beeplist, 0}, +#else + {ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play a GTK beep upon the selected events"), (void *)beeplist, 0}, +#endif #endif {ST_TOGGLE, N_("Omit alerts when marked as being away"), P_OFFINTNL(hex_away_omit_alerts), 0, 0, 0}, @@ -1059,7 +1063,19 @@ setup_filereq_cb (GtkWidget *entry, char *file) static void setup_browsefile_cb (GtkWidget *button, GtkWidget *entry) { - gtkutil_file_req (_("Select an Image File"), setup_filereq_cb, entry, NULL, NULL, 0); + /* used for background image only */ + char *filter; + int filter_type; + +#ifdef WIN32 + filter = "*png;*.tiff;*.gif;*.jpeg;*.jpg"; + filter_type = FRF_EXTENSIONS; +#else + filter = "image/*"; + filter_type = FRF_MIMETYPES; +#endif + gtkutil_file_req (_("Select an Image File"), setup_filereq_cb, + entry, NULL, filter, filter_type|FRF_RECENTLYUSED); } static void @@ -1659,7 +1675,18 @@ static void setup_snd_browse_cb (GtkWidget *button, GtkEntry *entry) { char *sounds_dir = g_build_filename (get_xdir (), HEXCHAT_SOUND_DIR, NULL); - gtkutil_file_req (_("Select a sound file"), setup_snd_filereq_cb, entry, sounds_dir, NULL, FRF_FILTERISINITIAL); + char *filter = NULL; + int filter_type; +#ifdef WIN32 /* win32 only supports wav, others could support anything */ + filter = "*.wav"; + filter_type = FRF_EXTENSIONS; +#else + filter = "audio/*"; + filter_type = FRF_MIMETYPES; +#endif + + gtkutil_file_req (_("Select a sound file"), setup_snd_filereq_cb, entry, + sounds_dir, filter, FRF_FILTERISINITIAL|filter_type); g_free (sounds_dir); } diff --git a/src/fe-gtk/urlgrab.c b/src/fe-gtk/urlgrab.c index cefa51c8..79a6d5f5 100644 --- a/src/fe-gtk/urlgrab.c +++ b/src/fe-gtk/urlgrab.c @@ -146,7 +146,7 @@ static void url_button_save (void) { gtkutil_file_req (_("Select an output filename"), - url_save_callback, NULL, get_xdir (), NULL, FRF_WRITE|FRF_FILTERISINITIAL); + url_save_callback, NULL, NULL, NULL, FRF_WRITE); } void diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index 7582f82c..3d2ae7ae 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -1901,10 +1901,10 @@ gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, { textentry *ent; int offset; - unsigned char *str; unsigned char *word; int len; int out_of_bounds = 0; + int len_to_offset = 0; ent = gtk_xtext_find_char (xtext, x, y, &offset, &out_of_bounds); if (!ent) @@ -1921,25 +1921,25 @@ gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, /*offset--;*/ /* FIXME: not all chars are 1 byte */ - str = ent->str + offset; + word = ent->str + offset; - while (!is_del (*str) && str != ent->str) - str--; - word = str + 1; + while (!is_del (*word) && word != ent->str) + { + word--; + len_to_offset++; + } + word++; + len_to_offset--; + + /* remove color characters from the length */ + gtk_xtext_strip_color (word, len_to_offset, xtext->scratch_buffer, &len_to_offset, NULL, slp, FALSE); len = 0; - str = word; - while (!is_del (*str) && len != ent->str_len) - { - str++; + while (!is_del (word[len]) && len != ent->str_len) len++; - } if (len > 0 && word[len-1]=='.') - { len--; - str--; - } if (ret_ent) *ret_ent = ent; @@ -1948,7 +1948,24 @@ gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, if (ret_len) *ret_len = len; /* Length before stripping */ - return gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, NULL, slp, FALSE); + word = gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, NULL, slp, FALSE); + + /* avoid turning the cursor into a hand for non-url part of the word */ + if (xtext->urlcheck_function (GTK_WIDGET (xtext), word)) + { + int start, end; + url_last (&start, &end); + + /* make sure we're not before the start of the match */ + if (len_to_offset < start) + return 0; + + /* and not after it */ + if (len_to_offset - start >= end - start) + return 0; + } + + return word; } #ifdef MOTION_MONITOR @@ -2139,6 +2156,7 @@ gtk_xtext_motion_notify (GtkWidget * widget, GdkEventMotion * event) { gdk_window_set_cursor (GTK_WIDGET (xtext)->window, xtext->resize_cursor); + xtext->cursor_hand = FALSE; xtext->cursor_resize = TRUE; } return FALSE; @@ -2161,6 +2179,7 @@ gtk_xtext_motion_notify (GtkWidget * widget, GdkEventMotion * event) gdk_window_set_cursor (GTK_WIDGET (xtext)->window, xtext->hand_cursor); xtext->cursor_hand = TRUE; + xtext->cursor_resize = FALSE; } /* un-render the old hilight */ diff --git a/src/htm/Makefile.am b/src/htm/Makefile.am new file mode 100644 index 00000000..85480402 --- /dev/null +++ b/src/htm/Makefile.am @@ -0,0 +1,12 @@ +MDTOOL_OPTS = --verbose + +theme_SCRIPTS = thememan.exe thememan +themedir = $(bindir) + +thememan.exe: htm-mono.csproj + $(MDTOOL) $(MDTOOL_OPTS) build $< + +clean-local: + rm -f thememan.exe thememan.exe.config thememan.exe.mdb thememan Main.resources + +EXTRA_DIST = thememan.in diff --git a/src/htm/thememan.in b/src/htm/thememan.in new file mode 100644 index 00000000..f6f80df6 --- /dev/null +++ b/src/htm/thememan.in @@ -0,0 +1,3 @@ +#!/bin/sh +exec_prefix="@exec_prefix@" +exec mono "@bindir@/thememan.exe" "$@" |