pidgin/pidgin

closing merged branch
port-changes-from-branch-2.x.y-to-default
14 months ago, Gary Kramlich
2f836435c33c
closing merged branch
/*
* Integration with Unity's messaging menu and launcher
* Copyright (C) 2013 Ankit Vani <a@nevitus.org>
*
* 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 "internal.h"
#include "account.h"
#include "savedstatuses.h"
#include "version.h"
#include "gtkplugin.h"
#include "gtkconv.h"
#include "gtkutils.h"
#include <unity.h>
#include <messaging-menu.h>
#define UNITY_PLUGIN_ID "gtk-unity-integration"
static MessagingMenuApp *mmapp = NULL;
static UnityLauncherEntry *launcher = NULL;
static guint n_sources = 0;
static gint launcher_count;
static gint messaging_menu_text;
static gboolean alert_chat_nick = TRUE;
enum {
LAUNCHER_COUNT_DISABLE,
LAUNCHER_COUNT_MESSAGES,
LAUNCHER_COUNT_SOURCES,
};
enum {
MESSAGING_MENU_COUNT,
MESSAGING_MENU_TIME,
};
static int attach_signals(PurpleConversation *conv);
static void detach_signals(PurpleConversation *conv);
static void
update_launcher()
{
guint count = 0;
GList *convs = NULL;
g_return_if_fail(launcher != NULL);
if (launcher_count == LAUNCHER_COUNT_DISABLE)
return;
if (launcher_count == LAUNCHER_COUNT_MESSAGES) {
for (convs = purple_conversations_get_all(); convs != NULL; convs = convs->next) {
PurpleConversation *conv = convs->data;
count += GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv),
"unity-message-count"));
}
} else {
count = n_sources;
}
if (launcher != NULL) {
if (count > 0)
unity_launcher_entry_set_count_visible(launcher, TRUE);
else
unity_launcher_entry_set_count_visible(launcher, FALSE);
unity_launcher_entry_set_count(launcher, count);
}
}
static gchar *
conversation_id(PurpleConversation *conv)
{
PurpleAccount *account = purple_conversation_get_account(conv);
return g_strconcat((PURPLE_IS_IM_CONVERSATION(conv) ? "im" :
PURPLE_IS_CHAT_CONVERSATION(conv) ? "chat" : "misc"), ":",
purple_conversation_get_name(conv), ":",
purple_account_get_username(account), ":",
purple_account_get_protocol_id(account), NULL);
}
static void
messaging_menu_add_conversation(PurpleConversation *conv, gint count)
{
gchar *id;
g_return_if_fail(count > 0);
id = conversation_id(conv);
/* GBytesIcon may be useful for messaging menu source icons using buddy
icon data for IMs */
if (!messaging_menu_app_has_source(mmapp, id))
messaging_menu_app_append_source(mmapp, id, NULL,
purple_conversation_get_title(conv));
if (messaging_menu_text == MESSAGING_MENU_TIME)
messaging_menu_app_set_source_time(mmapp, id, g_get_real_time());
else if (messaging_menu_text == MESSAGING_MENU_COUNT)
messaging_menu_app_set_source_count(mmapp, id, count);
messaging_menu_app_draw_attention(mmapp, id);
g_free(id);
}
static void
messaging_menu_remove_conversation(PurpleConversation *conv)
{
gchar *id = conversation_id(conv);
if (messaging_menu_app_has_source(mmapp, id))
messaging_menu_app_remove_source(mmapp, id);
g_free(id);
}
static void
refill_messaging_menu()
{
GList *convs;
for (convs = purple_conversations_get_all(); convs != NULL; convs = convs->next) {
PurpleConversation *conv = convs->data;
messaging_menu_add_conversation(conv,
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv),
"unity-message-count")));
}
}
static int
alert(PurpleConversation *conv)
{
gint count;
PidginConvWindow *purplewin = NULL;
if (conv == NULL || PIDGIN_CONVERSATION(conv) == NULL)
return 0;
purplewin = PIDGIN_CONVERSATION(conv)->win;
if (!pidgin_conv_window_has_focus(purplewin) ||
!pidgin_conv_window_is_active_conversation(conv))
{
count = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv),
"unity-message-count"));
if (!count++)
++n_sources;
g_object_set_data(G_OBJECT(conv), "unity-message-count",
GINT_TO_POINTER(count));
messaging_menu_add_conversation(conv, count);
update_launcher();
}
return 0;
}
static void
unalert(PurpleConversation *conv)
{
if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv), "unity-message-count")) > 0)
--n_sources;
g_object_set_data(G_OBJECT(conv), "unity-message-count",
GINT_TO_POINTER(0));
messaging_menu_remove_conversation(conv);
update_launcher();
}
static int
unalert_cb(GtkWidget *widget, gpointer data, PurpleConversation *conv)
{
unalert(conv);
return 0;
}
static gboolean
message_displayed_cb(PurpleConversation *conv, PurpleMessage *msg, gpointer _unused)
{
PurpleMessageFlags flags = purple_message_get_flags(msg);
if ((PURPLE_IS_CHAT_CONVERSATION(conv) && alert_chat_nick &&
!(flags & PURPLE_MESSAGE_NICK)))
return FALSE;
if ((flags & PURPLE_MESSAGE_RECV) && !(flags & PURPLE_MESSAGE_DELAYED))
alert(conv);
return FALSE;
}
static void
im_sent_im(PurpleAccount *account, PurpleMessage *msg, gpointer _unused)
{
PurpleIMConversation *im = NULL;
im = purple_conversations_find_im_with_account(
purple_message_get_recipient(msg), account);
unalert(PURPLE_CONVERSATION(im));
}
static void
chat_sent_im(PurpleAccount *account, PurpleMessage *msg, int id)
{
PurpleChatConversation *chat = NULL;
chat = purple_conversations_find_chat(purple_account_get_connection(account), id);
unalert(PURPLE_CONVERSATION(chat));
}
static void
conv_created(PurpleConversation *conv)
{
g_object_set_data(G_OBJECT(conv), "unity-message-count",
GINT_TO_POINTER(0));
attach_signals(conv);
}
static void
deleting_conv(PurpleConversation *conv)
{
detach_signals(conv);
unalert(conv);
}
static void
message_source_activated(MessagingMenuApp *app, const gchar *id,
gpointer user_data)
{
gchar **sections = g_strsplit(id, ":", 0);
PurpleConversation *conv = NULL;
PurpleAccount *account;
PidginConvWindow *purplewin = NULL;
char *type = sections[0];
char *cname = sections[1];
char *aname = sections[2];
char *protocol = sections[3];
account = purple_accounts_find(aname, protocol);
if (g_strcmp0(type, "im") == 0)
conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(cname, account));
else if (g_strcmp0(type, "chat") == 0)
conv = PURPLE_CONVERSATION(purple_conversations_find_chat_with_account(cname, account));
else
conv = purple_conversations_find_with_account(cname, account);
if (conv) {
unalert(conv);
purplewin = PIDGIN_CONVERSATION(conv)->win;
pidgin_conv_window_switch_gtkconv(purplewin, PIDGIN_CONVERSATION(conv));
gdk_window_focus(gtk_widget_get_window(purplewin->window), time(NULL));
}
g_strfreev (sections);
}
static PurpleSavedStatus *
create_transient_status(PurpleStatusPrimitive primitive, PurpleStatusType *status_type)
{
PurpleSavedStatus *saved_status = purple_savedstatus_new(NULL, primitive);
if(status_type != NULL) {
GList *tmp, *active_accts = purple_accounts_get_all_active();
for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
purple_savedstatus_set_substatus(saved_status,
(PurpleAccount*) tmp->data, status_type, NULL);
}
g_list_free(active_accts);
}
return saved_status;
}
static void
status_changed_cb(PurpleSavedStatus *saved_status)
{
MessagingMenuStatus status = MESSAGING_MENU_STATUS_AVAILABLE;
switch (purple_savedstatus_get_primitive_type(saved_status)) {
case PURPLE_STATUS_AVAILABLE:
case PURPLE_STATUS_MOOD:
case PURPLE_STATUS_TUNE:
case PURPLE_STATUS_UNSET:
status = MESSAGING_MENU_STATUS_AVAILABLE;
break;
case PURPLE_STATUS_AWAY:
case PURPLE_STATUS_EXTENDED_AWAY:
status = MESSAGING_MENU_STATUS_AWAY;
break;
case PURPLE_STATUS_INVISIBLE:
status = MESSAGING_MENU_STATUS_INVISIBLE;
break;
case PURPLE_STATUS_MOBILE:
case PURPLE_STATUS_OFFLINE:
status = MESSAGING_MENU_STATUS_OFFLINE;
break;
case PURPLE_STATUS_UNAVAILABLE:
status = MESSAGING_MENU_STATUS_BUSY;
break;
default:
g_assert_not_reached();
}
messaging_menu_app_set_status(mmapp, status);
}
static void
messaging_menu_status_changed(MessagingMenuApp *mmapp,
MessagingMenuStatus mm_status, gpointer user_data)
{
PurpleSavedStatus *saved_status;
PurpleStatusPrimitive primitive = PURPLE_STATUS_UNSET;
switch (mm_status) {
case MESSAGING_MENU_STATUS_AVAILABLE:
primitive = PURPLE_STATUS_AVAILABLE;
break;
case MESSAGING_MENU_STATUS_AWAY:
primitive = PURPLE_STATUS_AWAY;
break;
case MESSAGING_MENU_STATUS_BUSY:
primitive = PURPLE_STATUS_UNAVAILABLE;
break;
case MESSAGING_MENU_STATUS_INVISIBLE:
primitive = PURPLE_STATUS_INVISIBLE;
break;
case MESSAGING_MENU_STATUS_OFFLINE:
primitive = PURPLE_STATUS_OFFLINE;
break;
default:
g_assert_not_reached();
}
saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, NULL);
if (saved_status == NULL)
saved_status = create_transient_status(primitive, NULL);
purple_savedstatus_activate(saved_status);
}
static void
alert_config_cb(GtkWidget *widget, gpointer data)
{
gboolean on = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
purple_prefs_set_bool("/plugins/gtk/unity/alert_chat_nick", on);
alert_chat_nick = on;
}
static void
launcher_config_cb(GtkWidget *widget, gpointer data)
{
gint option = GPOINTER_TO_INT(data);
if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
return;
purple_prefs_set_int("/plugins/gtk/unity/launcher_count", option);
launcher_count = option;
if (option == LAUNCHER_COUNT_DISABLE)
unity_launcher_entry_set_count_visible(launcher, FALSE);
else
update_launcher();
}
static void
messaging_menu_config_cb(GtkWidget *widget, gpointer data)
{
gint option = GPOINTER_TO_INT(data);
if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
return;
purple_prefs_set_int("/plugins/gtk/unity/messaging_menu_text", option);
messaging_menu_text = option;
refill_messaging_menu();
}
static int
attach_signals(PurpleConversation *conv)
{
PidginConversation *gtkconv = NULL;
guint id;
gtkconv = PIDGIN_CONVERSATION(conv);
if (!gtkconv)
return 0;
id = g_signal_connect(G_OBJECT(gtkconv->entry), "focus-in-event",
G_CALLBACK(unalert_cb), conv);
g_object_set_data(G_OBJECT(conv), "unity-entry-signal", GUINT_TO_POINTER(id));
id = g_signal_connect(G_OBJECT(gtkconv->webview), "focus-in-event",
G_CALLBACK(unalert_cb), conv);
g_object_set_data(G_OBJECT(conv), "unity-webview-signal", GUINT_TO_POINTER(id));
return 0;
}
static void
detach_signals(PurpleConversation *conv)
{
PidginConversation *gtkconv = NULL;
guint id;
gtkconv = PIDGIN_CONVERSATION(conv);
if (!gtkconv)
return;
id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv), "unity-webview-signal"));
g_signal_handler_disconnect(gtkconv->webview, id);
id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv), "unity-entry-signal"));
g_signal_handler_disconnect(gtkconv->entry, id);
g_object_set_data(G_OBJECT(conv), "unity-message-count",
GINT_TO_POINTER(0));
}
static GtkWidget *
get_config_frame(PurplePlugin *plugin)
{
GtkWidget *ret = NULL, *frame = NULL;
GtkWidget *vbox = NULL, *toggle = NULL;
ret = gtk_box_new(GTK_ORIENTATION_VERTICAL, 18);
gtk_container_set_border_width(GTK_CONTAINER (ret), 12);
/* Alerts */
frame = pidgin_make_frame(ret, _("Chatroom alerts"));
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_container_add(GTK_CONTAINER(frame), vbox);
toggle = gtk_check_button_new_with_mnemonic(_("Chatroom message alerts _only where someone says your username"));
gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle),
purple_prefs_get_bool("/plugins/gtk/unity/alert_chat_nick"));
g_signal_connect(G_OBJECT(toggle), "toggled",
G_CALLBACK(alert_config_cb), NULL);
/* Launcher integration */
frame = pidgin_make_frame(ret, _("Launcher Icon"));
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_container_add(GTK_CONTAINER(frame), vbox);
toggle = gtk_radio_button_new_with_mnemonic(NULL, _("_Disable launcher integration"));
gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle),
purple_prefs_get_int("/plugins/gtk/unity/launcher_count") == LAUNCHER_COUNT_DISABLE);
g_signal_connect(G_OBJECT(toggle), "toggled",
G_CALLBACK(launcher_config_cb), GUINT_TO_POINTER(LAUNCHER_COUNT_DISABLE));
toggle = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(toggle),
_("Show number of unread _messages on launcher icon"));
gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle),
purple_prefs_get_int("/plugins/gtk/unity/launcher_count") == LAUNCHER_COUNT_MESSAGES);
g_signal_connect(G_OBJECT(toggle), "toggled",
G_CALLBACK(launcher_config_cb), GUINT_TO_POINTER(LAUNCHER_COUNT_MESSAGES));
toggle = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(toggle),
_("Show number of unread co_nversations on launcher icon"));
gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle),
purple_prefs_get_int("/plugins/gtk/unity/launcher_count") == LAUNCHER_COUNT_SOURCES);
g_signal_connect(G_OBJECT(toggle), "toggled",
G_CALLBACK(launcher_config_cb), GUINT_TO_POINTER(LAUNCHER_COUNT_SOURCES));
/* Messaging menu integration */
frame = pidgin_make_frame(ret, _("Messaging Menu"));
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_container_add(GTK_CONTAINER(frame), vbox);
toggle = gtk_radio_button_new_with_mnemonic(NULL,
_("Show number of _unread messages for conversations in messaging menu"));
gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle),
purple_prefs_get_int("/plugins/gtk/unity/messaging_menu_text") == MESSAGING_MENU_COUNT);
g_signal_connect(G_OBJECT(toggle), "toggled",
G_CALLBACK(messaging_menu_config_cb), GUINT_TO_POINTER(MESSAGING_MENU_COUNT));
toggle = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(toggle),
_("Show _elapsed time for unread conversations in messaging menu"));
gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle),
purple_prefs_get_int("/plugins/gtk/unity/messaging_menu_text") == MESSAGING_MENU_TIME);
g_signal_connect(G_OBJECT(toggle), "toggled",
G_CALLBACK(messaging_menu_config_cb), GUINT_TO_POINTER(MESSAGING_MENU_TIME));
gtk_widget_show_all(ret);
return ret;
}
static PidginPluginInfo *
plugin_query(GError **error)
{
const gchar * const authors[] = {
"Ankit Vani <a@nevitus.org>",
NULL
};
return pidgin_plugin_info_new(
"id", UNITY_PLUGIN_ID,
"name", N_("Unity Integration"),
"version", DISPLAY_VERSION,
"category", N_("Notification"),
"summary", N_("Provides integration with Unity."),
"description", N_("Provides integration with Unity's "
"messaging menu and launcher."),
"authors", authors,
"website", PURPLE_WEBSITE,
"abi-version", PURPLE_ABI_VERSION,
"gtk-config-frame-cb", get_config_frame,
NULL
);
}
static gboolean
plugin_load(PurplePlugin *plugin, GError **error)
{
GList *convs = purple_conversations_get_all();
PurpleSavedStatus *saved_status;
void *conv_handle = purple_conversations_get_handle();
void *gtk_conv_handle = pidgin_conversations_get_handle();
void *savedstat_handle = purple_savedstatuses_get_handle();
purple_prefs_add_none("/plugins/gtk");
purple_prefs_add_none("/plugins/gtk/unity");
purple_prefs_add_int("/plugins/gtk/unity/launcher_count", LAUNCHER_COUNT_SOURCES);
purple_prefs_add_int("/plugins/gtk/unity/messaging_menu_text", MESSAGING_MENU_COUNT);
purple_prefs_add_bool("/plugins/gtk/unity/alert_chat_nick", TRUE);
alert_chat_nick = purple_prefs_get_bool("/plugins/gtk/unity/alert_chat_nick");
mmapp = messaging_menu_app_new("pidgin.desktop");
g_object_ref(mmapp);
messaging_menu_app_register(mmapp);
messaging_menu_text = purple_prefs_get_int("/plugins/gtk/unity/messaging_menu_text");
g_signal_connect(mmapp, "activate-source",
G_CALLBACK(message_source_activated), NULL);
g_signal_connect(mmapp, "status-changed",
G_CALLBACK(messaging_menu_status_changed), NULL);
saved_status = purple_savedstatus_get_current();
status_changed_cb(saved_status);
purple_signal_connect(savedstat_handle, "savedstatus-changed", plugin,
PURPLE_CALLBACK(status_changed_cb), NULL);
launcher = unity_launcher_entry_get_for_desktop_id("pidgin.desktop");
g_object_ref(launcher);
launcher_count = purple_prefs_get_int("/plugins/gtk/unity/launcher_count");
purple_signal_connect(gtk_conv_handle, "displayed-im-msg", plugin,
PURPLE_CALLBACK(message_displayed_cb), NULL);
purple_signal_connect(gtk_conv_handle, "displayed-chat-msg", plugin,
PURPLE_CALLBACK(message_displayed_cb), NULL);
purple_signal_connect(conv_handle, "sent-im-msg", plugin,
PURPLE_CALLBACK(im_sent_im), NULL);
purple_signal_connect(conv_handle, "sent-chat-msg", plugin,
PURPLE_CALLBACK(chat_sent_im), NULL);
purple_signal_connect(conv_handle, "conversation-created", plugin,
PURPLE_CALLBACK(conv_created), NULL);
purple_signal_connect(conv_handle, "deleting-conversation", plugin,
PURPLE_CALLBACK(deleting_conv), NULL);
while (convs) {
PurpleConversation *conv = (PurpleConversation *)convs->data;
attach_signals(conv);
convs = convs->next;
}
return TRUE;
}
static gboolean
plugin_unload(PurplePlugin *plugin, GError **error)
{
GList *convs = purple_conversations_get_all();
while (convs) {
PurpleConversation *conv = (PurpleConversation *)convs->data;
unalert(conv);
detach_signals(conv);
convs = convs->next;
}
unity_launcher_entry_set_count_visible(launcher, FALSE);
messaging_menu_app_unregister(mmapp);
g_object_unref(launcher);
g_object_unref(mmapp);
return TRUE;
}
PURPLE_PLUGIN_INIT(unity, plugin_query, plugin_load, plugin_unload);