# HG changeset patch # User Daniel Atallah # Date 1190003360 0 # Node ID 93ea759f6ec9102e082b91773fa0c5343a100553 # Parent 32ab95bbfb99dcf5df52a0c0f73b3bac98121899# Parent 2a9f3a499b0d740294f2d9e0be4768909e6f447b merge of '18e1f28f6de13344c37092b56e228eec61772fd7' and 'de1eb82a27b8733c877b2b4634ac2f86d8c71cfd' diff -r 32ab95bbfb99 -r 93ea759f6ec9 COPYRIGHT --- a/COPYRIGHT Mon Sep 17 04:28:08 2007 +0000 +++ b/COPYRIGHT Mon Sep 17 04:29:20 2007 +0000 @@ -246,6 +246,7 @@ Kevin Miller Paul Miller Arkadiusz Miskiewicz +David Mohr Andrew Molloy Michael Monreal Benjamin Moody diff -r 32ab95bbfb99 -r 93ea759f6ec9 ChangeLog --- a/ChangeLog Mon Sep 17 04:28:08 2007 +0000 +++ b/ChangeLog Mon Sep 17 04:29:20 2007 +0000 @@ -13,6 +13,8 @@ * 'Move to' menu in buddy list context menu for moving buddies to other groups * Move "Smiley" to the top-level of the toolbar + * Save Pidgin's display in the command line for session + restoration. (David Mohr) Version 2.2.0 (09/13/2007): http://developer.pidgin.im/query?status=closed&milestone=2.2.0 diff -r 32ab95bbfb99 -r 93ea759f6ec9 libpurple/protocols/myspace/markup.c --- a/libpurple/protocols/myspace/markup.c Mon Sep 17 04:28:08 2007 +0000 +++ b/libpurple/protocols/myspace/markup.c Mon Sep 17 04:29:20 2007 +0000 @@ -1,691 +1,698 @@ -/* MySpaceIM Protocol Plugin - markup - * - * Copyright (C) 2007, Jeff Connelly - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#include "myspace.h" - -typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **); - -/* Internal functions */ - -static guint msim_point_to_purple_size(MsimSession *session, guint point); -static guint msim_purple_size_to_point(MsimSession *session, guint size); -static guint msim_height_to_point(MsimSession *session, guint height); -static guint msim_point_to_height(MsimSession *session, guint point); - -static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, gchar **begin, gchar **end); -static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, gchar **begin, gchar **end); -static gchar *msim_convert_xml(MsimSession *, const gchar *raw, MSIM_XMLNODE_CONVERT f); -static gchar *msim_convert_smileys_to_markup(gchar *before); -static double msim_round(double round); - - -/* Globals */ - -/* The names in in emoticon_names (for ) map to corresponding - * entries in emoticon_symbols (for the ASCII representation of the emoticon). - * - * Multiple emoticon symbols in Pidgin can map to one name. List the - * canonical form, as inserted by the "Smile!" dialog, first. For example, - * :) comes before :-), because although both are recognized as 'happy', - * the first is inserted by the smiley button (first symbol in theme). - * - * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */ -static struct MSIM_EMOTICON -{ - gchar *name; - gchar *symbol; -} msim_emoticons[] = { - /* Unfortunately, this list duplicates much of the file - * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because - * that file is part of Pidgin, but we're part of libpurple. - */ - { "bigsmile", ":D" }, - { "bigsmile", ":-D" }, - { "devil", "}:)" }, - { "frazzled", ":Z" }, - { "geek", "B)" }, - { "googles", "%)" }, - { "growl", ":E" }, - { "laugh", ":))" }, /* Must be before ':)' */ - { "happy", ":)" }, - { "happy", ":-)" }, - { "happi", ":)" }, - { "heart", ":X" }, - { "mohawk", "-:" }, - { "mad", "X(" }, - { "messed", "X)" }, - { "nerd", "Q)" }, - { "oops", ":G" }, - { "pirate", "P)" }, - { "scared", ":O" }, - { "sidefrown", ":{" }, - { "sinister", ":B" }, - { "smirk", ":," }, - { "straight", ":|" }, - { "tongue", ":P" }, - { "tongue", ":p" }, - { "tongy", ":P" }, - { "upset", "B|" }, - { "wink", ";-)" }, - { "wink", ";)" }, - { "winc", ";)" }, - { "worried", ":[" }, - { "kiss", ":x" }, - { NULL, NULL } -}; - - - -/* Indexes of this array + 1 map HTML font size to scale of normal font size. * - * Based on _point_sizes from libpurple/gtkimhtml.c - * 1 2 3 4 5 6 7 */ -static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 }; - -#define MAX_FONT_SIZE 7 /* Purple maximum font size */ -#define POINTS_PER_INCH 72 /* How many pt's in an inch */ - -/* Text formatting bits for */ -#define MSIM_TEXT_BOLD 1 -#define MSIM_TEXT_ITALIC 2 -#define MSIM_TEXT_UNDERLINE 4 - -/* Default baseline size of purple's fonts, in points. What is size 3 in points. - * _font_scale specifies scaling factor relative to this point size. Note this - * is only the default; it is configurable in account options. */ -#define MSIM_BASE_FONT_POINT_SIZE 8 - -/* Default display's DPI. 96 is common but it can differ. Also configurable - * in account options. */ -#define MSIM_DEFAULT_DPI 96 - - -/* round is part of C99, but sometimes is unavailable before then. - * Based on http://forums.belution.com/en/cpp/000/050/13.shtml - */ -double msim_round(double value) -{ - if (value < 0) { - return -(floor(-value + 0.5)); - } else { - return floor( value + 0.5); - } -} - - -/** Convert typographical font point size to HTML font size. - * Based on libpurple/gtkimhtml.c */ -static guint -msim_point_to_purple_size(MsimSession *session, guint point) -{ - guint size, this_point, base; - gdouble scale; - - base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); - - for (size = 0; - size < sizeof(_font_scale) / sizeof(_font_scale[0]); - ++size) { - scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; - this_point = (guint)msim_round(scale * base); - - if (this_point >= point) { - purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n", - point, size); - return size; - } - } - - /* No HTML font size was this big; return largest possible. */ - return this_point; -} - -/** Convert HTML font size to point size. */ -static guint -msim_purple_size_to_point(MsimSession *session, guint size) -{ - gdouble scale; - guint point; - guint base; - - scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; - - base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); - - point = (guint)msim_round(scale * base); - - purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n", - size, point); - - return point; -} - -/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */ -static guint -msim_height_to_point(MsimSession *session, guint height) -{ - guint dpi; - - dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); - - return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height); - - /* See also: libpurple/protocols/bonjour/jabber.c - * _font_size_ichat_to_purple */ -} - -/** Convert point size to msim pixel height font size specification, for outgoing messages. */ -static guint -msim_point_to_height(MsimSession *session, guint point) -{ - guint dpi; - - dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); - - return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point); -} - -/** Convert the msim markup (font) tag into HTML. */ -static void -msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - const gchar *face, *height_str, *decor_str; - GString *gs_end, *gs_begin; - guint decor, height; - - face = xmlnode_get_attrib(root, "f"); - height_str = xmlnode_get_attrib(root, "h"); - decor_str = xmlnode_get_attrib(root, "s"); - - if (height_str) { - height = atol(height_str); - } else { - height = 12; - } - - if (decor_str) { - decor = atol(decor_str); - } else { - decor = 0; - } - - gs_begin = g_string_new(""); - /* TODO: get font size working */ - if (height && !face) { - g_string_printf(gs_begin, "", - msim_point_to_purple_size(session, msim_height_to_point(session, height))); - } else if (height && face) { - g_string_printf(gs_begin, "", face, - msim_point_to_purple_size(session, msim_height_to_point(session, height))); - } else { - g_string_printf(gs_begin, ""); - } - - /* No support for font-size CSS? */ - /* g_string_printf(gs_begin, "", face, - msim_height_to_point(height)); */ - - gs_end = g_string_new(""); - - if (decor & MSIM_TEXT_BOLD) { - g_string_append(gs_begin, ""); - g_string_prepend(gs_end, ""); - } - - if (decor & MSIM_TEXT_ITALIC) { - g_string_append(gs_begin, ""); - g_string_append(gs_end, ""); - } - - if (decor & MSIM_TEXT_UNDERLINE) { - g_string_append(gs_begin, ""); - g_string_append(gs_end, ""); - } - - - *begin = gs_begin->str; - *end = gs_end->str; -} - -/** Convert a msim markup color to a color suitable for libpurple. - * - * @param msim Either a color name, or an rgb(x,y,z) code. - * - * @return A new string, either a color name or #rrggbb code. Must g_free(). - */ -static char * -msim_color_to_purple(const char *msim) -{ - guint red, green, blue; - - if (!msim) { - return g_strdup("black"); - } - - if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) { - /* Color name. */ - return g_strdup(msim); - } - /* TODO: rgba (alpha). */ - - return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue); -} - -/** Convert the msim markup (anchor) tag into HTML. */ -static void -msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - const gchar *href; - - href = xmlnode_get_attrib(root, "h"); - if (!href) { - href = ""; - } - - *begin = g_strdup_printf("%s", href, href); - *end = g_strdup(""); -} - -/** Convert the msim markup

(paragraph) tag into HTML. */ -static void -msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - /* Just pass through unchanged. - * - * Note: attributes currently aren't passed, if there are any. */ - *begin = g_strdup("

"); - *end = g_strdup("

"); -} - -/** Convert the msim markup tag (text color) into HTML. TODO: Test */ -static void -msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - const gchar *color; - gchar *purple_color; - - color = xmlnode_get_attrib(root, "v"); - if (!color) { - purple_debug_info("msim", "msim_markup_c_to_html: tag w/o v attr\n"); - *begin = g_strdup(""); - *end = g_strdup(""); - /* TODO: log as unrecognized */ - return; - } - - purple_color = msim_color_to_purple(color); - - *begin = g_strdup_printf("", purple_color); - - g_free(purple_color); - - /* *begin = g_strdup_printf("", color); */ - *end = g_strdup(""); -} - -/** Convert the msim markup tag (background color) into HTML. TODO: Test */ -static void -msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - const gchar *color; - gchar *purple_color; - - color = xmlnode_get_attrib(root, "v"); - if (!color) { - *begin = g_strdup(""); - *end = g_strdup(""); - purple_debug_info("msim", "msim_markup_b_to_html: w/o v attr\n"); - /* TODO: log as unrecognized. */ - return; - } - - purple_color = msim_color_to_purple(color); - - /* TODO: find out how to set background color. */ - *begin = g_strdup_printf("", - purple_color); - g_free(purple_color); - - *end = g_strdup("

"); -} - -/** Convert the msim markup tag (emoticon image) into HTML. */ -static void -msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - const gchar *name; - guint i; - struct MSIM_EMOTICON *emote; - - name = xmlnode_get_attrib(root, "n"); - if (!name) { - purple_debug_info("msim", "msim_markup_i_to_html: w/o n\n"); - *begin = g_strdup(""); - *end = g_strdup(""); - /* TODO: log as unrecognized */ - return; - } - - /* Find and use canonical form of smiley symbol. */ - for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { - if (g_str_equal(name, emote->name)) { - *begin = g_strdup(emote->symbol); - *end = g_strdup(""); - return; - } - } - - /* Couldn't find it, sorry. Try to degrade gracefully. */ - *begin = g_strdup_printf("**%s**", name); - *end = g_strdup(""); -} - -/** Convert an individual msim markup tag to HTML. */ -static void -msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, - gchar **end) -{ - if (g_str_equal(root->name, "f")) { - msim_markup_f_to_html(session, root, begin, end); - } else if (g_str_equal(root->name, "a")) { - msim_markup_a_to_html(session, root, begin, end); - } else if (g_str_equal(root->name, "p")) { - msim_markup_p_to_html(session, root, begin, end); - } else if (g_str_equal(root->name, "c")) { - msim_markup_c_to_html(session, root, begin, end); - } else if (g_str_equal(root->name, "b")) { - msim_markup_b_to_html(session, root, begin, end); - } else if (g_str_equal(root->name, "i")) { - msim_markup_i_to_html(session, root, begin, end); - } else { - purple_debug_info("msim", "msim_markup_tag_to_html: " - "unknown tag name=%s, ignoring", - (root && root->name) ? root->name : "(NULL)"); - *begin = g_strdup(""); - *end = g_strdup(""); - } -} - -/** Convert an individual HTML tag to msim markup. */ -static void -html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, - gchar **end) -{ - /* TODO: Coalesce nested tags into one tag! - * Currently, the 's' value will be overwritten when b/i/u is nested - * within another one, and only the inner-most formatting will be - * applied to the text. */ - if (!purple_utf8_strcasecmp(root->name, "root")) { - *begin = g_strdup(""); - *end = g_strdup(""); - } else if (!purple_utf8_strcasecmp(root->name, "b")) { - *begin = g_strdup_printf("", MSIM_TEXT_BOLD); - *end = g_strdup(""); - } else if (!purple_utf8_strcasecmp(root->name, "i")) { - *begin = g_strdup_printf("", MSIM_TEXT_ITALIC); - *end = g_strdup(""); - } else if (!purple_utf8_strcasecmp(root->name, "u")) { - *begin = g_strdup_printf("", MSIM_TEXT_UNDERLINE); - *end = g_strdup(""); - } else if (!purple_utf8_strcasecmp(root->name, "a")) { - const gchar *href, *link_text; - - href = xmlnode_get_attrib(root, "href"); - - if (!href) { - href = xmlnode_get_attrib(root, "HREF"); - } - - link_text = xmlnode_get_data(root); - - if (href) { - if (g_str_equal(link_text, href)) { - /* Purple gives us: URL - * Translate to - * Displayed as text of URL with link to URL - */ - *begin = g_strdup_printf("", href); - } else { - /* But if we get: text - * Translate to: text: - * - * Because official client only supports self-closed - * tags; you can't change the link text. - */ - *begin = g_strdup_printf("%s: ", link_text, href); - } - } else { - *begin = g_strdup(""); - } - - /* Sorry, kid. MySpace doesn't support you within tags. */ - xmlnode_free(root->child); - root->child = NULL; - - *end = g_strdup(""); - } else if (!purple_utf8_strcasecmp(root->name, "font")) { - const gchar *size; - const gchar *face; - - size = xmlnode_get_attrib(root, "size"); - face = xmlnode_get_attrib(root, "face"); - - if (face && size) { - *begin = g_strdup_printf("", face, - msim_point_to_height(session, - msim_purple_size_to_point(session, atoi(size)))); - } else if (face) { - *begin = g_strdup_printf("", face); - } else if (size) { - *begin = g_strdup_printf("", - msim_point_to_height(session, - msim_purple_size_to_point(session, atoi(size)))); - } else { - *begin = g_strdup(""); - } - - *end = g_strdup(""); - - /* TODO: color (bg uses ), emoticons */ - } else { - *begin = g_strdup_printf("[%s]", root->name); - *end = g_strdup_printf("[/%s]", root->name); - } -} - -/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup. - * - * @param f Function to convert tags. - * - * @return An HTML string. Caller frees. - */ -static gchar * -msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f) -{ - xmlnode *node; - gchar *begin, *inner, *end; - GString *final; - - if (!root || !root->name) { - return g_strdup(""); - } - - purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n", - root->name); - - begin = inner = end = NULL; - - final = g_string_new(""); - - f(session, root, &begin, &end); - - g_string_append(final, begin); - - /* Loop over all child nodes. */ - for (node = root->child; node != NULL; node = node->next) { - switch (node->type) { - case XMLNODE_TYPE_ATTRIB: - /* Attributes handled above. */ - break; - - case XMLNODE_TYPE_TAG: - /* A tag or tag with attributes. Recursively descend. */ - inner = msim_convert_xmlnode(session, node, f); - g_return_val_if_fail(inner != NULL, NULL); - - purple_debug_info("msim", " ** node name=%s\n", - (node && node->name) ? node->name : "(NULL)"); - break; - - case XMLNODE_TYPE_DATA: - /* Literal text. */ - inner = g_new0(char, node->data_sz + 1); - strncpy(inner, node->data, node->data_sz); - inner[node->data_sz] = 0; - - purple_debug_info("msim", " ** node data=%s\n", - inner ? inner : "(NULL)"); - break; - - default: - purple_debug_info("msim", - "msim_convert_xmlnode: strange node\n"); - inner = g_strdup(""); - } - - if (inner) { - g_string_append(final, inner); - } - } - - /* TODO: Note that msim counts each piece of text enclosed by as - * a paragraph and will display each on its own line. You actually have - * to _nest_ tags to intersperse different text in one paragraph! - * Comment out this line below to see. */ - g_string_append(final, end); - - purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n", - (final && final->str) ? final->str : "(NULL)"); - - return final->str; -} - -/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */ -static gchar * -msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f) -{ - xmlnode *root; - gchar *str; - gchar *enclosed_raw; - - g_return_val_if_fail(raw != NULL, NULL); - - /* Enclose text in one root tag, to try to make it valid XML for parsing. */ - enclosed_raw = g_strconcat("", raw, "", NULL); - - root = xmlnode_from_str(enclosed_raw, -1); - - if (!root) { - purple_debug_info("msim", "msim_markup_to_html: couldn't parse " - "%s as XML, returning raw: %s\n", enclosed_raw, raw); - /* TODO: msim_unrecognized */ - g_free(enclosed_raw); - return g_strdup(raw); - } - - g_free(enclosed_raw); - - str = msim_convert_xmlnode(session, root, f); - g_return_val_if_fail(str != NULL, NULL); - purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str); - - xmlnode_free(root); - - return str; -} - -/** Convert plaintext smileys to markup tags. - * - * @param before Original text with ASCII smileys. Will be freed. - * @return A new string with tags, if applicable. Must be g_free()'d. - */ -static gchar * -msim_convert_smileys_to_markup(gchar *before) -{ - gchar *old, *new, *replacement; - guint i; - struct MSIM_EMOTICON *emote; - - old = before; - new = NULL; - - for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { - gchar *name, *symbol; - - name = emote->name; - symbol = emote->symbol; - - replacement = g_strdup_printf("", name); - - purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n", - symbol ? symbol : "(NULL)", - replacement ? replacement : "(NULL)"); - new = purple_strreplace(old, symbol, replacement); - - g_free(replacement); - g_free(old); - - old = new; - } - - return new; -} - - -/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. - * - * @return Purple markup string, must be g_free()'d. */ -gchar * -msim_markup_to_html(MsimSession *session, const gchar *raw) -{ - return msim_convert_xml(session, raw, - (MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html)); -} - -/** High-level function to convert Purple (HTML) to MySpaceIM markup. - * - * TODO: consider using purple_markup_html_to_xhtml() to make valid XML. - * - * @return HTML markup string, must be g_free()'d. */ -gchar * -html_to_msim_markup(MsimSession *session, const gchar *raw) -{ - gchar *markup; - - markup = msim_convert_xml(session, raw, - (MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup)); - - if (purple_account_get_bool(session->account, "emoticons", TRUE)) { - /* Frees markup and allocates a new one. */ - markup = msim_convert_smileys_to_markup(markup); - } - - return markup; -} - - +/* MySpaceIM Protocol Plugin - markup + * + * Copyright (C) 2007, Jeff Connelly + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "myspace.h" + +typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **); + +/* Internal functions */ + +static guint msim_point_to_purple_size(MsimSession *session, guint point); +static guint msim_purple_size_to_point(MsimSession *session, guint size); +static guint msim_height_to_point(MsimSession *session, guint height); +static guint msim_point_to_height(MsimSession *session, guint point); + +static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, gchar **begin, gchar **end); +static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, gchar **begin, gchar **end); +static gchar *msim_convert_xml(MsimSession *, const gchar *raw, MSIM_XMLNODE_CONVERT f); +static gchar *msim_convert_smileys_to_markup(gchar *before); +static double msim_round(double round); + + +/* Globals */ + +/* The names in in emoticon_names (for ) map to corresponding + * entries in emoticon_symbols (for the ASCII representation of the emoticon). + * + * Multiple emoticon symbols in Pidgin can map to one name. List the + * canonical form, as inserted by the "Smile!" dialog, first. For example, + * :) comes before :-), because although both are recognized as 'happy', + * the first is inserted by the smiley button (first symbol in theme). + * + * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */ +static struct MSIM_EMOTICON +{ + gchar *name; + gchar *symbol; +} msim_emoticons[] = { + /* Unfortunately, this list duplicates much of the file + * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because + * that file is part of Pidgin, but we're part of libpurple. + */ + { "bigsmile", ":D" }, + { "bigsmile", ":-D" }, + { "devil", "}:)" }, + { "frazzled", ":Z" }, + { "geek", "B)" }, + { "googles", "%)" }, + { "growl", ":E" }, + { "laugh", ":))" }, /* Must be before ':)' */ + { "happy", ":)" }, + { "happy", ":-)" }, + { "happi", ":)" }, + { "heart", ":X" }, + { "mohawk", "-:" }, + { "mad", "X(" }, + { "messed", "X)" }, + { "nerd", "Q)" }, + { "oops", ":G" }, + { "pirate", "P)" }, + { "scared", ":O" }, + { "sidefrown", ":{" }, + { "sinister", ":B" }, + { "smirk", ":," }, + { "straight", ":|" }, + { "tongue", ":P" }, + { "tongue", ":p" }, + { "tongy", ":P" }, + { "upset", "B|" }, + { "wink", ";-)" }, + { "wink", ";)" }, + { "winc", ";)" }, + { "worried", ":[" }, + { "kiss", ":x" }, + { NULL, NULL } +}; + + + +/* Indexes of this array + 1 map HTML font size to scale of normal font size. * + * Based on _point_sizes from libpurple/gtkimhtml.c + * 1 2 3 4 5 6 7 */ +static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 }; + +#define MAX_FONT_SIZE 7 /* Purple maximum font size */ +#define POINTS_PER_INCH 72 /* How many pt's in an inch */ + +/* Text formatting bits for */ +#define MSIM_TEXT_BOLD 1 +#define MSIM_TEXT_ITALIC 2 +#define MSIM_TEXT_UNDERLINE 4 + +/* Default baseline size of purple's fonts, in points. What is size 3 in points. + * _font_scale specifies scaling factor relative to this point size. Note this + * is only the default; it is configurable in account options. */ +#define MSIM_BASE_FONT_POINT_SIZE 8 + +/* Default display's DPI. 96 is common but it can differ. Also configurable + * in account options. */ +#define MSIM_DEFAULT_DPI 96 + + +/* round is part of C99, but sometimes is unavailable before then. + * Based on http://forums.belution.com/en/cpp/000/050/13.shtml + */ +double msim_round(double value) +{ + if (value < 0) { + return -(floor(-value + 0.5)); + } else { + return floor( value + 0.5); + } +} + + +/** Convert typographical font point size to HTML font size. + * Based on libpurple/gtkimhtml.c */ +static guint +msim_point_to_purple_size(MsimSession *session, guint point) +{ + guint size, this_point, base; + gdouble scale; + + base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); + + for (size = 0; + size < sizeof(_font_scale) / sizeof(_font_scale[0]); + ++size) { + scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; + this_point = (guint)msim_round(scale * base); + + if (this_point >= point) { + purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n", + point, size); + return size; + } + } + + /* No HTML font size was this big; return largest possible. */ + return this_point; +} + +/** Convert HTML font size to point size. */ +static guint +msim_purple_size_to_point(MsimSession *session, guint size) +{ + gdouble scale; + guint point; + guint base; + + scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; + + base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); + + point = (guint)msim_round(scale * base); + + purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n", + size, point); + + return point; +} + +/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */ +static guint +msim_height_to_point(MsimSession *session, guint height) +{ + guint dpi; + + dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); + + return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height); + + /* See also: libpurple/protocols/bonjour/jabber.c + * _font_size_ichat_to_purple */ +} + +/** Convert point size to msim pixel height font size specification, for outgoing messages. */ +static guint +msim_point_to_height(MsimSession *session, guint point) +{ + guint dpi; + + dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); + + return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point); +} + +/** Convert the msim markup (font) tag into HTML. */ +static void +msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *face, *height_str, *decor_str; + GString *gs_end, *gs_begin; + guint decor, height; + + face = xmlnode_get_attrib(root, "f"); + height_str = xmlnode_get_attrib(root, "h"); + decor_str = xmlnode_get_attrib(root, "s"); + + if (height_str) { + height = atol(height_str); + } else { + height = 12; + } + + if (decor_str) { + decor = atol(decor_str); + } else { + decor = 0; + } + + gs_begin = g_string_new(""); + /* TODO: get font size working */ + if (height && !face) { + g_string_printf(gs_begin, "", + msim_point_to_purple_size(session, msim_height_to_point(session, height))); + } else if (height && face) { + g_string_printf(gs_begin, "", face, + msim_point_to_purple_size(session, msim_height_to_point(session, height))); + } else { + g_string_printf(gs_begin, ""); + } + + /* No support for font-size CSS? */ + /* g_string_printf(gs_begin, "", face, + msim_height_to_point(height)); */ + + gs_end = g_string_new(""); + + if (decor & MSIM_TEXT_BOLD) { + g_string_append(gs_begin, ""); + g_string_prepend(gs_end, ""); + } + + if (decor & MSIM_TEXT_ITALIC) { + g_string_append(gs_begin, ""); + g_string_append(gs_end, ""); + } + + if (decor & MSIM_TEXT_UNDERLINE) { + g_string_append(gs_begin, ""); + g_string_append(gs_end, ""); + } + + + *begin = gs_begin->str; + *end = gs_end->str; +} + +/** Convert a msim markup color to a color suitable for libpurple. + * + * @param msim Either a color name, or an rgb(x,y,z) code. + * + * @return A new string, either a color name or #rrggbb code. Must g_free(). + */ +static char * +msim_color_to_purple(const char *msim) +{ + guint red, green, blue; + + if (!msim) { + return g_strdup("black"); + } + + if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) { + /* Color name. */ + return g_strdup(msim); + } + /* TODO: rgba (alpha). */ + + return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue); +} + +/** Convert the msim markup (anchor) tag into HTML. */ +static void +msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *href; + + href = xmlnode_get_attrib(root, "h"); + if (!href) { + href = ""; + } + + *begin = g_strdup_printf("%s", href, href); + *end = g_strdup(""); +} + +/** Convert the msim markup

(paragraph) tag into HTML. */ +static void +msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + /* Just pass through unchanged. + * + * Note: attributes currently aren't passed, if there are any. */ + *begin = g_strdup("

"); + *end = g_strdup("

"); +} + +/** Convert the msim markup tag (text color) into HTML. TODO: Test */ +static void +msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *color; + gchar *purple_color; + + color = xmlnode_get_attrib(root, "v"); + if (!color) { + purple_debug_info("msim", "msim_markup_c_to_html: tag w/o v attr\n"); + *begin = g_strdup(""); + *end = g_strdup(""); + /* TODO: log as unrecognized */ + return; + } + + purple_color = msim_color_to_purple(color); + + *begin = g_strdup_printf("", purple_color); + + g_free(purple_color); + + /* *begin = g_strdup_printf("", color); */ + *end = g_strdup(""); +} + +/** Convert the msim markup tag (background color) into HTML. TODO: Test */ +static void +msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *color; + gchar *purple_color; + + color = xmlnode_get_attrib(root, "v"); + if (!color) { + *begin = g_strdup(""); + *end = g_strdup(""); + purple_debug_info("msim", "msim_markup_b_to_html: w/o v attr\n"); + /* TODO: log as unrecognized. */ + return; + } + + purple_color = msim_color_to_purple(color); + + /* TODO: find out how to set background color. */ + *begin = g_strdup_printf("", + purple_color); + g_free(purple_color); + + *end = g_strdup("

"); +} + +/** Convert the msim markup tag (emoticon image) into HTML. */ +static void +msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *name; + guint i; + struct MSIM_EMOTICON *emote; + + name = xmlnode_get_attrib(root, "n"); + if (!name) { + purple_debug_info("msim", "msim_markup_i_to_html: w/o n\n"); + *begin = g_strdup(""); + *end = g_strdup(""); + /* TODO: log as unrecognized */ + return; + } + + /* Find and use canonical form of smiley symbol. */ + for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { + if (g_str_equal(name, emote->name)) { + *begin = g_strdup(emote->symbol); + *end = g_strdup(""); + return; + } + } + + /* Couldn't find it, sorry. Try to degrade gracefully. */ + *begin = g_strdup_printf("**%s**", name); + *end = g_strdup(""); +} + +/** Convert an individual msim markup tag to HTML. */ +static void +msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, + gchar **end) +{ + if (g_str_equal(root->name, "f")) { + msim_markup_f_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "a")) { + msim_markup_a_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "p")) { + msim_markup_p_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "c")) { + msim_markup_c_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "b")) { + msim_markup_b_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "i")) { + msim_markup_i_to_html(session, root, begin, end); + } else { + purple_debug_info("msim", "msim_markup_tag_to_html: " + "unknown tag name=%s, ignoring", + (root && root->name) ? root->name : "(NULL)"); + *begin = g_strdup(""); + *end = g_strdup(""); + } +} + +/** Convert an individual HTML tag to msim markup. */ +static void +html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, + gchar **end) +{ + if (!purple_utf8_strcasecmp(root->name, "root") || + !purple_utf8_strcasecmp(root->name, "html")) { + *begin = g_strdup(""); + *end = g_strdup(""); + /* TODO: Coalesce nested tags into one tag! + * Currently, the 's' value will be overwritten when b/i/u is nested + * within another one, and only the inner-most formatting will be + * applied to the text. */ + } else if (!purple_utf8_strcasecmp(root->name, "b")) { + *begin = g_strdup_printf("", MSIM_TEXT_BOLD); + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "i")) { + *begin = g_strdup_printf("", MSIM_TEXT_ITALIC); + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "u")) { + *begin = g_strdup_printf("", MSIM_TEXT_UNDERLINE); + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "a")) { + const gchar *href, *link_text; + + href = xmlnode_get_attrib(root, "href"); + + if (!href) { + href = xmlnode_get_attrib(root, "HREF"); + } + + link_text = xmlnode_get_data(root); + + if (href) { + if (g_str_equal(link_text, href)) { + /* Purple gives us: URL + * Translate to + * Displayed as text of URL with link to URL + */ + *begin = g_strdup_printf("", href); + } else { + /* But if we get: text + * Translate to: text: + * + * Because official client only supports self-closed + * tags; you can't change the link text. + */ + *begin = g_strdup_printf("%s: ", link_text, href); + } + } else { + *begin = g_strdup(""); + } + + /* Sorry, kid. MySpace doesn't support you within tags. */ + xmlnode_free(root->child); + root->child = NULL; + + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "font")) { + const gchar *size; + const gchar *face; + + size = xmlnode_get_attrib(root, "size"); + face = xmlnode_get_attrib(root, "face"); + + if (face && size) { + *begin = g_strdup_printf("", face, + msim_point_to_height(session, + msim_purple_size_to_point(session, atoi(size)))); + } else if (face) { + *begin = g_strdup_printf("", face); + } else if (size) { + *begin = g_strdup_printf("", + msim_point_to_height(session, + msim_purple_size_to_point(session, atoi(size)))); + } else { + *begin = g_strdup(""); + } + + *end = g_strdup(""); + + /* TODO: color (bg uses ), emoticons */ + } else { + +#ifdef MSIM_MARKUP_SHOW_UNKNOWN_TAGS + *begin = g_strdup_printf("[%s]", root->name); + *end = g_strdup_printf("[/%s]", root->name); +#else + *begin = g_strdup(""); + *end = g_strdup(""); +#endif + } +} + +/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup. + * + * @param f Function to convert tags. + * + * @return An HTML string. Caller frees. + */ +static gchar * +msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f) +{ + xmlnode *node; + gchar *begin, *inner, *end; + GString *final; + + if (!root || !root->name) { + return g_strdup(""); + } + + purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n", + root->name); + + begin = inner = end = NULL; + + final = g_string_new(""); + + f(session, root, &begin, &end); + + g_string_append(final, begin); + + /* Loop over all child nodes. */ + for (node = root->child; node != NULL; node = node->next) { + switch (node->type) { + case XMLNODE_TYPE_ATTRIB: + /* Attributes handled above. */ + break; + + case XMLNODE_TYPE_TAG: + /* A tag or tag with attributes. Recursively descend. */ + inner = msim_convert_xmlnode(session, node, f); + g_return_val_if_fail(inner != NULL, NULL); + + purple_debug_info("msim", " ** node name=%s\n", + (node && node->name) ? node->name : "(NULL)"); + break; + + case XMLNODE_TYPE_DATA: + /* Literal text. */ + inner = g_new0(char, node->data_sz + 1); + strncpy(inner, node->data, node->data_sz); + inner[node->data_sz] = 0; + + purple_debug_info("msim", " ** node data=%s\n", + inner ? inner : "(NULL)"); + break; + + default: + purple_debug_info("msim", + "msim_convert_xmlnode: strange node\n"); + inner = g_strdup(""); + } + + if (inner) { + g_string_append(final, inner); + } + } + + /* TODO: Note that msim counts each piece of text enclosed by as + * a paragraph and will display each on its own line. You actually have + * to _nest_ tags to intersperse different text in one paragraph! + * Comment out this line below to see. */ + g_string_append(final, end); + + purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n", + (final && final->str) ? final->str : "(NULL)"); + + return final->str; +} + +/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */ +static gchar * +msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f) +{ + xmlnode *root; + gchar *str; + gchar *enclosed_raw; + + g_return_val_if_fail(raw != NULL, NULL); + + /* Enclose text in one root tag, to try to make it valid XML for parsing. */ + enclosed_raw = g_strconcat("", raw, "", NULL); + + root = xmlnode_from_str(enclosed_raw, -1); + + if (!root) { + purple_debug_info("msim", "msim_markup_to_html: couldn't parse " + "%s as XML, returning raw: %s\n", enclosed_raw, raw); + /* TODO: msim_unrecognized */ + g_free(enclosed_raw); + return g_strdup(raw); + } + + g_free(enclosed_raw); + + str = msim_convert_xmlnode(session, root, f); + g_return_val_if_fail(str != NULL, NULL); + purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str); + + xmlnode_free(root); + + return str; +} + +/** Convert plaintext smileys to markup tags. + * + * @param before Original text with ASCII smileys. Will be freed. + * @return A new string with tags, if applicable. Must be g_free()'d. + */ +static gchar * +msim_convert_smileys_to_markup(gchar *before) +{ + gchar *old, *new, *replacement; + guint i; + struct MSIM_EMOTICON *emote; + + old = before; + new = NULL; + + for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { + gchar *name, *symbol; + + name = emote->name; + symbol = emote->symbol; + + replacement = g_strdup_printf("", name); + + purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n", + symbol ? symbol : "(NULL)", + replacement ? replacement : "(NULL)"); + new = purple_strreplace(old, symbol, replacement); + + g_free(replacement); + g_free(old); + + old = new; + } + + return new; +} + + +/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. + * + * @return Purple markup string, must be g_free()'d. */ +gchar * +msim_markup_to_html(MsimSession *session, const gchar *raw) +{ + return msim_convert_xml(session, raw, + (MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html)); +} + +/** High-level function to convert Purple (HTML) to MySpaceIM markup. + * + * TODO: consider using purple_markup_html_to_xhtml() to make valid XML. + * + * @return HTML markup string, must be g_free()'d. */ +gchar * +html_to_msim_markup(MsimSession *session, const gchar *raw) +{ + gchar *markup; + + markup = msim_convert_xml(session, raw, + (MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup)); + + if (purple_account_get_bool(session->account, "emoticons", TRUE)) { + /* Frees markup and allocates a new one. */ + markup = msim_convert_smileys_to_markup(markup); + } + + return markup; +} + + diff -r 32ab95bbfb99 -r 93ea759f6ec9 libpurple/protocols/myspace/myspace.h --- a/libpurple/protocols/myspace/myspace.h Mon Sep 17 04:28:08 2007 +0000 +++ b/libpurple/protocols/myspace/myspace.h Mon Sep 17 04:29:20 2007 +0000 @@ -67,6 +67,9 @@ /*#define MSIM_DEBUG_LOGIN_CHALLENGE*/ /*#define MSIM_DEBUG_RXBUF */ +/* Encode unknown HTML tags in messages in [], instead of ignoring */ +#define MSIM_MARKUP_SHOW_UNKNOWN_TAGS + /* Define to cause init_plugin() to run some tests and print * the results to the Purple debug log, then exit. Useful to * run with 'pidgin -d' to see the output. Don't define if diff -r 32ab95bbfb99 -r 93ea759f6ec9 libpurple/util.c --- a/libpurple/util.c Mon Sep 17 04:28:08 2007 +0000 +++ b/libpurple/util.c Mon Sep 17 04:29:20 2007 +0000 @@ -68,7 +68,7 @@ }; static char *custom_user_dir = NULL; -static char *home_dir = NULL; +static char *user_dir = NULL; PurpleMenuAction * purple_menu_action_new(const char *label, PurpleCallback callback, gpointer data, @@ -2464,10 +2464,10 @@ { if (custom_user_dir != NULL) return custom_user_dir; - else if (!home_dir) - home_dir = g_build_filename(purple_home_dir(), ".purple", NULL); - - return home_dir; + else if (!user_dir) + user_dir = g_build_filename(purple_home_dir(), ".purple", NULL); + + return user_dir; } void purple_util_set_user_dir(const char *dir) diff -r 32ab95bbfb99 -r 93ea759f6ec9 pidgin/gtkmain.c --- a/pidgin/gtkmain.c Mon Sep 17 04:28:08 2007 +0000 +++ b/pidgin/gtkmain.c Mon Sep 17 04:29:20 2007 +0000 @@ -394,6 +394,9 @@ " -n, --nologin don't automatically login\n" " -l, --login[=NAME] automatically login (optional argument NAME specifies\n" " account(s) to use, separated by commas)\n" +#ifndef WIN32 + " --display=DISPLAY X display to use\n" +#endif " -v, --version display the current version and exit\n"), PIDGIN_NAME, VERSION, name); } @@ -481,6 +484,7 @@ {"nologin", no_argument, NULL, 'n'}, {"session", required_argument, NULL, 's'}, {"version", no_argument, NULL, 'v'}, + {"display", required_argument, NULL, 'D'}, {0, 0, 0, 0} }; @@ -626,6 +630,9 @@ case 'm': /* do not ensure single instance. */ opt_si = FALSE; break; + case 'D': /* --display */ + /* handled by gtk_init_check below */ + break; case '?': /* show terse help */ default: show_usage(argv[0], TRUE); diff -r 32ab95bbfb99 -r 93ea759f6ec9 pidgin/gtksession.c --- a/pidgin/gtksession.c Mon Sep 17 04:28:08 2007 +0000 +++ b/pidgin/gtksession.c Mon Sep 17 04:29:20 2007 +0000 @@ -36,6 +36,7 @@ #include #include #include +#include #define ERROR_LENGTH 512 @@ -141,7 +142,7 @@ /* my magic utility function */ static gchar **session_make_command(gchar *client_id, gchar *config_dir) { - gint i = 2; + gint i = 4; gint j = 0; gchar **ret; @@ -161,6 +162,9 @@ ret[j++] = g_strdup(config_dir); } + ret[j++] = g_strdup("--display"); + ret[j++] = g_strdup((gchar *)gdk_display_get_name(gdk_display_get_default())); + ret[j++] = NULL; return ret;