pidgin/plugins/unity.c

Wed, 01 Jan 2014 11:11:59 +0530

author
Ankit Vani <a@nevitus.org>
date
Wed, 01 Jan 2014 11:11:59 +0530
branch
soc.2013.gobjectification.plugins
changeset 36969
c78437610c6d
parent 35125
6e13f4f28638
child 36972
dfe5c47d56a9
permissions
-rw-r--r--

Refactored unity plugin to use the new plugin API

/*
 * 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 "version.h"
#include "account.h"
#include "savedstatuses.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;
	PidginWindow *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(PurpleAccount *account, const char *who, char *message,
		PurpleConversation *conv, PurpleMessageFlags flags)
{
	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, const char *receiver, const char *message)
{
	PurpleIMConversation *im = NULL;
	im = purple_conversations_find_im_with_account(receiver, account);
	unalert(PURPLE_CONVERSATION(im));
}

static void
chat_sent_im(PurpleAccount *account, const char *message, 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;
	PidginWindow *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_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_vbox_new(FALSE, 18);
	gtk_container_set_border_width(GTK_CONTAINER (ret), 12);

	/* Alerts */

	frame = pidgin_make_frame(ret, _("Chatroom alerts"));
	vbox = gtk_vbox_new(FALSE, 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_vbox_new(FALSE, 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 _conversations 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_vbox_new(FALSE, 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);

mercurial