/* dbus-plugin.c - hexchat plugin for remote access using D-Bus * Copyright (C) 2006 Claessens Xavier * * 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 St, Fifth Floor, Boston, MA 02110-1301, USA * * Claessens Xavier * xclaesse@gmail.com */ #define DBUS_API_SUBJECT_TO_CHANGE #include #include #include #include #include "hexchat-plugin.h" #define PNAME _("remote access") #define PDESC _("plugin for remote access using DBUS") #define PVERSION "" #define DBUS_SERVICE "org.hexchat.service" #define DBUS_OBJECT_PATH "/org/hexchat" static hexchat_plugin *ph; static guint last_context_id = 0; static GList *contexts = NULL; static GHashTable *clients = NULL; static DBusGConnection *connection; typedef struct RemoteObject RemoteObject; typedef struct RemoteObjectClass RemoteObjectClass; GType Remote_object_get_type (void); struct RemoteObject { GObject parent; guint last_hook_id; guint last_list_id; hexchat_context *context; char *dbus_path; char *filename; GHashTable *hooks; GHashTable *lists; void *handle; }; struct RemoteObjectClass { GObjectClass parent; }; typedef struct { guint id; int return_value; hexchat_hook *hook; RemoteObject *obj; } HookInfo; typedef struct { guint id; hexchat_context *context; } ContextInfo; enum { SERVER_SIGNAL, COMMAND_SIGNAL, PRINT_SIGNAL, UNLOAD_SIGNAL, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; #define REMOTE_TYPE_OBJECT (remote_object_get_type ()) #define REMOTE_OBJECT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), REMOTE_TYPE_OBJECT, RemoteObject)) #define REMOTE_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), REMOTE_TYPE_OBJECT, RemoteObjectClass)) #define REMOTE_IS_OBJECT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), REMOTE_TYPE_OBJECT)) #define REMOTE_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), REMOTE_TYPE_OBJECT)) #define REMOTE_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), REMOTE_TYPE_OBJECT, RemoteObjectClass)) #define REMOTE_OBJECT_ERROR (remote_object_error_quark ()) #define REMOTE_TYPE_ERROR (remote_object_error_get_type ()) G_DEFINE_TYPE (RemoteObject, remote_object, G_TYPE_OBJECT) /* Available services */ static gboolean remote_object_connect (RemoteObject *obj, const char *filename, const char *name, const char *desc, const char *version, DBusGMethodInvocation *context); static gboolean remote_object_disconnect (RemoteObject *obj, DBusGMethodInvocation *context); static gboolean remote_object_command (RemoteObject *obj, const char *command, GError **error); static gboolean remote_object_print (RemoteObject *obj, const char *text, GError **error); static gboolean remote_object_find_context (RemoteObject *obj, const char *server, const char *channel, guint *ret_id, GError **error); static gboolean remote_object_get_context (RemoteObject *obj, guint *ret_id, GError **error); static gboolean remote_object_set_context (RemoteObject *obj, guint id, gboolean *ret, GError **error); static gboolean remote_object_print (RemoteObject *obj, const char *text, GError **error); static gboolean remote_object_get_info (RemoteObject *obj, const char *id, char **ret_info, GError **error); static gboolean remote_object_get_prefs (RemoteObject *obj, const char *name, int *ret_type, char **ret_str, int *ret_int, GError **error); static gboolean remote_object_hook_command (RemoteObject *obj, const char *name, int pri, const char *help_text, int return_value, guint *ret_id, GError **error); static gboolean remote_object_hook_server (RemoteObject *obj, const char *name, int pri, int return_value, guint *ret_id, GError **error); static gboolean remote_object_hook_print (RemoteObject *obj, const char *name, int pri, int return_value, guint *ret_id, GError **error); static gboolean remote_object_unhook (RemoteObject *obj, guint id, GError **error); static gboolean remote_object_list_get (RemoteObject *obj, const char *name, guint *ret_id, GError **error); static gboolean remote_object_list_next (RemoteObject *obj, guint id, gboolean *ret, GError **error); static gboolean remote_object_list_str (RemoteObject *obj, guint id, const char *name, char **ret_str, GError **error); static gboolean remote_object_list_int (RemoteObject *obj, guint id, const char *name, int *ret_int, GError **error); static gboolean remote_object_list_time (RemoteObject *obj, guint id, const char *name, guint64 *ret_time, GError **error); static gboolean remote_object_list_fields (RemoteObject *obj, const char *name, char ***ret, GError **error); static gboolean remote_object_list_free (RemoteObject *obj, guint id, GError **error); static gboolean remote_object_emit_print (RemoteObject *obj, const char *event_name, const char *args[], gboolean *ret, GError **error); static gboolean remote_object_nickcmp (RemoteObject *obj, const char *nick1, const char *nick2, int *ret, GError **error); static gboolean remote_object_strip (RemoteObject *obj, const char *str, int len, int flag, char **ret_str, GError **error); static gboolean remote_object_send_modes (RemoteObject *obj, const char *targets[], int modes_per_line, char sign, char mode, GError **error); #include "remote-object-glue.h" #include "../marshal.h" /* Useful functions */ static char** build_list (char *word[]); static guint context_list_find_id (hexchat_context *context); static hexchat_context* context_list_find_context (guint id); /* Remote Object */ static void hook_info_destroy (gpointer data) { HookInfo *info = (HookInfo*)data; if (info == NULL) { return; } hexchat_unhook (ph, info->hook); g_free (info); } static void list_info_destroy (gpointer data) { hexchat_list_free (ph, (hexchat_list*)data); } static void remote_object_finalize (GObject *obj) { RemoteObject *self = (RemoteObject*)obj; g_hash_table_destroy (self->lists); g_hash_table_destroy (self->hooks); g_free (self->dbus_path); g_free (self->filename); hexchat_plugingui_remove (ph, self->handle); G_OBJECT_CLASS (remote_object_parent_class)->finalize (obj); } static void remote_object_init (RemoteObject *obj) { obj->hooks = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, hook_info_destroy); obj->lists = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, list_info_destroy); obj->dbus_path = NULL; obj->filename = NULL; obj->last_hook_id = 0; obj->last_list_id = 0; obj->context = hexchat_get_context (ph); } static void remote_object_class_init (RemoteObjectClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = remote_object_finalize; signals[SERVER_SIGNAL] = g_signal_new ("server_signal", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, _hexchat_marshal_VOID__POINTER_POINTER_UINT_UINT, G_TYPE_NONE, 4, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT); signals[COMMAND_SIGNAL] = g_signal_new ("command_signal", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, _hexchat_marshal_VOID__POINTER_POINTER_UINT_UINT, G_TYPE_NONE, 4, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT); signals[PRINT_SIGNAL] = g_signal_new ("print_signal", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, _hexchat_marshal_VOID__POINTER_POINTER_UINT_UINT, G_TYPE_NONE, 3, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT); signals[UNLOAD_SIGNAL] = g_signal_new ("unload_signal", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } /* Implementation of services */ static gboolean remote_object_connect (RemoteObject *obj, const char *filename, const char *name, const char *desc, const char *version, DBusGMethodInvocation *context) { static guint count = 0; char *sender, *path; RemoteObject *remote_object; sender = dbus_g_method_get_sender (context); remote_object = g_hash_table_lookup (clients, sender); if (remote_object != NULL) { dbus_g_method_return (context, remote_object->dbus_path); g_free (sender); return TRUE; } path = g_build_filename (DBUS_OBJECT_PATH, count++, NULL); remote_object = g_object_new (REMOTE_TYPE_OBJECT, NULL); remote_object->dbus_path = path; remote_object->filename = g_path_get_basename (filename); remote_object->handle = hexchat_plugingui_add (ph, remote_object->filename, name, desc, version, NULL); dbus_g_connection_register_g_object (connection, path, G_OBJECT (remote_object)); g_hash_table_insert (clients, sender, remote_object); dbus_g_method_return (context, path); return TRUE; } static gboolean remote_object_disconnect (RemoteObject *obj, DBusGMethodInvocation *context) { char *sender; sender = dbus_g_method_get_sender (context); g_hash_table_remove (clients, sender); g_free (sender); dbus_g_method_return (context); return TRUE; } static gboolean remote_object_command (RemoteObject *obj, const char *command, GError **error) { if (hexchat_set_context (ph, obj->context)) { hexchat_command (ph, command); } return TRUE; } static gboolean remote_object_print (RemoteObject *obj, const char *text, GError **error) { if (hexchat_set_context (ph, obj->context)) { hexchat_print (ph, text); } return TRUE; } static gboolean remote_object_find_context (RemoteObject *obj, const char *server, const char *channel, guint *ret_id, GError **error) { hexchat_context *context; if (*server == '\0') { server = NULL; } if (*channel == '\0') { channel = NULL; } context = hexchat_find_context (ph, server, channel); *ret_id = context_list_find_id (context); return TRUE; } static gboolean remote_object_get_context (RemoteObject *obj, guint *ret_id, GError **error) { *ret_id = context_list_find_id (obj->context); return TRUE; } static gboolean remote_object_set_context (RemoteObject *obj, guint id, gboolean *ret, GError **error) { hexchat_context *context; context = context_list_find_context (id); if (context == NULL) { *ret = FALSE; return TRUE; } obj->context = context; *ret = TRUE; return TRUE; } static gboolean remote_object_get_info (RemoteObject *obj, const char *id, char **ret_info, GError **error) { /* win_ptr is a GtkWindow* casted to char* and will crash * D-Bus if we send it as a string */ if (!hexchat_set_context (ph, obj->context) || g_str_equal (id, "win_ptr")) { *ret_info = NULL; return TRUE; } *ret_info = g_strdup (hexchat_get_info (ph, id)); return TRUE; } static gboolean remote_object_get_prefs (RemoteObject *obj, const char *name, int *ret_type, char **ret_str, int *ret_int, GError **error) { const char *str; if (!hexchat_set_context (ph, obj->context)) { *ret_type = 0; return TRUE; } *ret_type = hexchat_get_prefs (ph, name, &str, ret_int); *ret_str = g_strdup (str); return TRUE; } static int server_hook_cb (char *word[], char *word_eol[], void *userdata) { HookInfo *info = (HookInfo*)userdata; char **arg1; char **arg2; arg1 = build_list (word + 1); arg2 = build_list (word_eol + 1); info->obj->context = hexchat_get_context (ph); g_signal_emit (info->obj, signals[SERVER_SIGNAL], 0, arg1, arg2, info->id, context_list_find_id (info->obj->context)); g_strfreev (arg1); g_strfreev (arg2); return info->return_value; } static int command_hook_cb (char *word[], char *word_eol[], void *userdata) { HookInfo *info = (HookInfo*)userdata; char **arg1; char **arg2; arg1 = build_list (word + 1); arg2 = build_list (word_eol + 1); info->obj->context = hexchat_get_context (ph); g_signal_emit (info->obj, signals[COMMAND_SIGNAL], 0, arg1, arg2, info->id, context_list_find_id (info->obj->context)); g_strfreev (arg1); g_strfreev (arg2); return info->return_value; } static int print_hook_cb (char *word[], void *userdata) { HookInfo *info = (HookInfo*)userdata; char **arg1; arg1 = build_list (word + 1); info->obj->context = hexchat_get_context (ph); g_signal_emit (info->obj, signals[PRINT_SIGNAL], 0, arg1, info->id, context_list_find_id (info->obj->context)); g_strfreev (arg1); return info->return_value; } static gboolean remote_object_hook_command (RemoteObject *obj, const char *name, int priority, const char *help_text, int return_value, guint *ret_id, GError **error) { HookInfo *info; info = g_new0 (HookInfo, 1); info->obj = obj; info->return_value = return_value; info->id = ++obj->last_hook_id; info->hook = hexchat_hook_command (ph, name, priority, command_hook_cb, help_text, info); g_hash_table_insert (obj->hooks, &info->id, info); *ret_id = info->id; return TRUE; } static gboolean remote_object_hook_server (RemoteObject *obj, const char *name, int priority, int return_value, guint *ret_id, GError **error) { HookInfo *info; info = g_new0 (HookInfo, 1); info->obj = obj; info->return_value = return_value; info->id = ++obj->last_hook_id; info->hook = hexchat_hook_server (ph, name, priority, server_hook_cb, info); g_hash_table_insert (obj->hooks, &info->id, info); *ret_id = info->id; return TRUE; } static gboolean remote_object_hook_print (RemoteObject *obj, const char *name, int priority, int return_value, guint *ret_id, GError **error) { HookInfo *info; info = g_new0 (HookInfo, 1); info->obj = obj; info->return_value = return_value; info->id = ++obj->last_hook_id; info->hook = hexchat_hook_print (ph, name, priority, print_hook_cb, info); g_hash_table_insert (obj->hooks, &info->id, info); *ret_id = info->id; return TRUE; } static gboolean remote_object_unhook (RemoteObject *obj, guint id, GError **error) { g_hash_table_remove (obj->hooks, &id); return TRUE; } static gboolean remote_object_list_get (RemoteObject *obj, const char *name, guint *ret_id, GError **error) { hexchat_list *xlist; guint *id; if (!hexchat_set_context (ph, obj->context)) { *ret_id = 0; return TRUE; } xlist = hexchat_list_get (ph, name); if (xlist == NULL) { *ret_id = 0; return TRUE; } id = g_new (guint, 1); *id = ++obj->last_list_id; *ret_id = *id; g_hash_table_insert (obj->lists, id, xlist); return TRUE; } static gboolean remote_object_list_next (RemoteObject *obj, guint id, gboolean *ret, GError **error) { hexchat_list *xlist; xlist = g_hash_table_lookup (obj->lists, &id); if (xlist == NULL) { *ret = FALSE; return TRUE; } *ret = hexchat_list_next (ph, xlist); return TRUE; } static gboolean remote_object_list_str (RemoteObject *obj, guint id, const char *name, char **ret_str, GError **error) { hexchat_list *xlist; xlist = g_hash_table_lookup (obj->lists, &id); if (xlist == NULL && !hexchat_set_context (ph, obj->context)) { *ret_str = NULL; return TRUE; } if (g_str_equal (name, "context")) { *ret_str = NULL; return TRUE; } *ret_str = g_strdup (hexchat_list_str (ph, xlist, name)); return TRUE; } static gboolean remote_object_list_int (RemoteObject *obj, guint id, const char *name, int *ret_int, GError **error) { hexchat_list *xlist; xlist = g_hash_table_lookup (obj->lists, &id); if (xlist == NULL && !hexchat_set_context (ph, obj->context)) { *ret_int = -1; return TRUE; } if (g_str_equal (name, "context")) { hexchat_context *context; context = (hexchat_context*)hexchat_list_str (ph, xlist, name); *ret_int = context_list_find_id (context); } else { *ret_int = hexchat_list_int (ph, xlist, name); } return TRUE; } static gboolean remote_object_list_time (RemoteObject *obj, guint id, const char *name, guint64 *ret_time, GError **error) { hexchat_list *xlist; xlist = g_hash_table_lookup (obj->lists, &id); if (xlist == NULL) { *ret_time = (guint64) -1; return TRUE; } *ret_time = hexchat_list_time (ph, xlist, name); return TRUE; } static gboolean remote_object_list_fields (RemoteObject *obj, const char *name, char ***ret, GError **error) { *ret = g_strdupv ((char**)hexchat_list_fields (ph, name)); if (*ret == NULL) { *ret = g_new0 (char*, 1); } return TRUE; } static gboolean remote_object_list_free (RemoteObject *obj, guint id, GError **error) { g_hash_table_remove (obj->lists, &id); return TRUE; } static gboolean remote_object_emit_print (RemoteObject *obj, const char *event_name, const char *args[], gboolean *ret, GError **error) { const char *argv[4] = {NULL, NULL, NULL, NULL}; int i; for (i = 0; i < 4 && args[i] != NULL; i++) { argv[i] = args[i]; } *ret = hexchat_set_context (ph, obj->context); if (*ret) { *ret = hexchat_emit_print (ph, event_name, argv[0], argv[1], argv[2], argv[3]); } return TRUE; } static gboolean remote_object_nickcmp (RemoteObject *obj, const char *nick1, const char *nick2, int *ret, GError **error) { hexchat_set_context (ph, obj->context); *ret = hexchat_nickcmp (ph, nick1, nick2); return TRUE; } static gboolean remote_object_strip (RemoteObject *obj, const char *str, int len, int flag, char **ret_str, GError **error) { *ret_str = hexchat_strip (ph, str, len, flag); return TRUE; } static gboolean remote_object_send_modes (RemoteObject *obj, const char *targets[], int modes_per_line, char sign, char mode, GError **error) { if (hexchat_set_context (ph, obj->context)) { hexchat_send_modes (ph, targets, g_strv_length ((char**)targets), modes_per_line, sign, mode); } return TRUE; } /* DBUS stuffs */ static void name_owner_changed (DBusGProxy *driver_proxy, const char *name, const char *old_owner, const char *new_owner, void *user_data) { if (*new_owner == '\0') { /* this name has vanished */ g_hash_table_remove (clients, name); } } static gboolean init_dbus (void) { DBusGProxy *proxy; RemoteObject *remote; guint request_name_result; GError *error = NULL; dbus_g_object_type_install_info (REMOTE_TYPE_OBJECT, &dbus_glib_remote_object_object_info); connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); if (connection == NULL) { hexchat_printf (ph, _("Couldn't connect to session bus: %s\n"), error->message); g_error_free (error); return FALSE; } proxy = dbus_g_proxy_new_for_name (connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); if (!dbus_g_proxy_call (proxy, "RequestName", &error, G_TYPE_STRING, DBUS_SERVICE, G_TYPE_UINT, DBUS_NAME_FLAG_ALLOW_REPLACEMENT, G_TYPE_INVALID, G_TYPE_UINT, &request_name_result, G_TYPE_INVALID)) { hexchat_printf (ph, _("Failed to acquire %s: %s\n"), DBUS_SERVICE, error->message); g_error_free (error); return FALSE; } dbus_g_proxy_add_signal (proxy, "NameOwnerChanged", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID); dbus_g_proxy_connect_signal (proxy, "NameOwnerChanged", G_CALLBACK (name_owner_changed), NULL, NULL); remote = g_object_new (REMOTE_TYPE_OBJECT, NULL); dbus_g_connection_register_g_object (connection, DBUS_OBJECT_PATH"/Remote", G_OBJECT (remote)); return TRUE; } /* hexchat_plugin stuffs */ static char** build_list (char *word[]) { guint i; guint num = 0; char **result; if (word == NULL) { return NULL; } while (word[num] && word[num][0]) { num++; } result = g_new0 (char*, num + 1); for (i = 0; i < num; i++) { result[i] = g_strdup (word[i]); } return result; } static guint context_list_find_id (hexchat_context *context) { GList *l = NULL; for (l = contexts; l != NULL; l = l->next) { if (((ContextInfo*)l->data)->context == context) { return ((ContextInfo*)l->data)->id; } } return 0; } static hexchat_context* context_list_find_context (guint id) { GList *l = NULL; for (l = contexts; l != NULL; l = l->next) { if (((ContextInfo*)l->data)->id == id) { return ((ContextInfo*)l->data)->context; } } return NULL; } static int open_context_cb (char *word[], void *userdata) { ContextInfo *info; info = g_new0 (ContextInfo, 1); info->id = ++last_context_id; info->context = hexchat_get_context (ph); contexts = g_list_prepend (contexts, info); return HEXCHAT_EAT_NONE; } static int close_context_cb (char *word[], void *userdata) { GList *l; hexchat_context *context = hexchat_get_context (ph); for (l = contexts; l != NULL; l = l->next) { if (((ContextInfo*)l->data)->context == context) { g_free (l->data); contexts = g_list_delete_link (contexts, l); break; } } return HEXCHAT_EAT_NONE; } static gboolean clients_find_filename_foreach (gpointer key, gpointer value, gpointer user_data) { RemoteObject *obj = REMOTE_OBJECT (value); return g_str_equal (obj->filename, (char*)user_data); } static int unload_plugin_cb (char *word[], char *word_eol[], void *userdata) { RemoteObject *obj; obj = g_hash_table_find (clients, clients_find_filename_foreach, word[2]); if (obj != NULL) { g_signal_emit (obj, signals[UNLOAD_SIGNAL], 0); return HEXCHAT_EAT_ALL; } return HEXCHAT_EAT_NONE; } int dbus_plugin_init (hexchat_plugin *plugin_handle, char **plugin_name, char **plugin_desc, char **plugin_version, char *arg) { ph = plugin_handle; *plugin_name = PNAME; *plugin_desc = PDESC; *plugin_version = PVERSION; if (init_dbus()) { /*hexchat_printf (ph, _("%s loaded successfully!\n"), PNAME);*/ clients = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); hexchat_hook_print (ph, "Open Context", HEXCHAT_PRI_NORM, open_context_cb, NULL); hexchat_hook_print (ph, "Close Context", HEXCHAT_PRI_NORM, close_context_cb, NULL); hexchat_hook_command (ph, "unload", HEXCHAT_PRI_HIGHEST, unload_plugin_cb, NULL, NULL); } return TRUE; }