diff options
author | Stefano Karapetsas <[email protected]> | 2013-10-17 17:40:03 +0200 |
---|---|---|
committer | Stefano Karapetsas <[email protected]> | 2013-10-17 17:40:03 +0200 |
commit | eae33615af610f84fc2be016c5b222f44e741cc1 (patch) | |
tree | 8b7032af97743608cca3fbe60cca06d419e42ce2 /sendto/plugins | |
parent | eb157f54f3dbbbcf3b63f43073c9687f66497b45 (diff) | |
download | caja-extensions-eae33615af610f84fc2be016c5b222f44e741cc1.tar.bz2 caja-extensions-eae33615af610f84fc2be016c5b222f44e741cc1.tar.xz |
Add sendto extension
Diffstat (limited to 'sendto/plugins')
-rw-r--r-- | sendto/plugins/Makefile.am | 16 | ||||
-rw-r--r-- | sendto/plugins/caja-burn/Makefile.am | 19 | ||||
-rw-r--r-- | sendto/plugins/caja-burn/caja-burn.c | 189 | ||||
-rw-r--r-- | sendto/plugins/emailclient/Makefile.am | 17 | ||||
-rw-r--r-- | sendto/plugins/emailclient/emailclient.c | 274 | ||||
-rw-r--r-- | sendto/plugins/gajim/Makefile.am | 18 | ||||
-rw-r--r-- | sendto/plugins/gajim/gajim.c | 516 | ||||
-rw-r--r-- | sendto/plugins/nst-common.c | 98 | ||||
-rw-r--r-- | sendto/plugins/nst-common.h | 23 | ||||
-rw-r--r-- | sendto/plugins/pidgin/Makefile.am | 18 | ||||
-rw-r--r-- | sendto/plugins/pidgin/pidgin.c | 475 | ||||
-rw-r--r-- | sendto/plugins/removable-devices/Makefile.am | 19 | ||||
-rw-r--r-- | sendto/plugins/removable-devices/removable-devices.c | 259 | ||||
-rw-r--r-- | sendto/plugins/upnp/Makefile.am | 18 | ||||
-rw-r--r-- | sendto/plugins/upnp/upnp.c | 320 |
15 files changed, 2279 insertions, 0 deletions
diff --git a/sendto/plugins/Makefile.am b/sendto/plugins/Makefile.am new file mode 100644 index 0000000..10097e8 --- /dev/null +++ b/sendto/plugins/Makefile.am @@ -0,0 +1,16 @@ +SUBDIRS = . \ + caja-burn \ + emailclient \ + gajim \ + pidgin \ + removable-devices \ + upnp + +NST_COMMON_SOURCES = nst-common.c nst-common.h + +noinst_LTLIBRARIES = libnstcommon.la +libnstcommon_la_SOURCES = $(NST_COMMON_SOURCES) +libnstcommon_la_CFLAGS = $(SENDTO_CFLAGS) +libnstcommon_la_LIBADD = $(SENDTO_LIBS) + +EXTRA_DIST = $(NST_COMMON_SOURCES) diff --git a/sendto/plugins/caja-burn/Makefile.am b/sendto/plugins/caja-burn/Makefile.am new file mode 100644 index 0000000..50c309b --- /dev/null +++ b/sendto/plugins/caja-burn/Makefile.am @@ -0,0 +1,19 @@ +plugindir = $(libdir)/caja-sendto/plugins + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -DICONDIR=\"$(icondir)\" \ + -DLOCALEDIR="\"$(datadir)/locale\"" \ + -I$(top_srcdir)/sendto \ + -I$(top_builddir) \ + -I$(srcdir)/../ \ + $(SENDTO_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED) + +plugin_LTLIBRARIES = libnstburn.la + +libnstburn_la_SOURCES = caja-burn.c +libnstburn_la_LDFLAGS = -module -avoid-version +libnstburn_la_LIBADD = $(SENDTO_LIBS) $(builddir)/../libnstcommon.la + diff --git a/sendto/plugins/caja-burn/caja-burn.c b/sendto/plugins/caja-burn/caja-burn.c new file mode 100644 index 0000000..07d2e41 --- /dev/null +++ b/sendto/plugins/caja-burn/caja-burn.c @@ -0,0 +1,189 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Copyright (C) 2008 Jader Henrique da Silva + * + * 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 av. + * + * 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 02110-1301 USA. + * + * Author: Jader Henrique da Silva <[email protected]> + */ + +#include "config.h" +#include <string.h> +#include <glib/gi18n-lib.h> +#include "nst-common.h" +#include "caja-sendto-plugin.h" + +enum { + COL_PIXBUF, + COL_LABEL, + NUM_COLS, +}; + +#define COMBOBOX_OPTION_NEW_DVD 0 +#define COMBOBOX_OPTION_EXISTING_DVD 1 + +static GFile *burn = NULL; + +static +gboolean init (NstPlugin *plugin) +{ + GtkIconTheme *it; + char *cmd; + + g_print ("Init caja burn plugin\n"); + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + it = gtk_icon_theme_get_default (); + gtk_icon_theme_append_search_path (it, DATADIR "/brasero/icons"); + + cmd = g_find_program_in_path ("brasero"); + if (cmd == NULL) + return FALSE; + g_free (cmd); + + burn = g_file_new_for_uri ("burn:/"); + + return TRUE; +} + +static +GtkWidget* get_contacts_widget (NstPlugin *plugin) +{ + GtkWidget *widget; + GtkCellRenderer *renderer; + GtkListStore *store; + GtkTreeModel *model; + GFileEnumerator *fenum; + GFileInfo *file_info = NULL; + int selection = COMBOBOX_OPTION_NEW_DVD; + + fenum = g_file_enumerate_children (burn, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (fenum != NULL) { + file_info = g_file_enumerator_next_file (fenum, NULL, NULL); + g_object_unref (fenum); + } + + store = gtk_list_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_STRING); + + gtk_list_store_insert_with_values (store, NULL, + INT_MAX, + COL_PIXBUF, "media-optical-blank", + COL_LABEL, _("New CD/DVD"), + -1); + + if (file_info != NULL) { + gtk_list_store_insert_with_values (store, NULL, + INT_MAX, + COL_PIXBUF, "media-optical-data-new", + COL_LABEL, _("Existing CD/DVD"), + -1); + g_object_unref (file_info); + selection = COMBOBOX_OPTION_EXISTING_DVD; + } + + model = GTK_TREE_MODEL (store); + widget = gtk_combo_box_new_with_model (model); + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), + renderer, + FALSE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), + renderer, + "icon-name", COL_PIXBUF, + NULL); + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), + renderer, + TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), + renderer, + "text", COL_LABEL, + NULL); + + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), selection); + + return widget; +} + +static +gboolean send_files (NstPlugin *plugin, + GtkWidget *burntype_widget, + GList *file_list) +{ + GFileEnumerator *fenum; + GFileInfo *file_info; + GFile *child; + + if (gtk_combo_box_get_active (GTK_COMBO_BOX (burntype_widget)) == COMBOBOX_OPTION_NEW_DVD) { + fenum = g_file_enumerate_children (burn, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (fenum != NULL) { + while ((file_info = g_file_enumerator_next_file (fenum, NULL, NULL)) != NULL) { + child = g_file_get_child (burn, + g_file_info_get_name(file_info)); + + g_object_unref (file_info); + g_file_delete (child, NULL, NULL); + g_object_unref (child); + } + g_object_unref (fenum); + } + } + + copy_files_to (file_list, burn); + + gtk_show_uri (NULL, "burn:///", GDK_CURRENT_TIME, NULL); + + return TRUE; +} + +static +gboolean destroy (NstPlugin *plugin){ + + g_object_unref (burn); + burn = NULL; + return TRUE; + +} + +static +NstPluginInfo plugin_info = { + "brasero", + "caja-burn", + N_("CD/DVD Creator"), + NULL, + CAJA_CAPS_SEND_DIRECTORIES, + init, + get_contacts_widget, + NULL, + send_files, + destroy +}; + +NST_INIT_PLUGIN (plugin_info) + diff --git a/sendto/plugins/emailclient/Makefile.am b/sendto/plugins/emailclient/Makefile.am new file mode 100644 index 0000000..31ba40e --- /dev/null +++ b/sendto/plugins/emailclient/Makefile.am @@ -0,0 +1,17 @@ +plugindir = $(libdir)/caja-sendto/plugins + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -DICONDIR=\"$(icondir)\" \ + -DLOCALEDIR="\"$(datadir)/locale\"" \ + -I$(top_srcdir)/sendto \ + -I$(top_builddir) \ + $(SENDTO_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + $(WARN_CFLAGS) + +plugin_LTLIBRARIES = libnstemailclient.la + +libnstemailclient_la_SOURCES = emailclient.c +libnstemailclient_la_LDFLAGS = -module -avoid-version +libnstemailclient_la_LIBADD = $(SENDTO_LIBS) diff --git a/sendto/plugins/emailclient/emailclient.c b/sendto/plugins/emailclient/emailclient.c new file mode 100644 index 0000000..14cb998 --- /dev/null +++ b/sendto/plugins/emailclient/emailclient.c @@ -0,0 +1,274 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Copyright (C) 2004 Roberto Majadas <[email protected]> + * Copyright (C) 2012 Stefano Karapetsas <[email protected]> + * + * 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 av. + * + * 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 02110-1301 USA. + * + * Authors: Roberto Majadas <[email protected]> + * Stefano Karapetsas <[email protected]> + */ + +#include "config.h" + +#include <glib/gi18n-lib.h> +#include <string.h> +#include "caja-sendto-plugin.h" +#include <gio/gio.h> + +typedef enum { + MAILER_UNKNOWN, + MAILER_EVO, + MAILER_BALSA, + MAILER_SYLPHEED, + MAILER_THUNDERBIRD, +} MailerType; + +static char *mail_cmd = NULL; +static MailerType type = MAILER_UNKNOWN; + +static char * +get_evo_cmd (void) +{ + char *tmp = NULL; + char *retval; + char *cmds[] = {"evolution", + "evolution-2.0", + "evolution-2.2", + "evolution-2.4", + "evolution-2.6", + "evolution-2.8", /* for the future */ + "evolution-3.0", /* but how far to go ? */ + NULL}; + guint i; + + + for (i = 0; cmds[i] != NULL; i++) { + tmp = g_find_program_in_path (cmds[i]); + if (tmp != NULL) + break; + } + + if (tmp == NULL) + return NULL; + + retval = g_strdup_printf ("%s --component=mail %%s", tmp); + g_free (tmp); + return retval; +} + +static gboolean +init (NstPlugin *plugin) +{ + GAppInfo *app_info = NULL; + + g_print ("Init email client plugin\n"); + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + app_info = g_app_info_get_default_for_uri_scheme ("mailto"); + if (app_info) { + mail_cmd = g_strdup(g_app_info_get_executable (app_info)); + g_object_unref (app_info); + } + + if (mail_cmd == NULL || *mail_cmd == '\0') { + g_free (mail_cmd); + mail_cmd = get_evo_cmd (); + type = MAILER_EVO; + } else { + /* Find what the default mailer is */ + if (strstr (mail_cmd, "balsa")) + type = MAILER_BALSA; + else if (strstr (mail_cmd, "thunder") || strstr (mail_cmd, "seamonkey")) { + char **strv; + + type = MAILER_THUNDERBIRD; + + /* Thunderbird sucks, see + * https://bugzilla.gnome.org/show_bug.cgi?id=614222 */ + strv = g_strsplit (mail_cmd, " ", -1); + g_free (mail_cmd); + mail_cmd = g_strdup_printf ("%s %%s", strv[0]); + g_strfreev (strv); + } else if (strstr (mail_cmd, "sylpheed") || strstr (mail_cmd, "claws")) + type = MAILER_SYLPHEED; + else if (strstr (mail_cmd, "anjal")) + type = MAILER_EVO; + } + + if (mail_cmd == NULL) + return FALSE; + + return TRUE; +} + +static +GtkWidget* get_contacts_widget (NstPlugin *plugin) +{ + GtkWidget *entry; + + // TODO: add an email address format check + entry = gtk_entry_new(); + + return entry; +} + +static void +get_evo_mailto (GtkWidget *contact_widget, GString *mailto, GList *file_list) +{ + GList *l; + + g_string_append (mailto, "mailto:"); + + const char *text; + + text = gtk_entry_get_text (GTK_ENTRY (contact_widget)); + if (text != NULL && *text != '\0') + g_string_append_printf (mailto, "\"%s\"", text); + else + g_string_append (mailto, "\"\""); + + g_string_append_printf (mailto,"?attach=\"%s\"", (char *)file_list->data); + for (l = file_list->next ; l; l=l->next){ + g_string_append_printf (mailto,"&attach=\"%s\"", (char *)l->data); + } +} + +static void +get_balsa_mailto (GtkWidget *contact_widget, GString *mailto, GList *file_list) +{ + GList *l; + + if (strstr (mail_cmd, " -m ") == NULL && strstr (mail_cmd, " --compose=") == NULL) + g_string_append (mailto, " --compose="); + + const char *text; + + text = gtk_entry_get_text (GTK_ENTRY (contact_widget)); + if (text != NULL && *text != '\0') + g_string_append_printf (mailto, "\"%s\"", text); + else + g_string_append (mailto, "\"\""); + + g_string_append_printf (mailto," --attach=\"%s\"", (char *)file_list->data); + for (l = file_list->next ; l; l=l->next){ + g_string_append_printf (mailto," --attach=\"%s\"", (char *)l->data); + } +} + +static void +get_thunderbird_mailto (GtkWidget *contact_widget, GString *mailto, GList *file_list) +{ + GList *l; + + g_string_append (mailto, "-compose \""); + + const char *text; + + text = gtk_entry_get_text (GTK_ENTRY (contact_widget)); + if (text != NULL && *text != '\0') + g_string_append_printf (mailto, "to='%s',", text); + + g_string_append_printf (mailto,"attachment='%s", (char *)file_list->data); + for (l = file_list->next ; l; l=l->next){ + g_string_append_printf (mailto,",%s", (char *)l->data); + } + g_string_append (mailto, "'\""); +} + +static void +get_sylpheed_mailto (GtkWidget *contact_widget, GString *mailto, GList *file_list) +{ + GList *l; + + g_string_append (mailto, "--compose "); + + const char *text; + + text = gtk_entry_get_text (GTK_ENTRY (contact_widget)); + if (text != NULL && *text != '\0') + g_string_append_printf (mailto, "\"%s\" ", text); + else + g_string_append (mailto, "\"\""); + + g_string_append_printf (mailto,"--attach \"%s\"", (char *)file_list->data); + for (l = file_list->next ; l; l=l->next){ + g_string_append_printf (mailto," \"%s\"", (char *)l->data); + } +} + +static gboolean +send_files (NstPlugin *plugin, + GtkWidget *contact_widget, + GList *file_list) +{ + gchar *cmd; + GString *mailto; + + mailto = g_string_new (""); + switch (type) { + case MAILER_BALSA: + get_balsa_mailto (contact_widget, mailto, file_list); + break; + case MAILER_SYLPHEED: + get_sylpheed_mailto (contact_widget, mailto, file_list); + break; + case MAILER_THUNDERBIRD: + get_thunderbird_mailto (contact_widget, mailto, file_list); + break; + case MAILER_EVO: + default: + get_evo_mailto (contact_widget, mailto, file_list); + } + + cmd = g_strdup_printf (mail_cmd, mailto->str); + g_string_free (mailto, TRUE); + + g_message ("Mailer type: %d", type); + g_message ("Command: %s", cmd); + + g_spawn_command_line_async (cmd, NULL); + g_free (cmd); + + return TRUE; +} + +static +gboolean destroy (NstPlugin *plugin){ + g_free (mail_cmd); + mail_cmd = NULL; + return TRUE; +} + +static +NstPluginInfo plugin_info = { + "emblem-mail", + "emailclient", + N_("Email"), + NULL, + CAJA_CAPS_NONE, + init, + get_contacts_widget, + NULL, + send_files, + destroy +}; + +NST_INIT_PLUGIN (plugin_info) + diff --git a/sendto/plugins/gajim/Makefile.am b/sendto/plugins/gajim/Makefile.am new file mode 100644 index 0000000..5acd5b0 --- /dev/null +++ b/sendto/plugins/gajim/Makefile.am @@ -0,0 +1,18 @@ +plugindir = $(libdir)/caja-sendto/plugins + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -DICONDIR=\"$(icondir)\" \ + -DLOCALEDIR="\"$(datadir)/locale\"" \ + -I$(top_srcdir)/sendto \ + -I$(top_builddir) \ + $(SENDTO_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + $(WARN_CFLAGS) + +plugin_LTLIBRARIES = libnstgajim.la + +libnstgajim_la_SOURCES = gajim.c +libnstgajim_la_LDFLAGS = -module -avoid-version +libnstgajim_la_LIBADD = $(SENDTO_LIBS) + diff --git a/sendto/plugins/gajim/gajim.c b/sendto/plugins/gajim/gajim.c new file mode 100644 index 0000000..bb00d1d --- /dev/null +++ b/sendto/plugins/gajim/gajim.c @@ -0,0 +1,516 @@ +/* + * gajim.c + * gajim plugin for caja-sendto + * + * Copyright (C) 2006 Dimitur Kirov + * 2006 Roberto Majadas <[email protected]> + * + * 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 av. + * + * 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 02110-1301 USA. + * + */ + +#include "config.h" +#include <glib/gi18n-lib.h> +#include <dbus/dbus.h> +#include <dbus/dbus-glib.h> +#include "caja-sendto-plugin.h" + +#define OBJ_PATH "/org/gajim/dbus/RemoteObject" +#define INTERFACE "org.gajim.dbus.RemoteInterface" +#define SERVICE "org.gajim.dbus" + +const gchar *COMPLETION_PROPS[] = {"name", "jid"}; +/* list of contacts, which are not offline */ +static GHashTable *jid_table = NULL; +static gchar *iconset; + + +DBusGProxy *proxy = NULL; + +/* + * contact cb, gets property from contact dict + * and put online contacts to jid_table + */ +static void +_foreach_contact(gpointer contact, gpointer user_data) +{ + const gchar *show; + + GValue *value; + GHashTable *contact_table; + + /* holds contact props of already exisiting jid/nick */ + GHashTable *existing_contact; + + /* name of the contact in completion list + it may be jid, nick, jid (account), or nick(account) */ + GString *contact_str; + + gchar *jid; + gchar *account; + gint i; + + if (contact == NULL) { + g_warning("Null contact in the list"); + return; + } + contact_table = (GHashTable *) contact; + account = (gchar *) user_data; + + value = g_hash_table_lookup(contact_table, "show"); + if (value == NULL || !G_VALUE_HOLDS_STRING(value)) { + g_warning("String expected (contact - show)"); + g_hash_table_destroy(contact_table); + return; + } + show = g_value_get_string ((GValue *)value); + if(g_str_equal(show, "offline") || g_str_equal(show, "error")) { + g_hash_table_destroy(contact_table); + return; + } + /* remove unneeded item with key resource and add account + to contact properties */ + g_hash_table_insert(contact_table, "account", account); + g_hash_table_remove(contact_table, "resource"); + + /* add nick the same way as jid */ + for(i=0;i<2;i++) { + value = g_hash_table_lookup(contact_table, COMPLETION_PROPS[i]); + if(value == NULL || !G_VALUE_HOLDS_STRING(value)) { + g_warning("String expected (contact - name)"); + return; + } + jid = g_value_dup_string((GValue *)value); + existing_contact = g_hash_table_lookup(jid_table, jid); + if(existing_contact) { + /* add existing contact as nick (account) */ + contact_str = g_string_new(jid); + g_string_append(contact_str, " ("); + g_string_append(contact_str, + g_hash_table_lookup(existing_contact, "account")); + g_string_append(contact_str, ")"); + g_hash_table_insert(jid_table, contact_str->str, + existing_contact); + g_string_free(contact_str, FALSE); + + /* add current contact as nick (account) */ + contact_str = g_string_new(jid); + g_string_append(contact_str, " ("); + g_string_append(contact_str, + g_hash_table_lookup(contact_table, "account")); + g_string_append(contact_str, ")"); + g_hash_table_insert(jid_table, contact_str->str, + contact_table); + g_string_free(contact_str, FALSE); + } + else { + g_hash_table_insert(jid_table, jid, contact_table); + } + } + +} + +/* + * connect to session bus, onsuccess return TRUE + */ +static gboolean +init_dbus (void) +{ + DBusGConnection *connection; + GError *error; + gchar **accounts; + + error = NULL; + connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if(error != NULL) { + g_warning("[Gajim] unable to get session bus, error was:\n %s", error->message); + g_error_free(error); + return FALSE; + } + proxy = dbus_g_proxy_new_for_name(connection, + SERVICE, + OBJ_PATH, + INTERFACE); + dbus_g_connection_unref(connection); + if (proxy == NULL){ + return FALSE; + } + + error = NULL; + if (!dbus_g_proxy_call (proxy, "list_accounts", &error, G_TYPE_INVALID, + G_TYPE_STRV, &accounts, G_TYPE_INVALID)) + { + g_object_unref(proxy); + g_error_free(error); + return FALSE; + } + g_strfreev(accounts); + return TRUE; +} + +/* + * Print appropriate warnings when dbus raised error + * on queries + */ +static void +_handle_dbus_exception (GError *error, gboolean empty_list_messages) +{ + if (error == NULL) { + g_warning("[Gajim] unable to parse result"); + return; + } + else if (error->domain == DBUS_GERROR && error->code == DBUS_GERROR_REMOTE_EXCEPTION) { + g_warning ("[Gajim] caught remote method exception %s: %s", + dbus_g_error_get_name (error), + error->message); + } + else if(empty_list_messages) { + /* empty list and error goes here */ + g_warning ("[Gajim] empty result set: %d %d %s\n", error->domain, + error->code, error->message); + } + g_error_free (error); +} + +/* + * query object, about the contact list for each account + * and fill all available contacts in the contacts table + */ +static gboolean +_get_contacts (void) +{ + GError *error; + GSList *contacts_list; + GHashTable *prefs_map; + gchar **accounts; + gchar **account_iter; + gchar *account; + + error = NULL; + + if (proxy == NULL) { + g_warning("[Gajim] unable to connect to session bus"); + return FALSE; + } + /* get gajim prefs and lookup for iconset */ + if (!dbus_g_proxy_call(proxy, "prefs_list", &error, G_TYPE_INVALID, + dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_STRING), + &prefs_map, G_TYPE_INVALID)) + { + _handle_dbus_exception(error, TRUE); + return FALSE; + } + gpointer iconset_ptr = g_hash_table_lookup(prefs_map, "iconset"); + if (iconset_ptr != NULL) { + iconset = g_strdup((gchar *)iconset_ptr); + } else { + g_warning("[Gajim] unable to get prefs value for iconset"); + return FALSE; + } + g_hash_table_destroy(prefs_map); + /* END get gajim prefs */ + error= NULL; + if (!dbus_g_proxy_call (proxy, "list_accounts", &error, G_TYPE_INVALID, + G_TYPE_STRV, + &accounts, G_TYPE_INVALID)) + { + _handle_dbus_exception(error, TRUE); + return FALSE; + } + for(account_iter = accounts; *account_iter ; account_iter++) { + account = g_strdup(*account_iter); + error = NULL; + /* query gajim remote object and put results in 'contacts_list' */ + if (!dbus_g_proxy_call (proxy, "list_contacts", &error, + G_TYPE_STRING, account, /* call arguments */ + G_TYPE_INVALID, /* delimiter */ + /* return value is collection of maps */ + dbus_g_type_get_collection ("GSList", + dbus_g_type_get_map ("GHashTable", + G_TYPE_STRING, G_TYPE_VALUE)), + &contacts_list, G_TYPE_INVALID)) + { + _handle_dbus_exception(error, FALSE); + error = NULL; + continue; + } + g_slist_foreach (contacts_list, _foreach_contact, account); + g_slist_free(contacts_list); + } + g_strfreev (accounts); + return TRUE; +} + +static gboolean +init (NstPlugin *plugin) +{ + g_print ("Init gajim plugin\n"); + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + /* connect to gajim dbus service */ + jid_table = g_hash_table_new (g_str_hash, g_str_equal); + if (!init_dbus()) { + return FALSE; + } + return TRUE; +} + + +static void +_set_pixbuf_from_status (const gchar *show, GdkPixbuf **pixbuf) +{ + GString *pixbuf_path; + GError *error; + + pixbuf_path = g_string_new(GAJIM_SHARE_DIR); + g_string_append_c(pixbuf_path, '/'); + g_string_append(pixbuf_path, "data"); + g_string_append_c(pixbuf_path, '/'); + g_string_append(pixbuf_path, "iconsets"); + g_string_append_c(pixbuf_path, '/'); + g_string_append(pixbuf_path, iconset); + g_string_append_c(pixbuf_path, '/'); + g_string_append(pixbuf_path, "16x16"); + g_string_append_c(pixbuf_path, '/'); + g_string_append(pixbuf_path, show); + g_string_append(pixbuf_path, ".png"); + if(g_file_test(pixbuf_path->str, G_FILE_TEST_EXISTS) && + g_file_test(pixbuf_path->str, G_FILE_TEST_IS_REGULAR)) { + error = NULL; + *pixbuf = gdk_pixbuf_new_from_file(pixbuf_path->str, &error); + if(error != NULL) { + g_error_free(error); + } + } + g_string_free(pixbuf_path, FALSE); +} + +static void +_add_contact_to_model(gpointer key, gpointer value, gpointer user_data) +{ + GtkTreeIter *iter; + GtkListStore *store; + GdkPixbuf *pixbuf; + GValue *val; + GHashTable *contact_props; + const gchar *show; + + contact_props = (GHashTable *) value; + pixbuf = NULL; + val = g_hash_table_lookup(contact_props, "show"); + if (value == NULL || !G_VALUE_HOLDS_STRING(val)) { + g_warning("String expected (contact - show)"); + pixbuf = NULL; + } else { + show = g_value_get_string ((GValue *)val); + _set_pixbuf_from_status(show, &pixbuf); + } + + store = (GtkListStore *) user_data; + iter = g_malloc (sizeof(GtkTreeIter)); + gtk_list_store_append (store, iter); + gtk_list_store_set (store, iter, 0, pixbuf, 1, key, -1); + g_free (iter); +} + +/* + * put gajim contacts to jid_list + * filtering only these which are connected + */ +static gboolean +add_gajim_contacts_to_model (GtkListStore *store) +{ + if(!_get_contacts()) { + return FALSE; + } + if(g_hash_table_size(jid_table) == 0) { + return FALSE; + } + g_hash_table_foreach(jid_table, _add_contact_to_model, store); + return TRUE; +} + +/* + * fill completion model for the entry, using list of + * available gajim contacts + */ +static GtkWidget * +get_contacts_widget (NstPlugin *plugin) +{ + GtkWidget *entry; + GtkEntryCompletion *completion; + GtkListStore *store; + GtkCellRenderer *renderer; + GtkTreeModel *completion_model; + + entry = gtk_entry_new (); + completion = gtk_entry_completion_new (); + + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), + renderer, + FALSE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (completion), renderer, + "pixbuf", 0, NULL); + + + store = gtk_list_store_new (2, GDK_TYPE_PIXBUF, G_TYPE_STRING); + if(!add_gajim_contacts_to_model (store)) { + gtk_widget_set_sensitive(entry, FALSE); + } + completion_model = GTK_TREE_MODEL (store); + gtk_entry_completion_set_model (completion, completion_model); + gtk_entry_set_completion (GTK_ENTRY (entry), completion); + gtk_entry_completion_set_text_column (completion, 1); + g_object_unref (completion_model); + g_object_unref (completion); + return entry; +} + +static void +show_error (const gchar *title, const gchar *message) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new_with_markup(NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, NULL); + gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), + g_markup_printf_escaped("<b>%s</b>\n\n%s", title, message)); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + +} + +static gboolean +send_files (NstPlugin *plugin, + GtkWidget *contact_widget, + GList *file_list) +{ + GError *error; + GValue *value; + GList *file_iter; + GHashTable *contact_props; + + gchar *send_to; + gchar *jid; + gchar *account; + gchar *file_path; + + if(proxy == NULL) { + show_error(_("Unable to send file"), + _("There is no connection to gajim remote service.")); + return FALSE; + } + send_to = (gchar *) gtk_entry_get_text (GTK_ENTRY(contact_widget)); + g_debug("[Gajim] sending to: %s", send_to); + if (strlen (send_to) != 0){ + contact_props = g_hash_table_lookup (jid_table, send_to); + if(contact_props == NULL) { + jid = send_to; + account = NULL; + } + else { + value = g_hash_table_lookup(contact_props, "jid"); + if(value == NULL || !G_VALUE_HOLDS_STRING(value)) { + g_warning("[Gajim] string expected (contact - jid)"); + return FALSE; + } + + jid = g_value_dup_string((GValue *)value); + account = g_hash_table_lookup(contact_props, "account"); + } + } + else { + g_warning("[Gajim] missing recipient"); + show_error(_("Sending file failed"), + _("Recipient is missing.")); + return FALSE; + } + + error= NULL; + for(file_iter = file_list; file_iter != NULL; file_iter = file_iter->next) { + char *uri = file_iter->data; + + g_debug("[Gajim] file: %s", uri); + error= NULL; + file_path = g_filename_from_uri(uri, NULL, &error); + if(error != NULL) { + g_warning("%d Unable to convert URI `%s' to absolute file path", + error->code, uri); + g_error_free(error); + continue; + } + + g_debug("[Gajim] file: %s", file_path); + if(account) { + dbus_g_proxy_call (proxy, "send_file", &error, + G_TYPE_STRING, file_path, + G_TYPE_STRING, jid, + G_TYPE_STRING, account, + G_TYPE_INVALID, + G_TYPE_INVALID); + } else { + dbus_g_proxy_call (proxy, "send_file", &error, + G_TYPE_STRING, file_path, + G_TYPE_STRING, jid, + G_TYPE_INVALID, + G_TYPE_INVALID); + } + g_free(file_path); + if(error != NULL) + { + if(error->domain != DBUS_GERROR || error->code != DBUS_GERROR_INVALID_ARGS) { + g_warning("[Gajim] sending file %s to %s failed:", uri, send_to); + g_error_free(error); + show_error(_("Sending file failed"), _("Unknown recipient.")); + return FALSE; + } + g_error_free(error); + } + } + return TRUE; +} + +static gboolean +destroy (NstPlugin *plugin) +{ + if (proxy != NULL) { + g_object_unref(proxy); + } + g_hash_table_destroy(jid_table); + return TRUE; +} + +static +NstPluginInfo plugin_info = { + "im-jabber", + "gajim", + N_("Instant Message (Gajim)"), + NULL, + CAJA_CAPS_NONE, + init, + get_contacts_widget, + NULL, + send_files, + destroy +}; + +NST_INIT_PLUGIN (plugin_info) + diff --git a/sendto/plugins/nst-common.c b/sendto/plugins/nst-common.c new file mode 100644 index 0000000..2d2f102 --- /dev/null +++ b/sendto/plugins/nst-common.c @@ -0,0 +1,98 @@ +/* + * + * 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 av. + * + * 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 02110-1301 USA. + * + * Author: Maxim Ermilov <[email protected]> + */ + +#include <gio/gio.h> + +static gboolean +copy_fobject (GFile* source, GFile* dst) +{ + GFileEnumerator* en; + GFileInfo* info; + GError *err = NULL; + char *file_name; + GFile *dest; + + file_name = g_file_get_basename (source); + dest = g_file_get_child (dst, file_name); + g_free (file_name); + + if (g_file_query_file_type (source, G_FILE_QUERY_INFO_NONE, NULL) != G_FILE_TYPE_DIRECTORY) { + gboolean ret; + ret = g_file_copy (source, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, NULL); + + g_object_unref (dest); + + return ret; + } + + en = g_file_enumerate_children (source, "*", G_FILE_QUERY_INFO_NONE, NULL, NULL); + if (!g_file_make_directory (dest, NULL, NULL)) { + g_object_unref (en); + g_object_unref (dest); + return FALSE; + } + + while ((info = g_file_enumerator_next_file (en, NULL, &err)) != NULL) { + const char *name; + + name = g_file_info_get_name (G_FILE_INFO (info)); + + if (name != NULL) { + GFile *child; + + child = g_file_get_child (source, name); + + if (!copy_fobject (child, dest)) { + g_object_unref (en); + g_object_unref (dest); + g_object_unref (child); + + return FALSE; + } + g_object_unref (child); + } + + g_object_unref (info); + } + g_object_unref (en); + g_object_unref (dest); + + if (err != NULL) + return FALSE; + return TRUE; +} + +gboolean +copy_files_to (GList *file_list, GFile *dest) +{ + GList *l; + gboolean retval = TRUE; + + for (l = file_list; l != NULL; l = l->next) { + GFile *source; + + source = g_file_new_for_commandline_arg (l->data); + if (copy_fobject (source, dest) == FALSE) + retval = FALSE; + g_object_unref (source); + } + + return retval; +} diff --git a/sendto/plugins/nst-common.h b/sendto/plugins/nst-common.h new file mode 100644 index 0000000..5a5e1dd --- /dev/null +++ b/sendto/plugins/nst-common.h @@ -0,0 +1,23 @@ +/* + * + * 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 av. + * + * 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 02110-1301 USA. + * + * Author: Maxim Ermilov <[email protected]> + */ + +#include <gio/gio.h> + +gboolean copy_files_to (GList *file_list, GFile *dest); diff --git a/sendto/plugins/pidgin/Makefile.am b/sendto/plugins/pidgin/Makefile.am new file mode 100644 index 0000000..b7f650a --- /dev/null +++ b/sendto/plugins/pidgin/Makefile.am @@ -0,0 +1,18 @@ +plugindir = $(libdir)/caja-sendto/plugins + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -DICONDIR=\"$(icondir)\" \ + -DLOCALEDIR="\"$(datadir)/locale\"" \ + -I$(top_srcdir)/sendto \ + -I$(top_builddir) \ + $(SENDTO_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + $(WARN_CFLAGS) + +plugin_LTLIBRARIES = libnstpidgin.la + +libnstpidgin_la_SOURCES = pidgin.c +libnstpidgin_la_LDFLAGS = -module -avoid-version +libnstpidgin_la_LIBADD = $(SENDTO_LIBS) + diff --git a/sendto/plugins/pidgin/pidgin.c b/sendto/plugins/pidgin/pidgin.c new file mode 100644 index 0000000..941d2b6 --- /dev/null +++ b/sendto/plugins/pidgin/pidgin.c @@ -0,0 +1,475 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * pidgin.c + * pidgin plugin for caja-sendto + * + * Copyright (C) 2004 Roberto Majadas + * Copyright (C) 2009 Pascal Terjan + * + * 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 av. + * + * 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 02110-1301 USA. + * + * Author: Roberto Majadas <[email protected]> + */ + +#include "config.h" +#include <glib/gi18n-lib.h> +#include <dbus/dbus.h> +#include <dbus/dbus-glib.h> +#include "caja-sendto-plugin.h" + +#define OBJ_PATH "/im/pidgin/purple/PurpleObject" +#define INTERFACE "im.pidgin.purple.PurpleInterface" +#define SERVICE "im.pidgin.purple.PurpleService" + +static DBusGProxy *proxy = NULL; +static GHashTable *contact_hash = NULL; + +typedef struct _ContactData { + int account; + int id; + char *name; + char *alias; +} ContactData; + +enum { + COL_ICON, + COL_ALIAS, + NUM_COLS +}; + +/* + * Print appropriate warnings when dbus raised error + * on queries + */ +static void +handle_dbus_exception(GError *error) +{ + if (error == NULL) { + g_warning("[Pidgin] unable to parse result"); + return; + } + else if (error->domain == DBUS_GERROR && error->code == DBUS_GERROR_REMOTE_EXCEPTION) { + g_warning ("[Pidgin] caught remote method exception %s: %s", + dbus_g_error_get_name (error), + error->message); + } + g_error_free (error); +} + +static gboolean +init (NstPlugin *plugin) +{ + DBusGConnection *connection; + GError *error; + GArray *accounts; + + g_print ("Init pidgin plugin\n"); + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + error = NULL; + connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if(error != NULL) { + g_warning("[Pidgin] unable to get session bus, error was:\n %s", error->message); + g_error_free(error); + return FALSE; + } + + proxy = dbus_g_proxy_new_for_name(connection, + SERVICE, + OBJ_PATH, + INTERFACE); + dbus_g_connection_unref(connection); + if (proxy == NULL) + return FALSE; + + error = NULL; + if (!dbus_g_proxy_call (proxy, "PurpleAccountsGetAllActive", &error, G_TYPE_INVALID, + DBUS_TYPE_G_INT_ARRAY, &accounts, G_TYPE_INVALID)) { + g_object_unref(proxy); + g_error_free(error); + return FALSE; + } + g_array_free(accounts, TRUE); + + return TRUE; +} + +static GdkPixbuf * +get_buddy_icon(int id) +{ + GError *error; + GdkPixbuf *pixbuf = NULL; + gchar *path = NULL; + int icon; + + error=NULL; + if (!dbus_g_proxy_call (proxy, "PurpleBuddyGetIcon", &error, + G_TYPE_INT, id, + G_TYPE_INVALID, + G_TYPE_INT, &icon, G_TYPE_INVALID)) { + handle_dbus_exception(error); + } + if (icon) { + if (!dbus_g_proxy_call (proxy, "PurpleBuddyIconGetFullPath", &error, + G_TYPE_INT, icon, + G_TYPE_INVALID, + G_TYPE_STRING, &path, G_TYPE_INVALID)) { + handle_dbus_exception(error); + } + //FIXME Get the size from somewhere + pixbuf = gdk_pixbuf_new_from_file_at_scale(path, 24, 24, TRUE, NULL); + } + + return pixbuf; +} + +static void +add_pidgin_contacts_to_model (GtkTreeStore *store, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + GError *error; + GArray *contacts_list; + GArray *accounts; + int i, j; + + GdkPixbuf *icon; + GHashTableIter hiter; + GPtrArray *contacts_group; + ContactData *dat; + GValue val = {0,}; + + if(proxy == NULL) + return; + + error = NULL; + if (!dbus_g_proxy_call (proxy, "PurpleAccountsGetAllActive", &error, G_TYPE_INVALID, + DBUS_TYPE_G_INT_ARRAY, + &accounts, G_TYPE_INVALID)) { + handle_dbus_exception(error); + return; + } + + contact_hash = g_hash_table_new (g_str_hash, g_str_equal); + + for(i = 0; i < accounts->len; i++) { + int account = g_array_index(accounts, int, i); + error = NULL; + if (!dbus_g_proxy_call (proxy, "PurpleFindBuddies", &error, + G_TYPE_INT, account, + G_TYPE_STRING, NULL, + G_TYPE_INVALID, + DBUS_TYPE_G_INT_ARRAY, &contacts_list, G_TYPE_INVALID)) { + handle_dbus_exception(error); + continue; + } + for(j = 0; j < contacts_list->len ; j++) { + int id = g_array_index(contacts_list, int, j); + int online; + + error = NULL; + if (!dbus_g_proxy_call (proxy, "PurpleBuddyIsOnline", &error, + G_TYPE_INT, id, + G_TYPE_INVALID, + G_TYPE_INT, &online, G_TYPE_INVALID)) { + handle_dbus_exception(error); + continue; + } + if (!online) + continue; + + dat = g_new0 (ContactData, 1); + + dat->account = account; + dat->id = id; + + error = NULL; + if (!dbus_g_proxy_call (proxy, "PurpleBuddyGetName", &error, + G_TYPE_INT, id, + G_TYPE_INVALID, + G_TYPE_STRING, &dat->name, G_TYPE_INVALID)) { + handle_dbus_exception(error); + g_free(dat); + continue; + } + if (!dbus_g_proxy_call (proxy, "PurpleBuddyGetAlias", &error, + G_TYPE_INT, id, + G_TYPE_INVALID, + G_TYPE_STRING, &dat->alias, G_TYPE_INVALID)) { + handle_dbus_exception(error); + } + + contacts_group = g_hash_table_lookup (contact_hash, dat->alias); + if (contacts_group == NULL){ + GPtrArray *new_group = g_ptr_array_new (); + g_ptr_array_add (new_group, dat); + g_hash_table_insert (contact_hash, dat->alias, new_group); + } else { + g_ptr_array_add (contacts_group, dat); + } + } + g_array_free(contacts_list, TRUE); + } + g_array_free (accounts, TRUE); + + g_hash_table_iter_init (&hiter, contact_hash); + while (g_hash_table_iter_next (&hiter, NULL, (gpointer)&contacts_group)) { + gint accounts; + + dat = g_ptr_array_index (contacts_group, 0); + + accounts = contacts_group->len; + + gtk_tree_store_append (store, parent, NULL); + gtk_tree_store_set (store, parent, COL_ICON, NULL, COL_ALIAS, dat->alias, -1); + + gint i; + for (i = 0; i < accounts; ++i) { + dat = g_ptr_array_index (contacts_group, i); + + icon = get_buddy_icon(dat->id); + + if (accounts == 1) { + g_value_init(&val, GDK_TYPE_PIXBUF); + g_value_set_object (&val, (gpointer)icon); + gtk_tree_store_set_value (store, parent, COL_ICON, &val); + g_value_unset (&val); + break; + } + gtk_tree_store_append (store, iter, parent); + gtk_tree_store_set (store, iter, + COL_ICON, icon, + COL_ALIAS, dat->alias, + -1); + } + } +} + +static void +customize (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer text) +{ + gboolean has_child; + has_child = gtk_tree_model_iter_has_child (tree_model, iter); + if (text) { + if (has_child) + g_object_set (G_OBJECT(cell), "xpad", 18, NULL); + else + g_object_set (G_OBJECT(cell), "xpad", 2, NULL); + } + g_object_set (G_OBJECT(cell), "sensitive", !has_child, NULL); +} + +static GtkWidget * +get_contacts_widget (NstPlugin *plugin) +{ + GtkWidget *cb; + GtkCellRenderer *renderer; + GtkTreeStore *store; + GtkTreeModel *model; + GtkTreeIter *iter, *iter2; + + iter = g_malloc (sizeof(GtkTreeIter)); + iter2 = g_malloc (sizeof(GtkTreeIter)); + store = gtk_tree_store_new (NUM_COLS, GDK_TYPE_PIXBUF, G_TYPE_STRING); + add_pidgin_contacts_to_model (store, iter, iter2); + model = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (store)); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), COL_ALIAS, + GTK_SORT_ASCENDING); + cb = gtk_combo_box_new_with_model (model); + + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cb), + renderer, + FALSE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (cb), + renderer, + "pixbuf", COL_ICON, + NULL); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (cb), renderer, + customize, + (gboolean *)FALSE, NULL); + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cb), + renderer, + TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (cb), + renderer, + "text", COL_ALIAS, + NULL); + g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (cb), renderer, + customize, + (gboolean *)TRUE, NULL); + + gtk_combo_box_set_active (GTK_COMBO_BOX (cb), 0); + gtk_combo_box_get_active_iter (GTK_COMBO_BOX(cb), iter); + if (gtk_tree_model_iter_has_child (model, iter)) { + GtkTreePath *path = gtk_tree_path_new_from_indices (0, 0, -1); + gtk_tree_model_get_iter (model, iter2, path); + gtk_tree_path_free (path); + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (cb), iter2); + } + + g_free (iter); + g_free (iter2); + return cb; +} + +static +gboolean send_file(int account, const char *who, const char *filename) +{ + GError *error; + int connection; + + error = NULL; + if (!dbus_g_proxy_call(proxy, "PurpleAccountGetConnection", &error, + G_TYPE_INT, account, + G_TYPE_INVALID, + G_TYPE_INT, &connection, G_TYPE_INVALID)) { + handle_dbus_exception(error); + return FALSE; + } + + if (!connection) { + g_warning("[Pidgin] account is not connected"); + return FALSE; + } + + error = NULL; + if (!dbus_g_proxy_call(proxy, "ServSendFile", &error, + G_TYPE_INT, connection, + G_TYPE_STRING, who, + G_TYPE_STRING, filename, + G_TYPE_INVALID, G_TYPE_INVALID)) { + handle_dbus_exception(error); + return FALSE; + } + return TRUE; +} + +static +gboolean send_files (NstPlugin *plugin, GtkWidget *contact_widget, + GList *file_list) +{ + GError *error; + GList *file_iter; + + GFile *file; + gchar *file_path; + + gint depth; + GtkTreeIter iter; + GtkTreePath *path; + gint *indices; + const gchar *alias; + GPtrArray *contacts_group; + ContactData *dat; + GValue val = {0,}; + + + if(proxy == NULL) + return FALSE; + + gtk_combo_box_get_active_iter (GTK_COMBO_BOX (contact_widget), &iter); + path = gtk_tree_model_get_path (GTK_TREE_MODEL ( + gtk_combo_box_get_model (GTK_COMBO_BOX( + contact_widget))), &iter); + depth = gtk_tree_path_get_depth(path); + indices = gtk_tree_path_get_indices(path); + gtk_tree_path_free (path); + gtk_tree_model_get_value (GTK_TREE_MODEL (gtk_combo_box_get_model ( + GTK_COMBO_BOX(contact_widget))), + &iter, COL_ALIAS, &val); + alias = g_value_get_string (&val); + contacts_group = g_hash_table_lookup (contact_hash, alias); + g_value_unset (&val); + dat = g_ptr_array_index (contacts_group, (depth == 2)?indices[1]:0); + + for(file_iter = file_list; file_iter != NULL; + file_iter = g_list_next(file_iter)) { + error= NULL; + + file = g_file_new_for_uri ((gchar *)file_iter->data); + file_path = g_file_get_path (file); + g_object_unref (file); + + if(file_path == NULL) { + g_warning("[Pidgin] %d Unable to convert URI `%s' to absolute file path", + error->code, (gchar *)file_iter->data); + g_error_free(error); + continue; + } + + if(!send_file(dat->account, dat->name, file_path)) + g_warning("[Pidgin] Failed to send %s file to %s", file_path, dat->name); + } + return TRUE; +} + +static void +free_contact (ContactData *dat) +{ + g_free(dat->name); + g_free(dat->alias); + g_free(dat); +} + +static gboolean +destroy (NstPlugin *plugin) +{ + GHashTableIter iter; + GPtrArray *contacts_group; + ContactData *dat; + + g_hash_table_iter_init (&iter, contact_hash); + while (g_hash_table_iter_next (&iter, NULL, (gpointer)&contacts_group)) { + gint accounts; + accounts = contacts_group->len; + + gint i; + for (i = 0; i < accounts; ++i) { + dat = g_ptr_array_index (contacts_group, i); + free_contact (dat); + } + g_ptr_array_free (contacts_group, TRUE); + } + g_hash_table_destroy (contact_hash); + return TRUE; +} + +static +NstPluginInfo plugin_info = { + "im", + "pidgin", + N_("Instant Message (Pidgin)"), + NULL, + CAJA_CAPS_NONE, + init, + get_contacts_widget, + NULL, + send_files, + destroy +}; + +NST_INIT_PLUGIN (plugin_info) + diff --git a/sendto/plugins/removable-devices/Makefile.am b/sendto/plugins/removable-devices/Makefile.am new file mode 100644 index 0000000..294ca90 --- /dev/null +++ b/sendto/plugins/removable-devices/Makefile.am @@ -0,0 +1,19 @@ +plugindir = $(libdir)/caja-sendto/plugins + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -DICONDIR=\"$(icondir)\" \ + -DLOCALEDIR="\"$(datadir)/locale\"" \ + -I$(top_srcdir)/sendto \ + -I$(top_builddir) \ + -I$(srcdir)/../ \ + $(SENDTO_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + $(WARN_CFLAGS) + +plugin_LTLIBRARIES = libnstremovable_devices.la + +libnstremovable_devices_la_SOURCES = removable-devices.c +libnstremovable_devices_la_LDFLAGS = -module -avoid-version +libnstremovable_devices_la_LIBADD = $(SENDTO_LIBS) $(builddir)/../libnstcommon.la + diff --git a/sendto/plugins/removable-devices/removable-devices.c b/sendto/plugins/removable-devices/removable-devices.c new file mode 100644 index 0000000..0ecea14 --- /dev/null +++ b/sendto/plugins/removable-devices/removable-devices.c @@ -0,0 +1,259 @@ +/* + * + * 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 av. + * + * 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 02110-1301 USA. + * + * Authors: Maxim Ermilov <[email protected]> + * Bastien Nocera <[email protected]> + */ + +#include "config.h" +#include <string.h> +#include <glib/gi18n-lib.h> +#include <gio/gio.h> +#include "nst-common.h" +#include "caja-sendto-plugin.h" + +enum { + NAME_COL, + ICON_COL, + MOUNT_COL, + NUM_COLS, +}; + +GVolumeMonitor* vol_monitor = NULL; +GtkWidget *cb; + +static void +cb_mount_removed (GVolumeMonitor *volume_monitor, + GMount *mount, + NstPlugin *plugin) +{ + GtkTreeIter iter; + GtkListStore *store; + gboolean b, found; + + store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (cb))); + b = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); + found = FALSE; + + while (b) { + GMount *m; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, MOUNT_COL, &m, -1); + if (m == mount) { + gtk_list_store_remove (store, &iter); + g_object_unref (m); + found = TRUE; + break; + } + g_object_unref (m); + b = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); + } + + /* If a mount was removed */ + if (found != FALSE) { + /* And it was the selected one */ + if (gtk_combo_box_get_active (GTK_COMBO_BOX (cb)) == -1) { + /* Select the first item in the list */ + gtk_combo_box_set_active (GTK_COMBO_BOX (cb), 0); + } + } +} + +static void +cb_mount_changed (GVolumeMonitor *volume_monitor, + GMount *mount, + NstPlugin *plugin) +{ + GtkTreeIter iter; + gboolean b; + GtkListStore *store; + + if (g_mount_is_shadowed (mount) != FALSE) { + cb_mount_removed (volume_monitor, mount, plugin); + return; + } + + store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (cb))); + b = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); + + while (b) { + GMount *m; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, MOUNT_COL, &m, -1); + + if (m == mount) { + char *name; + + name = g_mount_get_name (mount); + gtk_list_store_set (store, &iter, + NAME_COL, name, + ICON_COL, g_mount_get_icon (mount), + -1); + g_free (name); + g_object_unref (m); + break; + } + g_object_unref (m); + b = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); + } +} + +static void +cb_mount_added (GVolumeMonitor *volume_monitor, + GMount *mount, + NstPlugin *plugin) +{ + char *name; + GtkTreeIter iter; + GtkTreeModel *model; + gboolean select_added; + + if (g_mount_is_shadowed (mount) != FALSE) + return; + + name = g_mount_get_name (mount); + model = gtk_combo_box_get_model (GTK_COMBO_BOX (cb)); + + select_added = gtk_tree_model_iter_n_children (model, NULL) == 0; + + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + NAME_COL, name, + ICON_COL, g_mount_get_icon (mount), + MOUNT_COL, mount, + -1); + + g_free (name); + + if (select_added != FALSE) + gtk_combo_box_set_active (GTK_COMBO_BOX (cb), 0); + +} + +static gboolean +init (NstPlugin *plugin) +{ + g_print ("Init removable-devices plugin\n"); + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + vol_monitor = g_volume_monitor_get (); + cb = gtk_combo_box_new (); + + return TRUE; +} + +static GtkWidget* +get_contacts_widget (NstPlugin *plugin) +{ + GtkListStore *store; + GList *l, *mounts; + GtkTreeIter iter; + GtkCellRenderer *text_renderer, *icon_renderer; + + mounts = g_volume_monitor_get_mounts (vol_monitor); + + store = gtk_list_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_ICON, G_TYPE_OBJECT); + + for (l = mounts; l != NULL; l = l->next) { + char *name; + + if (g_mount_is_shadowed (l->data) != FALSE) { + g_object_unref (l->data); + continue; + } + + name = g_mount_get_name (l->data); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + NAME_COL, name, + ICON_COL, g_mount_get_icon (l->data), + MOUNT_COL, l->data, + -1); + g_free (name); + + g_object_unref (l->data); + } + g_list_free (mounts); + + gtk_cell_layout_clear (GTK_CELL_LAYOUT (cb)); + gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (store)); + + text_renderer = gtk_cell_renderer_text_new (); + icon_renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cb), icon_renderer, FALSE); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cb), text_renderer, TRUE); + + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (cb), text_renderer, "text", 0, NULL); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (cb), icon_renderer, "gicon", 1, NULL); + gtk_combo_box_set_active (GTK_COMBO_BOX (cb), 0); + + g_signal_connect (G_OBJECT (vol_monitor), "mount-removed", G_CALLBACK (cb_mount_removed), plugin); + g_signal_connect (G_OBJECT (vol_monitor), "mount-added", G_CALLBACK (cb_mount_added), plugin); + g_signal_connect (G_OBJECT (vol_monitor), "mount-changed", G_CALLBACK (cb_mount_changed), plugin); + + return cb; +} + +static gboolean +send_files (NstPlugin *plugin, GtkWidget *contact_widget, + GList *file_list) +{ + GtkListStore *store; + GtkTreeIter iter; + GMount *dest_mount; + GFile *mount_root; + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (contact_widget), &iter) == FALSE) + return TRUE; + + store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (cb))); + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, MOUNT_COL, &dest_mount, -1); + mount_root = g_mount_get_root (dest_mount); + + copy_files_to (file_list, mount_root); + + g_object_unref (mount_root); + + return TRUE; +} + +static gboolean +destroy (NstPlugin *plugin) +{ + gtk_widget_destroy (cb); + + g_object_unref (vol_monitor); + return TRUE; +} + +static +NstPluginInfo plugin_info = { + "folder-remote", + "folder-remote", + N_("Removable disks and shares"), + NULL, + CAJA_CAPS_SEND_DIRECTORIES, + init, + get_contacts_widget, + NULL, + send_files, + destroy +}; + +NST_INIT_PLUGIN (plugin_info) + diff --git a/sendto/plugins/upnp/Makefile.am b/sendto/plugins/upnp/Makefile.am new file mode 100644 index 0000000..4127abd --- /dev/null +++ b/sendto/plugins/upnp/Makefile.am @@ -0,0 +1,18 @@ +plugindir = $(libdir)/caja-sendto/plugins + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -DICONDIR=\"$(icondir)\" \ + -DLOCALEDIR="\"$(datadir)/locale\"" \ + -I$(top_srcdir)/sendto \ + -I$(top_builddir) \ + $(SENDTO_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + $(WARN_CFLAGS) + +plugin_LTLIBRARIES = libnstupnp.la + +libnstupnp_la_SOURCES = upnp.c +libnstupnp_la_LDFLAGS = -module -avoid-version +libnstupnp_la_LIBADD = $(SENDTO_LIBS) + diff --git a/sendto/plugins/upnp/upnp.c b/sendto/plugins/upnp/upnp.c new file mode 100644 index 0000000..b381587 --- /dev/null +++ b/sendto/plugins/upnp/upnp.c @@ -0,0 +1,320 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Copyright (C) 2008 Zeeshan Ali (Khattak) + * Copyright (C) 2006 Peter Enseleit + * + * 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 av. + * + * 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 02110-1301 USA. + * + * Author: Zeeshan Ali (Khattak) <[email protected]> + * Peter Enseleit <[email protected]> + * Roberto Majadas <[email protected]> + * + */ + +#include "config.h" + +#include <glib/gi18n-lib.h> +#include <libgupnp/gupnp.h> +#include "caja-sendto-plugin.h" + +#define MEDIA_SERVER "urn:schemas-upnp-org:device:MediaServer:1" +#define CDS "urn:schemas-upnp-org:service:ContentDirectory" + +enum { + UDN_COL, + NAME_COL, + INTERFACE_COL, + NUM_COLS +}; + +static GtkWidget *combobox; +static GtkTreeModel *model; +static GUPnPContextManager *context_manager; + +static gboolean +find_device (const gchar *udn, + GtkTreeIter *iter) +{ + gboolean found = FALSE; + + if (!gtk_tree_model_get_iter_first (model, iter)) + return FALSE; + + do { + gchar *tmp; + + gtk_tree_model_get (model, + iter, + UDN_COL, &tmp, + -1); + + if (tmp != NULL && strcmp (tmp, udn) == 0) + found = TRUE; + + g_free (tmp); + } while (!found && gtk_tree_model_iter_next (model, iter)); + + return found; +} + +static gboolean +check_required_actions (GUPnPServiceIntrospection *introspection) +{ + if (gupnp_service_introspection_get_action (introspection, + "CreateObject") == NULL) + return FALSE; + if (gupnp_service_introspection_get_action (introspection, + "ImportResource") == NULL) + return FALSE; + return TRUE; +} + +static void +get_introspection_cb (GUPnPServiceInfo *service_info, + GUPnPServiceIntrospection *introspection, const GError *error, + gpointer user_data) +{ + GUPnPDeviceInfo *device_info; + gchar *name; + const gchar *udn, *interface; + GtkTreeIter iter; + GUPnPContext *context; + + device_info = GUPNP_DEVICE_INFO (user_data); + + if (introspection != NULL) { + /* If introspection is available, make sure required actions + * are implemented. + */ + if (!check_required_actions (introspection)) + goto error; + } + + udn = gupnp_device_info_get_udn (device_info); + if (G_UNLIKELY (udn == NULL)) + goto error; + + /* First check if the device is already added */ + if (find_device (udn, &iter)) + goto error; + + name = gupnp_device_info_get_friendly_name (device_info); + if (name == NULL) + name = g_strdup (udn); + + context = gupnp_device_info_get_context (device_info); + interface = gssdp_client_get_interface (GSSDP_CLIENT (context)); + + gtk_list_store_insert_with_values (GTK_LIST_STORE (model), NULL, -1, + UDN_COL, udn, + NAME_COL, name, + INTERFACE_COL, interface, + -1); + + g_free (name); + +error: + /* We don't need the proxy objects anymore */ + g_object_unref (service_info); + g_object_ref (device_info); +} + +static void +device_proxy_available_cb (GUPnPControlPoint *cp, + GUPnPDeviceProxy *proxy) +{ + GUPnPServiceInfo *info; + + info = gupnp_device_info_get_service (GUPNP_DEVICE_INFO (proxy), CDS); + if (G_UNLIKELY (info == NULL)) { + /* No ContentDirectory implemented? Not interesting. */ + return; + } + + gupnp_service_info_get_introspection_async (info, + get_introspection_cb, + g_object_ref (proxy)); +} + +static void +device_proxy_unavailable_cb (GUPnPControlPoint *cp, + GUPnPDeviceProxy *proxy) +{ + GtkTreeIter iter; + const gchar *udn; + + udn = gupnp_device_info_get_udn (GUPNP_DEVICE_INFO (proxy)); + if (udn == NULL) + return; + + /* First check if the device is already added */ + if (find_device (udn, &iter)) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); +} + +static void +on_context_available (GUPnPContextManager *context_manager, + GUPnPContext *context, + gpointer user_data) +{ + GUPnPControlPoint *cp; + + cp = gupnp_control_point_new (context, MEDIA_SERVER); + + g_signal_connect (cp, + "device-proxy-available", + G_CALLBACK (device_proxy_available_cb), + NULL); + g_signal_connect (cp, + "device-proxy-unavailable", + G_CALLBACK (device_proxy_unavailable_cb), + NULL); + + gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE); + + /* Let context manager take care of the control point life cycle */ + gupnp_context_manager_manage_control_point (context_manager, cp); + g_object_unref (cp); +} + +static gboolean +init (NstPlugin *plugin) +{ + GtkListStore *store; + GtkCellRenderer *renderer; + char *upload_cmd; + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + upload_cmd = g_find_program_in_path ("gupnp-upload"); + if (upload_cmd == NULL) + return FALSE; + g_free (upload_cmd); + + context_manager = gupnp_context_manager_new (NULL, 0); + g_assert (context_manager != NULL); + g_signal_connect (context_manager, "context-available", + G_CALLBACK (on_context_available), NULL); + + combobox = gtk_combo_box_new (); + + store = gtk_list_store_new (NUM_COLS, + G_TYPE_STRING, /* UDN */ + G_TYPE_STRING, /* Name */ + G_TYPE_STRING); /* Network Interface */ + model = GTK_TREE_MODEL (store); + gtk_combo_box_set_model (GTK_COMBO_BOX (combobox), model); + + renderer = gtk_cell_renderer_text_new (); + + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combobox), + renderer, + TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combobox), + renderer, + "text", NAME_COL); + + return TRUE; +} + +static GtkWidget* +get_contacts_widget (NstPlugin *plugin) +{ + return combobox; +} + +static gboolean +send_files (NstPlugin *plugin, + GtkWidget *contact_widget, + GList *file_list) +{ + gchar *upload_cmd, *udn, *interface; + GPtrArray *argv; + gboolean ret; + GList *l; + GtkTreeIter iter; + GError *err = NULL; + + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combobox), &iter)) + return FALSE; + + gtk_tree_model_get (model, &iter, UDN_COL, &udn, INTERFACE_COL, + &interface, -1); + + upload_cmd = g_find_program_in_path ("gupnp-upload"); + if (upload_cmd == NULL) + return FALSE; + + argv = g_ptr_array_new (); + g_ptr_array_add (argv, upload_cmd); + g_ptr_array_add (argv, "-t"); + g_ptr_array_add (argv, "15"); /* discovery timeout (seconds) */ + g_ptr_array_add (argv, "-e"); + g_ptr_array_add (argv, interface); + g_ptr_array_add (argv, udn); + for (l = file_list ; l; l=l->next) { + gchar *file_path; + + file_path = g_filename_from_uri (l->data, NULL, NULL); + g_ptr_array_add (argv, file_path); + } + g_ptr_array_add (argv, NULL); + + ret = g_spawn_async (NULL, (gchar **) argv->pdata, + NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &err); + + if (ret == FALSE) { + g_warning ("Could not send files to MediaServer: %s", + err->message); + g_error_free (err); + } + + g_ptr_array_free (argv, TRUE); + g_free (upload_cmd); + g_free (udn); + + return ret; +} + +static gboolean +destroy (NstPlugin *plugin) +{ + gtk_widget_destroy (combobox); + g_object_unref (model); + + g_object_unref (context_manager); + + return TRUE; +} + +static +NstPluginInfo plugin_info = { + "folder-remote", + "upnp", + N_("UPnP Media Server"), + NULL, + CAJA_CAPS_NONE, + init, + get_contacts_widget, + NULL, + send_files, + destroy +}; + +NST_INIT_PLUGIN (plugin_info) + |