gaim/gaim

I feel dumb that it took me so long to get this right.
oldstatus v1_2_1
2005-04-03, Mark Doliner
b59671364e74
I feel dumb that it took me so long to get this right.
I hope it's right. I'm going to do a bit o' testing.
/**
* Remote control plugin for Gaim
*
* Copyright (C) 2003 Christian Hammond.
* Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
*
* 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* @todo Make this a core plugin!
*/
#include "internal.h"
#include "gtkgaim.h"
#ifndef _WIN32
# include <sys/un.h>
#endif
#include <signal.h>
#include <getopt.h>
#include "conversation.h"
#include "core.h"
#include "debug.h"
#include "prpl.h"
#include "notify.h"
#include "version.h"
/* XXX */
#include "away.h"
#include "gtkconv.h"
#include "gtkplugin.h"
#include "gaim.h"
#include "prefs.h"
#include <gaim-remote/remote.h>
#define REMOTE_PLUGIN_ID "gtk-remote"
struct UI {
GIOChannel *channel;
guint inpa;
};
#ifndef _WIN32
static gint UI_fd = -1;
static guint watcher = 0;
#endif
static int gaim_session = 0;
static GSList *uis = NULL;
/* AIM URI's ARE FUN :-D */
static const char *
gaim_remote_handle_uri(const char *uri)
{
const char *username;
GString *str;
GList *conn;
GaimConnection *gc = NULL;
GaimAccount *account;
gaim_debug(GAIM_DEBUG_INFO, "gaim_remote_handle_uri", "Handling URI: %s\n", uri);
/* Well, we'd better check to make sure we have at least one
AIM account connected. */
for (conn = gaim_connections_get_all(); conn != NULL; conn = conn->next) {
gc = conn->data;
account = gaim_connection_get_account(gc);
username = gaim_account_get_username(account);
if (strcmp(gaim_account_get_protocol_id(account), "prpl-oscar") == 0 &&
username != NULL && isalpha(*username)) {
break;
}
}
if (gc == NULL)
return _("Not connected to AIM");
/* aim:goim?screenname=screenname&message=message */
if (!g_ascii_strncasecmp(uri, "aim:goim?", strlen("aim:goim?"))) {
char *who, *what;
GaimConversation *c;
uri = uri + strlen("aim:goim?");
if (!(who = strstr(uri, "screenname="))) {
return _("No screenname given.");
}
/* spaces are encoded as +'s */
who = who + strlen("screenname=");
str = g_string_new(NULL);
while (*who && (*who != '&')) {
g_string_append_c(str, *who == '+' ? ' ' : *who);
who++;
}
who = g_strdup(str->str);
g_string_free(str, TRUE);
what = strstr(uri, "message=");
if (what) {
what = what + strlen("message=");
str = g_string_new(NULL);
while (*what && (*what != '&' || !g_ascii_strncasecmp(what, "&amp;", 5))) {
g_string_append_c(str, *what == '+' ? ' ' : *what);
what++;
}
what = g_strdup(str->str);
g_string_free(str, TRUE);
}
c = gaim_conversation_new(GAIM_CONV_IM, gc->account, who);
g_free(who);
if (what) {
GaimGtkConversation *gtkconv = GAIM_GTK_CONVERSATION(c);
gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer, what, -1);
g_free(what);
}
} else if (!g_ascii_strncasecmp(uri, "aim:addbuddy?", strlen("aim:addbuddy?"))) {
char *who, *group;
uri = uri + strlen("aim:addbuddy?");
/* spaces are encoded as +'s */
if (!(who = strstr(uri, "screenname="))) {
return _("No screenname given.");
}
who = who + strlen("screenname=");
str = g_string_new(NULL);
while (*who && (*who != '&')) {
g_string_append_c(str, *who == '+' ? ' ' : *who);
who++;
}
who = g_strdup(str->str);
g_string_free(str, TRUE);
group = strstr(uri, "group=");
if (group) {
group = group + strlen("group=");
str = g_string_new(NULL);
while (*group && (*group != '&' || !g_ascii_strncasecmp(group, "&amp;", 5))) {
g_string_append_c(str, *group == '+' ? ' ' : *group);
group++;
}
group = g_strdup(str->str);
g_string_free(str, TRUE);
}
gaim_debug(GAIM_DEBUG_MISC, "gaim_remote_handle_uri", "who: %s\n", who);
gaim_blist_request_add_buddy(gc->account, who, group, NULL);
g_free(who);
if (group)
g_free(group);
} else if (!g_ascii_strncasecmp(uri, "aim:gochat?", strlen("aim:gochat?"))) {
char *room;
GHashTable *components;
int exch = 5;
uri = uri + strlen("aim:gochat?");
/* spaces are encoded as +'s */
if (!(room = strstr(uri, "roomname="))) {
return _("No roomname given.");
}
room = room + strlen("roomname=");
str = g_string_new(NULL);
while (*room && (*room != '&')) {
g_string_append_c(str, *room == '+' ? ' ' : *room);
room++;
}
room = g_strdup(str->str);
g_string_free(str, TRUE);
components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
g_free);
g_hash_table_replace(components, g_strdup("room"), room);
g_hash_table_replace(components, g_strdup("exchange"),
g_strdup_printf("%d", exch));
serv_join_chat(gc, components);
g_hash_table_destroy(components);
} else {
return _("Invalid AIM URI");
}
return NULL;
}
#if 0
static guchar *
UI_build(guint32 *len, guchar type, guchar subtype, va_list args)
{
guchar *buffer;
guint32 pos;
int size;
void *data;
*len = sizeof(guchar) * 2 + 4;
buffer = g_malloc(*len);
pos = 0;
memcpy(buffer + pos, &type, sizeof(type)); pos += sizeof(type);
memcpy(buffer + pos, &subtype, sizeof(subtype)); pos += sizeof(subtype);
/* we come back and do size last */
pos += 4;
size = va_arg(args, int);
while (size != -1) {
*len += size;
buffer = g_realloc(buffer, *len);
data = va_arg(args, void *);
memcpy(buffer + pos, data, size);
pos += size;
size = va_arg(args, int);
}
pos -= sizeof(guchar) * 2 + 4;
/* now we do size */
memcpy(buffer + sizeof(guchar) * 2, &pos, 4);
return buffer;
}
static gint
UI_write(struct UI *ui, guchar *data, gint len)
{
GError *error = NULL;
gint sent;
/* we'll let the write silently fail because the read will pick it up as dead */
g_io_channel_write_chars(ui->channel, data, len, &sent, &error);
if (error)
g_error_free(error);
return sent;
}
static void
UI_build_write(struct UI *ui, guchar type, guchar subtype, ...)
{
va_list ap;
gchar *data;
guint32 len;
va_start(ap, subtype);
data = UI_build(&len, type, subtype, ap);
va_end(ap);
UI_write(ui, data, len);
g_free(data);
}
static void
UI_broadcast(guchar *data, gint len)
{
GSList *u = uis;
while (u) {
struct UI *ui = u->data;
UI_write(ui, data, len);
u = u->next;
}
}
static void
UI_build_broadcast(guchar type, guchar subtype, ...)
{
va_list ap;
gchar *data;
guint32 len;
if (!uis)
return;
va_start(ap, subtype);
data = UI_build(&len, type, subtype, ap);
va_end(ap);
UI_broadcast(data, len);
g_free(data);
}
#endif
#ifndef _WIN32
static void
meta_handler(struct UI *ui, guchar subtype, gchar *data)
{
GaimRemotePacket *p;
GError *error = NULL;
switch (subtype) {
case CUI_META_LIST:
break;
case CUI_META_QUIT:
while (uis) {
ui = uis->data;
uis = g_slist_remove(uis, ui);
g_io_channel_shutdown(ui->channel, TRUE, &error);
g_source_remove(ui->inpa);
g_free(ui);
}
g_timeout_add(0, gaim_core_quit_cb, NULL);
break;
case CUI_META_DETACH:
uis = g_slist_remove(uis, ui);
g_io_channel_shutdown(ui->channel, TRUE, &error);
g_source_remove(ui->inpa);
g_free(ui);
break;
case CUI_META_PING:
p = gaim_remote_packet_new(CUI_TYPE_META, CUI_META_ACK);
gaim_remote_session_send_packet(g_io_channel_unix_get_fd(ui->channel),
p);
gaim_remote_packet_free(p);
break;
default:
gaim_debug(GAIM_DEBUG_WARNING, "cui",
"Unhandled meta subtype %d\n", subtype);
break;
}
if(error)
g_error_free(error);
}
static void
plugin_handler(struct UI *ui, guchar subtype, gpointer data)
{
#ifdef GAIM_PLUGINS
guint id;
GaimPlugin *p;
switch (subtype) {
/*
case CUI_PLUGIN_LIST:
break;
*/
case CUI_PLUGIN_LOAD:
gaim_plugin_load(gaim_plugin_probe(data));
break;
case CUI_PLUGIN_UNLOAD:
memcpy(&id, data, sizeof(id));
p = g_list_nth_data(gaim_plugins_get_loaded(), id);
if (p) {
gaim_plugin_unload(p);
}
break;
default:
gaim_debug(GAIM_DEBUG_WARNING, "cui",
"Unhandled plugin subtype %d\n", subtype);
break;
}
#endif
}
static void
user_handler(struct UI *ui, guchar subtype, gchar *data)
{
guint id;
GaimAccount *account;
switch (subtype) {
/*
case CUI_USER_LIST:
break;
case CUI_USER_ADD:
break;
case CUI_USER_REMOVE:
break;
case CUI_USER_MODIFY:
break;
*/
case CUI_USER_SIGNON:
if (!data)
return;
memcpy(&id, data, sizeof(id));
account = g_list_nth_data(gaim_accounts_get_all(), id);
if (account)
gaim_account_connect(account);
/* don't need to do anything here because the UI will get updates from other handlers */
break;
case CUI_USER_AWAY:
{
GSList* l;
const char* default_away_name = gaim_prefs_get_string("/core/away/default_message");
for(l = away_messages; l; l = l->next) {
if(!strcmp(default_away_name, ((struct away_message *)l->data)->name)) {
do_away_message(NULL, l->data);
break;
}
}
}
break;
case CUI_USER_BACK:
do_im_back(NULL, NULL);
break;
default:
gaim_debug(GAIM_DEBUG_WARNING, "cui",
"Unhandled user subtype %d\n", subtype);
break;
}
}
static void
message_handler(struct UI *ui, guchar subtype, gchar *data)
{
switch (subtype) {
case CUI_MESSAGE_LIST:
break;
case CUI_MESSAGE_SEND:
if (!data)
return;
{
guint id;
GaimConnection *gc;
guint len;
char *who, *msg;
gint flags;
int pos = 0;
memcpy(&id, data + pos, sizeof(id));
pos += sizeof(id);
gc = g_list_nth_data(gaim_connections_get_all(), id);
if (!gc)
return;
memcpy(&len, data + pos, sizeof(len));
pos += sizeof(len);
who = g_strndup(data + pos, len + 1);
pos += len;
memcpy(&len, data + pos, sizeof(len));
pos += sizeof(len);
msg = g_strndup(data + pos, len + 1);
pos += len;
memcpy(&flags, data + pos, sizeof(flags));
serv_send_im(gc, who, msg, flags);
g_free(who);
g_free(msg);
}
break;
case CUI_MESSAGE_RECV:
break;
default:
gaim_debug(GAIM_DEBUG_WARNING, "cui",
"Unhandled message subtype %d\n", subtype);
break;
}
}
static gint
gaim_recv(GIOChannel *source, gchar *buf, gint len)
{
gint total = 0;
guint cur;
GError *error = NULL;
while (total < len) {
if (g_io_channel_read_chars(source, buf + total, len - total, (gsize *) &cur, &error) != G_IO_STATUS_NORMAL) {
if (error)
g_error_free(error);
return -1;
}
if (cur == 0)
return total;
total += cur;
}
return total;
}
static void
remote_handler(struct UI *ui, guchar subtype, gchar *data, int len)
{
const char *resp;
char *send;
switch (subtype) {
case CUI_REMOTE_CONNECTIONS:
break;
case CUI_REMOTE_URI:
send = g_malloc(len + 1);
memcpy(send, data, len);
send[len] = 0;
resp = gaim_remote_handle_uri(send);
g_free(send);
/* report error */
break;
default:
gaim_debug(GAIM_DEBUG_WARNING, "cui",
"Unhandled remote subtype %d\n", subtype);
break;
}
}
static gboolean
UI_readable(GIOChannel *source, GIOCondition cond, gpointer data)
{
struct UI *ui = data;
gchar type;
gchar subtype;
gint len;
GError *error = NULL;
gchar *in;
/* no byte order worries! this'll change if we go to TCP */
if (gaim_recv(source, &type, sizeof(type)) != sizeof(type)) {
gaim_debug(GAIM_DEBUG_ERROR, "cui", "UI has abandoned us!\n");
uis = g_slist_remove(uis, ui);
g_io_channel_shutdown(ui->channel, TRUE, &error);
if(error) {
g_error_free(error);
error = NULL;
}
g_source_remove(ui->inpa);
g_free(ui);
return FALSE;
}
if (gaim_recv(source, &subtype, sizeof(subtype)) != sizeof(subtype)) {
gaim_debug(GAIM_DEBUG_ERROR, "cui", "UI has abandoned us!\n");
uis = g_slist_remove(uis, ui);
g_io_channel_shutdown(ui->channel, TRUE, &error);
if(error) {
g_error_free(error);
error = NULL;
}
g_source_remove(ui->inpa);
g_free(ui);
return FALSE;
}
if (gaim_recv(source, (gchar *)&len, sizeof(len)) != sizeof(len)) {
gaim_debug(GAIM_DEBUG_ERROR, "cui", "UI has abandoned us!\n");
uis = g_slist_remove(uis, ui);
g_io_channel_shutdown(ui->channel, TRUE, &error);
if(error) {
g_error_free(error);
error = NULL;
}
g_source_remove(ui->inpa);
g_free(ui);
return FALSE;
}
if (len) {
in = g_new0(gchar, len);
if (gaim_recv(source, in, len) != len) {
gaim_debug(GAIM_DEBUG_ERROR, "cui", "UI has abandoned us!\n");
uis = g_slist_remove(uis, ui);
g_io_channel_shutdown(ui->channel, TRUE, &error);
if(error) {
g_error_free(error);
error = NULL;
}
g_source_remove(ui->inpa);
g_free(ui);
return FALSE;
}
} else
in = NULL;
switch (type) {
case CUI_TYPE_META:
meta_handler(ui, subtype, in);
break;
case CUI_TYPE_PLUGIN:
plugin_handler(ui, subtype, in);
break;
case CUI_TYPE_USER:
user_handler(ui, subtype, in);
break;
/*
case CUI_TYPE_CONN:
conn_handler(ui, subtype, in);
break;
case CUI_TYPE_BUDDY:
buddy_handler(ui, subtype, in);
break;
*/
case CUI_TYPE_MESSAGE:
message_handler(ui, subtype, in);
break;
/*
case CUI_TYPE_CHAT:
chat_handler(ui, subtype, in);
break;
*/
case CUI_TYPE_REMOTE:
remote_handler(ui, subtype, in, len);
break;
default:
gaim_debug(GAIM_DEBUG_WARNING, "cui",
"Unhandled type %d\n", type);
break;
}
if (in)
g_free(in);
return TRUE;
}
static gboolean
socket_readable(GIOChannel *source, GIOCondition cond, gpointer data)
{
struct sockaddr_un saddr;
guint len = sizeof(saddr);
gint fd;
struct UI *ui;
if ((fd = accept(UI_fd, (struct sockaddr *)&saddr, &len)) == -1)
return FALSE;
ui = g_new0(struct UI, 1);
uis = g_slist_append(uis, ui);
ui->channel = g_io_channel_unix_new(fd);
ui->inpa = g_io_add_watch(ui->channel, G_IO_IN | G_IO_HUP | G_IO_ERR, UI_readable, ui);
g_io_channel_unref(ui->channel);
gaim_debug(GAIM_DEBUG_MISC, "cui", "Got one\n");
return TRUE;
}
static gint
open_socket(char **error)
{
struct sockaddr_un saddr;
gint fd;
while (gaim_remote_session_exists(gaim_session))
gaim_session++;
gaim_debug(GAIM_DEBUG_MISC, "cui", "Session: %d\n", gaim_session);
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) {
mode_t m = umask(0177);
saddr.sun_family = AF_UNIX;
g_snprintf(saddr.sun_path, sizeof(saddr.sun_path), "%s" G_DIR_SEPARATOR_S "gaim_%s.%d",
g_get_tmp_dir(), g_get_user_name(), gaim_session);
/* Unlink any existing socket for this session */
unlink (saddr.sun_path);
if (bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)) != -1)
listen(fd, 100);
else {
char *tmp = g_locale_to_utf8(strerror(errno), -1, NULL, NULL, NULL);
*error = g_strdup_printf(_("Failed to assign %s to a socket:\n%s"),
saddr.sun_path, tmp);
g_log(NULL, G_LOG_LEVEL_CRITICAL,
"Failed to assign %s to a socket (Error: %s)",
saddr.sun_path, tmp);
g_free(tmp);
umask(m);
return -1;
}
umask(m);
} else
g_log(NULL, G_LOG_LEVEL_CRITICAL, "Unable to open socket: %s", strerror(errno));
return fd;
}
#endif /*! _WIN32*/
static gboolean
plugin_load(GaimPlugin *plugin)
{
#ifndef _WIN32
GIOChannel *channel;
char *buf;
if ((UI_fd = open_socket(&buf)) < 0) {
gaim_notify_error(NULL, NULL, _("Unable to open socket"), buf);
g_free(buf);
return FALSE;
}
channel = g_io_channel_unix_new(UI_fd);
watcher = g_io_add_watch(channel, G_IO_IN, socket_readable, NULL);
g_io_channel_unref(channel);
return TRUE;
#else
return FALSE;
#endif
}
static gboolean
plugin_unload(GaimPlugin *plugin)
{
/* don't save prefs after plugins are gone... */
#ifndef _WIN32
char buf[1024];
g_source_remove(watcher);
close(UI_fd);
g_snprintf(buf, sizeof(buf), "%s" G_DIR_SEPARATOR_S "gaim_%s.%d",
g_get_tmp_dir(), g_get_user_name(), gaim_session);
unlink(buf);
gaim_debug(GAIM_DEBUG_MISC, "core", "Removed core\n");
return TRUE;
#else
return FALSE;
#endif
}
static GaimPluginInfo info =
{
GAIM_PLUGIN_MAGIC,
GAIM_MAJOR_VERSION,
GAIM_MINOR_VERSION,
GAIM_PLUGIN_STANDARD, /**< type */
GAIM_GTK_PLUGIN_TYPE, /**< ui_requirement */
0, /**< flags */
NULL, /**< dependencies */
GAIM_PRIORITY_DEFAULT, /**< priority */
REMOTE_PLUGIN_ID, /**< id */
N_("Remote Control"), /**< name */
VERSION, /**< version */
/** summary */
N_("Provides remote control for gaim applications."),
/** description */
N_("Gives Gaim the ability to be remote-controlled through third-party "
"applications or through the gaim-remote tool."),
"Sean Egan <sean.egan@binghamton.edu>",
GAIM_WEBSITE, /**< homepage */
plugin_load, /**< load */
plugin_unload, /**< unload */
NULL, /**< destroy */
NULL, /**< ui_info */
NULL, /**< extra_info */
NULL,
NULL
};
static void
__init_plugin(GaimPlugin *plugin)
{
}
/* This may be horribly wrong. Oh the mayhem! */
GAIM_INIT_PLUGIN(remote, __init_plugin, info)