# HG changeset patch # User Gabriel Schulhof # Date 1192266839 0 # Node ID 28b11826cee46d7ca19904a6a86f77b3d98f4641 # Parent c3c85233eb4a8fb00ad112723946e868b87c5bed# Parent 3969ac8237e37a735b594dca5d3de3a7c34b4812 propagate from branch 'im.pidgin.pidgin' (head f28fe5ed42cadd9ebe524e358fa913a04817da8e) to branch 'org.maemo.garage.pidgin.pidgin.dialog-transience' (head 62363c8571cca41f806b9b206027f9503b2df986) diff -r c3c85233eb4a -r 28b11826cee4 ChangeLog.API --- a/ChangeLog.API Sat Oct 13 09:09:05 2007 +0000 +++ b/ChangeLog.API Sat Oct 13 09:13:59 2007 +0000 @@ -1,13 +1,13 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -Version 2.2.2 (??/??/????): +version 2.2.2 (??/??/????): libpurple: Changed: * The size parameter of purple_util_write_data_to_file_absolute has been changed to gssize instead of a size_t to correctly indicate that -1 can be used for a nul-delimited string. -Version 2.2.0 (09/13/2007): +version 2.2.0 (09/13/2007): libpurple: Added: * PURPLE_MESSAGE_INVISIBLE flag, which can be used by @@ -64,7 +64,7 @@ * gnt_util_parse_xhtml_to_textview to parse XHTML strings in a GntTextView (this works only if libxml2 is available) -Version 2.1.1 (08/20/2007): +version 2.1.1 (08/20/2007): libpurple: Changed: * PurpleAccountUiOps.request_authorize's authorize_cb and diff -r c3c85233eb4a -r 28b11826cee4 configure.ac --- a/configure.ac Sat Oct 13 09:09:05 2007 +0000 +++ b/configure.ac Sat Oct 13 09:13:59 2007 +0000 @@ -142,13 +142,21 @@ dnl If we don't have msgfmt, then po/ is going to fail -- ensure that dnl AM_GLIB_GNU_GETTEXT found it. -if test x$MSGFMT = xno +if test x$MSGFMT = xno -o x$MSGFMT$GMSGFMT = x then AC_ERROR([ The msgfmt command is required to build libpurple. If it is installed on your system, ensure that it is in your path. If it is not, install GNU gettext to continue. + +If you have msgfmt installed, but for some reason this error message +is still displayed, you have encountered what appears to be a bug in +third-party configure macros. Try setting the MSGFMT environment +variable to the absolute path to your msgfmt binary and trying +configure again, like this: + +MSGFMT=/path/to/msgfmt ./configure ... ]) fi diff -r c3c85233eb4a -r 28b11826cee4 libpurple/plugins/log_reader.c --- a/libpurple/plugins/log_reader.c Sat Oct 13 09:09:05 2007 +0000 +++ b/libpurple/plugins/log_reader.c Sat Oct 13 09:13:59 2007 +0000 @@ -103,9 +103,10 @@ } else { char *filename = g_build_filename(path, file, NULL); FILE *handle = g_fopen(filename, "rb"); - char *contents; + char contents[57]; /* XXX: This is really inflexible. */ char *contents2; struct adium_logger_data *data; + size_t rd; PurpleLog *log; if (!handle) { @@ -113,11 +114,9 @@ continue; } - /* XXX: This is really inflexible. */ - contents = g_malloc(57); - fread(contents, 56, 1, handle); + rd = fread(contents, 56, 1, handle) == 0; fclose(handle); - contents[56] = '\0'; + contents[rd] = '\0'; /* XXX: This is fairly inflexible. */ contents2 = contents; @@ -135,11 +134,9 @@ purple_debug_error("Adium log parse", "Contents timestamp parsing error\n"); - g_free(contents); g_free(filename); continue; } - g_free(contents); data = g_new0(struct adium_logger_data, 1); data->path = filename; @@ -168,21 +165,20 @@ } else { char *filename = g_build_filename(path, file, NULL); FILE *handle = g_fopen(filename, "rb"); - char *contents; + char contents[14]; /* XXX: This is really inflexible. */ char *contents2; struct adium_logger_data *data; PurpleLog *log; + size_t rd; if (!handle) { g_free(filename); continue; } - /* XXX: This is really inflexible. */ - contents = g_malloc(14); - fread(contents, 13, 1, handle); + rd = fread(contents, 13, 1, handle); fclose(handle); - contents[13] = '\0'; + contents[rd] = '\0'; contents2 = contents; while (*contents2 && *contents2 != '(') @@ -195,13 +191,10 @@ purple_debug_error("Adium log parse", "Contents timestamp parsing error\n"); - g_free(contents); g_free(filename); continue; } - g_free(contents); - tm.tm_year -= 1900; tm.tm_mon -= 1; @@ -1446,7 +1439,7 @@ file = g_fopen(data->path, "rb"); fseek(file, data->offset, SEEK_SET); - fread(read, data->length, 1, file); + data->length = fread(read, data->length, 1, file); fclose(file); if (read[data->length-1] == '\n') { @@ -1945,7 +1938,7 @@ contents = g_malloc(data->length + 2); fseek(file, data->offset, SEEK_SET); - fread(contents, data->length, 1, file); + data->length = fread(contents, data->length, 1, file); fclose(file); contents[data->length] = '\n'; @@ -2098,6 +2091,361 @@ g_free(data); } +/************************************************************************* + * aMSN Logger * + *************************************************************************/ + +/* The aMSN logger doesn't write logs, only reads them. This is to include + * aMSN logs in the log viewer transparently. + */ + +static PurpleLogLogger *amsn_logger; + +struct amsn_logger_data { + char *path; + int offset; + int length; +}; + +#define AMSN_LOG_CONV_START "|\"LRED[Conversation started on " +#define AMSN_LOG_CONV_END "|\"LRED[You have closed the window on " +#define AMSN_LOG_CONV_EXTRA "01 Aug 2001 00:00:00]" + +/* `log_dir`/username@hotmail.com/logs/buddyname@hotmail.com.log */ +/* `log_dir`/username@hotmail.com/logs/Month Year/buddyname@hotmail.com.log */ +static GList *amsn_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account) +{ + GList *list = NULL; + struct amsn_logger_data *data; + const char *logdir; + char *username; + char *log_path; + char *buddy_log; + char *filename; + GDir *dir; + const char *name; + GError *error; + char *contents; + PurpleLog *log; + GList *files = NULL; + GList *f; + + logdir = purple_prefs_get_string("/plugins/core/log_reader/amsn/log_directory"); + + /* By clearing the log directory path, this logger can be (effectively) disabled. */ + if (!logdir || !*logdir) + return NULL; + + /* aMSN only works with MSN/WLM */ + if (strcmp(account->protocol_id, "prpl-msn")) + return NULL; + + username = g_strdup(purple_normalize(account, account->username)); + buddy_log = g_strdup_printf("%s.log", purple_normalize(account, sn)); + log_path = g_build_filename(logdir, username, "logs", NULL); + + /* First check in the top-level */ + filename = g_build_filename(log_path, buddy_log, NULL); + if (g_file_test(filename, G_FILE_TEST_EXISTS)) + files = g_list_prepend(files, filename); + else + g_free(filename); + + /* Check in previous months */ + dir = g_dir_open(log_path, 0, NULL); + if (dir) { + while ((name = g_dir_read_name(dir)) != NULL) { + filename = g_build_filename(log_path, name, buddy_log, NULL); + if (g_file_test(filename, G_FILE_TEST_EXISTS)) + files = g_list_prepend(files, filename); + else + g_free(filename); + } + g_dir_close(dir); + } + + g_free(log_path); + + /* New versions use 'friendlier' directory names */ + purple_util_chrreplace(username, '@', '_'); + purple_util_chrreplace(username, '.', '_'); + + log_path = g_build_filename(logdir, username, "logs", NULL); + + /* First check in the top-level */ + filename = g_build_filename(log_path, buddy_log, NULL); + if (g_file_test(filename, G_FILE_TEST_EXISTS)) + files = g_list_prepend(files, filename); + else + g_free(filename); + + /* Check in previous months */ + dir = g_dir_open(log_path, 0, NULL); + if (dir) { + while ((name = g_dir_read_name(dir)) != NULL) { + filename = g_build_filename(log_path, name, buddy_log, NULL); + if (g_file_test(filename, G_FILE_TEST_EXISTS)) + files = g_list_prepend(files, filename); + else + g_free(filename); + } + g_dir_close(dir); + } + + g_free(log_path); + g_free(username); + g_free(buddy_log); + + /* Loop through files looking for logs */ + for(f = g_list_first(files); f; f = g_list_next(f)) { + filename = f->data; + purple_debug_info("aMSN logger", "Reading %s\n", filename); + error = NULL; + if (!g_file_get_contents(filename, &contents, NULL, &error)) { + purple_debug_error("aMSN logger", + "Couldn't read file %s: %s \n", filename, + (error && error->message) ? + error->message : "Unknown error"); + if (error) + g_error_free(error); + } else { + char *c = contents; + gboolean found_start = FALSE; + char *start_log = c; + int offset = 0; + struct tm tm; + while (c && *c) { + if (purple_str_has_prefix(c, AMSN_LOG_CONV_START)) { + char month[4]; + if (sscanf(c + strlen(AMSN_LOG_CONV_START), + "%u %3s %u %u:%u:%u", + &tm.tm_mday, (char*)&month, &tm.tm_year, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { + found_start = FALSE; + purple_debug_error("aMSN logger", + "Error parsing start date for %s\n", + filename); + } else { + const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL}; + tm.tm_year -= 1900; + + /* Let the C library deal with + * daylight savings time. + */ + tm.tm_isdst = -1; + + /* Ugly hack, in case current locale + * is not English. This code is taken + * from log.c. + */ + for (tm.tm_mon = 0; months[tm.tm_mon]; tm.tm_mon++) { + if (strcmp(month, months[tm.tm_mon]) == 0) + break; + } + found_start = TRUE; + offset = c - contents; + start_log = c; + } + } else if (purple_str_has_prefix(c, AMSN_LOG_CONV_END) && found_start) { + data = g_new0(struct amsn_logger_data, 1); + data->path = g_strdup(filename); + data->offset = offset; + data->length = c - start_log + + strlen(AMSN_LOG_CONV_END) + + strlen(AMSN_LOG_CONV_EXTRA); + log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, mktime(&tm), NULL); + log->logger = amsn_logger; + log->logger_data = data; + list = g_list_prepend(list, log); + found_start = FALSE; + + purple_debug_info("aMSN logger", + "Found log for %s:" + " path = (%s)," + " offset = (%d)," + " length = (%d)\n", + sn, data->path, data->offset, data->length); + } + c = strstr(c, "\n"); + c++; + } + + /* I've seen the file end without the AMSN_LOG_CONV_END bit */ + if (found_start) { + data = g_new0(struct amsn_logger_data, 1); + data->path = g_strdup(filename); + data->offset = offset; + data->length = c - start_log + + strlen(AMSN_LOG_CONV_END) + + strlen(AMSN_LOG_CONV_EXTRA); + log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, mktime(&tm), NULL); + log->logger = amsn_logger; + log->logger_data = data; + list = g_list_prepend(list, log); + found_start = FALSE; + + purple_debug_info("aMSN logger", + "Found log for %s:" + " path = (%s)," + " offset = (%d)," + " length = (%d)\n", + sn, data->path, data->offset, data->length); + } + g_free(contents); + } + g_free(filename); + } + + g_list_free(files); + + return list; +} + +/* Really it's |"L, but the string's been escaped */ +#define AMSN_LOG_FORMAT_TAG "|"L" + +static char *amsn_logger_read(PurpleLog *log, PurpleLogReadFlags *flags) +{ + struct amsn_logger_data *data; + FILE *file; + char *contents; + char *escaped; + GString *formatted; + char *start; + gboolean in_span = FALSE; + + if (flags != NULL) + *flags = PURPLE_LOG_READ_NO_NEWLINE; + + g_return_val_if_fail(log != NULL, g_strdup("")); + + data = log->logger_data; + + g_return_val_if_fail(data->path != NULL, g_strdup("")); + g_return_val_if_fail(data->length > 0, g_strdup("")); + + contents = g_malloc(data->length + 2); + + file = g_fopen(data->path, "rb"); + g_return_val_if_fail(file != NULL, g_strdup("")); + + fseek(file, data->offset, SEEK_SET); + data->length = fread(contents, data->length, 1, file); + fclose(file); + + contents[data->length] = '\n'; + contents[data->length + 1] = '\0'; + + escaped = g_markup_escape_text(contents, -1); + g_free(contents); + contents = escaped; + + formatted = g_string_sized_new(data->length + 2); + + start = contents; + while (start && *start) { + char *end; + char *old_tag; + char *tag; + end = strstr(start, "\n"); + if (!end) + break; + *end = '\0'; + if (purple_str_has_prefix(start, AMSN_LOG_FORMAT_TAG) && in_span) { + /* New format for this line */ + g_string_append(formatted, "
"); + in_span = FALSE; + } else if (start != contents) { + /* Continue format from previous line */ + g_string_append(formatted, "
"); + } + old_tag = start; + tag = strstr(start, AMSN_LOG_FORMAT_TAG); + while (tag) { + g_string_append_len(formatted, old_tag, tag - old_tag); + tag += strlen(AMSN_LOG_FORMAT_TAG); + if (in_span) { + g_string_append(formatted, ""); + in_span = FALSE; + } + if (*tag == 'C') { + /* |"LCxxxxxx is a hex colour */ + char colour[7]; + strncpy(colour, tag + 1, 6); + colour[6] = '\0'; + g_string_append_printf(formatted, "", colour); + /* This doesn't appear to work? */ + /* g_string_append_printf(formatted, "", tag + 1); */ + in_span = TRUE; + old_tag = tag + 7; /* C + xxxxxx */ + } else { + /* |"Lxxx is a 3-digit colour code */ + if (purple_str_has_prefix(tag, "RED")) { + g_string_append(formatted, ""); + in_span = TRUE; + } else if (purple_str_has_prefix(tag, "GRA")) { + g_string_append(formatted, ""); + in_span = TRUE; + } else if (purple_str_has_prefix(tag, "NOR")) { + g_string_append(formatted, ""); + in_span = TRUE; + } else if (purple_str_has_prefix(tag, "ITA")) { + g_string_append(formatted, ""); + in_span = TRUE; + } else if (purple_str_has_prefix(tag, "GRE")) { + g_string_append(formatted, ""); + in_span = TRUE; + } else { + purple_debug_info("aMSN logger", "Unknown colour format: %3s\n", tag); + } + old_tag = tag + 3; + } + tag = strstr(tag, AMSN_LOG_FORMAT_TAG); + } + g_string_append(formatted, old_tag); + start = end + 1; + } + if (in_span) + g_string_append(formatted, ""); + + g_free(contents); + + return g_string_free(formatted, FALSE); +} + +static int amsn_logger_size(PurpleLog *log) +{ + struct amsn_logger_data *data; + char *text; + int size; + + g_return_val_if_fail(log != NULL, 0); + + data = log->logger_data; + + if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) { + return data ? data->length : 0; + } + + text = amsn_logger_read(log, NULL); + size = strlen(text); + g_free(text); + + return size; +} + +static void amsn_logger_finalize(PurpleLog *log) +{ + struct amsn_logger_data *data; + + g_return_if_fail(log != NULL); + + data = log->logger_data; + g_free(data->path); + g_free(data); +} + /***************************************************************************** * Plugin Code * *****************************************************************************/ @@ -2347,6 +2695,19 @@ #endif purple_prefs_add_string("/plugins/core/log_reader/qip/log_directory", path ? path : ""); g_free(path); + + /* Add aMSN Messenger log directory preference. */ + purple_prefs_add_none("/plugins/core/log_reader/amsn"); + + /* Calculate default aMSN log directory. */ +#ifdef _WIN32 + folder = wpurple_get_special_folder(CSIDL_PROFILE); /* Silly aMSN, not using CSIDL_APPDATA */ + path = g_build_filename(folder, "amsn", NULL); +#else + path = g_build_filename(purple_home_dir(), ".amsn", NULL); +#endif + purple_prefs_add_string("/plugins/core/log_reader/amsn/log_directory", path); + g_free(path); } static gboolean @@ -2429,6 +2790,18 @@ trillian_logger_size); purple_log_logger_add(trillian_logger); + /* The names of IM clients are marked for translation at the request of + translators who wanted to transliterate them. Many translators + choose to leave them alone. Choose what's best for your language. */ + amsn_logger = purple_log_logger_new("amsn", _("aMSN"), 6, + NULL, + NULL, + amsn_logger_finalize, + amsn_logger_list, + amsn_logger_read, + amsn_logger_size); + purple_log_logger_add(amsn_logger); + return TRUE; } @@ -2445,6 +2818,7 @@ purple_log_logger_remove(msn_logger); purple_log_logger_remove(trillian_logger); purple_log_logger_remove(qip_logger); + purple_log_logger_remove(amsn_logger); return TRUE; } @@ -2505,6 +2879,10 @@ "/plugins/core/log_reader/trillian/log_directory", _("Trillian")); purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/log_reader/amsn/log_directory", _("aMSN")); + purple_plugin_pref_frame_add(frame, ppref); + return frame; } diff -r c3c85233eb4a -r 28b11826cee4 libpurple/protocols/bonjour/Makefile.mingw --- a/libpurple/protocols/bonjour/Makefile.mingw Sat Oct 13 09:09:05 2007 +0000 +++ b/libpurple/protocols/bonjour/Makefile.mingw Sat Oct 13 09:13:59 2007 +0000 @@ -36,7 +36,7 @@ -I$(PIDGIN_TREE_TOP) LIB_PATHS += -L$(GTK_TOP)/lib \ - -L$(BONJOUR_TOP)/lib \ + -L$(BONJOUR_TOP)/lib/win32 \ -L$(LIBXML2_TOP)/lib \ -L$(PURPLE_TOP) diff -r c3c85233eb4a -r 28b11826cee4 pidgin/gtkdocklet.c --- a/pidgin/gtkdocklet.c Sat Oct 13 09:09:05 2007 +0000 +++ b/pidgin/gtkdocklet.c Sat Oct 13 09:13:59 2007 +0000 @@ -407,7 +407,7 @@ GdkPixbuf *pixbuf; GtkWidget *image; - menuitem = gtk_image_menu_item_new_with_mnemonic(str); + menuitem = gtk_image_menu_item_new_with_label(str); if (menu) gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); @@ -473,6 +473,87 @@ return menuitem; } + + +static void +plugin_act(GtkObject *obj, PurplePluginAction *pam) +{ + if (pam && pam->callback) + pam->callback(pam); +} + +static void +build_plugin_actions(GtkWidget *menu, PurplePlugin *plugin, + gpointer context) +{ + GtkWidget *menuitem; + PurplePluginAction *action = NULL; + GList *actions, *l; + + actions = PURPLE_PLUGIN_ACTIONS(plugin, context); + + for (l = actions; l != NULL; l = l->next) + { + if (l->data) + { + action = (PurplePluginAction *) l->data; + action->plugin = plugin; + action->context = context; + + menuitem = gtk_menu_item_new_with_label(action->label); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(plugin_act), action); + g_object_set_data_full(G_OBJECT(menuitem), "plugin_action", + action, + (GDestroyNotify)purple_plugin_action_free); + gtk_widget_show(menuitem); + } + else + pidgin_separator(menu); + } + + g_list_free(actions); +} + + +static void +docklet_plugin_actions(GtkWidget *menu) +{ + GtkWidget *menuitem, *submenu; + PurplePlugin *plugin = NULL; + GList *l; + int c = 0; + + g_return_if_fail(menu != NULL); + + /* Add a submenu for each plugin with custom actions */ + for (l = purple_plugins_get_loaded(); l; l = l->next) { + plugin = (PurplePlugin *) l->data; + + if (PURPLE_IS_PROTOCOL_PLUGIN(plugin)) + continue; + + if (!PURPLE_PLUGIN_HAS_ACTIONS(plugin)) + continue; + + menuitem = gtk_image_menu_item_new_with_label(_(plugin->info->name)); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + gtk_widget_show(menuitem); + + submenu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu); + gtk_widget_show(submenu); + + build_plugin_actions(submenu, plugin, NULL); + + c++; + } + if(c>0) + pidgin_separator(menu); +} + static void docklet_menu() { static GtkWidget *menu = NULL; @@ -539,6 +620,9 @@ pidgin_separator(menu); + /* add plugin actions */ + docklet_plugin_actions(menu); + pidgin_new_item_from_stock(menu, _("Quit"), GTK_STOCK_QUIT, G_CALLBACK(purple_core_quit), NULL, 0, 0, NULL); #ifdef _WIN32 diff -r c3c85233eb4a -r 28b11826cee4 pidgin/plugins/history.c --- a/pidgin/plugins/history.c Sat Oct 13 09:09:05 2007 +0000 +++ b/pidgin/plugins/history.c Sat Oct 13 09:13:59 2007 +0000 @@ -42,6 +42,7 @@ GtkIMHtmlOptions options = GTK_IMHTML_NO_COLOURS; char *header; char *protocol; + char *escaped_alias; convtype = purple_conversation_get_type(c); gtkconv = PIDGIN_CONVERSATION(c); @@ -120,10 +121,12 @@ if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)))) gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "
", options); - header = g_strdup_printf(_("Conversation with %s on %s:
"), alias, + escaped_alias = g_markup_escape_text(alias, -1); + header = g_strdup_printf(_("Conversation with %s on %s:
"), escaped_alias, purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time))); gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), header, options); g_free(header); + g_free(escaped_alias); g_strchomp(history); gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), history, options);