# HG changeset patch # User Sadrul Habib Chowdhury # Date 1197969601 0 # Node ID 5814ae85e7568876f6380f661c79a51d65569f16 # Parent f5d961556972ce776ff4df2160d805c870a3f7a6# Parent a3f0c30eb0bfc293fd3704cea0c123377ec7d252 propagate from branch 'im.pidgin.pidgin.sadrul.tooltips' (head 2c79eb43e6f1106231cc15098ea6467b7fc1e4d5) to branch 'im.pidgin.pidgin.next.minor' (head 991ec59f7dc223bc484d829a9133c5fdcced6e78) diff -r f5d961556972 -r 5814ae85e756 ChangeLog --- a/ChangeLog Tue Dec 18 09:17:39 2007 +0000 +++ b/ChangeLog Tue Dec 18 09:20:01 2007 +0000 @@ -8,6 +8,11 @@ Bonjour protocol. Avahi (or Apple's Bonjour runtime on win32) is now required to use Bonjour. + Pidgin: + * Added the ability to theme conversation name colors (red and blue) + through your GTK+ theme, and exposed those theme settings to the + Pidgin GTK+ Theme Control plugin (Dustin Howett) + Finch: * Color is used in the buddylist to indicate status, and the conversation window to indicate various message attributes. Look at the sample gntrc @@ -16,6 +21,27 @@ request dialog. M-d will properly delete-forward-word, and M-f has been fixed to imitate readline's behavior. +version 2.3.1 (12/7/2007): + http://developer.pidgin.im/query?status=closed&milestone=2.3.1 + NOTE: Due to the way this release was made, it is possible that + bugs marked as fixed in 2.3.1 will not be fixed until the + next release. + + * Fixed a number of MSN bugs introduced in 2.3.0, resolving problems + connecting to MSN and random local display name changes + * Going idle on MySpaceIM will no longer clear your status and message. + * Idle MySpaceIM buddies should now appear online at login. + * Fixed crashes in XMPP when discovering a client's capabilities + * Don't set the current tune title if it's NULL (XMPP/Google Talk) + * Don't allow buddies to be manually added to Bonjour + * Don't advertise IPv6 on Bonjour because we don't support it + * Compile fixes for FreeBSD and Solaris + * Update QQ client version so some accounts can connect again + * Do not allow ISON requests to stack in IRC, preventing flooding IRC + servers when temporary network outages are restored + * Plug several leaks in the perl plugin loader + * Prevent autoaccept plugin overwriting existing files + version 2.3.0 (11/24/2007): http://developer.pidgin.im/query?status=closed&milestone=2.3.0 NOTE: Some bugs marked fixed in 2.2.1, 2.2.2 or 2.2.3 may not diff -r f5d961556972 -r 5814ae85e756 ChangeLog.API --- a/ChangeLog.API Tue Dec 18 09:17:39 2007 +0000 +++ b/ChangeLog.API Tue Dec 18 09:20:01 2007 +0000 @@ -1,5 +1,23 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.4.0 (??/??/????): + libpurple: + Added: + * purple_certificate_add_ca_search_path. (Florian Quèze) + + Pidgin: + Added: + * pidgin_create_dialog to create a window that closes on escape. Also + added utility functions pidgin_dialog_get_vbox_with_properties, + pidgin_dialog_get_vbox, pidgin_dialog_get_action_area to access the + contents in the created dialog. (Peter 'fmoo' Ruibal) + * pidgin_dialog_add_button to add buttons to a dialog created by + pidgin_create_dialog. + * GTK_IMHTML_NO_SMILEY for GtkIMHtmlOptions means not to look for + smileys in the text. (Florian 'goutnet' Delizy) + * pidgin_auto_parent_window to make a window transient for a suitable + parent window. + version 2.3.2 (??/??/????): Finch: libgnt: diff -r f5d961556972 -r 5814ae85e756 NEWS --- a/NEWS Tue Dec 18 09:17:39 2007 +0000 +++ b/NEWS Tue Dec 18 09:20:01 2007 +0000 @@ -1,5 +1,19 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +2.3.1 (12/7/2007): + Stu: I'm sorry for the MSN problems and the plugin crashes in 2.3.0. + Hopefully this will redeem us. This fixes a number of bugs. I'm a + bit late but I'd like to welcome John to the team. Enjoy! + + Luke: I've done absolutely nothing in the last 2 weeks, except watch + others commit bug and, more, leak fixes. People should be noticing + remarkably fewer memory leaks now than 2 or more releases ago. + + Kevin: I'm not quite sure what happened to our MySpaceIM Summer of + Code student, but I fixed a few MySpace bugs with idle and status. + I will try to fix some of the other more significant bugs, after I + figure out the protocol, especially including grouping issues. + 2.3.0 (11/20/2007): Luke: While this does not have the new MSN code, rest assured that we are working on it and that it is nearing release. This contains diff -r f5d961556972 -r 5814ae85e756 doc/pidgin.1.in --- a/doc/pidgin.1.in Tue Dec 18 09:17:39 2007 +0000 +++ b/doc/pidgin.1.in Tue Dec 18 09:20:01 2007 +0000 @@ -88,8 +88,8 @@ .TP .B Add Buddy Pounce A Buddy Pounce is a configurable automated action to be performed when the -buddy's state changes. This will open the \fBBuddy Pounce\fR dialog to be -discussed later. +buddy's state changes. This will open the \fBBuddy Pounce\fR dialog, which +will be discussed later. .TP .B View Log Pidgin is capable of automatically logging messages. These logs are @@ -118,7 +118,7 @@ .SH ACCOUNT EDITOR The account editor consists of a list of accounts and information about -them. It can be accessed by selecting \fBManage\fR from the Tools menu. +them. It can be accessed by selecting \fBManage\fR from the Accounts menu. Clicking \fIDelete\fR will delete the currently selected account. Clicking \fIAdd\fR or \fIModify\fR will invoke a \fBModify Account\fR window. Here, the user can add or alter account information. When creating diff -r f5d961556972 -r 5814ae85e756 libpurple/account.c --- a/libpurple/account.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/account.c Tue Dec 18 09:20:01 2007 +0000 @@ -331,6 +331,12 @@ if(err == NULL) return node; + /* It doesn't make sense to have transient errors persist across a + * restart. + */ + if(!purple_connection_error_is_fatal (err->type)) + return node; + child = xmlnode_new_child(node, "type"); snprintf(type_str, sizeof(type_str), "%u", err->type); xmlnode_insert_data(child, type_str, -1); diff -r f5d961556972 -r 5814ae85e756 libpurple/buddyicon.c --- a/libpurple/buddyicon.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/buddyicon.c Tue Dec 18 09:20:01 2007 +0000 @@ -98,8 +98,7 @@ { const char *dirname; char *path; - FILE *file = NULL; - + g_return_if_fail(img != NULL); if (!purple_buddy_icons_is_caching()) @@ -120,24 +119,12 @@ } } - if ((file = g_fopen(path, "wb")) != NULL) - { - if (!fwrite(purple_imgstore_get_data(img), purple_imgstore_get_size(img), 1, file)) - { - purple_debug_error("buddyicon", "Error writing %s: %s\n", - path, g_strerror(errno)); - } - else - purple_debug_info("buddyicon", "Wrote cache file: %s\n", path); - - fclose(file); - } - else - { + if (!g_file_test(path, G_FILE_TEST_EXISTS)) { + purple_util_write_data_to_file_absolute(path, purple_imgstore_get_data(img), + purple_imgstore_get_size(img)); + } else { purple_debug_error("buddyicon", "Unable to create file %s: %s\n", path, g_strerror(errno)); - g_free(path); - return; } g_free(path); } diff -r f5d961556972 -r 5814ae85e756 libpurple/certificate.c --- a/libpurple/certificate.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/certificate.c Tue Dec 18 09:20:01 2007 +0000 @@ -627,7 +627,7 @@ /** System directory to probe for CA certificates */ /* This is set in the lazy_init function */ -static const gchar *x509_ca_syspath = NULL; +static GList *x509_ca_paths = NULL; /** A list of loaded CAs, populated from the above path whenever the lazy_init happens. Contains pointers to x509_ca_elements */ @@ -674,6 +674,7 @@ GDir *certdir; const gchar *entry; GPatternSpec *pempat; + GList *iter = NULL; if (x509_ca_initialized) return TRUE; @@ -687,54 +688,48 @@ return FALSE; } - /* Attempt to point at the appropriate system path */ - if (NULL == x509_ca_syspath) { -#ifdef _WIN32 - x509_ca_syspath = g_build_filename(DATADIR, - "ca-certs", NULL); -#else - x509_ca_syspath = g_build_filename(DATADIR, - "purple", "ca-certs", NULL); -#endif - } - - /* Populate the certificates pool from the system path */ - certdir = g_dir_open(x509_ca_syspath, 0, NULL); - g_return_val_if_fail(certdir, FALSE); - /* Use a glob to only read .pem files */ pempat = g_pattern_spec_new("*.pem"); - - while ( (entry = g_dir_read_name(certdir)) ) { - gchar *fullpath; - PurpleCertificate *crt; - if ( !g_pattern_match_string(pempat, entry) ) { + /* Populate the certificates pool from the search path(s) */ + for (iter = x509_ca_paths; iter; iter = iter->next) { + certdir = g_dir_open(iter->data, 0, NULL); + if (!certdir) { + purple_debug_error("certificate/x509/ca", "Couldn't open location '%s'\n", iter->data); continue; } - fullpath = g_build_filename(x509_ca_syspath, entry, NULL); - - /* TODO: Respond to a failure in the following? */ - crt = purple_certificate_import(x509, fullpath); + while ( (entry = g_dir_read_name(certdir)) ) { + gchar *fullpath; + PurpleCertificate *crt; + + if ( !g_pattern_match_string(pempat, entry) ) { + continue; + } + + fullpath = g_build_filename(iter->data, entry, NULL); + + /* TODO: Respond to a failure in the following? */ + crt = purple_certificate_import(x509, fullpath); - if (x509_ca_quiet_put_cert(crt)) { - purple_debug_info("certificate/x509/ca", - "Loaded %s\n", - fullpath); - } else { - purple_debug_error("certificate/x509/ca", - "Failed to load %s\n", - fullpath); + if (x509_ca_quiet_put_cert(crt)) { + purple_debug_info("certificate/x509/ca", + "Loaded %s\n", + fullpath); + } else { + purple_debug_error("certificate/x509/ca", + "Failed to load %s\n", + fullpath); + } + + purple_certificate_destroy(crt); + g_free(fullpath); } - - purple_certificate_destroy(crt); - g_free(fullpath); + g_dir_close(certdir); } g_pattern_spec_free(pempat); - g_dir_close(certdir); - + purple_debug_info("certificate/x509/ca", "Lazy init completed.\n"); x509_ca_initialized = TRUE; @@ -744,6 +739,17 @@ static gboolean x509_ca_init(void) { + /* Attempt to point at the appropriate system path */ + if (NULL == x509_ca_paths) { +#ifdef _WIN32 + x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR, + "ca-certs", NULL)); +#else + x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR, + "purple", "ca-certs", NULL)); +#endif + } + /* Attempt to initialize now, but if it doesn't work, that's OK; it will get done later */ if ( ! x509_ca_lazy_init()) { @@ -752,7 +758,7 @@ "dependency is not yet registered. " "It has been deferred to later.\n"); } - + return TRUE; } @@ -768,6 +774,9 @@ g_list_free(x509_ca_certs); x509_ca_certs = NULL; x509_ca_initialized = FALSE; + g_list_foreach(x509_ca_paths, (GFunc)g_free, NULL); + g_list_free(x509_ca_paths); + x509_ca_paths = NULL; } /** Look up a ca_element by dn */ @@ -1901,3 +1910,10 @@ g_byte_array_free(sha_bin, TRUE); } +void purple_certificate_add_ca_search_path(const char *path) +{ + if (g_list_find_custom(x509_ca_paths, path, (GCompareFunc)strcmp)) + return; + x509_ca_paths = g_list_append(x509_ca_paths, g_strdup(path)); +} + diff -r f5d961556972 -r 5814ae85e756 libpurple/certificate.h --- a/libpurple/certificate.h Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/certificate.h Tue Dec 18 09:20:01 2007 +0000 @@ -786,6 +786,12 @@ void purple_certificate_display_x509(PurpleCertificate *crt); +/** + * Add a search path for certificates. + * + * @param path Path to search for certificates. + */ +void purple_certificate_add_ca_search_path(const char *path); #ifdef __cplusplus } diff -r f5d961556972 -r 5814ae85e756 libpurple/plugins/perl/common/Prpl.xs --- a/libpurple/plugins/perl/common/Prpl.xs Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/plugins/perl/common/Prpl.xs Tue Dec 18 09:20:01 2007 +0000 @@ -54,3 +54,20 @@ Purple::Account account const char *name time_t login_time + +int +purple_prpl_send_raw(gc, str) + Purple::Connection gc + const char *str +PREINIT: + PurplePluginProtocolInfo *prpl_info; +CODE: + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); + if (prpl_info && prpl_info->send_raw != NULL) { + RETVAL = prpl_info->send_raw(gc, str, strlen(str)); + } else { + RETVAL = 0; + } +OUTPUT: + RETVAL + diff -r f5d961556972 -r 5814ae85e756 libpurple/prefs.c --- a/libpurple/prefs.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/prefs.c Tue Dec 18 09:20:01 2007 +0000 @@ -914,7 +914,7 @@ if(pref) { if(pref->type != PURPLE_PREF_PATH) { purple_debug_error("prefs", - "purple_prefs_set_path: %s not a string pref\n", name); + "purple_prefs_set_path: %s not a path pref\n", name); return; } diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/bonjour/bonjour.c --- a/libpurple/protocols/bonjour/bonjour.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Tue Dec 18 09:20:01 2007 +0000 @@ -109,7 +109,7 @@ gc->proto_data = bd = g_new0(BonjourData, 1); /* Start waiting for jabber connections (iChat style) */ - bd->jabber_data = g_new(BonjourJabber, 1); + bd->jabber_data = g_new0(BonjourJabber, 1); bd->jabber_data->port = BONJOUR_DEFAULT_PORT_INT; bd->jabber_data->account = account; diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/bonjour/bonjour.h --- a/libpurple/protocols/bonjour/bonjour.h Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.h Tue Dec 18 09:20:01 2007 +0000 @@ -44,7 +44,7 @@ { BonjourDnsSd *dns_sd_data; BonjourJabber *jabber_data; - GList *xfer_lists; + GSList *xfer_lists; } BonjourData; #endif /* _BONJOUR_H_ */ diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/bonjour/bonjour_ft.c --- a/libpurple/protocols/bonjour/bonjour_ft.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour_ft.c Tue Dec 18 09:20:01 2007 +0000 @@ -149,7 +149,7 @@ static PurpleXfer* bonjour_si_xfer_find(BonjourData *bd, const char *sid, const char *from) { - GList *xfers = NULL; + GSList *xfers = NULL; PurpleXfer *xfer = NULL; XepXfer *xf = NULL; @@ -309,7 +309,7 @@ if(xf != NULL) { bd = (BonjourData*)xf->data; if(bd != NULL) { - bd->xfer_lists = g_list_remove(bd->xfer_lists, xfer); + bd->xfer_lists = g_slist_remove(bd->xfer_lists, xfer); purple_debug_info("bonjour", "B free xfer from lists(%p).\n", bd->xfer_lists); } if (xf->proxy_connection != NULL) @@ -359,7 +359,7 @@ purple_xfer_set_cancel_send_fnc(xfer, bonjour_xfer_cancel_send); purple_xfer_set_end_fnc(xfer, bonjour_xfer_end); - bd->xfer_lists = g_list_append(bd->xfer_lists, xfer); + bd->xfer_lists = g_slist_append(bd->xfer_lists, xfer); return xfer; } @@ -605,7 +605,7 @@ purple_xfer_set_cancel_recv_fnc(xfer, bonjour_xfer_cancel_recv); purple_xfer_set_end_fnc(xfer, bonjour_xfer_end); - bd->xfer_lists = g_list_append(bd->xfer_lists, xfer); + bd->xfer_lists = g_slist_append(bd->xfer_lists, xfer); purple_xfer_request(xfer); } diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/bonjour/jabber.c --- a/libpurple/protocols/bonjour/jabber.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Tue Dec 18 09:20:01 2007 +0000 @@ -77,33 +77,8 @@ static void xep_iq_parse(xmlnode *packet, PurpleConnection *connection, PurpleBuddy *pb); -#if 0 /* this isn't used anywhere... */ -static const char * -_font_size_purple_to_ichat(int size) -{ - switch (size) { - case 1: - return "8"; - case 2: - return "10"; - case 3: - return "12"; - case 4: - return "14"; - case 5: - return "17"; - case 6: - return "21"; - case 7: - return "24"; - } - - return "12"; -} -#endif - static BonjourJabberConversation * -bonjour_jabber_conv_new(PurpleBuddy *pb) { +bonjour_jabber_conv_new(PurpleBuddy *pb, PurpleAccount *account, const char *ip) { BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1); bconv->socket = -1; @@ -111,13 +86,14 @@ bconv->tx_handler = 0; bconv->rx_handler = 0; bconv->pb = pb; + bconv->account = account; + bconv->ip = g_strdup(ip); bonjour_parser_setup(bconv); return bconv; } - static const char * _font_size_ichat_to_purple(int size) { @@ -143,34 +119,48 @@ { xmlnode *body_node, *html_node, *events_node; PurpleConnection *gc = pb->account->gc; - char *body, *html_body = NULL; - const char *ichat_balloon_color = NULL; - const char *ichat_text_color = NULL; - const char *font_face = NULL; - const char *font_size = NULL; - const char *font_color = NULL; + gchar *body = NULL; gboolean composing_event = FALSE; body_node = xmlnode_get_child(message_node, "body"); - if (body_node == NULL) + html_node = xmlnode_get_child(message_node, "html"); + + if (body_node == NULL && html_node == NULL) { + purple_debug_error("bonjour", "No body or html node found, discarding message.\n"); return; - body = xmlnode_get_data(body_node); + } - html_node = xmlnode_get_child(message_node, "html"); - if (html_node != NULL) - { + events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event"); + if (events_node != NULL) { + if (xmlnode_get_child(events_node, "composing") != NULL) + composing_event = TRUE; + if (xmlnode_get_child(events_node, "id") != NULL) { + /* The user is just typing */ + /* TODO: Deal with typing notification */ + return; + } + } + + if (html_node != NULL) { xmlnode *html_body_node; html_body_node = xmlnode_get_child(html_node, "body"); - if (html_body_node != NULL) - { + if (html_body_node != NULL) { xmlnode *html_body_font_node; + const char *ichat_balloon_color = NULL; + const char *ichat_text_color = NULL; + const char *font_face = NULL; + const char *font_size = NULL; + const char *font_color = NULL; ichat_balloon_color = xmlnode_get_attrib(html_body_node, "ichatballooncolor"); ichat_text_color = xmlnode_get_attrib(html_body_node, "ichattextcolor"); html_body_font_node = xmlnode_get_child(html_body_node, "font"); - if (html_body_font_node != NULL) - { /* Types of messages sent by iChat */ + + /* Types of messages sent by iChat */ + if (html_body_font_node != NULL) { + gchar *html_body; + font_face = xmlnode_get_attrib(html_body_font_node, "face"); /* The absolute iChat font sizes should be converted to 1..7 range */ font_size = xmlnode_get_attrib(html_body_font_node, "ABSZ"); @@ -178,84 +168,73 @@ font_size = _font_size_ichat_to_purple(atoi(font_size)); font_color = xmlnode_get_attrib(html_body_font_node, "color"); html_body = xmlnode_get_data(html_body_font_node); + if (html_body == NULL) /* This is the kind of formated messages that Purple creates */ html_body = xmlnode_to_str(html_body_font_node, NULL); + + if (html_body != NULL) { + /* Use some sane defaults */ + if (font_face == NULL) font_face = "Helvetica"; + if (font_size == NULL) font_size = "3"; + if (ichat_text_color == NULL) ichat_text_color = "#000000"; + if (ichat_balloon_color == NULL) ichat_balloon_color = "#FFFFFF"; + + body = g_strdup_printf("%s", + font_face, font_size, ichat_text_color, ichat_balloon_color, + html_body); + + g_free(html_body); + } } } } - events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event"); - if (events_node != NULL) - { - if (xmlnode_get_child(events_node, "composing") != NULL) - composing_event = TRUE; - if (xmlnode_get_child(events_node, "id") != NULL) - { - /* The user is just typing */ - /* TODO: Deal with typing notification */ - g_free(body); - g_free(html_body); - return; - } - } + /* Compose the message */ + if (body == NULL && body_node != NULL) + body = xmlnode_get_data(body_node); - /* Compose the message */ - if (html_body != NULL) - { - g_free(body); - - if (font_face == NULL) font_face = "Helvetica"; - if (font_size == NULL) font_size = "3"; - if (ichat_text_color == NULL) ichat_text_color = "#000000"; - if (ichat_balloon_color == NULL) ichat_balloon_color = "#FFFFFF"; - body = g_strdup_printf("%s", - font_face, font_size, ichat_text_color, ichat_balloon_color, - html_body); + if (body == NULL) { + purple_debug_error("bonjour", "No html body or regular body found.\n"); + return; } - /* TODO: Should we do something with "composing_event" here? */ - /* Send the message to the UI */ serv_got_im(gc, pb->name, body, 0, time(NULL)); g_free(body); - g_free(html_body); } -struct _check_buddy_by_address_t { +struct _match_buddies_by_address_t { const char *address; - PurpleBuddy **pb; - BonjourJabber *bj; + GSList *matched_buddies; + BonjourJabber *jdata; }; static void -_check_buddy_by_address(gpointer key, gpointer value, gpointer data) +_match_buddies_by_address(gpointer key, gpointer value, gpointer data) { PurpleBuddy *pb = value; - BonjourBuddy *bb; - struct _check_buddy_by_address_t *cbba = data; + struct _match_buddies_by_address_t *mbba = data; /* * If the current PurpleBuddy's data is not null and the PurpleBuddy's account * is the same as the account requesting the check then continue to determine * whether one of the buddies IPs matches the target IP. */ - if (cbba->bj->account == pb->account) + if (mbba->jdata->account == pb->account && pb->proto_data != NULL) { - bb = pb->proto_data; - if (bb != NULL) { - const char *ip; - GSList *tmp = bb->ips; + const char *ip; + BonjourBuddy *bb = pb->proto_data; + GSList *tmp = bb->ips; - while(tmp) { - ip = tmp->data; - if (ip != NULL && g_ascii_strcasecmp(ip, cbba->address) == 0) { - *(cbba->pb) = pb; - break; - } - tmp = tmp->next; + while(tmp) { + ip = tmp->data; + if (ip != NULL && g_ascii_strcasecmp(ip, mbba->address) == 0) { + mbba->matched_buddies = g_slist_prepend(mbba->matched_buddies, pb); + break; } + tmp = tmp->next; } } } @@ -268,8 +247,6 @@ BonjourJabberConversation *bconv = bb->conversation; int ret, writelen; - /* TODO: Make sure that the stream has been established before sending */ - writelen = purple_circ_buffer_get_max_read(bconv->tx_buf); if (writelen == 0) { @@ -345,7 +322,7 @@ if (ret < len) { /* Don't interfere with the stream starting */ - if (bconv->tx_handler == 0) + if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start && bconv->tx_handler == 0) bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE, _send_data_write_cb, pb); purple_circ_buffer_append(bconv->tx_buf, message + ret, len - ret); @@ -357,6 +334,7 @@ void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet) { g_return_if_fail(packet != NULL); + g_return_if_fail(pb != NULL); if (!strcmp(packet->name, "message")) _jabber_parse_and_write_message_to_ui(packet, pb); @@ -370,31 +348,31 @@ static void _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition) { - PurpleBuddy *pb = data; + BonjourJabberConversation *bconv = data; gint len, message_length; static char message[4096]; - /*TODO: use a static buffer */ - /* Read the data from the socket */ if ((len = recv(socket, message, sizeof(message) - 1, 0)) == -1) { /* There have been an error reading from the socket */ if (errno != EAGAIN) { - BonjourBuddy *bb = pb->proto_data; const char *err = g_strerror(errno); purple_debug_warning("bonjour", "receive error: %s\n", err ? err : "(null)"); - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; + bonjour_jabber_close_conversation(bconv); + if (bconv->pb != NULL) { + BonjourBuddy *bb = bconv->pb->proto_data; + bb->conversation = NULL; + } /* I guess we really don't need to notify the user. * If they try to send another message it'll reconnect */ } return; } else if (len == 0) { /* The other end has closed the socket */ - purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name ? pb->name : "(null)"); - bonjour_jabber_stream_ended(pb); + purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", (bconv->pb && bconv->pb->name) ? bconv->pb->name : "(unknown)"); + bonjour_jabber_stream_ended(bconv); return; } else { message_length = len; @@ -408,30 +386,34 @@ purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", message, len); - bonjour_parser_process(pb, message, message_length); + bonjour_parser_process(bconv, message, message_length); } -void bonjour_jabber_stream_ended(PurpleBuddy *pb) { - BonjourBuddy *bb = pb->proto_data; +void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv) { - purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", pb->name); - - g_return_if_fail(bb != NULL); + purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", bconv->pb ? bconv->pb->name : "(unknown)"); /* Inform the user that the conversation has been closed */ - if (bb->conversation != NULL) { + if (bconv != NULL) { + BonjourBuddy *bb = NULL; + + if(bconv->pb != NULL) + bb = bconv->pb->proto_data; #if 0 - PurpleConversation *conv; - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account); - if (conv != NULL) { - char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name); - purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); - g_free(tmp); + if(bconv->pb != NULL) { + PurpleConversation *conv; + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bconv->pb->name, bconv->pb->account); + if (conv != NULL) { + char *tmp = g_strdup_printf(_("%s has closed the conversation."), bconv->pb->name); + purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); + g_free(tmp); + } } #endif /* Close the socket, clear the watcher and free memory */ - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; + bonjour_jabber_close_conversation(bconv); + if(bb) + bb->conversation = NULL; } } @@ -444,9 +426,7 @@ static void _start_stream(gpointer data, gint source, PurpleInputCondition condition) { - PurpleBuddy *pb = data; - BonjourBuddy *bb = pb->proto_data; - BonjourJabberConversation *bconv = bb->conversation; + BonjourJabberConversation *bconv = data; struct _stream_start_data *ss = bconv->stream_data; int len, ret; @@ -460,23 +440,26 @@ else if (ret <= 0) { const char *err = g_strerror(errno); PurpleConversation *conv; - const char *ip = NULL; + const char *bname = bconv->buddy_name; + BonjourBuddy *bb = NULL; - /* For better or worse, use the first IP*/ - if (bb->ips) - ip = bb->ips->data; + if(bconv->pb) { + bb = bconv->pb->proto_data; + bname = purple_buddy_get_name(bconv->pb); + } - purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n", - purple_buddy_get_name(pb), ip ? ip : "(null)", bb->port_p2pj, err ? err : "(null)"); + purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n", + bname ? bname : "(unknown)", bconv->ip, err ? err : "(null)"); - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account); + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->account); if (conv != NULL) purple_conversation_write(conv, NULL, _("Unable to send the message, the conversation couldn't be started."), PURPLE_MESSAGE_SYSTEM, time(NULL)); bonjour_jabber_close_conversation(bconv); - bb->conversation = NULL; + if(bb != NULL) + bb->conversation = NULL; return; } @@ -498,20 +481,27 @@ bconv->tx_handler = 0; bconv->sent_stream_start = FULLY_SENT; - bonjour_jabber_stream_started(pb); + bonjour_jabber_stream_started(bconv); } -static gboolean bonjour_jabber_send_stream_init(PurpleBuddy *pb, int client_socket) +static gboolean bonjour_jabber_send_stream_init(BonjourJabberConversation *bconv, int client_socket) { int ret, len; char *stream_start; - BonjourBuddy *bb = pb->proto_data; + const char *bname = bconv->buddy_name; + + if (bconv->pb != NULL) + bname = purple_buddy_get_name(bconv->pb); - stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), - purple_buddy_get_name(pb)); + /* If we have no idea who "to" is, use an empty string. + * If we don't know now, it is because the other side isn't playing nice, so they can't complain. */ + if (bname == NULL) + bname = ""; + + stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(bconv->account), bname); len = strlen(stream_start); - bb->conversation->sent_stream_start = PARTIALLY_SENT; + bconv->sent_stream_start = PARTIALLY_SENT; /* Start the stream */ ret = send(client_socket, stream_start, len, 0); @@ -520,14 +510,18 @@ ret = 0; else if (ret <= 0) { const char *err = g_strerror(errno); - const char *ip = NULL; + + purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n", + (*bname) ? bname : "(unknown)", bconv->ip, err ? err : "(null)"); - /* For better or worse, use the first IP*/ - if (bb->ips) - ip = bb->ips->data; - - purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n", - purple_buddy_get_name(pb), ip ? ip : "(null)", bb->port_p2pj, err ? err : "(null)"); + if (bconv->pb) { + PurpleConversation *conv; + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->account); + if (conv != NULL) + purple_conversation_write(conv, NULL, + _("Unable to send the message, the conversation couldn't be started."), + PURPLE_MESSAGE_SYSTEM, time(NULL)); + } close(client_socket); g_free(stream_start); @@ -539,63 +533,61 @@ if (ret < len) { struct _stream_start_data *ss = g_new(struct _stream_start_data, 1); ss->msg = g_strdup(stream_start + ret); - bb->conversation->stream_data = ss; + bconv->stream_data = ss; /* Finish sending the stream start */ - bb->conversation->tx_handler = purple_input_add(client_socket, - PURPLE_INPUT_WRITE, _start_stream, pb); + bconv->tx_handler = purple_input_add(client_socket, + PURPLE_INPUT_WRITE, _start_stream, bconv); } else - bb->conversation->sent_stream_start = FULLY_SENT; + bconv->sent_stream_start = FULLY_SENT; g_free(stream_start); return TRUE; } -static gboolean -_async_bonjour_jabber_close_conversation(gpointer data) { - BonjourJabberConversation *bconv = data; - bonjour_jabber_close_conversation(bconv); - return FALSE; -} +/* This gets called when we've successfully sent our + * AND when we've recieved a */ +void bonjour_jabber_stream_started(BonjourJabberConversation *bconv) { -void bonjour_jabber_stream_started(PurpleBuddy *pb) { - BonjourBuddy *bb = pb->proto_data; - BonjourJabberConversation *bconv = bb->conversation; - - if (bconv->sent_stream_start == NOT_SENT && !bonjour_jabber_send_stream_init(pb, bconv->socket)) { + if (bconv->sent_stream_start == NOT_SENT && !bonjour_jabber_send_stream_init(bconv, bconv->socket)) { const char *err = g_strerror(errno); - PurpleConversation *conv; - const char *ip = NULL; + const char *bname = bconv->buddy_name; - /* For better or worse, use the first IP*/ - if (bb->ips) - ip = bb->ips->data; + if (bconv->pb) + bname = purple_buddy_get_name(bconv->pb); + + purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n", + bname ? bname : "(unknown)", bconv->ip, err ? err : "(null)"); - purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n", - purple_buddy_get_name(pb), ip ? ip : "(null)", bb->port_p2pj, err ? err : "(null)"); + if (bconv->pb) { + PurpleConversation *conv; + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->account); + if (conv != NULL) + purple_conversation_write(conv, NULL, + _("Unable to send the message, the conversation couldn't be started."), + PURPLE_MESSAGE_SYSTEM, time(NULL)); + } - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account); - if (conv != NULL) - purple_conversation_write(conv, NULL, - _("Unable to send the message, the conversation couldn't be started."), - PURPLE_MESSAGE_SYSTEM, time(NULL)); + /* We don't want to recieve anything else */ + close(bconv->socket); + bconv->socket = -1; - close(bconv->socket); /* This must be asynchronous because it destroys the parser and we * may be in the middle of parsing. */ - purple_timeout_add(0, _async_bonjour_jabber_close_conversation, bb->conversation); - bb->conversation = NULL; + async_bonjour_jabber_close_conversation(bconv); return; } - /* If the stream has been completely started, we can start doing stuff */ - if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) { + /* If the stream has been completely started and we know who we're talking to, we can start doing stuff. */ + /* I don't think the circ_buffer can actually contain anything without a buddy being associated, but lets be explicit. */ + if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start + && bconv->pb && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) { /* Watch for when we can write the buffered messages */ bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE, - _send_data_write_cb, pb); + _send_data_write_cb, bconv->pb); /* We can probably write the data right now. */ - _send_data_write_cb(pb, bconv->socket, PURPLE_INPUT_WRITE); + _send_data_write_cb(bconv->pb, bconv->socket, PURPLE_INPUT_WRITE); } } @@ -603,15 +595,14 @@ static void _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition) { - PurpleBuddy *pb = NULL; + BonjourJabber *jdata = data; struct sockaddr_in their_addr; /* connector's address information */ socklen_t sin_size = sizeof(struct sockaddr); int client_socket; int flags; - BonjourBuddy *bb; char *address_text = NULL; - PurpleBuddyList *bl = purple_get_blist(); - struct _check_buddy_by_address_t *cbba; + struct _match_buddies_by_address_t *mbba; + BonjourJabberConversation *bconv; /* Check that it is a read condition */ if (condition != PURPLE_INPUT_READ) @@ -626,39 +617,35 @@ /* Look for the buddy that has opened the conversation and fill information */ address_text = inet_ntoa(their_addr.sin_addr); purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text); - cbba = g_new0(struct _check_buddy_by_address_t, 1); - cbba->address = address_text; - cbba->pb = &pb; - cbba->bj = data; - g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba); - g_free(cbba); - if (pb == NULL) - { + mbba = g_new0(struct _match_buddies_by_address_t, 1); + mbba->address = address_text; + mbba->jdata = jdata; + g_hash_table_foreach(purple_get_blist()->buddies, _match_buddies_by_address, mbba); + + if (mbba->matched_buddies == NULL) { purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheros comic\n"); + g_slist_free(mbba->matched_buddies); + g_free(mbba); close(client_socket); return; } - bb = pb->proto_data; - /* Check if the conversation has been previously started */ - /* This really shouldn't ever happen unless something weird is going on */ - if (bb->conversation == NULL) - { - bb->conversation = bonjour_jabber_conv_new(pb); + g_slist_free(mbba->matched_buddies); + g_free(mbba); - /* We wait for the stream start before doing anything else */ - bb->conversation->socket = client_socket; - bb->conversation->rx_handler = purple_input_add(client_socket, - PURPLE_INPUT_READ, _client_socket_handler, pb); + /* We've established that this *could* be from one of our buddies. + * Wait for the stream open to see if that matches too before assigning it. + */ + bconv = bonjour_jabber_conv_new(NULL, jdata->account, address_text); - } else { - purple_debug_warning("bonjour", "Ignoring incoming connection because an existing connection exists.\n"); - close(client_socket); - } + /* We wait for the stream start before doing anything else */ + bconv->socket = client_socket; + bconv->rx_handler = purple_input_add(client_socket, PURPLE_INPUT_READ, _client_socket_handler, bconv); + } gint -bonjour_jabber_start(BonjourJabber *data) +bonjour_jabber_start(BonjourJabber *jdata) { struct sockaddr_in my_addr; int yes = 1; @@ -666,20 +653,20 @@ gboolean bind_successful; /* Open a listening socket for incoming conversations */ - if ((data->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) + if ((jdata->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { purple_debug_error("bonjour", "Cannot open socket: %s\n", g_strerror(errno)); - purple_connection_error_reason (data->account->gc, + purple_connection_error_reason (jdata->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Cannot open socket")); return -1; } /* Make the socket reusable */ - if (setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0) + if (setsockopt(jdata->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0) { purple_debug_error("bonjour", "Error setting socket options: %s\n", g_strerror(errno)); - purple_connection_error_reason (data->account->gc, + purple_connection_error_reason (jdata->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Error setting socket options")); return -1; @@ -692,30 +679,30 @@ bind_successful = FALSE; for (i = 0; i < 10; i++) { - my_addr.sin_port = htons(data->port); - if (bind(data->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == 0) + my_addr.sin_port = htons(jdata->port); + if (bind(jdata->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == 0) { bind_successful = TRUE; break; } - data->port++; + jdata->port++; } /* On no! We tried 10 ports and could not bind to ANY of them */ if (!bind_successful) { purple_debug_error("bonjour", "Cannot bind socket: %s\n", g_strerror(errno)); - purple_connection_error_reason (data->account->gc, + purple_connection_error_reason (jdata->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Could not bind socket to port")); return -1; } /* Attempt to listen on the bound socket */ - if (listen(data->socket, 10) != 0) + if (listen(jdata->socket, 10) != 0) { purple_debug_error("bonjour", "Cannot listen on socket: %s\n", g_strerror(errno)); - purple_connection_error_reason (data->account->gc, + purple_connection_error_reason (jdata->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Could not listen on socket")); return -1; @@ -723,18 +710,18 @@ #if 0 /* TODO: Why isn't this being used? */ - data->socket = purple_network_listen(data->port, SOCK_STREAM); + data->socket = purple_network_listen(jdata->port, SOCK_STREAM); - if (data->socket == -1) + if (jdata->socket == -1) { purple_debug_error("bonjour", "No se ha podido crear el socket\n"); } #endif /* Open a watcher in the socket we have just opened */ - data->watcher_id = purple_input_add(data->socket, PURPLE_INPUT_READ, _server_socket_handler, data); + jdata->watcher_id = purple_input_add(jdata->socket, PURPLE_INPUT_READ, _server_socket_handler, jdata); - return data->port; + return jdata->port; } static void @@ -747,14 +734,9 @@ if (source < 0) { PurpleConversation *conv; - const char *ip = NULL; - - /* For better or worse, use the first IP*/ - if (bb->ips) - ip = bb->ips->data; purple_debug_error("bonjour", "Error connecting to buddy %s at %s:%d error: %s\n", - purple_buddy_get_name(pb), ip ? ip : "(null)", bb->port_p2pj, error ? error : "(null)"); + purple_buddy_get_name(pb), bb->conversation->ip, bb->port_p2pj, error ? error : "(null)"); conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account); if (conv != NULL) @@ -767,17 +749,12 @@ return; } - if (!bonjour_jabber_send_stream_init(pb, source)) { + if (!bonjour_jabber_send_stream_init(bb->conversation, source)) { const char *err = g_strerror(errno); PurpleConversation *conv; - const char *ip = NULL; - - /* For better or worse, use the first IP*/ - if (bb->ips) - ip = bb->ips->data; purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n", - purple_buddy_get_name(pb), ip ? ip : "(null)", bb->port_p2pj, err ? err : "(null)"); + purple_buddy_get_name(pb), bb->conversation->ip, bb->port_p2pj, err ? err : "(null)"); conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account); if (conv != NULL) @@ -794,19 +771,116 @@ /* Start listening for the stream acknowledgement */ bb->conversation->socket = source; bb->conversation->rx_handler = purple_input_add(source, - PURPLE_INPUT_READ, _client_socket_handler, pb); + PURPLE_INPUT_READ, _client_socket_handler, bb->conversation); +} + +void +bonjour_jabber_conv_match_by_name(BonjourJabberConversation *bconv) { + PurpleBuddy *pb; + + g_return_if_fail(bconv->ip != NULL); + g_return_if_fail(bconv->pb == NULL); + + pb = purple_find_buddy(bconv->account, bconv->buddy_name); + if (pb && pb->proto_data) { + BonjourBuddy *bb = pb->proto_data; + const char *ip; + GSList *tmp = bb->ips; + + purple_debug_info("bonjour", "Found buddy %s for incoming conversation \"from\" attrib.\n", + purple_buddy_get_name(pb)); + + /* Check that one of the buddy's IPs matches */ + while(tmp) { + ip = tmp->data; + if (ip != NULL && g_ascii_strcasecmp(ip, bconv->ip) == 0) { + BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data; + + purple_debug_info("bonjour", "Matched buddy %s to incoming conversation \"from\" attrib and IP (%s)\n", + purple_buddy_get_name(pb), bconv->ip); + + /* Attach conv. to buddy and remove from pending list */ + jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); + + /* Check if the buddy already has a conversation and, if so, replace it */ + if(bb->conversation != NULL && bb->conversation != bconv) + bonjour_jabber_close_conversation(bb->conversation); + + bconv->pb = pb; + bb->conversation = bconv; + + break; + } + tmp = tmp->next; + } + } + + /* We've failed to match a buddy - give up */ + if (bconv->pb == NULL) { + /* This must be asynchronous because it destroys the parser and we + * may be in the middle of parsing. + */ + async_bonjour_jabber_close_conversation(bconv); + } +} + + +void +bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv) { + BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data; + struct _match_buddies_by_address_t *mbba; + + mbba = g_new0(struct _match_buddies_by_address_t, 1); + mbba->address = bconv->ip; + mbba->jdata = jdata; + g_hash_table_foreach(purple_get_blist()->buddies, _match_buddies_by_address, mbba); + + /* If there is exactly one match, use it */ + if(mbba->matched_buddies != NULL) { + if(mbba->matched_buddies->next != NULL) + purple_debug_error("bonjour", "More than one buddy matched for ip %s.\n", bconv->ip); + else { + PurpleBuddy *pb = mbba->matched_buddies->data; + BonjourBuddy *bb = pb->proto_data; + + purple_debug_info("bonjour", "Matched buddy %s to incoming conversation using IP (%s)\n", + purple_buddy_get_name(pb), bconv->ip); + + /* Attach conv. to buddy and remove from pending list */ + jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); + + /* Check if the buddy already has a conversation and, if so, replace it */ + if (bb->conversation != NULL && bb->conversation != bconv) + bonjour_jabber_close_conversation(bb->conversation); + + bconv->pb = pb; + bb->conversation = bconv; + } + } else + purple_debug_error("bonjour", "No buddies matched for ip %s.\n", bconv->ip); + + /* We've failed to match a buddy - give up */ + if (bconv->pb == NULL) { + /* This must be asynchronous because it destroys the parser and we + * may be in the middle of parsing. + */ + async_bonjour_jabber_close_conversation(bconv); + } + + g_slist_free(mbba->matched_buddies); + g_free(mbba); } static PurpleBuddy * -_find_or_start_conversation(BonjourJabber *data, const gchar *to) +_find_or_start_conversation(BonjourJabber *jdata, const gchar *to) { PurpleBuddy *pb = NULL; BonjourBuddy *bb = NULL; - g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(jdata != NULL, NULL); g_return_val_if_fail(to != NULL, NULL); - pb = purple_find_buddy(data->account, to); + pb = purple_find_buddy(jdata->account, to); if (pb == NULL || pb->proto_data == NULL) /* You can not send a message to an offline buddy */ return NULL; @@ -818,24 +892,21 @@ { PurpleProxyConnectData *connect_data; PurpleProxyInfo *proxy_info; - const char *ip = NULL; - /* For better or worse, use the first IP*/ - if (bb->ips) - ip = bb->ips->data; + const char *ip = bb->ips->data; purple_debug_info("bonjour", "Starting conversation with %s\n", to); /* Make sure that the account always has a proxy of "none". * This is kind of dirty, but proxy_connect_none() isn't exposed. */ - proxy_info = purple_account_get_proxy_info(data->account); + proxy_info = purple_account_get_proxy_info(jdata->account); if (proxy_info == NULL) { proxy_info = purple_proxy_info_new(); - purple_account_set_proxy_info(data->account, proxy_info); + purple_account_set_proxy_info(jdata->account, proxy_info); } purple_proxy_info_set_type(proxy_info, PURPLE_PROXY_NONE); - connect_data = purple_proxy_connect(NULL, data->account, + connect_data = purple_proxy_connect(NULL, jdata->account, ip, bb->port_p2pj, _connected_to_buddy, pb); if (connect_data == NULL) { @@ -843,7 +914,7 @@ return NULL; } - bb->conversation = bonjour_jabber_conv_new(pb); + bb->conversation = bonjour_jabber_conv_new(pb, jdata->account, ip); bb->conversation->connect_data = connect_data; /* We don't want _send_data() to register the tx_handler; * that neeeds to wait until we're actually connected. */ @@ -853,7 +924,7 @@ } int -bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body) +bonjour_jabber_send_message(BonjourJabber *jdata, const gchar *to, const gchar *body) { xmlnode *message_node, *node, *node2; gchar *message; @@ -861,7 +932,7 @@ BonjourBuddy *bb; int ret; - pb = _find_or_start_conversation(data, to); + pb = _find_or_start_conversation(jdata, to); if (pb == NULL) { purple_debug_info("bonjour", "Can't send a message to an offline buddy (%s).\n", to); /* You can not send a message to an offline buddy */ @@ -872,7 +943,7 @@ message_node = xmlnode_new("message"); xmlnode_set_attrib(message_node, "to", bb->name); - xmlnode_set_attrib(message_node, "from", purple_account_get_username(data->account)); + xmlnode_set_attrib(message_node, "from", purple_account_get_username(jdata->account)); xmlnode_set_attrib(message_node, "type", "chat"); /* Enclose the message from the UI within a "font" node */ @@ -904,26 +975,57 @@ return ret; } +static gboolean +_async_bonjour_jabber_close_conversation_cb(gpointer data) { + BonjourJabberConversation *bconv = data; + bonjour_jabber_close_conversation(bconv); + return FALSE; +} + +void +async_bonjour_jabber_close_conversation(BonjourJabberConversation *bconv) { + BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data; + + jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); + + /* Disconnect this conv. from the buddy here so it can't be disposed of twice.*/ + if(bconv->pb != NULL) { + BonjourBuddy *bb = bconv->pb->proto_data; + if (bb->conversation == bconv) + bb->conversation = NULL; + } + + purple_timeout_add(0, _async_bonjour_jabber_close_conversation_cb, bconv); +} + void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv) { if (bconv != NULL) { - GList *xfers, *tmp_next; - BonjourData *bd = bconv->pb->account->gc->proto_data; + BonjourData *bd = NULL; + + if(PURPLE_CONNECTION_IS_VALID(bconv->account->gc)) { + bd = bconv->account->gc->proto_data; + bd->jabber_data->pending_conversations = g_slist_remove(bd->jabber_data->pending_conversations, bconv); + } /* Cancel any file transfers that are waiting to begin */ - xfers = bd->xfer_lists; - while(xfers != NULL) { - PurpleXfer *xfer = xfers->data; - tmp_next = xfers->next; - /* We only need to cancel this if it hasn't actually started transferring. */ - /* This will change if we ever support IBB transfers. */ - if (strcmp(xfer->who, bconv->pb->name) == 0 - && (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED - || purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN)) { - purple_xfer_cancel_remote(xfer); + /* There wont be any transfers if it hasn't been attached to a buddy */ + if (bconv->pb != NULL && bd != NULL) { + GSList *xfers, *tmp_next; + xfers = bd->xfer_lists; + while(xfers != NULL) { + PurpleXfer *xfer = xfers->data; + tmp_next = xfers->next; + /* We only need to cancel this if it hasn't actually started transferring. */ + /* This will change if we ever support IBB transfers. */ + if (strcmp(xfer->who, bconv->pb->name) == 0 + && (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED + || purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN)) { + purple_xfer_cancel_remote(xfer); + } + xfers = tmp_next; } - xfers = tmp_next; } /* Close the socket and remove the watcher */ @@ -952,25 +1054,26 @@ if (bconv->context != NULL) bonjour_parser_setup(bconv); + g_free(bconv->buddy_name); + g_free(bconv->ip); g_free(bconv); } } void -bonjour_jabber_stop(BonjourJabber *data) +bonjour_jabber_stop(BonjourJabber *jdata) { /* Close the server socket and remove the watcher */ - if (data->socket >= 0) - close(data->socket); - if (data->watcher_id > 0) - purple_input_remove(data->watcher_id); + if (jdata->socket >= 0) + close(jdata->socket); + if (jdata->watcher_id > 0) + purple_input_remove(jdata->watcher_id); /* Close all the conversation sockets and remove all the watchers after sending end streams */ - if (data->account->gc != NULL) - { + if (jdata->account->gc != NULL) { GSList *buddies, *l; - buddies = purple_find_buddies(data->account, purple_account_get_username(data->account)); + buddies = purple_find_buddies(jdata->account, NULL); for (l = buddies; l; l = l->next) { BonjourBuddy *bb = ((PurpleBuddy*) l->data)->proto_data; bonjour_jabber_close_conversation(bb->conversation); @@ -979,6 +1082,11 @@ g_slist_free(buddies); } + + while (jdata->pending_conversations != NULL) { + bonjour_jabber_close_conversation(jdata->pending_conversations->data); + jdata->pending_conversations = g_slist_delete_link(jdata->pending_conversations, jdata->pending_conversations); + } } XepIq * @@ -1075,7 +1183,7 @@ PurpleBuddy *pb = NULL; /* start the talk, reuse the message socket */ - pb = _find_or_start_conversation ((BonjourJabber*)iq->data, iq->to); + pb = _find_or_start_conversation((BonjourJabber*) iq->data, iq->to); /* Send the message */ if (pb != NULL) { /* Convert xml node into stream */ diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/bonjour/jabber.h --- a/libpurple/protocols/bonjour/jabber.h Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.h Tue Dec 18 09:20:01 2007 +0000 @@ -38,7 +38,8 @@ gint port; gint socket; gint watcher_id; - PurpleAccount* account; + PurpleAccount *account; + GSList *pending_conversations; } BonjourJabber; typedef struct _BonjourJabberConversation @@ -54,6 +55,11 @@ xmlParserCtxt *context; xmlnode *current; PurpleBuddy *pb; + PurpleAccount *account; + + /* The following are only needed before attaching to a PurpleBuddy */ + gchar *buddy_name; + gchar *ip; } BonjourJabberConversation; /** @@ -68,14 +74,20 @@ void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv); -void bonjour_jabber_stream_started(PurpleBuddy *pb); +void async_bonjour_jabber_close_conversation(BonjourJabberConversation *bconv); -void bonjour_jabber_stream_ended(PurpleBuddy *pb); +void bonjour_jabber_stream_started(BonjourJabberConversation *bconv); + +void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv); void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet); void bonjour_jabber_stop(BonjourJabber *data); +void bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv); + +void bonjour_jabber_conv_match_by_name(BonjourJabberConversation *bconv); + typedef enum { XEP_IQ_SET, XEP_IQ_GET, diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/bonjour/parser.c --- a/libpurple/protocols/bonjour/parser.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/bonjour/parser.c Tue Dec 18 09:20:01 2007 +0000 @@ -31,26 +31,59 @@ #include "util.h" #include "xmlnode.h" +static gboolean +parse_from_attrib_and_find_buddy(BonjourJabberConversation *bconv, int nb_attributes, const xmlChar **attributes) { + int i; + + /* If the "from" attribute is specified, attach it to the conversation. */ + for(i=0; i < nb_attributes * 5; i+=5) { + if(!xmlStrcmp(attributes[i], (xmlChar*) "from")) { + int len = attributes[i+4] - attributes[i+3]; + bconv->buddy_name = g_strndup(attributes[i+3], len); + bonjour_jabber_conv_match_by_name(bconv); + + return (bconv->pb != NULL); + } + } + + return FALSE; +} + static void bonjour_parser_element_start_libxml(void *user_data, const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) { - PurpleBuddy *pb = user_data; - BonjourBuddy *bb = pb->proto_data; - BonjourJabberConversation *bconv = bb->conversation; + BonjourJabberConversation *bconv = user_data; xmlnode *node; int i; - if(!element_name) { - return; - } else if(!xmlStrcmp(element_name, (xmlChar*) "stream")) { - bconv->recv_stream_start = TRUE; - bonjour_jabber_stream_started(pb); + g_return_if_fail(element_name != NULL); + + if(!xmlStrcmp(element_name, (xmlChar*) "stream")) { + if(!bconv->recv_stream_start) { + bconv->recv_stream_start = TRUE; + + if (bconv->pb == NULL) + parse_from_attrib_and_find_buddy(bconv, nb_attributes, attributes); + + bonjour_jabber_stream_started(bconv); + } } else { + /* If we haven't yet attached a buddy and this isn't "", + * try to get a "from" attribute as a last resort to match our buddy. */ + if(bconv->pb == NULL + && !(prefix && !xmlStrcmp(prefix, (xmlChar*) "stream") + && !xmlStrcmp(element_name, (xmlChar*) "features")) + && !parse_from_attrib_and_find_buddy(bconv, nb_attributes, attributes)) + /* We've run out of options for finding who the conversation is from + using explicitly specified stuff; see if we can make a good match + by using the IP */ + bonjour_jabber_conv_match_by_ip(bconv); + if(bconv->current) node = xmlnode_new_child(bconv->current, (const char*) element_name); else @@ -82,27 +115,19 @@ } } -static gboolean _async_bonjour_jabber_stream_ended_cb(gpointer data) { - bonjour_jabber_stream_ended((PurpleBuddy *) data); - return FALSE; -} - static void bonjour_parser_element_end_libxml(void *user_data, const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace) { - PurpleBuddy *pb = user_data; - BonjourBuddy *bb = pb->proto_data; - BonjourJabberConversation *bconv = bb->conversation; + BonjourJabberConversation *bconv = user_data; if(!bconv->current) { /* We don't keep a reference to the start stream xmlnode, * so we have to check for it here to close the conversation */ - if(!xmlStrcmp(element_name, (xmlChar*) "stream")) { + if(!xmlStrcmp(element_name, (xmlChar*) "stream")) /* Asynchronously close the conversation to prevent bonjour_parser_setup() * being called from within this context */ - purple_timeout_add(0, _async_bonjour_jabber_stream_ended_cb, pb); - } + async_bonjour_jabber_close_conversation(bconv); return; } @@ -112,7 +137,7 @@ } else { xmlnode *packet = bconv->current; bconv->current = NULL; - bonjour_jabber_process_packet(pb, packet); + bonjour_jabber_process_packet(bconv->pb, packet); xmlnode_free(packet); } } @@ -120,9 +145,7 @@ static void bonjour_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len) { - PurpleBuddy *pb = user_data; - BonjourBuddy *bb = pb->proto_data; - BonjourJabberConversation *bconv = bb->conversation; + BonjourJabberConversation *bconv = user_data; if(!bconv->current) return; @@ -184,21 +207,17 @@ } -void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len) +void bonjour_parser_process(BonjourJabberConversation *bconv, const char *buf, int len) { - BonjourBuddy *bb = pb->proto_data; - g_return_if_fail(bb != NULL); - g_return_if_fail(bb->conversation != NULL); - - if (bb->conversation->context == NULL) { + if (bconv->context == NULL) { /* libxml inconsistently starts parsing on creating the * parser, so do a ParseChunk right afterwards to force it. */ - bb->conversation->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, pb, buf, len, NULL); - xmlParseChunk(bb->conversation->context, "", 0, 0); - } else if (xmlParseChunk(bb->conversation->context, buf, len, 0) < 0) { + bconv->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, bconv, buf, len, NULL); + xmlParseChunk(bconv->context, "", 0, 0); + } else if (xmlParseChunk(bconv->context, buf, len, 0) < 0) /* TODO: What should we do here - I assume we should display an error or something (maybe just print something to the conv?) */ purple_debug_error("bonjour", "Error parsing xml.\n"); - } + } diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/bonjour/parser.h --- a/libpurple/protocols/bonjour/parser.h Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/bonjour/parser.h Tue Dec 18 09:20:01 2007 +0000 @@ -28,6 +28,6 @@ #include "jabber.h" void bonjour_parser_setup(BonjourJabberConversation *bconv); -void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len); +void bonjour_parser_process(BonjourJabberConversation *bconv, const char *buf, int len); #endif /* _PURPLE_BONJOUR_PARSER_H_ */ diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/jabber/auth.c --- a/libpurple/protocols/jabber/auth.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/jabber/auth.c Tue Dec 18 09:20:01 2007 +0000 @@ -330,14 +330,21 @@ disallow_plaintext_auth); g_free(msg); return; - /* Everything else has failed, so fail the - * connection. Should probably have a better - * error here. - */ + } else { - purple_connection_error_reason (js->gc, - PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, - _("Server does not use any supported authentication method")); + /* We have no mechs which can work. + * Try falling back on the old jabber:iq:auth method. We get here if the server supports + * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of + * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect + * jabber:iq:auth in this situation. iChat Server in particular offers SASL GSSAPI by default, which is often + * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails. + * + * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However, + * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms. + * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers + * which would connect without issue otherwise. -evands + */ + jabber_auth_start_old(js); return; } /* not reached */ @@ -563,6 +570,75 @@ } } +/*! + * @brief Given the server challenge (message) and the key (password), calculate the HMAC-MD5 digest + * + * This is the crammd5 response. Inspired by cyrus-sasl's _sasl_hmac_md5() + */ +static void +auth_hmac_md5(const char *challenge, size_t challenge_len, const char *key, size_t key_len, guchar *digest) +{ + PurpleCipher *cipher; + PurpleCipherContext *context; + int i; + /* inner padding - key XORd with ipad */ + unsigned char k_ipad[65]; + /* outer padding - key XORd with opad */ + unsigned char k_opad[65]; + + cipher = purple_ciphers_find_cipher("md5"); + + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (strlen(key) > 64) { + guchar keydigest[16]; + + context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, (const guchar *)key, strlen(key)); + purple_cipher_context_digest(context, 16, keydigest, NULL); + purple_cipher_context_destroy(context); + + key = (char *)keydigest; + key_len = 16; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + memset(k_ipad, '\0', sizeof k_ipad); + memset(k_opad, '\0', sizeof k_opad); + memcpy(k_ipad, (void *)key, key_len); + memcpy(k_opad, (void *)key, key_len); + + /* XOR key with ipad and opad values */ + for (i=0; i<64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + /* perform inner MD5 */ + context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, k_ipad, 64); /* start with inner pad */ + purple_cipher_context_append(context, (const guchar *)challenge, challenge_len); /* then text of datagram */ + purple_cipher_context_digest(context, 16, digest, NULL); /* finish up 1st pass */ + purple_cipher_context_destroy(context); + + /* perform outer MD5 */ + context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, k_opad, 64); /* start with outer pad */ + purple_cipher_context_append(context, digest, 16); /* then results of 1st hash */ + purple_cipher_context_digest(context, 16, digest, NULL); /* finish up 2nd pass */ + purple_cipher_context_destroy(context); +} + static void auth_old_cb(JabberStream *js, xmlnode *packet, gpointer data) { JabberIq *iq; @@ -608,6 +684,33 @@ jabber_iq_set_callback(iq, auth_old_result_cb, NULL); jabber_iq_send(iq); + } else if(js->stream_id && xmlnode_get_child(query, "crammd5")) { + const char *challenge; + guchar digest[16]; + char h[17], *p; + int i; + + iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); + query = xmlnode_get_child(iq->node, "query"); + + x = xmlnode_new_child(query, "username"); + xmlnode_insert_data(x, js->user->node, -1); + x = xmlnode_new_child(query, "resource"); + xmlnode_insert_data(x, js->user->resource, -1); + + x = xmlnode_new_child(query, "crammd5"); + challenge = xmlnode_get_attrib(xmlnode_get_child(query, "crammd5"), "challenge"); + auth_hmac_md5(challenge, strlen(challenge), pw, strlen(pw), &digest); + + /* Translate the digest to a hexadecimal notation */ + p = h; + for(i=0; i<16; i++, p+=2) + snprintf(p, 3, "%02x", digest[i]); + xmlnode_insert_data(x, h, -1); + + jabber_iq_set_callback(iq, auth_old_result_cb, NULL); + jabber_iq_send(iq); + } else if(xmlnode_get_child(query, "password")) { if(js->gsc == NULL && !purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE)) { diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.c Tue Dec 18 09:20:01 2007 +0000 @@ -1153,8 +1153,10 @@ void jabber_vcard_fetch_mine(JabberStream *js) { - JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, "vcard-temp"); - + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET); + + xmlnode *vcard = xmlnode_new_child(iq->node, "vCard"); + xmlnode_set_namespace(vcard, "vcard-temp"); jabber_iq_set_callback(iq, jabber_vcard_save_mine, NULL); jabber_iq_send(iq); diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Tue Dec 18 09:20:01 2007 +0000 @@ -53,7 +53,7 @@ OPT_PROTO_SLASH_COMMANDS_NATIVE, NULL, /* user_splits */ NULL, /* protocol_options */ - {"png", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ + {"png", 32, 32, 96, 96, 0, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ jabber_list_icon, /* list_icon */ jabber_list_emblem, /* list_emblems */ jabber_status_text, /* status_text */ diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/msn/contact.c --- a/libpurple/protocols/msn/contact.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/msn/contact.c Tue Dec 18 09:20:01 2007 +0000 @@ -1289,16 +1289,16 @@ purple_debug_info("MSNCL", "Adding group %s with guid = %s to the userlist\n", state->new_group_name, guid); msn_group_new(session->userlist, guid, state->new_group_name); - g_free(guid); - if (state->action & MSN_ADD_BUDDY) { msn_userlist_add_buddy(session->userlist, state->who, state->new_group_name); } else if (state->action & MSN_MOVE_BUDDY) { msn_add_contact_to_group(session->contact, state, state->who, guid); + g_free(guid); return; } + g_free(guid); } else { purple_debug_info("MSNCL", "Adding group %s failed\n", state->new_group_name); diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/sametime/sametime.c --- a/libpurple/protocols/sametime/sametime.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/sametime/sametime.c Tue Dec 18 09:20:01 2007 +0000 @@ -3749,7 +3749,7 @@ client = purple_account_get_int(account, MW_KEY_CLIENT, mwLogin_BINARY); major = purple_account_get_int(account, MW_KEY_MAJOR, 0x001e); - minor = purple_account_get_int(account, MW_KEY_MINOR, 0x001d); + minor = purple_account_get_int(account, MW_KEY_MINOR, 0x196f); DEBUG_INFO("client id: 0x%04x\n", client); DEBUG_INFO("client major: 0x%04x\n", major); diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/simple/sipmsg.c --- a/libpurple/protocols/simple/sipmsg.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/simple/sipmsg.c Tue Dec 18 09:20:01 2007 +0000 @@ -45,7 +45,10 @@ line = g_strndup(msg, tmp - msg); smsg = sipmsg_parse_header(line); - smsg->body = g_strdup(tmp + 4); + if(smsg != NULL) + smsg->body = g_strdup(tmp + 4); + else + purple_debug_error("SIMPLE", "No header parsed from line: %s\n", line); g_free(line); return smsg; diff -r f5d961556972 -r 5814ae85e756 libpurple/protocols/yahoo/util.c --- a/libpurple/protocols/yahoo/util.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/protocols/yahoo/util.c Tue Dec 18 09:20:01 2007 +0000 @@ -715,12 +715,15 @@ } else if (((len - i) >= 4) && !strncmp(&src[i], ">", 4)) { g_string_append_c(dest, '>'); i += 3; - } else if (((len - i) >= 5) && !strncmp(&src[i], "&", 4)) { + } else if (((len - i) >= 5) && !strncmp(&src[i], "&", 5)) { g_string_append_c(dest, '&'); i += 4; - } else if (((len - i) >= 6) && !strncmp(&src[i], """, 4)) { + } else if (((len - i) >= 6) && !strncmp(&src[i], """, 6)) { g_string_append_c(dest, '"'); i += 5; + } else if (((len - i) >= 6) && !strncmp(&src[i], "'", 6)) { + g_string_append_c(dest, '\''); + i += 5; } else { g_string_append_c(dest, src[i]); } diff -r f5d961556972 -r 5814ae85e756 libpurple/status.c --- a/libpurple/status.c Tue Dec 18 09:17:39 2007 +0000 +++ b/libpurple/status.c Tue Dec 18 09:20:01 2007 +0000 @@ -607,13 +607,10 @@ if (old_status != NULL) { - tmp = g_strdup_printf(_("%s changed status from %s to %s"), buddy_alias, + tmp = g_strdup_printf(_("%s (%s) changed status from %s to %s"), buddy_alias, buddy->name, purple_status_get_name(old_status), purple_status_get_name(new_status)); - logtmp = g_strdup_printf(_("%s (%s) changed status from %s to %s"), buddy_alias, buddy->name, - purple_status_get_name(old_status), - purple_status_get_name(new_status)); - + logtmp = g_markup_escape_text(tmp, -1); } else { @@ -621,18 +618,15 @@ if (purple_status_is_active(new_status)) { - tmp = g_strdup_printf(_("%s is now %s"), buddy_alias, + tmp = g_strdup_printf(_("%s (%s) is now %s"), buddy_alias, buddy->name, purple_status_get_name(new_status)); - logtmp = g_strdup_printf(_("%s (%s) is now %s"), buddy_alias, buddy->name, - purple_status_get_name(new_status)); - + logtmp = g_markup_escape_text(tmp, -1); } else { - tmp = g_strdup_printf(_("%s is no longer %s"), buddy_alias, + tmp = g_strdup_printf(_("%s (%s) is no longer %s"), buddy_alias, buddy->name, purple_status_get_name(new_status)); - logtmp = g_strdup_printf(_("%s (%s) is no longer %s"), buddy_alias, buddy->name, - purple_status_get_name(new_status)); + logtmp = g_markup_escape_text(tmp, -1); } } @@ -1244,12 +1238,15 @@ if (log != NULL) { - char *tmp = g_strdup_printf(_("%s became idle"), + char *tmp, *tmp2; + tmp = g_strdup_printf(_("%s became idle"), purple_buddy_get_alias(buddy)); + tmp2 = g_markup_escape_text(tmp, -1); + g_free(tmp); purple_log_write(log, PURPLE_MESSAGE_SYSTEM, - purple_buddy_get_alias(buddy), current_time, tmp); - g_free(tmp); + purple_buddy_get_alias(buddy), current_time, tmp2); + g_free(tmp2); } } } @@ -1261,12 +1258,15 @@ if (log != NULL) { - char *tmp = g_strdup_printf(_("%s became unidle"), + char *tmp, *tmp2; + tmp = g_strdup_printf(_("%s became unidle"), purple_buddy_get_alias(buddy)); + tmp2 = g_markup_escape_text(tmp, -1); + g_free(tmp); purple_log_write(log, PURPLE_MESSAGE_SYSTEM, - purple_buddy_get_alias(buddy), current_time, tmp); - g_free(tmp); + purple_buddy_get_alias(buddy), current_time, tmp2); + g_free(tmp2); } } } @@ -1321,13 +1321,15 @@ if (log != NULL) { - char *msg; + char *msg, *tmp; if (idle) - msg = g_strdup_printf(_("+++ %s became idle"), purple_account_get_username(account)); + tmp = g_strdup_printf(_("+++ %s became idle"), purple_account_get_username(account)); else - msg = g_strdup_printf(_("+++ %s became unidle"), purple_account_get_username(account)); + tmp = g_strdup_printf(_("+++ %s became unidle"), purple_account_get_username(account)); + msg = g_markup_escape_text(tmp, -1); + g_free(tmp); purple_log_write(log, PURPLE_MESSAGE_SYSTEM, purple_account_get_username(account), (idle ? idle_time : current_time), msg); diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkaccount.c --- a/pidgin/gtkaccount.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkaccount.c Tue Dec 18 09:20:01 2007 +0000 @@ -1437,7 +1437,6 @@ GtkWidget *win; GtkWidget *main_vbox; GtkWidget *vbox; - GtkWidget *bbox; GtkWidget *dbox; GtkWidget *notebook; GtkWidget *button; @@ -1475,16 +1474,14 @@ if ((dialog->plugin = purple_find_prpl(dialog->protocol_id)) != NULL) dialog->prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(dialog->plugin); - dialog->window = win = pidgin_create_window((type == PIDGIN_ADD_ACCOUNT_DIALOG) ? _("Add Account") : _("Modify Account"), + dialog->window = win = pidgin_create_dialog((type == PIDGIN_ADD_ACCOUNT_DIALOG) ? _("Add Account") : _("Modify Account"), PIDGIN_HIG_BORDER, "account", FALSE); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(account_win_destroy_cb), dialog); /* Setup the vbox */ - main_vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), main_vbox); - gtk_widget_show(main_vbox); + main_vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); notebook = gtk_notebook_new(); gtk_box_pack_start(GTK_BOX(main_vbox), notebook, FALSE, FALSE, 0); @@ -1511,8 +1508,6 @@ if (!dialog->prpl_info || !dialog->prpl_info->register_user) gtk_widget_hide(button); - - /* Setup the page with 'Advanced'. */ dialog->bottom_vbox = dbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_container_set_border_width(GTK_CONTAINER(dbox), PIDGIN_HIG_BORDER); @@ -1524,30 +1519,13 @@ add_protocol_options(dialog, dbox); add_proxy_options(dialog, dbox); - /* Setup the button box */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(main_vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); - /* Cancel button */ - button = gtk_button_new_from_stock(GTK_STOCK_CANCEL); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(cancel_account_prefs_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL, G_CALLBACK(cancel_account_prefs_cb), dialog); /* Save button */ - button = gtk_button_new_from_stock(GTK_STOCK_SAVE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE, G_CALLBACK(ok_account_prefs_cb), dialog); if (dialog->account == NULL) gtk_widget_set_sensitive(button, FALSE); - - gtk_widget_show(button); - dialog->ok_button = button; /* Set up DND */ @@ -1561,9 +1539,6 @@ g_signal_connect(G_OBJECT(dialog->window), "drag_data_received", G_CALLBACK(account_dnd_recv), dialog); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(ok_account_prefs_cb), dialog); - /* Show the window. */ gtk_widget_show(win); } @@ -2313,7 +2288,6 @@ AccountsWindow *dialog; GtkWidget *win; GtkWidget *vbox; - GtkWidget *bbox; GtkWidget *sw; GtkWidget *button; int width, height; @@ -2328,7 +2302,7 @@ width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width"); height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/height"); - dialog->window = win = pidgin_create_window(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE); + dialog->window = win = pidgin_create_dialog(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); g_signal_connect(G_OBJECT(win), "delete_event", @@ -2337,57 +2311,28 @@ G_CALLBACK(configure_cb), accounts_window); /* Setup the vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); /* Setup the scrolled window that will contain the list of accounts. */ sw = create_accounts_list(dialog); gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); gtk_widget_show(sw); - /* Button box. */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); - /* Add button */ - button = gtk_button_new_from_stock(GTK_STOCK_ADD); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(add_account_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD, G_CALLBACK(add_account_cb), dialog); /* Modify button */ - button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY); + button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY, G_CALLBACK(modify_account_cb), dialog); dialog->modify_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); gtk_widget_set_sensitive(button, FALSE); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(modify_account_cb), dialog); /* Delete button */ - button = gtk_button_new_from_stock(GTK_STOCK_DELETE); + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE, G_CALLBACK(ask_delete_account_cb), dialog); dialog->delete_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); gtk_widget_set_sensitive(button, FALSE); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(ask_delete_account_cb), dialog); /* Close button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(close_accounts_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, G_CALLBACK(close_accounts_cb), dialog); purple_signal_connect(pidgin_account_get_handle(), "account-modified", accounts_window, diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkblist.c --- a/pidgin/gtkblist.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkblist.c Tue Dec 18 09:20:01 2007 +0000 @@ -4542,6 +4542,13 @@ } static void +clear_elsewhere_errors(PidginMiniDialog *mini_dialog, + gpointer unused) +{ + elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error); +} + +static void ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist) { PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); @@ -4556,6 +4563,12 @@ pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"), reconnect_elsewhere_accounts, NULL); + /* Make dismissing the dialog clear the errors. The "destroy" signal + * does not appear to fire at quit, which is fortunate! + */ + g_signal_connect(G_OBJECT(mini_dialog), "destroy", + (GCallback) clear_elsewhere_errors, NULL); + add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog)); /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */ diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkcertmgr.c --- a/pidgin/gtkcertmgr.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkcertmgr.c Tue Dec 18 09:20:01 2007 +0000 @@ -559,7 +559,6 @@ CertMgrDialog *dlg; GtkWidget *win; GtkWidget *vbox; - GtkWidget *bbox; /* Enumerate all the certificates on file */ { @@ -599,7 +598,7 @@ dlg = certmgr_dialog = g_new0(CertMgrDialog, 1); win = dlg->window = - pidgin_create_window(_("Certificate Manager"),/* Title */ + pidgin_create_dialog(_("Certificate Manager"),/* Title */ PIDGIN_HIG_BORDER, /*Window border*/ "certmgr", /* Role */ TRUE); /* Allow resizing */ @@ -611,9 +610,7 @@ gtk_window_set_default_size(GTK_WINDOW(win), 400, 400); /* Main vbox */ - vbox = gtk_vbox_new( FALSE, PIDGIN_HIG_BORDER ); - gtk_container_add(GTK_CONTAINER(win), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); /* Notebook of various certificate managers */ dlg->notebook = gtk_notebook_new(); @@ -622,19 +619,9 @@ 0); gtk_widget_show(dlg->notebook); - /* Box for the close button */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); - /* Close button */ - dlg->closebutton = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), dlg->closebutton, FALSE, FALSE, 0); - gtk_widget_show(dlg->closebutton); - g_signal_connect(G_OBJECT(dlg->closebutton), "clicked", - G_CALLBACK(certmgr_close_cb), dlg); + dlg->closebutton = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, + G_CALLBACK(certmgr_close_cb), dlg); /* Add the defined certificate managers */ /* TODO: Find a way of determining whether each is shown or not */ diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkconn.c --- a/pidgin/gtkconn.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkconn.c Tue Dec 18 09:20:01 2007 +0000 @@ -184,36 +184,39 @@ static void pidgin_connection_network_connected () { - GList *list = purple_accounts_get_all_active(); + GList *list, *l; PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist(); if(gtkblist) pidgin_status_box_set_network_available(PIDGIN_STATUS_BOX(gtkblist->statusbox), TRUE); - while (list) { - PurpleAccount *account = (PurpleAccount*)list->data; + l = list = purple_accounts_get_all_active(); + while (l) { + PurpleAccount *account = (PurpleAccount*)l->data; g_hash_table_remove(auto_reconns, account); if (purple_account_is_disconnected(account)) do_signon(account); - list = list->next; + l = l->next; } + g_list_free(list); } static void pidgin_connection_network_disconnected () { - GList *l = purple_accounts_get_all_active(); + GList *list, *l; PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist(); PurplePluginProtocolInfo *prpl_info = NULL; PurpleConnection *gc = NULL; - + if(gtkblist) pidgin_status_box_set_network_available(PIDGIN_STATUS_BOX(gtkblist->statusbox), FALSE); + l = list = purple_accounts_get_all_active(); while (l) { PurpleAccount *a = (PurpleAccount*)l->data; if (!purple_account_is_disconnected(a)) { gc = purple_account_get_connection(a); - if (gc && gc->prpl) + if (gc && gc->prpl) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); if (prpl_info) { if (prpl_info->keepalive) @@ -224,6 +227,7 @@ } l = l->next; } + g_list_free(list); } static void pidgin_connection_notice(PurpleConnection *gc, const char *text) diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkconv.c --- a/pidgin/gtkconv.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkconv.c Tue Dec 18 09:20:01 2007 +0000 @@ -95,9 +95,10 @@ #define PIDGIN_CONV_ALL ((1 << 7) - 1) -#define SEND_COLOR "#204a87" -#define RECV_COLOR "#cc0000" -#define HIGHLIGHT_COLOR "#AF7F00" +#define DEFAULT_SEND_COLOR "#204a87" +#define DEFAULT_RECV_COLOR "#cc0000" +#define DEFAULT_HIGHLIGHT_COLOR "#AF7F00" +#define DEFAULT_ACTION_COLOR "#062585" /* Undef this to turn off "custom-smiley" debug messages */ #define DEBUG_CUSTOM_SMILEY @@ -3790,7 +3791,7 @@ if (is_me) { GdkColor send_color; - gdk_color_parse(SEND_COLOR, &send_color); + gdk_color_parse(DEFAULT_SEND_COLOR, &send_color); #if GTK_CHECK_VERSION(2,6,0) gtk_list_store_insert_with_values(ls, &iter, @@ -4343,14 +4344,15 @@ /* Show a maximum of 4 lines */ lines = MIN(lines, 4); - wrapped_lines = MIN(wrapped_lines, 4); + wrapped_lines = MIN(MAX(wrapped_lines, 2), 4); pad_top = gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(gtkconv->entry)); pad_bottom = gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(gtkconv->entry)); pad_inside = gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(gtkconv->entry)); - height = (oneline.height + pad_top + pad_bottom) * (lines + 1); - height += (oneline.height + pad_inside) * (wrapped_lines - lines); + height = (oneline.height + pad_top + pad_bottom) * lines; + if (wrapped_lines > lines) + height += (oneline.height + pad_inside) * (wrapped_lines - lines); gtkconv->auto_resize = TRUE; g_idle_add(reset_auto_resize_cb, gtkconv); @@ -5601,6 +5603,7 @@ } else { if (purple_message_meify(new_message, -1)) { + GdkColor *col; str = g_malloc(1024); if (flags & PURPLE_MESSAGE_AUTO_RESP) { @@ -5613,9 +5616,20 @@ } if (flags & PURPLE_MESSAGE_NICK) - strcpy(color, HIGHLIGHT_COLOR); + gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "highlight-name-color", &col, NULL); else - strcpy(color, "#062585"); + gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "action-name-color", &col, NULL); + + if(col) { + g_snprintf(color, sizeof(color), "#%02X%02X%02X", + col->red >> 8, col->green >> 8, col->blue >> 8); + } + else { + if (flags & PURPLE_MESSAGE_NICK) + strcpy(color, DEFAULT_HIGHLIGHT_COLOR); + else + strcpy(color, DEFAULT_ACTION_COLOR); + } } else { str = g_malloc(1024); @@ -5627,19 +5641,46 @@ g_snprintf(str, 1024, "%s:", alias_escaped); tag_end_offset = 1; } - if (flags & PURPLE_MESSAGE_NICK) - strcpy(color, HIGHLIGHT_COLOR); + if (flags & PURPLE_MESSAGE_NICK) { + GdkColor *col; + gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "highlight-name-color", &col, NULL); + if(col) { + g_snprintf(color, sizeof(color), "#%02X%02X%02X", + col->red >> 8, col->green >> 8, col->blue >> 8); + } + else { + strcpy(color, DEFAULT_HIGHLIGHT_COLOR); + } + } else if (flags & PURPLE_MESSAGE_RECV) { if (type == PURPLE_CONV_TYPE_CHAT) { GdkColor *col = get_nick_color(gtkconv, name); g_snprintf(color, sizeof(color), "#%02X%02X%02X", col->red >> 8, col->green >> 8, col->blue >> 8); - } else - strcpy(color, RECV_COLOR); + } else { + GdkColor *col; + gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "receive-name-color", &col, NULL); + if(col) { + g_snprintf(color, sizeof(color), "#%02X%02X%02X", + col->red >> 8, col->green >> 8, col->blue >> 8); + } + else { + strcpy(color, DEFAULT_RECV_COLOR); + } + } } - else if (flags & PURPLE_MESSAGE_SEND) - strcpy(color, SEND_COLOR); + else if (flags & PURPLE_MESSAGE_SEND) { + GdkColor *col; + gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "send-name-color", &col, NULL); + if(col) { + g_snprintf(color, sizeof(color), "#%02X%02X%02X", + col->red >> 8, col->green >> 8, col->blue >> 8); + } + else { + strcpy(color, DEFAULT_SEND_COLOR); + } + } else { purple_debug_error("gtkconv", "message missing flags\n"); strcpy(color, "#000000"); @@ -8277,7 +8318,7 @@ } if (e->button == 3) { - /* Right click was pressed. Popup the Send To menu. */ + /* Right click was pressed. Popup the context menu. */ GtkWidget *menu = gtk_menu_new(), *sub; gboolean populated = populate_menu_with_options(menu, gtkconv, TRUE); sub = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv->win->menu.send_to)); @@ -9922,8 +9963,8 @@ GdkColor send_color; time_t breakout_time; - gdk_color_parse(HIGHLIGHT_COLOR, &nick_highlight); - gdk_color_parse(SEND_COLOR, &send_color); + gdk_color_parse(DEFAULT_HIGHLIGHT_COLOR, &nick_highlight); + gdk_color_parse(DEFAULT_SEND_COLOR, &send_color); srand(background.red + background.green + background.blue + 1); diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkft.c --- a/pidgin/gtkft.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkft.c Tue Dec 18 09:20:01 2007 +0000 @@ -745,7 +745,6 @@ PidginXferDialog *dialog; GtkWidget *window; GtkWidget *vbox1, *vbox2; - GtkWidget *bbox; GtkWidget *sw; GtkWidget *button; GtkWidget *expander; @@ -759,15 +758,13 @@ purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished"); /* Create the window. */ - dialog->window = window = pidgin_create_window(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE); + dialog->window = window = pidgin_create_dialog(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_win_cb), dialog); /* Create the parent vbox for everything. */ - vbox1 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(window), vbox1); - gtk_widget_show(vbox1); + vbox1 = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER); /* Create the main vbox for top half of the window. */ vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); @@ -812,71 +809,35 @@ gtk_container_add(GTK_CONTAINER(expander), table); gtk_widget_show(table); - /* Now the button box for the buttons */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox1), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); - /* Open button */ - button = gtk_button_new_from_stock(GTK_STOCK_OPEN); + button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_OPEN, G_CALLBACK(open_button_cb), dialog); gtk_widget_set_sensitive(button, FALSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); dialog->open_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(open_button_cb), dialog); - /* Pause button */ - button = gtk_button_new_with_mnemonic(_("_Pause")); + button = pidgin_dialog_add_button(GTK_DIALOG(window), _("_Pause"), G_CALLBACK(pause_button_cb), dialog); gtk_widget_set_sensitive(button, FALSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); dialog->pause_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(pause_button_cb), dialog); - /* Resume button */ - button = gtk_button_new_with_mnemonic(_("_Resume")); + button = pidgin_dialog_add_button(GTK_DIALOG(window), _("_Resume"), G_CALLBACK(resume_button_cb), dialog); gtk_widget_set_sensitive(button, FALSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); dialog->resume_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(resume_button_cb), dialog); - /* Remove button */ - button = gtk_button_new_from_stock(GTK_STOCK_REMOVE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_REMOVE, G_CALLBACK(remove_button_cb), dialog); gtk_widget_hide(button); dialog->remove_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(remove_button_cb), dialog); - /* Stop button */ - button = gtk_button_new_from_stock(GTK_STOCK_STOP); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); + button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP, G_CALLBACK(stop_button_cb), dialog); gtk_widget_set_sensitive(button, FALSE); dialog->stop_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(stop_button_cb), dialog); - /* Close button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); + button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, G_CALLBACK(close_button_cb), dialog); dialog->close_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(close_button_cb), dialog); - #ifdef _WIN32 g_signal_connect(G_OBJECT(dialog->window), "show", G_CALLBACK(winpidgin_ensure_onscreen), dialog->window); diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkimhtml.c --- a/pidgin/gtkimhtml.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkimhtml.c Tue Dec 18 09:20:01 2007 +0000 @@ -1012,7 +1012,7 @@ static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext) { GtkTextIter iter; - GtkIMHtmlOptions flags = plaintext ? 0 : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS); + GtkIMHtmlOptions flags = plaintext ? GTK_IMHTML_NO_SMILEY : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS); if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE); @@ -1397,6 +1397,22 @@ _("Hyperlink prelight color"), _("Color to draw hyperlinks when mouse is over them."), GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("send-name-color", + _("Sent Message Name Color"), + _("Color to draw the name of a message you sent."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("receive-name-color", + _("Received Message Name Color"), + _("Color to draw the name of a message you received."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("highlight-name-color", + _("\"Attention\" Name Color"), + _("Color to draw the name of a message you received containing your name."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("action-name-color", + _("Action Message Name Color"), + _("Color to draw the name of an action message."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); binding_set = gtk_binding_set_by_class (parent_class); gtk_binding_entry_add_signal (binding_set, GDK_b, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_BOLD); @@ -2982,6 +2998,7 @@ pos += tlen; g_free(tag); /* This was allocated back in VALID_TAG() */ } else if (imhtml->edit.link == NULL && + !(options & GTK_IMHTML_NO_SMILEY) && gtk_imhtml_is_smiley(imhtml, fonts, c, &smilelen)) { GtkIMHtmlFontDetail *fd; gchar *sml = NULL; diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkimhtml.h --- a/pidgin/gtkimhtml.h Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkimhtml.h Tue Dec 18 09:20:01 2007 +0000 @@ -225,7 +225,8 @@ GTK_IMHTML_RETURN_LOG = 1 << 7, GTK_IMHTML_USE_POINTSIZE = 1 << 8, GTK_IMHTML_NO_FORMATTING = 1 << 9, - GTK_IMHTML_USE_SMOOTHSCROLLING = 1 << 10 + GTK_IMHTML_USE_SMOOTHSCROLLING = 1 << 10, + GTK_IMHTML_NO_SMILEY = 1 << 11, } GtkIMHtmlOptions; enum { diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkimhtmltoolbar.c --- a/pidgin/gtkimhtmltoolbar.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Tue Dec 18 09:20:01 2007 +0000 @@ -1096,8 +1096,8 @@ {PIDGIN_STOCK_TOOLBAR_TEXT_SMALLER, do_small, &toolbar->smaller_size, _("Decrease Font Size")}, {"", NULL, NULL, NULL}, {PIDGIN_STOCK_TOOLBAR_FONT_FACE, toggle_font, &toolbar->font, _("Font Face")}, - {PIDGIN_STOCK_TOOLBAR_FGCOLOR, toggle_bg_color, &toolbar->bgcolor, _("Background Color")}, - {PIDGIN_STOCK_TOOLBAR_BGCOLOR, toggle_fg_color, &toolbar->fgcolor, _("Foreground Color")}, + {PIDGIN_STOCK_TOOLBAR_BGCOLOR, toggle_bg_color, &toolbar->bgcolor, _("Background Color")}, + {PIDGIN_STOCK_TOOLBAR_FGCOLOR, toggle_fg_color, &toolbar->fgcolor, _("Foreground Color")}, {"", NULL, NULL, NULL}, {PIDGIN_STOCK_CLEAR, clear_formatting_cb, &toolbar->clear, _("Reset Formatting")}, {"", NULL, NULL, NULL}, diff -r f5d961556972 -r 5814ae85e756 pidgin/gtknotify.c --- a/pidgin/gtknotify.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtknotify.c Tue Dec 18 09:20:01 2007 +0000 @@ -284,6 +284,8 @@ gtk_misc_set_alignment(GTK_MISC(label), 0, 0); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + pidgin_auto_parent_window(dialog); + gtk_widget_show_all(dialog); return dialog; @@ -684,6 +686,8 @@ g_object_set_data(G_OBJECT(window), "info-widget", imhtml); /* Show the window */ + pidgin_auto_parent_window(window); + gtk_widget_show(window); return window; @@ -894,6 +898,8 @@ pidgin_notify_searchresults_new_rows(gc, results, data); /* Show the window */ + pidgin_auto_parent_window(window); + gtk_widget_show(window); return data; } diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkpounce.c --- a/pidgin/gtkpounce.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkpounce.c Tue Dec 18 09:20:01 2007 +0000 @@ -1317,7 +1317,6 @@ pidgin_pounces_manager_show(void) { PouncesManager *dialog; - GtkWidget *bbox; GtkWidget *button; GtkWidget *list; GtkWidget *vbox; @@ -1334,7 +1333,7 @@ width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/width"); height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/height"); - dialog->window = win = pidgin_create_window(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE); + dialog->window = win = pidgin_create_dialog(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); g_signal_connect(G_OBJECT(win), "delete_event", @@ -1343,61 +1342,33 @@ G_CALLBACK(pounces_manager_configure_cb), dialog); /* Setup the vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); /* List of saved buddy pounces */ list = create_pounces_list(dialog); gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0); - /* Button box. */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); + /* Add button */ + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD, G_CALLBACK(pounces_manager_add_cb), dialog); + gtk_widget_set_sensitive(button, (purple_accounts_get_all() != NULL)); - /* Add button */ - button = gtk_button_new_from_stock(GTK_STOCK_ADD); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_set_sensitive(button, (purple_accounts_get_all() != NULL)); purple_signal_connect(purple_connections_get_handle(), "signed-on", pounces_manager, PURPLE_CALLBACK(pounces_manager_connection_cb), button); purple_signal_connect(purple_connections_get_handle(), "signed-off", pounces_manager, PURPLE_CALLBACK(pounces_manager_connection_cb), button); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(pounces_manager_add_cb), dialog); /* Modify button */ - button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY); - dialog->modify_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY, G_CALLBACK(pounces_manager_modify_cb), dialog); gtk_widget_set_sensitive(button, FALSE); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(pounces_manager_modify_cb), dialog); + dialog->modify_button = button; /* Delete button */ - button = gtk_button_new_from_stock(GTK_STOCK_DELETE); - dialog->delete_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE, G_CALLBACK(pounces_manager_delete_cb), dialog); gtk_widget_set_sensitive(button, FALSE); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(pounces_manager_delete_cb), dialog); + dialog->delete_button = button; /* Close button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(pounces_manager_close_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, G_CALLBACK(pounces_manager_close_cb), dialog); gtk_widget_show(win); } diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkprefs.c --- a/pidgin/gtkprefs.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkprefs.c Tue Dec 18 09:20:01 2007 +0000 @@ -2150,7 +2150,6 @@ void pidgin_prefs_show(void) { GtkWidget *vbox; - GtkWidget *bbox; GtkWidget *notebook; GtkWidget *button; @@ -2166,31 +2165,20 @@ /* Back to instant-apply! I win! BU-HAHAHA! */ /* Create the window */ - prefs = pidgin_create_window(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE); + prefs = pidgin_create_dialog(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE); g_signal_connect(G_OBJECT(prefs), "destroy", G_CALLBACK(delete_prefs), NULL); - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(prefs), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(prefs), FALSE, PIDGIN_HIG_BORDER); /* The notebook */ prefsnotebook = notebook = gtk_notebook_new (); gtk_box_pack_start (GTK_BOX (vbox), notebook, FALSE, FALSE, 0); gtk_widget_show(prefsnotebook); - /* The buttons to press! */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); - gtk_widget_show (bbox); - - button = gtk_button_new_from_stock (GTK_STOCK_CLOSE); + button = pidgin_dialog_add_button(GTK_DIALOG(prefs), GTK_STOCK_CLOSE, NULL, NULL); g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), prefs); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); prefs_notebook_init(); diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkprivacy.c --- a/pidgin/gtkprivacy.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkprivacy.c Tue Dec 18 09:20:01 2007 +0000 @@ -45,6 +45,7 @@ GtkWidget *add_button; GtkWidget *remove_button; GtkWidget *clear_button; + GtkWidget *close_button; GtkWidget *button_box; GtkWidget *allow_widget; @@ -259,19 +260,22 @@ gtk_widget_hide(dialog->allow_widget); gtk_widget_hide(dialog->block_widget); - gtk_widget_hide(dialog->button_box); + gtk_widget_hide_all(dialog->button_box); if (new_type == PURPLE_PRIVACY_ALLOW_USERS) { gtk_widget_show(dialog->allow_widget); - gtk_widget_show(dialog->button_box); + gtk_widget_show_all(dialog->button_box); dialog->in_allow_list = TRUE; } else if (new_type == PURPLE_PRIVACY_DENY_USERS) { gtk_widget_show(dialog->block_widget); - gtk_widget_show(dialog->button_box); + gtk_widget_show_all(dialog->button_box); dialog->in_allow_list = FALSE; } + gtk_widget_show_all(dialog->close_button); + gtk_widget_show(dialog->button_box); + purple_blist_schedule_save(); pidgin_blist_refresh(purple_get_blist()); } @@ -355,7 +359,6 @@ privacy_dialog_new(void) { PidginPrivacyDialog *dialog; - GtkWidget *bbox; GtkWidget *hbox; GtkWidget *vbox; GtkWidget *button; @@ -367,15 +370,13 @@ dialog = g_new0(PidginPrivacyDialog, 1); - dialog->win = pidgin_create_window(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", TRUE); + dialog->win = pidgin_create_dialog(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", TRUE); g_signal_connect(G_OBJECT(dialog->win), "delete_event", G_CALLBACK(destroy_cb), dialog); /* Main vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(dialog->win), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(dialog->win), FALSE, PIDGIN_HIG_BORDER); /* Description label */ label = gtk_label_new( @@ -433,52 +434,27 @@ gtk_box_pack_start(GTK_BOX(vbox), dialog->block_widget, TRUE, TRUE, 0); /* Add the button box for Add, Remove, Clear */ - dialog->button_box = bbox = gtk_hbutton_box_new(); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_SPREAD); - gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); + dialog->button_box = pidgin_dialog_get_action_area(GTK_DIALOG(dialog->win)); /* Add button */ - button = gtk_button_new_from_stock(GTK_STOCK_ADD); + button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_ADD, G_CALLBACK(add_cb), dialog); dialog->add_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(add_cb), dialog); /* Remove button */ - button = gtk_button_new_from_stock(GTK_STOCK_REMOVE); + button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_REMOVE, G_CALLBACK(remove_cb), dialog); dialog->remove_button = button; gtk_widget_set_sensitive(button, FALSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(remove_cb), dialog); /* Clear button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLEAR); + button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_CLEAR, G_CALLBACK(clear_cb), dialog); dialog->clear_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(clear_cb), dialog); - - /* Another button box. */ - bbox = gtk_hbutton_box_new(); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); - gtk_widget_show(bbox); /* Close button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); + button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_CLOSE, G_CALLBACK(close_cb), dialog); + dialog->close_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(close_cb), dialog); - + type_changed_cb(GTK_OPTION_MENU(dialog->type_menu), dialog); +#if 0 if (dialog->account->perm_deny == PURPLE_PRIVACY_ALLOW_USERS) { gtk_widget_show(dialog->allow_widget); gtk_widget_show(dialog->button_box); @@ -489,7 +465,7 @@ gtk_widget_show(dialog->button_box); dialog->in_allow_list = FALSE; } - +#endif return dialog; } diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkrequest.c --- a/pidgin/gtkrequest.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkrequest.c Tue Dec 18 09:20:01 2007 +0000 @@ -439,6 +439,8 @@ pidgin_set_accessible_label (entry, label); data->u.input.entry = entry; + pidgin_auto_parent_window(dialog); + /* Show everything. */ gtk_widget_show(dialog); @@ -546,6 +548,8 @@ g_object_set_data(G_OBJECT(dialog), "radio", radio); /* Show everything. */ + pidgin_auto_parent_window(dialog); + gtk_widget_show_all(dialog); return data; @@ -661,6 +665,8 @@ gtk_dialog_set_default_response(GTK_DIALOG(dialog), default_action); /* Show everything. */ + pidgin_auto_parent_window(dialog); + gtk_widget_show_all(dialog); return data; @@ -1059,7 +1065,6 @@ GtkWidget *vbox; GtkWidget *vbox2; GtkWidget *hbox; - GtkWidget *bbox; GtkWidget *frame; GtkWidget *label; GtkWidget *table; @@ -1089,9 +1094,9 @@ #ifdef _WIN32 - data->dialog = win = pidgin_create_window(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ; + data->dialog = win = pidgin_create_dialog(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ; #else /* !_WIN32 */ - data->dialog = win = pidgin_create_window(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ; + data->dialog = win = pidgin_create_dialog(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ; #endif /* _WIN32 */ g_signal_connect(G_OBJECT(win), "delete_event", @@ -1099,7 +1104,7 @@ /* Setup the main horizontal box */ hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), hbox); + gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(win))), hbox); gtk_widget_show(hbox); /* Dialog icon. */ @@ -1382,39 +1387,21 @@ g_object_unref(sg); - /* Button box. */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); - /* Cancel button */ - button = gtk_button_new_from_stock(text_to_stock(cancel_text)); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(multifield_cancel_cb), data); - + button = pidgin_dialog_add_button(GTK_DIALOG(win), text_to_stock(cancel_text), G_CALLBACK(multifield_cancel_cb), data); GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); /* OK button */ - button = gtk_button_new_from_stock(text_to_stock(ok_text)); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - + button = pidgin_dialog_add_button(GTK_DIALOG(win), text_to_stock(ok_text), G_CALLBACK(multifield_ok_cb), data); data->ok_button = button; - GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); gtk_window_set_default(GTK_WINDOW(win), button); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(multifield_ok_cb), data); - if (!purple_request_fields_all_required_filled(fields)) gtk_widget_set_sensitive(button, FALSE); + pidgin_auto_parent_window(win); + gtk_widget_show(win); return data; @@ -1622,6 +1609,8 @@ G_CALLBACK(file_ok_check_if_exists_cb), data); #endif /* FILECHOOSER */ + pidgin_auto_parent_window(filesel); + data->dialog = filesel; gtk_widget_show(filesel); @@ -1673,6 +1662,8 @@ #endif data->dialog = dirsel; + pidgin_auto_parent_window(dirsel); + gtk_widget_show(dirsel); return (void *)data; diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkroomlist.c --- a/pidgin/gtkroomlist.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkroomlist.c Tue Dec 18 09:20:01 2007 +0000 @@ -531,15 +531,13 @@ dialog->account = account; /* Create the window. */ - dialog->window = window = pidgin_create_window(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE); + dialog->window = window = pidgin_create_dialog(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_win_cb), dialog); /* Create the parent vbox for everything. */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(window), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER); vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_container_add(GTK_CONTAINER(vbox), vbox2); @@ -584,19 +582,14 @@ gtk_widget_show(dialog->progress); /* button box */ - bbox = gtk_hbutton_box_new(); + bbox = pidgin_dialog_get_action_area(GTK_DIALOG(window)); gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); /* stop button */ - dialog->stop_button = gtk_button_new_from_stock(GTK_STOCK_STOP); - gtk_box_pack_start(GTK_BOX(bbox), dialog->stop_button, FALSE, FALSE, 0); - g_signal_connect(G_OBJECT(dialog->stop_button), "clicked", + dialog->stop_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP, G_CALLBACK(stop_button_cb), dialog); gtk_widget_set_sensitive(dialog->stop_button, FALSE); - gtk_widget_show(dialog->stop_button); /* list button */ dialog->list_button = pidgin_pixbuf_button_from_stock(_("_Get List"), GTK_STOCK_REFRESH, @@ -625,11 +618,8 @@ gtk_widget_show(dialog->join_button); /* close button */ - dialog->close_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), dialog->close_button, FALSE, FALSE, 0); - g_signal_connect(G_OBJECT(dialog->close_button), "clicked", + dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, G_CALLBACK(close_button_cb), dialog); - gtk_widget_show(dialog->close_button); /* show the dialog window and return the dialog */ gtk_widget_show(dialog->window); diff -r f5d961556972 -r 5814ae85e756 pidgin/gtksavedstatuses.c --- a/pidgin/gtksavedstatuses.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtksavedstatuses.c Tue Dec 18 09:20:01 2007 +0000 @@ -594,7 +594,7 @@ width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/width"); height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/height"); - dialog->window = win = pidgin_create_window(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE); + dialog->window = win = pidgin_create_dialog(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); g_signal_connect(G_OBJECT(win), "delete_event", @@ -603,18 +603,14 @@ G_CALLBACK(configure_cb), dialog); /* Setup the vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); /* List of saved status states */ list = create_saved_status_list(dialog); gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0); /* Button box. */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); + bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win)); /* Use button */ button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE, @@ -627,36 +623,23 @@ G_CALLBACK(status_window_use_cb), dialog); /* Add button */ - button = gtk_button_new_from_stock(GTK_STOCK_ADD); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_window_add_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD, + G_CALLBACK(status_window_add_cb), dialog); /* Modify button */ - button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY); + button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY, + G_CALLBACK(status_window_modify_cb), dialog); dialog->modify_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + + /* Delete button */ + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE, + G_CALLBACK(status_window_delete_cb), dialog); + dialog->delete_button = button; gtk_widget_set_sensitive(button, FALSE); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_window_modify_cb), dialog); - - /* Delete button */ - button = gtk_button_new_from_stock(GTK_STOCK_DELETE); - dialog->delete_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_set_sensitive(button, FALSE); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_window_delete_cb), dialog); - /* Close button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_window_close_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, + G_CALLBACK(status_window_close_cb), dialog); purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed", status_window, @@ -1147,14 +1130,13 @@ if (edit) dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status)); - dialog->window = win = pidgin_create_window(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE); + dialog->window = win = pidgin_create_dialog(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(status_editor_destroy_cb), dialog); /* Setup the vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); @@ -1257,23 +1239,18 @@ (saved_status != NULL) && purple_savedstatus_has_substatuses(saved_status)); /* Button box */ - bbox = gtk_hbutton_box_new(); + bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win)); gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); /* Cancel button */ - button = gtk_button_new_from_stock(GTK_STOCK_CANCEL); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_editor_cancel_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL, + G_CALLBACK(status_editor_cancel_cb), dialog); /* Use button */ button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE, PIDGIN_BUTTON_HORIZONTAL); gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(status_editor_ok_cb), dialog); @@ -1284,19 +1261,15 @@ gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); if (dialog->original_title == NULL) gtk_widget_set_sensitive(button, FALSE); - g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(status_editor_ok_cb), dialog); /* Save button */ - button = gtk_button_new_from_stock(GTK_STOCK_SAVE); - dialog->save_button = GTK_BUTTON(button); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE, + G_CALLBACK(status_editor_ok_cb), dialog); if (dialog->original_title == NULL) gtk_widget_set_sensitive(button, FALSE); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_editor_ok_cb), dialog); + dialog->save_button = GTK_BUTTON(button); gtk_widget_show_all(win); g_object_unref(sg); @@ -1447,8 +1420,6 @@ char *tmp; SubStatusEditor *dialog; GtkSizeGroup *sg; - GtkWidget *bbox; - GtkWidget *button; GtkWidget *combo; GtkWidget *hbox; GtkWidget *frame; @@ -1486,15 +1457,14 @@ dialog->account = account; tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account)); - dialog->window = win = pidgin_create_window(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE); + dialog->window = win = pidgin_create_dialog(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE); g_free(tmp); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(substatus_editor_destroy_cb), dialog); /* Setup the vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); @@ -1543,25 +1513,13 @@ dialog->toolbar = GTK_IMHTMLTOOLBAR(toolbar); gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0); - /* Button box */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - /* Cancel button */ - button = gtk_button_new_from_stock(GTK_STOCK_CANCEL); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(substatus_editor_cancel_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL, + G_CALLBACK(substatus_editor_cancel_cb), dialog); /* OK button */ - button = gtk_button_new_from_stock(GTK_STOCK_OK); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(substatus_editor_ok_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_OK, + G_CALLBACK(substatus_editor_ok_cb), dialog); /* Seed the input widgets with the current values */ diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkutils.c --- a/pidgin/gtkutils.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkutils.c Tue Dec 18 09:20:01 2007 +0000 @@ -132,12 +132,9 @@ } } -GtkWidget * -pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable) +static +void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable) { - GtkWindow *wnd = NULL; - - wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); if (title) gtk_window_set_title(wnd, title); #ifdef _WIN32 @@ -148,11 +145,63 @@ if (role) gtk_window_set_role(wnd, role); gtk_window_set_resizable(wnd, resizable); +} + +GtkWidget * +pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable) +{ + GtkWindow *wnd = NULL; + + wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + pidgin_window_init(wnd, title, border_width, role, resizable); + + return GTK_WIDGET(wnd); +} + +GtkWidget * +pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable) +{ + GtkWindow *wnd = NULL; + + wnd = GTK_WINDOW(gtk_dialog_new()); + pidgin_window_init(wnd, title, border_width, role, resizable); + g_object_set(G_OBJECT(wnd), "has-separator", FALSE, NULL); return GTK_WIDGET(wnd); } GtkWidget * +pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing) +{ + GtkBox *vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox); + gtk_box_set_homogeneous(vbox, homogeneous); + gtk_box_set_spacing(vbox, spacing); + return GTK_WIDGET(vbox); +} + +GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog) +{ + return GTK_DIALOG(dialog)->vbox; +} + +GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog) +{ + return GTK_DIALOG(dialog)->action_area; +} + +GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label, + GCallback callback, gpointer callbackdata) +{ + GtkWidget *button = gtk_button_new_from_stock(label); + GtkWidget *bbox = pidgin_dialog_get_action_area(dialog); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + if (callback) + g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata); + gtk_widget_show(button); + return button; +} + +GtkWidget * pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret) { GtkWidget *frame; @@ -3269,3 +3318,107 @@ gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text)); } +gboolean pidgin_auto_parent_window(GtkWidget *widget) +{ +#if 0 + /* This looks at the most recent window that received focus, and makes + * that the parent window. */ +#ifndef _WIN32 + static GdkAtom _WindowTime = GDK_NONE; + static GdkAtom _Cardinal = GDK_NONE; + GList *windows = NULL; + GtkWidget *parent = NULL; + time_t window_time = 0; + + windows = gtk_window_list_toplevels(); + + if (_WindowTime == GDK_NONE) { + _WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME")); + } + if (_Cardinal == GDK_NONE) { + _Cardinal = gdk_atom_intern("CARDINAL", FALSE); + } + + while (windows) { + GtkWidget *window = windows->data; + guchar *data = NULL; + int al = 0; + time_t value; + + windows = g_list_delete_link(windows, windows); + + if (window == widget || + !GTK_WIDGET_VISIBLE(window)) + continue; + + if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE, + NULL, NULL, &al, &data)) + continue; + value = *(time_t *)data; + if (window_time < value) { + window_time = value; + parent = window; + } + g_free(data); + } + if (windows) + g_list_free(windows); + if (parent) { + if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) { + /* The window is in focus, and the new window was not triggered by a keypress/click + * event. So do not set it transient, to avoid focus stealing and all that. + */ + return FALSE; + } + gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent)); + return TRUE; + } + return FALSE; +#endif +#else + /* This finds the currently active window and makes that the parent window. */ + GList *windows = NULL; + GtkWidget *parent = NULL; + GdkEvent *event = gtk_get_current_event(); + GdkWindow *menu = NULL; + + if (event == NULL) + /* The window was not triggered by a user action. */ + return FALSE; + + /* We need to special case events from a popup menu. */ + if (event->type == GDK_BUTTON_RELEASE) { + /* XXX: Neither of the following works: + menu = event->button.window; + menu = gdk_window_get_parent(event->button.window); + menu = gdk_window_get_toplevel(event->button.window); + */ + } else if (event->type == GDK_KEY_PRESS) + menu = event->key.window; + + windows = gtk_window_list_toplevels(); + while (windows) { + GtkWidget *window = windows->data; + windows = g_list_delete_link(windows, windows); + + if (window == widget || + !GTK_WIDGET_VISIBLE(window)) { + continue; + } + + if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) || + (menu && menu == window->window)) { + parent = window; + break; + } + } + if (windows) + g_list_free(windows); + if (parent) { + gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent)); + return TRUE; + } + return FALSE; +#endif +} + diff -r f5d961556972 -r 5814ae85e756 pidgin/gtkutils.h --- a/pidgin/gtkutils.h Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/gtkutils.h Tue Dec 18 09:20:01 2007 +0000 @@ -121,6 +121,61 @@ GtkWidget *pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable); /** + * Creates a new dialog window + * + * @param title The window title, or @c NULL + * @param border_width The window's desired border width + * @param role A string indicating what the window is responsible for doing, or @c NULL + * @param resizable Whether the window should be resizable (@c TRUE) or not (@c FALSE) + * + * @since 2.4.0 + */ +GtkWidget *pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable); + +/** + * Retrieves the main content box (vbox) from a pidgin dialog window + * + * @param dialog The dialog window + * @param homogeneous TRUE if all children are to be given equal space allotments. + * @param spacing the number of pixels to place by default between children + * + * @since 2.4.0 + */ +GtkWidget *pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing); + +/** + * Retrieves the main content box (vbox) from a pidgin dialog window + * + * @param dialog The dialog window + * + * @since 2.4.0 + */ +GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog); + +/** + * Add a button to a dialog created by #pidgin_create_dialog. + * + * @param dialog The dialog window + * @param label The stock-id or the label for the button + * @param callback The callback function for the button + * @param callbackdata The user data for the callback function + * + * @return The created button. + * @since 2.4.0 + */ +GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label, + GCallback callback, gpointer callbackdata); + +/** + * Retrieves the action area (button box) from a pidgin dialog window + * + * @param dialog The dialog window + * + * @since 2.4.0 + */ +GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog); + +/** * Toggles the sensitivity of a widget. * * @param widget @c NULL. Used for signal handlers. @@ -725,5 +780,15 @@ */ void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text); +/** + * Automatically make a window transient to a suitable parent window. + * + * @param window The window to make transient. + * + * @return Whether the window was made transient or not. + * @since 2.4.0 + */ +gboolean pidgin_auto_parent_window(GtkWidget *window); + #endif /* _PIDGINUTILS_H_ */ diff -r f5d961556972 -r 5814ae85e756 pidgin/plugins/pidginrc.c --- a/pidgin/plugins/pidginrc.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/plugins/pidginrc.c Tue Dec 18 09:20:01 2007 +0000 @@ -30,17 +30,29 @@ static const gchar *color_prefs[] = { "/plugins/gtk/purplerc/color/GtkWidget::cursor-color", "/plugins/gtk/purplerc/color/GtkWidget::secondary-cursor-color", - "/plugins/gtk/purplerc/color/GtkIMHtml::hyperlink-color" + "/plugins/gtk/purplerc/color/GtkIMHtml::hyperlink-color", + "/plugins/gtk/purplerc/color/GtkIMHtml::send-name-color", + "/plugins/gtk/purplerc/color/GtkIMHtml::receive-name-color", + "/plugins/gtk/purplerc/color/GtkIMHtml::highlight-name-color", + "/plugins/gtk/purplerc/color/GtkIMHtml::action-name-color" }; static const gchar *color_prefs_set[] = { "/plugins/gtk/purplerc/set/color/GtkWidget::cursor-color", "/plugins/gtk/purplerc/set/color/GtkWidget::secondary-cursor-color", - "/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-color" + "/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-color", + "/plugins/gtk/purplerc/set/color/GtkIMHtml::send-name-color", + "/plugins/gtk/purplerc/set/color/GtkIMHtml::receive-name-color", + "/plugins/gtk/purplerc/set/color/GtkIMHtml::highlight-name-color", + "/plugins/gtk/purplerc/set/color/GtkIMHtml::action-name-color" }; static const gchar *color_names[] = { N_("Cursor Color"), N_("Secondary Cursor Color"), - N_("Hyperlink Color") + N_("Hyperlink Color"), + N_("Sent Message Name Color"), + N_("Received Message Name Color"), + N_("Highlighted Message Name Color"), + N_("Action Message Name Color") }; static GtkWidget *color_widgets[G_N_ELEMENTS(color_prefs)]; diff -r f5d961556972 -r 5814ae85e756 pidgin/win32/gtkwin32dep.c --- a/pidgin/win32/gtkwin32dep.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/win32/gtkwin32dep.c Tue Dec 18 09:20:01 2007 +0000 @@ -40,6 +40,7 @@ #include "debug.h" #include "notify.h" +#include "network.h" #include "resource.h" #include "idletrack.h" @@ -51,6 +52,7 @@ #include "gtkwin32dep.h" #include "win32dep.h" #include "gtkconv.h" +#include "gtkconn.h" #include "util.h" #include "wspell.h" @@ -64,6 +66,7 @@ typedef BOOL (CALLBACK* LPFNFLASHWINDOWEX)(PFLASHWINFO); static LPFNFLASHWINDOWEX MyFlashWindowEx = NULL; +static gboolean pwm_handles_connections = TRUE; /* @@ -202,6 +205,43 @@ #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13) #define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14) +static void* +winpidgin_netconfig_changed_cb(void *data) +{ + pwm_handles_connections = FALSE; + + return NULL; +} + +static void* +winpidgin_get_handle(void) +{ + static int handle; + + return &handle; +} + +static gboolean +winpidgin_pwm_reconnect() +{ + purple_signal_disconnect(purple_network_get_handle(), "network-configuration-changed", + winpidgin_get_handle(), PURPLE_CALLBACK(winpidgin_netconfig_changed_cb)); + + if (pwm_handles_connections == TRUE) { + PurpleConnectionUiOps *ui_ops = pidgin_connections_get_ui_ops(); + + purple_debug_info("winpidgin", "Resumed from standby, reconnecting accounts.\n"); + + if (ui_ops != NULL && ui_ops->network_connected != NULL) + ui_ops->network_connected(); + } else { + purple_debug_info("winpidgin", "Resumed from standby, gtkconn will handle reconnecting.\n"); + pwm_handles_connections = TRUE; + } + + return FALSE; +} + static LRESULT CALLBACK message_window_handler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (msg == PIDGIN_WM_FOCUS_REQUEST) { @@ -213,6 +253,28 @@ purple_debug_info("winpidgin", "Got protocol handler request: %s\n", proto_msg ? proto_msg : ""); purple_got_protocol_handler_uri(proto_msg); return TRUE; + } else if (msg == WM_POWERBROADCAST) { + if (wparam == PBT_APMQUERYSUSPEND) { + purple_debug_info("winpidgin", "Windows requesting permission to suspend.\n"); + return TRUE; + } else if (wparam == PBT_APMSUSPEND) { + PurpleConnectionUiOps *ui_ops = pidgin_connections_get_ui_ops(); + + purple_debug_info("winpidgin", "Entering system standby, disconnecting accounts.\n"); + + if (ui_ops != NULL && ui_ops->network_disconnected != NULL) + ui_ops->network_disconnected(); + + purple_signal_connect(purple_network_get_handle(), "network-configuration-changed", winpidgin_get_handle(), + PURPLE_CALLBACK(winpidgin_netconfig_changed_cb), NULL); + + return TRUE; + } else if (wparam == PBT_APMRESUMESUSPEND) { + purple_debug_info("winpidgin", "Resuming from system standby.\n"); + /* TODO: It seems like it'd be wise to use the NLA message, if possible, instead of this. */ + purple_timeout_add_seconds(1, winpidgin_pwm_reconnect, NULL); + return TRUE; + } } return DefWindowProc(hwnd, msg, wparam, lparam); @@ -242,7 +304,7 @@ /* Create the window */ if(!(win_hwnd = CreateWindow(wname, TEXT("WinpidginMsgWin"), 0, 0, 0, 0, 0, - HWND_MESSAGE, NULL, winpidgin_exe_hinstance(), 0))) { + NULL, NULL, winpidgin_exe_hinstance(), 0))) { purple_debug_error("winpidgin", "Unable to create message window.\n"); return NULL; diff -r f5d961556972 -r 5814ae85e756 pidgin/win32/nsis/pidgin-installer.nsi --- a/pidgin/win32/nsis/pidgin-installer.nsi Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/win32/nsis/pidgin-installer.nsi Tue Dec 18 09:20:01 2007 +0000 @@ -1154,11 +1154,21 @@ !macro RunCheckMacro UN Function ${UN}RunCheck Push $R0 - System::Call 'kernel32::OpenMutex(i 2031617, b 0, t "pidgin_is_running") i .R0' - IntCmp $R0 0 done - MessageBox MB_OK|MB_ICONEXCLAMATION $(PIDGIN_IS_RUNNING) /SD IDOK + Push $R1 + + IntOp $R1 0 + 0 + retry_runcheck: + ; Close the Handle (needed if we're retrying) + IntCmp $R1 0 +2 + System::Call 'kernel32::CloseHandle(i $R1) i .R1' + System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_is_running") i .R1 ?e' + Pop $R0 + IntCmp $R0 0 +3 ;This could check for ERROR_ALREADY_EXISTS(183), but lets just assume + MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(PIDGIN_IS_RUNNING) /SD IDCANCEL IDRETRY retry_runcheck Abort + done: + Pop $R1 Pop $R0 FunctionEnd !macroend @@ -1169,10 +1179,16 @@ Push $R0 Push $R1 Push $R2 - System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_installer_running") i .r1 ?e' + + IntOp $R1 0 + 0 + retry_runcheck: + ; Close the Handle (needed if we're retrying) + IntCmp $R1 0 +2 + System::Call 'kernel32::CloseHandle(i $R1) i .R1' + System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_installer_running") i .R1 ?e' Pop $R0 - StrCmp $R0 0 +3 - MessageBox MB_OK|MB_ICONEXCLAMATION $(INSTALLER_IS_RUNNING) /SD IDOK + IntCmp $R0 0 +3 ;This could check for ERROR_ALREADY_EXISTS(183), but lets just assume + MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(INSTALLER_IS_RUNNING) /SD IDCANCEL IDRETRY retry_runcheck Abort Call RunCheck StrCpy $name "Pidgin ${PIDGIN_VERSION}" diff -r f5d961556972 -r 5814ae85e756 pidgin/win32/winpidgin.c --- a/pidgin/win32/winpidgin.c Tue Dec 18 09:17:39 2007 +0000 +++ b/pidgin/win32/winpidgin.c Tue Dec 18 09:20:01 2007 +0000 @@ -445,11 +445,12 @@ #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13) #define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14) -static BOOL winpidgin_set_running() { +static BOOL winpidgin_set_running(BOOL fail_if_running) { HANDLE h; if ((h = CreateMutex(NULL, FALSE, "pidgin_is_running"))) { - if (GetLastError() == ERROR_ALREADY_EXISTS) { + DWORD err = GetLastError(); + if (err == ERROR_ALREADY_EXISTS && fail_if_running) { HWND msg_win; printf("An instance of Pidgin is already running.\n"); @@ -465,7 +466,8 @@ NULL, MB_OK | MB_TOPMOST); return FALSE; - } + } else + printf("Error (%d) accessing \"pidgin_is_running\" mutex.\n", err); } return TRUE; } @@ -628,8 +630,8 @@ winpidgin_set_locale(); /* If help, version or multiple flag used, do not check Mutex */ - if (!strstr(lpszCmdLine, "-h") && !strstr(lpszCmdLine, "-v") && !strstr(lpszCmdLine, "-m")) - if (!getenv("PIDGIN_MULTI_INST") && !winpidgin_set_running()) + if (!strstr(lpszCmdLine, "-h") && !strstr(lpszCmdLine, "-v")) + if (!winpidgin_set_running(getenv("PIDGIN_MULTI_INST") == NULL && strstr(lpszCmdLine, "-m") == NULL)) return 0; /* Now we are ready for Pidgin .. */