* Pidgin is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* 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 "glibcompat.h" /* for purple_g_stat on win32 */
#include <gdk/gdkkeysyms.h>
#include "conversation.h"
#include "pidgin/minidialog.h"
/******************************************************************************
*****************************************************************************/
COMPLETION_DISPLAYED_COLUMN, /* displayed completion value */
COMPLETION_BUDDY_COLUMN, /* buddy name */
COMPLETION_NORMALIZED_COLUMN, /* UTF-8 normalized & casefolded buddy name */
COMPLETION_COMPARISON_COLUMN, /* UTF-8 normalized & casefolded value for comparison */
COMPLETION_ACCOUNT_COLUMN, /* account */
/******************************************************************************
*****************************************************************************/
PidginFilterBuddyCompletionEntryFunc filter_func;
gpointer filter_func_user_data;
GtkFileChooserNative *icon_filesel;
void (*callback)(const char*,gpointer);
struct _old_button_clicked_cb_data
PidginUtilMiniDialogCallback cb;
/******************************************************************************
*****************************************************************************/
static guint accels_save_timer = 0;
static GSList *minidialogs = NULL;
/******************************************************************************
*****************************************************************************/
void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable)
gtk_window_set_title(wnd, title);
gtk_window_set_title(wnd, PIDGIN_ALERT_TITLE);
gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width);
gtk_window_set_role(wnd, role);
gtk_window_set_resizable(wnd, resizable);
pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
pidgin_window_init(wnd, title, border_width, role, resizable);
pidgin_create_small_button(GtkWidget *image)
button = gtk_button_new();
gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
/* don't allow focus on the close button */
gtk_widget_set_focus_on_click(button, FALSE);
gtk_container_add(GTK_CONTAINER(button), image);
pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable)
wnd = GTK_WINDOW(gtk_dialog_new());
pidgin_window_init(wnd, title, border_width, role, resizable);
pidgin_create_video_widget(void)
GdkRGBA color = {0.0, 0.0, 0.0, 1.0};
video = gtk_drawing_area_new();
gtk_widget_override_background_color(video, GTK_STATE_FLAG_NORMAL, &color);
/* In order to enable client shadow decorations, GtkDialog from GTK+ 3.0
* uses ARGB visual which by default gets inherited by its child widgets.
* XVideo adaptors on the other hand often support just depth 24 and
* rendering video through xvimagesink onto a widget inside a GtkDialog
* then results in no visible output.
* This ensures the default system visual of the drawing area doesn't get
* overridden by the widget's parent.
gtk_widget_set_visual(video,
gdk_screen_get_system_visual(gtk_widget_get_screen(video)));
pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing)
GtkBox *vbox = GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog)));
gtk_box_set_homogeneous(vbox, homogeneous);
gtk_box_set_spacing(vbox, spacing);
GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog)
return gtk_dialog_get_content_area(GTK_DIALOG(dialog));
GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog)
return gtk_dialog_get_action_area(GTK_DIALOG(dialog));
GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
GCallback callback, gpointer callbackdata)
GtkWidget *button = gtk_button_new_with_mnemonic(label);
GtkWidget *bbox = pidgin_dialog_get_action_area(dialog);
/* Handle stock labels if passed in until nothing calls this
* expecting a GtkStock button */
if (gtk_stock_lookup(label, &item)) {
g_object_set(button, "use-stock", TRUE, NULL);
gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata);
pidgin_set_sensitive_if_input(GtkWidget *entry, GtkWidget *dialog)
const char *text = gtk_entry_get_text(GTK_ENTRY(entry));
gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK,
GtkWidget *pidgin_separator(GtkWidget *menu)
menuitem = gtk_separator_menu_item_new();
gtk_widget_show(menuitem);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str,
GCallback cb, gpointer data, gboolean checked)
menuitem = gtk_check_menu_item_new_with_mnemonic(str);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), checked);
g_signal_connect(G_OBJECT(menuitem), "activate", cb, data);
gtk_widget_show_all(menuitem);
pidgin_pixbuf_toolbar_button_from_stock(const char *icon)
GtkWidget *button, *image, *bbox;
button = gtk_toggle_button_new();
gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER(button), bbox);
image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
gtk_widget_show_all(bbox);
pidgin_pixbuf_button_from_stock(const char *text, const char *icon,
PidginButtonOrientation style)
GtkWidget *button, *image, *bbox, *ibox, *lbox = NULL;
button = gtk_button_new();
if (style == PIDGIN_BUTTON_HORIZONTAL) {
bbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
ibox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
lbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
ibox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
lbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add(GTK_CONTAINER(button), bbox);
gtk_box_pack_start(GTK_BOX(bbox), ibox, TRUE, TRUE, 0);
image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_BUTTON);
gtk_box_pack_end(GTK_BOX(ibox), image, FALSE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(bbox), lbox, TRUE, TRUE, 0);
label = GTK_LABEL(gtk_label_new(NULL));
gtk_label_set_text_with_mnemonic(label, text);
gtk_label_set_mnemonic_widget(label, button);
gtk_box_pack_start(GTK_BOX(lbox), GTK_WIDGET(label),
pidgin_set_accessible_label(button, label);
gtk_widget_show_all(bbox);
GtkWidget *pidgin_new_menu_item(GtkWidget *menu, const char *mnemonic,
const char *icon, GCallback cb, gpointer data)
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE);
menuitem = gtk_menu_item_new();
g_signal_connect(G_OBJECT(menuitem), "activate", cb, data);
image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_MENU);
gtk_container_add(GTK_CONTAINER(box), image);
label = gtk_label_new_with_mnemonic(mnemonic);
gtk_container_add(GTK_CONTAINER(box), label);
gtk_container_add(GTK_CONTAINER(menuitem), box);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
gtk_widget_show_all(menuitem);
pidgin_make_frame(GtkWidget *parent, const char *title)
GtkWidget *vbox, *vbox2, *hbox;
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BOX_SPACE);
gtk_box_pack_start(GTK_BOX(parent), vbox, FALSE, FALSE, 0);
label = GTK_LABEL(gtk_label_new(NULL));
labeltitle = g_strdup_printf("<span weight=\"bold\">%s</span>", title);
gtk_label_set_markup(label, labeltitle);
gtk_label_set_xalign(GTK_LABEL(label), 0);
gtk_label_set_yalign(GTK_LABEL(label), 0);
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0);
gtk_widget_show(GTK_WIDGET(label));
pidgin_set_accessible_label(vbox, label);
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
label = GTK_LABEL(gtk_label_new(" "));
gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 0);
gtk_widget_show(GTK_WIDGET(label));
vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BOX_SPACE);
gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 0);
g_object_set_data(G_OBJECT(vbox2), "main-vbox", vbox);
aop_option_menu_get_selected(GtkWidget *optmenu)
g_return_val_if_fail(optmenu != NULL, NULL);
if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(optmenu), &iter))
gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)),
&iter, AOP_DATA_COLUMN, &data, -1);
aop_menu_cb(GtkWidget *optmenu, GCallback cb)
((void (*)(GtkWidget *, gpointer, gpointer))cb)(optmenu,
aop_option_menu_get_selected(optmenu),
g_object_get_data(G_OBJECT(optmenu), "user_data"));
aop_option_menu_replace_menu(GtkWidget *optmenu, AopMenu *new_aop_menu)
gtk_combo_box_set_model(GTK_COMBO_BOX(optmenu), new_aop_menu->model);
gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), new_aop_menu->default_item);
pidgin_create_icon_from_protocol(PurpleProtocol *protocol, PidginProtocolIconSize size, PurpleAccount *account)
const char *protoname = NULL;
protoname = purple_protocol_class_list_icon(protocol, account, NULL);
* Status icons will be themeable too, and then it will look up
* protoname from the theme
tmp = g_strconcat("im-", protoname, ".png", NULL);
filename = g_build_filename(PURPLE_DATADIR,
"pidgin", "icons", "hicolor",
(size == PIDGIN_PROTOCOL_ICON_SMALL) ? "16x16" :
((size == PIDGIN_PROTOCOL_ICON_MEDIUM) ? "22x22" :
pixbuf = pidgin_pixbuf_new_from_file(filename);
aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data)
GtkWidget *optmenu = NULL;
GtkCellRenderer *cr = NULL;
optmenu = gtk_combo_box_new();
gtk_widget_show(optmenu);
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(optmenu), cr = gtk_cell_renderer_pixbuf_new(), FALSE);
gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(optmenu), cr, "pixbuf", AOP_ICON_COLUMN);
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(optmenu), cr = gtk_cell_renderer_text_new(), TRUE);
gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(optmenu), cr, "text", AOP_NAME_COLUMN);
aop_option_menu_replace_menu(optmenu, aop_menu);
g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
g_signal_connect(G_OBJECT(optmenu), "changed", G_CALLBACK(aop_menu_cb), cb);
aop_option_menu_select_by_data(GtkWidget *optmenu, gpointer data)
model = gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu));
if (gtk_tree_model_get_iter_first(model, &iter)) {
gtk_tree_model_get(model, &iter, AOP_DATA_COLUMN, &iter_data, -1);
gtk_combo_box_set_active_iter(GTK_COMBO_BOX(optmenu), &iter);
} while (gtk_tree_model_iter_next(model, &iter));
create_protocols_menu(const char *default_proto_id)
AopMenu *aop_menu = NULL;
PurpleProtocol *protocol;
GdkPixbuf *pixbuf = NULL;
ls = gtk_list_store_new(AOP_COLUMN_COUNT, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
aop_menu = g_malloc0(sizeof(AopMenu));
aop_menu->default_item = 0;
aop_menu->model = GTK_TREE_MODEL(ls);
list = purple_protocols_get_all();
protocol = PURPLE_PROTOCOL(p->data);
pixbuf = pidgin_create_icon_from_protocol(protocol, PIDGIN_PROTOCOL_ICON_SMALL, NULL);
gtk_list_store_append(ls, &iter);
gtk_list_store_set(ls, &iter,
AOP_NAME_COLUMN, purple_protocol_get_name(protocol),
AOP_DATA_COLUMN, purple_protocol_get_id(protocol),
if (default_proto_id != NULL && purple_strequal(purple_protocol_get_id(protocol), default_proto_id))
aop_menu->default_item = i;
pidgin_protocol_option_menu_new(const char *id, GCallback cb,
return aop_option_menu_new(create_protocols_menu(id), cb, user_data);
pidgin_protocol_option_menu_get_selected(GtkWidget *optmenu)
return (const char *)aop_option_menu_get_selected(optmenu);
pidgin_save_accels_cb(GtkAccelGroup *accel_group, guint arg1,
GdkModifierType arg2, GClosure *arg3,
purple_debug(PURPLE_DEBUG_MISC, "accels",
"accel changed, scheduling save.\n");
accels_save_timer = g_timeout_add_seconds(5, pidgin_save_accels,
pidgin_save_accels(gpointer data)
filename = g_build_filename(purple_config_dir(), "accels", NULL);
purple_debug(PURPLE_DEBUG_MISC, "accels", "saving accels to %s\n", filename);
gtk_accel_map_save(filename);
filename = g_build_filename(purple_config_dir(), "accels", NULL);
gtk_accel_map_load(filename);
show_retrieveing_info(PurpleConnection *conn, const char *name)
PurpleNotifyUserInfo *info = purple_notify_user_info_new();
purple_notify_user_info_add_pair_plaintext(info, _("Information"), _("Retrieving..."));
purple_notify_userinfo(conn, name, info, NULL, NULL);
purple_notify_user_info_destroy(info);
void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name)
show_retrieveing_info(conn, name);
purple_serv_get_info(conn, name);
void pidgin_retrieve_user_info_in_chat(PurpleConnection *conn, const char *name, int chat)
PurpleProtocol *protocol = NULL;
pidgin_retrieve_user_info(conn, name);
protocol = purple_connection_get_protocol(conn);
who = purple_protocol_chat_iface_get_user_real_name(protocol, conn, chat, name);
pidgin_retrieve_user_info(conn, who ? who : name);
pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts,
PurpleAccount **ret_account, char **ret_protocol,
char **ret_username, char **ret_alias)
g_return_val_if_fail(msg != NULL, FALSE);
g_return_val_if_fail(ret_protocol != NULL, FALSE);
g_return_val_if_fail(ret_username != NULL, FALSE);
while (*s != '\r' && *s != '\n' && *s != '\0')
while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ' ')
if (*s != '\0') *s++ = '\0';
/* Clear past any whitespace */
/* Now let's grab until the end of the line. */
while (*s != '\r' && *s != '\n' && *s != '\0')
if (*s == '\r') *s++ = '\0';
if (*s == '\n') *s++ = '\0';
if (strchr(key, ':') != NULL)
if (!g_ascii_strcasecmp(key, "X-IM-Username:"))
username = g_strdup(value);
else if (!g_ascii_strcasecmp(key, "X-IM-Protocol:"))
protocol = g_strdup(value);
else if (!g_ascii_strcasecmp(key, "X-IM-Alias:"))
if (username != NULL && protocol != NULL)
*ret_username = username;
*ret_protocol = protocol;
/* Check for a compatible account. */
PurpleAccount *account = NULL;
list = purple_accounts_get_all();
list = purple_connections_get_all();
for (l = list; l != NULL; l = l->next)
PurpleProtocol *proto = NULL;
account = (PurpleAccount *)l->data;
proto = purple_protocols_find(
purple_account_get_protocol_id(account));
gc = (PurpleConnection *)l->data;
account = purple_connection_get_account(gc);
proto = purple_connection_get_protocol(gc);
protoname = purple_protocol_class_list_icon(proto, account, NULL);
if (purple_strequal(protoname, protocol))
/* Special case for AIM and ICQ */
if (account == NULL && (purple_strequal(protocol, "aim") ||
purple_strequal(protocol, "icq")))
for (l = list; l != NULL; l = l->next)
PurpleProtocol *proto = NULL;
account = (PurpleAccount *)l->data;
proto = purple_protocols_find(
purple_account_get_protocol_id(account));
gc = (PurpleConnection *)l->data;
account = purple_connection_get_account(gc);
proto = purple_connection_get_protocol(gc);
protoname = purple_protocol_class_list_icon(proto, account, NULL);
if (purple_strequal(protoname, "aim") || purple_strequal(protoname, "icq"))
pidgin_set_accessible_label(GtkWidget *w, GtkLabel *l)
const gchar *existing_name;
acc = gtk_widget_get_accessible (w);
/* If this object has no name, set it's name with the label text */
existing_name = atk_object_get_name (acc);
label_text = gtk_label_get_text(l);
atk_object_set_name (acc, label_text);
pidgin_set_accessible_relations(w, l);
pidgin_set_accessible_relations (GtkWidget *w, GtkLabel *l)
acc = gtk_widget_get_accessible (w);
label = gtk_widget_get_accessible(GTK_WIDGET(l));
/* Make sure mnemonics work */
gtk_label_set_mnemonic_widget(l, w);
/* Create the labeled-by relation */
set = atk_object_ref_relation_set (acc);
relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABELLED_BY);
atk_relation_set_add (set, relation);
g_object_unref (relation);
/* Create the label-for relation */
set = atk_object_ref_relation_set (label);
relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABEL_FOR);
atk_relation_set_add (set, relation);
g_object_unref (relation);
pidgin_menu_position_func_helper(GtkMenu *menu,
GtkRequisition requisition;
gint space_left, space_right, space_above, space_below;
g_return_if_fail(GTK_IS_MENU(menu));
widget = GTK_WIDGET(menu);
screen = gtk_widget_get_screen(widget);
rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
* We need the requisition to figure out the right place to
* popup the menu. In fact, we always need to ask here, since
* if a size_request was queued while we weren't popped up,
* the requisition won't have been recomputed yet.
gtk_widget_get_preferred_size(widget, NULL, &requisition);
monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
* The placement of popup menus horizontally works like this (with
* - If there is enough room to the right (left) of the mouse cursor,
* position the menu there.
* - Otherwise, if if there is enough room to the left (right) of the
* mouse cursor, position the menu there.
* - Otherwise if the menu is smaller than the monitor, position it
* on the side of the mouse cursor that has the most space available
* - Otherwise (if there is simply not enough room for the menu on the
* monitor), position it as far left (right) as possible.
* Positioning in the vertical direction is similar: first try below
* mouse cursor, then above.
gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
space_left = *x - monitor.x;
space_right = monitor.x + monitor.width - *x - 1;
space_above = *y - monitor.y;
space_below = monitor.y + monitor.height - *y - 1;
/* position horizontally */
if (requisition.width <= space_left ||
requisition.width <= space_right)
if ((rtl && requisition.width <= space_left) ||
(!rtl && requisition.width > space_right))
*x = *x - requisition.width + 1;
/* x is clamped on-screen further down */
else if (requisition.width <= monitor.width)
/* the menu is too big to fit on either side of the mouse
* cursor, but smaller than the monitor. Position it on
* the side that has the most space
if (space_left > space_right)
*x = monitor.x + monitor.width - requisition.width;
else /* menu is simply too big for the monitor */
*x = monitor.x + monitor.width - requisition.width;
/* Position vertically. The algorithm is the same as above, but
* simpler because we don't have to take RTL into account.
if (requisition.height <= space_above ||
requisition.height <= space_below)
if (requisition.height > space_below) {
*y = *y - requisition.height + 1;
*y = CLAMP (*y, monitor.y,
monitor.y + monitor.height - requisition.height);
if (space_below >= space_above)
*y = monitor.y + monitor.height - requisition.height;
pidgin_menu_popup_at_treeview_selection(GtkWidget *menu, GtkWidget *treeview)
GtkTreeViewColumn *column;
gtk_tree_view_get_cursor(GTK_TREE_VIEW(treeview), &path, &column);
g_return_if_fail(path != NULL);
column = gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), 0);
bin_window = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(treeview));
gtk_tree_view_get_cell_area(GTK_TREE_VIEW(treeview), path, column, &rect);
gtk_menu_popup_at_rect(GTK_MENU(menu), bin_window, &rect,
GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST,
gtk_tree_path_free(path);
static void dnd_image_ok_callback(_DndData *data, int choice)
PurpleConversation *conv;
if (g_stat(data->filename, &st)) {
str = g_strdup_printf(_("The following error has occurred loading %s: %s"),
data->filename, g_strerror(errno));
purple_notify_error(NULL, NULL,
_("Failed to load image"), str, NULL);
buddy = purple_blist_find_buddy(data->account, data->who);
purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
contact = purple_buddy_get_contact(buddy);
purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, data->filename);
purple_serv_send_file(purple_account_get_connection(data->account), data->who, data->filename);
conv = PURPLE_CONVERSATION(purple_im_conversation_new(data->account, data->who));
if (!g_file_get_contents(data->filename, &filedata, &size,
str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, err->message);
purple_notify_error(NULL, NULL,
_("Failed to load image"), str, NULL);
shortname = strrchr(data->filename, G_DIR_SEPARATOR);
shortname = shortname ? shortname + 1 : data->filename;
img = purple_image_new_from_data((guint8 *)filedata, size);
purple_image_set_friendly_filename(img, shortname);
# warning fix this when talkatu has a way to programmatically insert an image
// pidgin_webview_insert_image(PIDGIN_WEBVIEW(gtkconv->entry), img);
static void dnd_image_cancel_callback(_DndData *data, int choice)
static void dnd_set_icon_ok_cb(_DndData *data)
dnd_image_ok_callback(data, DND_BUDDY_ICON);
static void dnd_set_icon_cancel_cb(_DndData *data)
pidgin_dnd_file_send_image(PurpleAccount *account, const gchar *who,
PurpleConnection *gc = purple_account_get_connection(account);
PurpleProtocol *protocol = NULL;
_DndData *data = g_new0(_DndData, 1);
gboolean ft = FALSE, im = FALSE;
data->who = g_strdup(who);
data->filename = g_strdup(filename);
protocol = purple_connection_get_protocol(gc);
if (!(purple_connection_get_flags(gc) & PURPLE_CONNECTION_FLAG_NO_IMAGES))
if (protocol && PURPLE_IS_PROTOCOL_XFER(protocol)) {
PurpleProtocolXferInterface *iface =
PURPLE_PROTOCOL_XFER_GET_IFACE(protocol);
ft = purple_protocol_xfer_can_receive(
PURPLE_PROTOCOL_XFER(protocol),
ft = (iface->send_file) ? TRUE : FALSE;
purple_request_choice(NULL, NULL,
_("You have dragged an image"),
_("You can send this image as a file "
"transfer, embed it into this message, "
"or use it as the buddy icon for this user."),
(gpointer)DND_FILE_TRANSFER, _("OK"),
(GCallback)dnd_image_ok_callback, _("Cancel"),
(GCallback)dnd_image_cancel_callback,
purple_request_cpar_from_account(account), data,
_("Set as buddy icon"), DND_BUDDY_ICON,
_("Send image file"), DND_FILE_TRANSFER,
_("Insert in message"), DND_IM_IMAGE,
} else if (!(im || ft)) {
purple_request_yes_no(NULL, NULL, _("You have dragged an image"),
_("Would you like to set it as the buddy icon for this user?"),
PURPLE_DEFAULT_ACTION_NONE,
purple_request_cpar_from_account(account),
data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb);
purple_request_choice(NULL, NULL,
_("You have dragged an image"),
(ft ? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
_("You can insert this image into this message, or use it as the buddy icon for this user")),
GINT_TO_POINTER(ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
_("OK"), (GCallback)dnd_image_ok_callback,
_("Cancel"), (GCallback)dnd_image_cancel_callback,
purple_request_cpar_from_account(account),
_("Set as buddy icon"), DND_BUDDY_ICON,
(ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
pidgin_dnd_file_send_desktop(PurpleAccount *account, const gchar *who,
PurpleConversation *conv;
PidginConversation *gtkconv;
desktop_file = g_key_file_new();
if (!g_key_file_load_from_file(desktop_file, filename, G_KEY_FILE_NONE, &error)) {
purple_debug_warning("D&D", "Failed to load %s: %s\n",
filename, error->message);
name = g_key_file_get_string(desktop_file, G_KEY_FILE_DESKTOP_GROUP,
G_KEY_FILE_DESKTOP_KEY_NAME, &error);
purple_debug_warning("D&D", "Failed to read the Name from a desktop file: %s\n",
type = g_key_file_get_string(desktop_file, G_KEY_FILE_DESKTOP_GROUP,
G_KEY_FILE_DESKTOP_KEY_TYPE, &error);
purple_debug_warning("D&D", "Failed to read the Type from a desktop file: %s\n",
url = g_key_file_get_string(desktop_file, G_KEY_FILE_DESKTOP_GROUP,
G_KEY_FILE_DESKTOP_KEY_URL, &error);
purple_debug_warning("D&D", "Failed to read the Type from a desktop file: %s\n",
/* If any of this is null, do nothing. */
if (!name || !type || !url) {
/* I don't know if we really want to do anything here. Most of
* the desktop item types are crap like "MIME Type" (I have no
* clue how that would be a desktop item) and "Comment"...
* nothing we can really send. The only logical one is
* "Application," but do we really want to send a binary and
* nothing else? Probably not. I'll just give an error and
/* The original patch sent the icon used by the launcher. That's probably wrong */
if (purple_strequal(type, "Link")) {
purple_notify_error(NULL, NULL, _("Cannot send launcher"),
_("You dragged a desktop launcher. Most "
"likely you wanted to send the target "
"of this launcher instead of this "
"launcher itself."), NULL);
GtkTextBuffer *buffer = NULL;
GtkTextMark *mark = NULL;
conv = PURPLE_CONVERSATION(purple_im_conversation_new(account, who));
gtkconv = PIDGIN_CONVERSATION(conv);
buffer = talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv->editor));
mark = gtk_text_buffer_get_insert(buffer);
gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
talkatu_buffer_insert_link(TALKATU_BUFFER(buffer), &iter, name, url);
pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who)
GList *files = purple_uri_list_extract_filenames((const gchar *) gtk_selection_data_get_data(sd));
PurpleConnection *gc = purple_account_get_connection(account);
g_return_if_fail(account != NULL);
g_return_if_fail(who != NULL);
for ( ; files; files = g_list_delete_link(files, files)) {
basename = g_path_get_basename(filename);
/* XXX - Make ft API support creating a transfer with more than one file */
if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
/* XXX - make ft api suupport sending a directory */
/* Are we dealing with a directory? */
if (g_file_test(filename, G_FILE_TEST_IS_DIR)) {
str = g_strdup_printf(_("Cannot send folder %s."), basename);
str2 = g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME);
purple_notify_error(NULL, NULL, str, str2,
purple_request_cpar_from_connection(gc));
/* Are we dealing with an image? */
pb = pidgin_pixbuf_new_from_file(filename);
pidgin_dnd_file_send_image(account, who, filename);
g_object_unref(G_OBJECT(pb));
/* Are we trying to send a .desktop file? */
else if (g_str_has_suffix(basename, ".desktop")) {
pidgin_dnd_file_send_desktop(account, who, filename);
/* Everything is fine, let's send */
purple_serv_send_file(gc, who, filename);
void pidgin_buddy_icon_get_scale_size(GdkPixbuf *buf, PurpleBuddyIconSpec *spec, PurpleBuddyIconScaleFlags rules, int *width, int *height)
*width = gdk_pixbuf_get_width(buf);
*height = gdk_pixbuf_get_height(buf);
if ((spec == NULL) || !(spec->scale_rules & rules))
purple_buddy_icon_spec_get_scaled_size(spec, width, height);
/* and now for some arbitrary sanity checks */
GdkPixbuf * pidgin_create_status_icon(PurpleStatusPrimitive prim, GtkWidget *w, const char *size)
GtkIconSize icon_size = gtk_icon_size_from_name(size);
GdkPixbuf *pixbuf = NULL;
const char *stock = pidgin_stock_id_from_status_primitive(prim);
pixbuf = gtk_widget_render_icon (w, stock ? stock : PIDGIN_STOCK_STATUS_AVAILABLE,
stock_id_from_status_primitive_idle(PurpleStatusPrimitive prim, gboolean idle)
const char *stock = NULL;
case PURPLE_STATUS_UNSET:
case PURPLE_STATUS_UNAVAILABLE:
stock = idle ? PIDGIN_STOCK_STATUS_BUSY_I : PIDGIN_STOCK_STATUS_BUSY;
stock = idle ? PIDGIN_STOCK_STATUS_AWAY_I : PIDGIN_STOCK_STATUS_AWAY;
case PURPLE_STATUS_EXTENDED_AWAY:
stock = idle ? PIDGIN_STOCK_STATUS_XA_I : PIDGIN_STOCK_STATUS_XA;
case PURPLE_STATUS_INVISIBLE:
stock = PIDGIN_STOCK_STATUS_INVISIBLE;
case PURPLE_STATUS_OFFLINE:
stock = idle ? PIDGIN_STOCK_STATUS_OFFLINE_I : PIDGIN_STOCK_STATUS_OFFLINE;
stock = idle ? PIDGIN_STOCK_STATUS_AVAILABLE_I : PIDGIN_STOCK_STATUS_AVAILABLE;
pidgin_stock_id_from_status_primitive(PurpleStatusPrimitive prim)
return stock_id_from_status_primitive_idle(prim, FALSE);
pidgin_stock_id_from_presence(PurplePresence *presence)
PurpleStatusPrimitive prim;
g_return_val_if_fail(presence, NULL);
status = purple_presence_get_active_status(presence);
type = purple_status_get_status_type(status);
prim = purple_status_type_get_primitive(type);
idle = purple_presence_is_idle(presence);
return stock_id_from_status_primitive_idle(prim, idle);
pidgin_create_protocol_icon(PurpleAccount *account, PidginProtocolIconSize size)
PurpleProtocol *protocol;
g_return_val_if_fail(account != NULL, NULL);
protocol = purple_protocols_find(purple_account_get_protocol_id(account));
return pidgin_create_icon_from_protocol(protocol, size, account);
menu_action_cb(GtkMenuItem *item, gpointer object)
void (*callback)(gpointer, gpointer);
callback = g_object_get_data(G_OBJECT(item), "purplecallback");
data = g_object_get_data(G_OBJECT(item), "purplecallbackdata");
pidgin_append_menu_action(GtkWidget *menu, PurpleActionMenu *act,
return pidgin_separator(menu);
menuitem = gtk_menu_item_new_with_mnemonic(
purple_action_menu_get_label(act));
list = purple_action_menu_get_children(act);
callback = purple_action_menu_get_callback(act);
g_object_set_data(G_OBJECT(menuitem),
g_object_set_data(G_OBJECT(menuitem),
purple_action_menu_get_data(act));
g_signal_connect(G_OBJECT(menuitem), "activate",
G_CALLBACK(menu_action_cb),
gtk_widget_set_sensitive(menuitem, FALSE);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
GtkWidget *submenu = NULL;
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
submenu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
group = gtk_menu_get_accel_group(GTK_MENU(menu));
char *path = g_strdup_printf("%s/%s",
gtk_menu_item_get_accel_path(GTK_MENU_ITEM(menuitem)),
purple_action_menu_get_label(act));
gtk_menu_set_accel_path(GTK_MENU(submenu), path);
gtk_menu_set_accel_group(GTK_MENU(submenu), group);
for (l = list; l; l = l->next) {
PurpleActionMenu *act = (PurpleActionMenu *)l->data;
pidgin_append_menu_action(submenu, act, object);
purple_action_menu_set_children(act, NULL);
purple_action_menu_free(act);
static gboolean buddyname_completion_match_func(GtkEntryCompletion *completion,
const gchar *key, GtkTreeIter *iter, gpointer user_data)
model = gtk_entry_completion_get_model(completion);
gtk_tree_model_get_value(model, iter, COMPLETION_NORMALIZED_COLUMN, &val1);
tmp = g_value_get_string(&val1);
if (tmp != NULL && g_str_has_prefix(tmp, key)) {
gtk_tree_model_get_value(model, iter, COMPLETION_COMPARISON_COLUMN, &val2);
tmp = g_value_get_string(&val2);
if (tmp != NULL && g_str_has_prefix(tmp, key)) {
static gboolean buddyname_completion_match_selected_cb(GtkEntryCompletion *completion,
GtkTreeModel *model, GtkTreeIter *iter, PidginCompletionData *data)
GtkWidget *optmenu = data->accountopt;
gtk_tree_model_get_value(model, iter, COMPLETION_BUDDY_COLUMN, &val);
gtk_entry_set_text(GTK_ENTRY(data->entry), g_value_get_string(&val));
gtk_tree_model_get_value(model, iter, COMPLETION_ACCOUNT_COLUMN, &val);
account = g_value_get_pointer(&val);
aop_option_menu_select_by_data(optmenu, account);
add_buddyname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias,
const PurpleAccount *account, const char *buddyname)
gboolean completion_added = FALSE;
gchar *normalized_buddyname;
tmp = g_utf8_normalize(buddyname, -1, G_NORMALIZE_DEFAULT);
normalized_buddyname = g_utf8_casefold(tmp, -1);
/* There's no sense listing things like: 'xxx "xxx"'
when the name and buddy alias match. */
if (buddy_alias && !purple_strequal(buddy_alias, buddyname)) {
char *completion_entry = g_strdup_printf("%s \"%s\"", buddyname, buddy_alias);
char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT);
tmp = g_utf8_casefold(tmp2, -1);
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter,
COMPLETION_DISPLAYED_COLUMN, completion_entry,
COMPLETION_BUDDY_COLUMN, buddyname,
COMPLETION_NORMALIZED_COLUMN, normalized_buddyname,
COMPLETION_COMPARISON_COLUMN, tmp,
COMPLETION_ACCOUNT_COLUMN, account,
g_free(completion_entry);
/* There's no sense listing things like: 'xxx "xxx"'
when the name and contact alias match. */
if (contact_alias && !purple_strequal(contact_alias, buddyname)) {
/* We don't want duplicates when the contact and buddy alias match. */
if (!purple_strequal(contact_alias, buddy_alias)) {
char *completion_entry = g_strdup_printf("%s \"%s\"",
buddyname, contact_alias);
char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT);
tmp = g_utf8_casefold(tmp2, -1);
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter,
COMPLETION_DISPLAYED_COLUMN, completion_entry,
COMPLETION_BUDDY_COLUMN, buddyname,
COMPLETION_NORMALIZED_COLUMN, normalized_buddyname,
COMPLETION_COMPARISON_COLUMN, tmp,
COMPLETION_ACCOUNT_COLUMN, account,
g_free(completion_entry);
if (completion_added == FALSE) {
/* Add the buddy's name. */
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter,
COMPLETION_DISPLAYED_COLUMN, buddyname,
COMPLETION_BUDDY_COLUMN, buddyname,
COMPLETION_NORMALIZED_COLUMN, normalized_buddyname,
COMPLETION_COMPARISON_COLUMN, NULL,
COMPLETION_ACCOUNT_COLUMN, account,
g_free(normalized_buddyname);
static void get_log_set_name(PurpleLogSet *set, gpointer value, PidginCompletionData *data)
PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
gpointer user_data = data->filter_func_user_data;
/* 1. Don't show buddies because we will have gotten them already.
* 2. The boxes that use this autocomplete code handle only IMs. */
if (!set->buddy && set->type == PURPLE_LOG_IM) {
PidginBuddyCompletionEntry entry;
entry.entry.logged_buddy = set;
if (filter_func(&entry, user_data)) {
add_buddyname_autocomplete_entry(data->store,
NULL, NULL, set->account, set->name);
add_completion_list(PidginCompletionData *data)
PurpleBlistNode *gnode, *cnode, *bnode;
PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
gpointer user_data = data->filter_func_user_data;
gtk_list_store_clear(data->store);
for (gnode = purple_blist_get_default_root(); gnode != NULL;
if (!PURPLE_IS_GROUP(gnode))
for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
if (!PURPLE_IS_CONTACT(cnode))
g_object_get(cnode, "alias", &alias, NULL);
for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
PidginBuddyCompletionEntry entry;
entry.entry.buddy = (PurpleBuddy *) bnode;
if (filter_func(&entry, user_data)) {
add_buddyname_autocomplete_entry(data->store,
purple_buddy_get_contact_alias(entry.entry.buddy),
purple_buddy_get_account(entry.entry.buddy),
purple_buddy_get_name(entry.entry.buddy)
sets = purple_log_get_log_sets();
g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data);
g_hash_table_destroy(sets);
buddyname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data)
purple_signals_disconnect_by_handle(widget);
repopulate_autocomplete(gpointer something, gpointer data)
add_completion_list(data);
pidgin_setup_screenname_autocomplete(
GtkWidget *entry, GtkWidget *chooser,
PidginFilterBuddyCompletionEntryFunc filter_func, gpointer user_data)
PidginCompletionData *data;
* Store the displayed completion value, the buddy name, the UTF-8
* normalized & casefolded buddy name, the UTF-8 normalized &
* casefolded value for comparison, and the account.
GtkEntryCompletion *completion;
data = g_new0(PidginCompletionData, 1);
store = gtk_list_store_new(COMPLETION_COLUMN_COUNT, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
data->accountopt = chooser;
if (filter_func == NULL) {
data->filter_func = pidgin_screenname_autocomplete_default_filter;
data->filter_func_user_data = NULL;
data->filter_func = filter_func;
data->filter_func_user_data = user_data;
add_completion_list(data);
/* Sort the completion list by buddy name */
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
completion = gtk_entry_completion_new();
gtk_entry_completion_set_match_func(completion, buddyname_completion_match_func, NULL, NULL);
g_signal_connect(G_OBJECT(completion), "match-selected",
G_CALLBACK(buddyname_completion_match_selected_cb), data);
gtk_entry_set_completion(GTK_ENTRY(entry), completion);
g_object_unref(completion);
gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store));
gtk_entry_completion_set_text_column(completion, COMPLETION_DISPLAYED_COLUMN);
purple_signal_connect(purple_connections_get_handle(), "signed-on", entry,
PURPLE_CALLBACK(repopulate_autocomplete), data);
purple_signal_connect(purple_connections_get_handle(), "signed-off", entry,
PURPLE_CALLBACK(repopulate_autocomplete), data);
purple_signal_connect(purple_accounts_get_handle(), "account-added", entry,
PURPLE_CALLBACK(repopulate_autocomplete), data);
purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry,
PURPLE_CALLBACK(repopulate_autocomplete), data);
g_signal_connect(G_OBJECT(entry), "destroy", G_CALLBACK(buddyname_autocomplete_destroyed_cb), data);
pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry *completion_entry, gpointer all_accounts) {
gboolean all = GPOINTER_TO_INT(all_accounts);
if (completion_entry->is_buddy) {
return all || purple_account_is_connected(purple_buddy_get_account(completion_entry->entry.buddy));
return all || (completion_entry->entry.logged_buddy->account != NULL && purple_account_is_connected(completion_entry->entry.logged_buddy->account));
void pidgin_set_cursor(GtkWidget *widget, GdkCursorType cursor_type)
g_return_if_fail(widget != NULL);
if (gtk_widget_get_window(widget) == NULL)
display = gtk_widget_get_display(widget);
cursor = gdk_cursor_new_for_display(display, cursor_type);
gdk_window_set_cursor(gtk_widget_get_window(widget), cursor);
gdk_display_flush(gdk_window_get_display(gtk_widget_get_window(widget)));
void pidgin_clear_cursor(GtkWidget *widget)
g_return_if_fail(widget != NULL);
if (gtk_widget_get_window(widget) == NULL)
gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
icon_filesel_choose_cb(GtkWidget *widget, gint response, struct _icon_chooser *dialog)
char *filename, *current_folder;
if (response != GTK_RESPONSE_ACCEPT) {
dialog->callback(NULL, dialog->data);
filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog->icon_filesel));
current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel));
if (current_folder != NULL) {
purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder);
dialog->callback(filename, dialog->data);
icon_preview_change_cb(GtkFileChooser *widget, struct _icon_chooser *dialog)
char *basename, *markup, *size;
filename = gtk_file_chooser_get_preview_filename(
GTK_FILE_CHOOSER(dialog->icon_filesel));
if (!filename || g_stat(filename, &st) || !(pixbuf = pidgin_pixbuf_new_from_file_at_size(filename, 128, 128)))
gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
gdk_pixbuf_get_file_info(filename, &width, &height);
basename = g_path_get_basename(filename);
size = g_format_size(st.st_size);
markup = g_strdup_printf(_("<b>File:</b> %s\n"
"<b>Image size:</b> %dx%d"),
basename, size, width, height);
gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), pixbuf);
gtk_label_set_markup(GTK_LABEL(dialog->icon_text), markup);
g_object_unref(G_OBJECT(pixbuf));
pidgin_buddy_icon_chooser_new(GtkWindow *parent,
void (*callback)(const char *, gpointer),
struct _icon_chooser *dialog = g_new0(struct _icon_chooser, 1);
const char *current_folder;
dialog->callback = callback;
current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder");
dialog->icon_filesel = gtk_file_chooser_native_new(
_("Buddy Icon"), parent, GTK_FILE_CHOOSER_ACTION_OPEN, _("_Open"),
if ((current_folder != NULL) && (*current_folder != '\0'))
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel),
dialog->icon_preview = gtk_image_new();
dialog->icon_text = gtk_label_new(NULL);
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BOX_SPACE);
gtk_widget_set_size_request(GTK_WIDGET(vbox), -1, 50);
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_preview), TRUE, FALSE, 0);
gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_text), FALSE, FALSE, 0);
gtk_widget_show_all(vbox);
gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog->icon_filesel), vbox);
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog->icon_filesel), TRUE);
gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog->icon_filesel), FALSE);
g_signal_connect(G_OBJECT(dialog->icon_filesel), "update-preview",
G_CALLBACK(icon_preview_change_cb), dialog);
g_signal_connect(G_OBJECT(dialog->icon_filesel), "response",
G_CALLBACK(icon_filesel_choose_cb), dialog);
icon_preview_change_cb(NULL, dialog);
return dialog->icon_filesel;
* Returns: %TRUE if any string from array @a exists in array @b.
str_array_match(char **a, char **b)
for (i = 0; a[i] != NULL; i++)
for (j = 0; b[j] != NULL; j++)
if (!g_ascii_strcasecmp(a[i], b[j]))
pidgin_convert_buddy_icon(PurpleProtocol *protocol, const char *path, size_t *len)
PurpleBuddyIconSpec *spec;
int orig_width, orig_height, new_width, new_height;
GdkPixbuf *pixbuf, *original;
spec = purple_protocol_get_icon_spec(protocol);
g_return_val_if_fail(spec->format != NULL, NULL);
format = gdk_pixbuf_get_file_info(path, &orig_width, &orig_height);
purple_debug_warning("buddyicon", "Could not get file info of %s\n", path);
pixbuf_formats = gdk_pixbuf_format_get_extensions(format);
protocol_formats = g_strsplit(spec->format, ",", 0);
if (str_array_match(pixbuf_formats, protocol_formats) && /* This is an acceptable format AND */
(!(spec->scale_rules & PURPLE_ICON_SCALE_SEND) || /* The protocol doesn't scale before it sends OR */
(spec->min_width <= orig_width && spec->max_width >= orig_width &&
spec->min_height <= orig_height && spec->max_height >= orig_height))) /* The icon is the correct size */
g_strfreev(pixbuf_formats);
if (!g_file_get_contents(path, &contents, &length, &error)) {
purple_debug_warning("buddyicon", "Could not get file contents "
"of %s: %s\n", path, error->message);
g_strfreev(protocol_formats);
if (spec->max_filesize == 0 || length < spec->max_filesize) {
/* The supplied image fits the file size, dimensions and type
constraints. Great! Return it without making any changes. */
g_strfreev(protocol_formats);
/* The image was too big. Fall-through and try scaling it down. */
g_strfreev(pixbuf_formats);
/* The original image wasn't compatible. Scale it or convert file type. */
pixbuf = gdk_pixbuf_new_from_file(path, &error);
purple_debug_warning("buddyicon", "Could not open icon '%s' for "
"conversion: %s\n", path, error->message);
g_strfreev(protocol_formats);
original = g_object_ref(pixbuf);
new_height = orig_height;
/* Make sure the image is the correct dimensions */
if (spec->scale_rules & PURPLE_ICON_SCALE_SEND &&
(orig_width < spec->min_width || orig_width > spec->max_width ||
orig_height < spec->min_height || orig_height > spec->max_height))
purple_buddy_icon_spec_get_scaled_size(spec, &new_width, &new_height);
g_object_unref(G_OBJECT(pixbuf));
pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
for (i = 0; protocol_formats[i]; i++) {
const char *value = NULL;
purple_debug_info("buddyicon", "Converting buddy icon to %s\n", protocol_formats[i]);
if (purple_strequal(protocol_formats[i], "png")) {
} else if (purple_strequal(protocol_formats[i], "jpeg")) {
sprintf(tmp_buf, "%u", quality);
if (!gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length,
protocol_formats[i], &error, key, value, NULL))
/* The NULL checking of error is necessary due to this bug:
* http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
purple_debug_warning("buddyicon",
"Could not convert to %s: %s\n", protocol_formats[i],
(error && error->message) ? error->message : "Unknown error");
/* We couldn't convert to this image type. Try the next
if (spec->max_filesize == 0 || length <= spec->max_filesize) {
/* We were able to save the image as this image type and
have it be within the size constraints. Great! Return
purple_debug_info("buddyicon", "Converted image from "
"%dx%d to %dx%d, format=%s, quality=%u, "
"filesize=%" G_GSIZE_FORMAT "\n",
orig_width, orig_height, new_width, new_height,
protocol_formats[i], quality, length);
g_strfreev(protocol_formats);
g_object_unref(G_OBJECT(pixbuf));
g_object_unref(G_OBJECT(original));
if (!purple_strequal(protocol_formats[i], "jpeg")) {
/* File size was too big and we can't lower the quality,
so skip to the next image type. */
/* File size was too big, but we're dealing with jpeg so try
/* We couldn't save the image in any format that was below the max
file size. Maybe we can reduce the image dimensions? */
new_width = orig_width * scale_factor;
new_height = orig_height * scale_factor;
g_object_unref(G_OBJECT(pixbuf));
pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
} while ((new_width > 10 || new_height > 10) && new_width > spec->min_width && new_height > spec->min_height);
g_strfreev(protocol_formats);
g_object_unref(G_OBJECT(pixbuf));
g_object_unref(G_OBJECT(original));
tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
path, purple_protocol_get_name(protocol));
purple_notify_error(NULL, _("Icon Error"), _("Could not set icon"), tmp, NULL);
void pidgin_set_urgent(GtkWindow *window, gboolean urgent)
winpidgin_window_flash(window, urgent);
gtk_window_set_urgency_hint(window, urgent);
pidgin_utils_get_handle(void)
static void connection_signed_off_cb(PurpleConnection *gc)
for (list = minidialogs; list; list = l_next) {
if (g_object_get_data(G_OBJECT(list->data), "gc") == gc) {
gtk_widget_destroy(GTK_WIDGET(list->data));
static void alert_killed_cb(GtkWidget *widget)
minidialogs = g_slist_remove(minidialogs, widget);
old_mini_dialog_button_clicked_cb(PidginMiniDialog *mini_dialog,
struct _old_button_clicked_cb_data *data = user_data;
data->cb(data->data, button);
old_mini_dialog_destroy_cb(GtkWidget *dialog,
g_list_free_full(cb_datas, g_free);
mini_dialog_init(PidginMiniDialog *mini_dialog, PurpleConnection *gc, void *user_data, va_list args)
static gboolean first_call = TRUE;
purple_signal_connect(purple_connections_get_handle(), "signed-off",
pidgin_utils_get_handle(),
PURPLE_CALLBACK(connection_signed_off_cb), NULL);
g_object_set_data(G_OBJECT(mini_dialog), "gc" ,gc);
g_signal_connect(G_OBJECT(mini_dialog), "destroy",
G_CALLBACK(alert_killed_cb), NULL);
while ((button_text = va_arg(args, char*))) {
struct _old_button_clicked_cb_data *data = NULL;
PidginMiniDialogCallback wrapper_cb = NULL;
PidginUtilMiniDialogCallback callback =
va_arg(args, PidginUtilMiniDialogCallback);
data = g_new0(struct _old_button_clicked_cb_data, 1);
wrapper_cb = old_mini_dialog_button_clicked_cb;
pidgin_mini_dialog_add_button(mini_dialog, button_text,
cb_datas = g_list_append(cb_datas, data);
g_signal_connect(G_OBJECT(mini_dialog), "destroy",
G_CALLBACK(old_mini_dialog_destroy_cb), cb_datas);
#define INIT_AND_RETURN_MINI_DIALOG(mini_dialog) \
va_start(args, user_data); \
mini_dialog_init(mini_dialog, gc, user_data, args); \
return GTK_WIDGET(mini_dialog);
pidgin_make_mini_dialog(PurpleConnection *gc,
PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new(primary, secondary, icon_name);
INIT_AND_RETURN_MINI_DIALOG(mini_dialog);
pidgin_make_mini_dialog_with_custom_icon(PurpleConnection *gc,
PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new_with_custom_icon(primary, secondary, custom_icon);
INIT_AND_RETURN_MINI_DIALOG(mini_dialog);
* "This is so dead sexy."
* "Best movie of the year."
* This is the function that handles CTRL+F searching in the buddy list.
* It finds the top-most buddy/group/chat/whatever containing the
* It's somewhat ineffecient, because we strip all the HTML from the
* "name" column of the buddy list (because the GtkTreeModel does not
* contain the screen name in a non-markedup format). But the alternative
* is to add an extra column to the GtkTreeModel. And this function is
* used rarely, so it shouldn't matter TOO much.
gboolean pidgin_tree_view_search_equal_func(GtkTreeModel *model, gint column,
const gchar *key, GtkTreeIter *iter, gpointer data)
if (g_ascii_strcasecmp(key, "Global Thermonuclear War") == 0)
purple_notify_info(NULL, "WOPR", "Wouldn't you prefer a nice "
"game of chess?", NULL, NULL);
gtk_tree_model_get(model, iter, column, &withmarkup, -1);
if (withmarkup == NULL) /* This is probably a separator */
tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
enteredstring = g_utf8_casefold(tmp, -1);
nomarkup = purple_markup_strip_html(withmarkup);
tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT);
normalized = g_utf8_casefold(tmp, -1);
if (g_str_has_prefix(normalized, enteredstring)) {
/* Use Pango to separate by words. */
len = g_utf8_strlen(normalized, -1);
log_attrs = g_new(PangoLogAttr, len + 1);
pango_get_log_attrs(normalized, strlen(normalized), -1, NULL, log_attrs, len + 1);
for (i = 0; i < (len - 1) ; i++)
if (log_attrs[i].is_word_start &&
g_str_has_prefix(word, enteredstring)) {
word = g_utf8_next_char(word);
/* The non-Pango version. */
gunichar c = g_utf8_get_char(word);
if (!g_unichar_isalnum(c))
word = g_utf8_find_next_char(word, NULL);
if (g_str_has_prefix(word, enteredstring))
word = g_utf8_find_next_char(word, NULL);
const char *pidgin_get_dim_grey_string(GtkWidget *widget) {
static char dim_grey_string[8] = "";
GtkStyleContext *context;
context = gtk_widget_get_style_context(widget);
gtk_style_context_get_color(context, gtk_style_context_get_state(context),
gtk_style_context_get_background_color(context,
gtk_style_context_get_state(context),
g_snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x",
(unsigned int)((fg.red + bg.red) * 0.5 * 255),
(unsigned int)((fg.green + bg.green) * 0.5 * 255),
(unsigned int)((fg.blue + bg.blue) * 0.5 * 255));
combo_box_changed_cb(GtkComboBoxText *combo_box, GtkEntry *entry)
char *text = gtk_combo_box_text_get_active_text(combo_box);
gtk_entry_set_text(entry, text ? text : "");
entry_key_pressed_cb(GtkWidget *entry, GdkEventKey *key, GtkComboBoxText *combo)
if (key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_Up) {
gtk_combo_box_popup(GTK_COMBO_BOX(combo));
pidgin_text_combo_box_entry_new(const char *default_item, GList *items)
GtkComboBoxText *ret = NULL;
GtkWidget *the_entry = NULL;
ret = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new_with_entry());
the_entry = gtk_bin_get_child(GTK_BIN(ret));
gtk_entry_set_text(GTK_ENTRY(the_entry), default_item);
for (; items != NULL ; items = items->next) {
char *text = items->data;
gtk_combo_box_text_append_text(ret, text);
g_signal_connect(G_OBJECT(ret), "changed", (GCallback)combo_box_changed_cb, the_entry);
g_signal_connect_after(G_OBJECT(the_entry), "key-press-event", G_CALLBACK(entry_key_pressed_cb), ret);
const char *pidgin_text_combo_box_entry_get_text(GtkWidget *widget)
return gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((widget)))));
void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text)
gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((widget)))), (text));
pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label)
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
gtk_box_pack_start(vbox, hbox, FALSE, FALSE, 0);
label = gtk_label_new_with_mnemonic(widget_label);
gtk_label_set_xalign(GTK_LABEL(label), 0);
gtk_size_group_add_widget(sg, label);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(hbox), widget, expand, TRUE, 0);
gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
pidgin_set_accessible_label(widget, GTK_LABEL(label));
gboolean pidgin_auto_parent_window(GtkWidget *widget)
/* This looks at the most recent window that received focus, and makes
* that the parent window. */
static GdkAtom _WindowTime = GDK_NONE;
static GdkAtom _Cardinal = GDK_NONE;
GtkWidget *parent = NULL;
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);
GtkWidget *window = windows->data;
windows = g_list_delete_link(windows, windows);
!gtk_widget_get_visible(window))
if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
if (window_time < value) {
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.
gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
/* This finds the currently active window and makes that the parent window. */
GtkWindow *parent = NULL;
GdkEvent *event = gtk_get_current_event();
PurpleNotifyType notify_type;
parent_from = g_object_get_data(G_OBJECT(widget), "pidgin-parent-from");
if (purple_request_is_valid_ui_handle(parent_from, NULL)) {
gtk_window_set_transient_for(GTK_WINDOW(widget),
gtk_window_get_transient_for(
pidgin_request_get_dialog_window(parent_from)));
if (purple_notify_is_valid_ui_handle(parent_from, ¬ify_type) &&
notify_type == PURPLE_NOTIFY_MESSAGE)
gtk_window_set_transient_for(GTK_WINDOW(widget),
gtk_window_get_transient_for(GTK_WINDOW(parent_from)));
/* The window was not triggered by a user action. */
/* 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();
GtkWindow *window = GTK_WINDOW(windows->data);
windows = g_list_delete_link(windows, windows);
if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window),
"pidgin-window-is-closing")))
parent = gtk_window_get_transient_for(window);
if (GTK_WIDGET(window) == widget ||
!gtk_widget_get_visible(GTK_WIDGET(window))) {
if (gtk_window_has_toplevel_focus(window) ||
(menu && menu == gtk_widget_get_window(GTK_WIDGET(window)))) {
gtk_window_set_transient_for(GTK_WINDOW(widget), parent);
static GObject *pidgin_pixbuf_from_data_helper(const guchar *buf, gsize count, gboolean animated)
loader = gdk_pixbuf_loader_new();
if (!gdk_pixbuf_loader_write(loader, buf, count, &error) || error) {
purple_debug_warning("gtkutils", "gdk_pixbuf_loader_write() "
"failed with size=%" G_GSIZE_FORMAT ": %s\n", count,
error ? error->message : "(no error message)");
g_object_unref(G_OBJECT(loader));
if (!gdk_pixbuf_loader_close(loader, &error) || error) {
purple_debug_warning("gtkutils", "gdk_pixbuf_loader_close() "
"failed for image of size %" G_GSIZE_FORMAT ": %s\n", count,
error ? error->message : "(no error message)");
g_object_unref(G_OBJECT(loader));
pixbuf = G_OBJECT(gdk_pixbuf_loader_get_animation(loader));
pixbuf = G_OBJECT(gdk_pixbuf_loader_get_pixbuf(loader));
purple_debug_warning("gtkutils", "%s() returned NULL for image "
"of size %" G_GSIZE_FORMAT "\n",
animated ? "gdk_pixbuf_loader_get_animation"
: "gdk_pixbuf_loader_get_pixbuf", count);
g_object_unref(G_OBJECT(loader));
g_object_unref(G_OBJECT(loader));
GdkPixbuf *pidgin_pixbuf_from_data(const guchar *buf, gsize count)
return GDK_PIXBUF(pidgin_pixbuf_from_data_helper(buf, count, FALSE));
GdkPixbufAnimation *pidgin_pixbuf_anim_from_data(const guchar *buf, gsize count)
return GDK_PIXBUF_ANIMATION(pidgin_pixbuf_from_data_helper(buf, count, TRUE));
pidgin_pixbuf_from_image(PurpleImage *image)
return pidgin_pixbuf_from_data(purple_image_get_data(image),
purple_image_get_data_size(image));
GdkPixbuf *pidgin_pixbuf_new_from_file(const gchar *filename)
g_return_val_if_fail(filename != NULL, NULL);
g_return_val_if_fail(filename[0] != '\0', NULL);
pixbuf = gdk_pixbuf_new_from_file(filename, &error);
purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file() "
"returned %s for file %s: %s\n",
pixbuf ? "something" : "nothing",
error ? error->message : "(no error message)");
g_object_unref(G_OBJECT(pixbuf));
GdkPixbuf *pidgin_pixbuf_new_from_file_at_size(const char *filename, int width, int height)
g_return_val_if_fail(filename != NULL, NULL);
g_return_val_if_fail(filename[0] != '\0', NULL);
pixbuf = gdk_pixbuf_new_from_file_at_size(filename,
purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_size() "
"returned %s for file %s: %s\n",
pixbuf ? "something" : "nothing",
error ? error->message : "(no error message)");
g_object_unref(G_OBJECT(pixbuf));
GdkPixbuf *pidgin_pixbuf_new_from_file_at_scale(const char *filename, int width, int height, gboolean preserve_aspect_ratio)
g_return_val_if_fail(filename != NULL, NULL);
g_return_val_if_fail(filename[0] != '\0', NULL);
pixbuf = gdk_pixbuf_new_from_file_at_scale(filename,
width, height, preserve_aspect_ratio, &error);
purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_scale() "
"returned %s for file %s: %s\n",
pixbuf ? "something" : "nothing",
error ? error->message : "(no error message)");
g_object_unref(G_OBJECT(pixbuf));
pidgin_pixbuf_scale_down(GdkPixbuf *src, guint max_width, guint max_height,
GdkInterpType interp_type, gboolean preserve_ratio)
g_return_val_if_fail(src != NULL, NULL);
if (max_width == 0 || max_height == 0) {
g_return_val_if_reached(NULL);
cur_w = gdk_pixbuf_get_width(src);
cur_h = gdk_pixbuf_get_height(src);
if (cur_w <= max_width && cur_h <= max_height)
/* cur_ratio = cur_w / cur_h
* max_ratio = max_w / max_h
cur_w = MIN(cur_w, max_width);
cur_h = MIN(cur_h, max_height);
} else if ((guint64)cur_w * max_height > (guint64)max_width * cur_h) {
/* cur_w / cur_h > max_width / max_height */
cur_h = (guint64)max_width * cur_h / cur_w;
cur_w = (guint64)max_height * cur_w / cur_h;
dst = gdk_pixbuf_scale_simple(src, cur_w, cur_h, interp_type);
pidgin_make_scrollable(GtkWidget *child, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy, GtkShadowType shadow_type, int width, int height)
GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), hscrollbar_policy, vscrollbar_policy);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), shadow_type);
if (width != -1 || height != -1)
gtk_widget_set_size_request(sw, width, height);
gtk_container_add(GTK_CONTAINER(sw), child);