1750 lines
53 KiB
C
1750 lines
53 KiB
C
|
/* LIBPIKA - The PIKA Library
|
||
|
* Copyright (C) 1995-2003 Peter Mattis and Spencer Kimball
|
||
|
*
|
||
|
* pikaplugin.c
|
||
|
* Copyright (C) 2019 Michael Natterer <mitch@gimp.org>
|
||
|
*
|
||
|
* This library is free software: you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU Lesser General Public
|
||
|
* License as published by the Free Software Foundation; either
|
||
|
* version 3 of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* This library 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
|
||
|
* Lesser General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Lesser General Public
|
||
|
* License along with this library. If not, see
|
||
|
* <https://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include <errno.h>
|
||
|
#include <libintl.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include "pika.h"
|
||
|
|
||
|
#include "libpikabase/pikaprotocol.h"
|
||
|
#include "libpikabase/pikawire.h"
|
||
|
|
||
|
#include "pika-private.h"
|
||
|
#include "pika-shm.h"
|
||
|
#include "pikagpparams.h"
|
||
|
#include "pikaplugin-private.h"
|
||
|
#include "pikaplugin_pdb.h"
|
||
|
#include "pikaprocedure-private.h"
|
||
|
|
||
|
|
||
|
/**
|
||
|
* PikaPlugIn:
|
||
|
*
|
||
|
* The base class for plug-ins to derive from.
|
||
|
*
|
||
|
* PikaPlugIn manages the plug-in's [class@Procedure] objects. The procedures a
|
||
|
* plug-in implements are registered with PIKA by returning a list of their
|
||
|
* names from either [vfunc@GimpPlugIn.query_procedures] or
|
||
|
* [vfunc@GimpPlugIn.init_procedures].
|
||
|
*
|
||
|
* Every PIKA plug-in has to be implemented as a subclass and make it known to
|
||
|
* the libpika infrastructure and the main PIKA application by passing its
|
||
|
* `GType` to [func@MAIN].
|
||
|
*
|
||
|
* [func@MAIN] passes the 'argc' and 'argv' of the platform's main() function,
|
||
|
* along with the `GType`, to [func@main], which creates an instance of the
|
||
|
* plug-in's `PikaPlugIn` subclass and calls its virtual functions, depending
|
||
|
* on how the plug-in was called by PIKA.
|
||
|
*
|
||
|
* There are 3 different ways PIKA calls a plug-in: "query", "init" and "run".
|
||
|
*
|
||
|
* The plug-in is called in "query" mode once after it was installed, or when
|
||
|
* the cached plug-in information in the config file "pluginrc" needs to be
|
||
|
* recreated. In "query" mode, [vfunc@GimpPlugIn.query_procedures] is called
|
||
|
* and returns a list of procedure names the plug-in implements. This is the
|
||
|
* "normal" place to register procedures, because the existence of most
|
||
|
* procedures doesn't depend on things that change between PIKA sessions.
|
||
|
*
|
||
|
* The plug-in is called in "init" mode at each PIKA startup, and
|
||
|
* [vfunc@PlugIn.init_procedures] is called and returns a list of procedure
|
||
|
* names this plug-in implements. This only happens if the plug-in actually
|
||
|
* implements [vfunc@GimpPlugIn.init_procedures]. A plug-in only needs to
|
||
|
* implement init_procedures if the existence of its procedures can change
|
||
|
* between PIKA sessions, for example if they depend on the presence of
|
||
|
* external tools, or hardware like scanners, or online services, or whatever
|
||
|
* variable circumstances.
|
||
|
*
|
||
|
* In order to register the plug-in's procedures with the main PIKA application
|
||
|
* in the plug-in's "query" and "init" modes, [class@PlugIn] calls
|
||
|
* [vfunc@PlugIn.create_procedure] on all procedure names in the exact order of
|
||
|
* the list returned by [vfunc@PlugIn.query_procedures] or
|
||
|
* [vfunc@PlugIn.init_procedures] and then registers the returned
|
||
|
* [class@Procedure].
|
||
|
*
|
||
|
* The plug-in is called in "run" mode whenever one of the procedures it
|
||
|
* implements is called by either the main PIKA application or any other
|
||
|
* plug-in. In "run" mode, one of the procedure names returned by
|
||
|
* [vfunc@PlugIn.query_procedures] or [vfunc@PlugIn.init_procedures] is passed
|
||
|
* to [vfunc@PlugIn.create_procedure] which must return a [class@Procedure] for
|
||
|
* the passed name. The procedure is then executed by calling
|
||
|
* [method@Procedure.run].
|
||
|
*
|
||
|
* In any of the three modes, [vfunc@PlugIn.quit] is called before the plug-in
|
||
|
* process exits, so the plug-in can perform whatever cleanup necessary.
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
*/
|
||
|
|
||
|
|
||
|
#define WRITE_BUFFER_SIZE 1024
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_error_quark:
|
||
|
*
|
||
|
* Generic #GQuark error domain for plug-ins. Plug-ins are welcome to
|
||
|
* create their own domain when they want to handle advanced error
|
||
|
* handling. Often, you just want to pass an error message to the core.
|
||
|
* This domain can be used for such simple usage.
|
||
|
*
|
||
|
* See #GError for information on error domains.
|
||
|
*/
|
||
|
G_DEFINE_QUARK (pika-plug-in-error-quark, pika_plug_in_error)
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
PROP_0,
|
||
|
PROP_READ_CHANNEL,
|
||
|
PROP_WRITE_CHANNEL,
|
||
|
N_PROPS
|
||
|
};
|
||
|
|
||
|
|
||
|
typedef struct _PikaPlugInMenuBranch PikaPlugInMenuBranch;
|
||
|
|
||
|
struct _PikaPlugInMenuBranch
|
||
|
{
|
||
|
gchar *menu_path;
|
||
|
gchar *menu_label;
|
||
|
};
|
||
|
|
||
|
struct _PikaPlugInPrivate
|
||
|
{
|
||
|
GIOChannel *read_channel;
|
||
|
GIOChannel *write_channel;
|
||
|
|
||
|
gchar write_buffer[WRITE_BUFFER_SIZE];
|
||
|
gulong write_buffer_index;
|
||
|
|
||
|
guint extension_source_id;
|
||
|
|
||
|
gchar *translation_domain_name;
|
||
|
GFile *translation_domain_path;
|
||
|
|
||
|
gchar *help_domain_name;
|
||
|
GFile *help_domain_uri;
|
||
|
|
||
|
GList *menu_branches;
|
||
|
|
||
|
GList *temp_procedures;
|
||
|
|
||
|
GList *procedure_stack;
|
||
|
GHashTable *displays;
|
||
|
GHashTable *images;
|
||
|
GHashTable *items;
|
||
|
GHashTable *resources;
|
||
|
};
|
||
|
|
||
|
|
||
|
static void pika_plug_in_constructed (GObject *object);
|
||
|
static void pika_plug_in_dispose (GObject *object);
|
||
|
static void pika_plug_in_finalize (GObject *object);
|
||
|
static void pika_plug_in_set_property (GObject *object,
|
||
|
guint property_id,
|
||
|
const GValue *value,
|
||
|
GParamSpec *pspec);
|
||
|
static void pika_plug_in_get_property (GObject *object,
|
||
|
guint property_id,
|
||
|
GValue *value,
|
||
|
GParamSpec *pspec);
|
||
|
|
||
|
static gboolean pika_plug_in_real_set_i18n (PikaPlugIn *plug_in,
|
||
|
const gchar *procedure_name,
|
||
|
gchar **gettext_domain,
|
||
|
gchar **catalog_dir);
|
||
|
|
||
|
static void pika_plug_in_register (PikaPlugIn *plug_in,
|
||
|
GList *procedures);
|
||
|
|
||
|
static gboolean pika_plug_in_write (GIOChannel *channel,
|
||
|
const guint8 *buf,
|
||
|
gulong count,
|
||
|
gpointer user_data);
|
||
|
static gboolean pika_plug_in_flush (GIOChannel *channel,
|
||
|
gpointer user_data);
|
||
|
static gboolean pika_plug_in_io_error_handler (GIOChannel *channel,
|
||
|
GIOCondition cond,
|
||
|
gpointer data);
|
||
|
|
||
|
static void pika_plug_in_loop (PikaPlugIn *plug_in);
|
||
|
static void pika_plug_in_single_message (PikaPlugIn *plug_in);
|
||
|
static void pika_plug_in_process_message (PikaPlugIn *plug_in,
|
||
|
PikaWireMessage *msg);
|
||
|
static void pika_plug_in_proc_run (PikaPlugIn *plug_in,
|
||
|
GPProcRun *proc_run);
|
||
|
static void pika_plug_in_temp_proc_run (PikaPlugIn *plug_in,
|
||
|
GPProcRun *proc_run);
|
||
|
static void pika_plug_in_proc_run_internal (PikaPlugIn *plug_in,
|
||
|
GPProcRun *proc_run,
|
||
|
PikaProcedure *procedure,
|
||
|
GPProcReturn *proc_return);
|
||
|
static gboolean pika_plug_in_extension_read (GIOChannel *channel,
|
||
|
GIOCondition condition,
|
||
|
gpointer data);
|
||
|
|
||
|
static void pika_plug_in_push_procedure (PikaPlugIn *plug_in,
|
||
|
PikaProcedure *procedure);
|
||
|
static void pika_plug_in_pop_procedure (PikaPlugIn *plug_in,
|
||
|
PikaProcedure *procedure);
|
||
|
static void pika_plug_in_destroy_hashes (PikaPlugIn *plug_in);
|
||
|
static void pika_plug_in_destroy_proxies (GHashTable *hash_table,
|
||
|
gboolean destroy_all);
|
||
|
|
||
|
static void pika_plug_in_init_i18n (PikaPlugIn *plug_in);
|
||
|
|
||
|
|
||
|
G_DEFINE_TYPE_WITH_PRIVATE (PikaPlugIn, pika_plug_in, G_TYPE_OBJECT)
|
||
|
|
||
|
#define parent_class pika_plug_in_parent_class
|
||
|
|
||
|
static GParamSpec *props[N_PROPS] = { NULL, };
|
||
|
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_class_init (PikaPlugInClass *klass)
|
||
|
{
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
|
||
|
object_class->constructed = pika_plug_in_constructed;
|
||
|
object_class->dispose = pika_plug_in_dispose;
|
||
|
object_class->finalize = pika_plug_in_finalize;
|
||
|
object_class->set_property = pika_plug_in_set_property;
|
||
|
object_class->get_property = pika_plug_in_get_property;
|
||
|
|
||
|
klass->set_i18n = pika_plug_in_real_set_i18n;
|
||
|
|
||
|
/**
|
||
|
* PikaPlugIn:read-channel:
|
||
|
*
|
||
|
* The [struct@GLib.IOChannel] to read from PIKA
|
||
|
*/
|
||
|
props[PROP_READ_CHANNEL] =
|
||
|
g_param_spec_boxed ("read-channel",
|
||
|
"Read channel",
|
||
|
"The GIOChanel to read from PIKA",
|
||
|
G_TYPE_IO_CHANNEL,
|
||
|
PIKA_PARAM_READWRITE |
|
||
|
G_PARAM_CONSTRUCT_ONLY);
|
||
|
|
||
|
/**
|
||
|
* PikaPlugIn:write-channel:
|
||
|
*
|
||
|
* The [struct@GLib.IOChannel] to write to PIKA
|
||
|
*/
|
||
|
props[PROP_WRITE_CHANNEL] =
|
||
|
g_param_spec_boxed ("write-channel",
|
||
|
"Write channel",
|
||
|
"The GIOChanel to write to PIKA",
|
||
|
G_TYPE_IO_CHANNEL,
|
||
|
PIKA_PARAM_READWRITE |
|
||
|
G_PARAM_CONSTRUCT_ONLY);
|
||
|
|
||
|
g_object_class_install_properties (object_class, N_PROPS, props);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_init (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
plug_in->priv = pika_plug_in_get_instance_private (plug_in);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_constructed (GObject *object)
|
||
|
{
|
||
|
PikaPlugIn *plug_in = PIKA_PLUG_IN (object);
|
||
|
|
||
|
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||
|
|
||
|
g_assert (plug_in->priv->read_channel != NULL);
|
||
|
g_assert (plug_in->priv->write_channel != NULL);
|
||
|
|
||
|
gp_init ();
|
||
|
|
||
|
pika_wire_set_writer (pika_plug_in_write);
|
||
|
pika_wire_set_flusher (pika_plug_in_flush);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_dispose (GObject *object)
|
||
|
{
|
||
|
PikaPlugIn *plug_in = PIKA_PLUG_IN (object);
|
||
|
|
||
|
if (plug_in->priv->extension_source_id)
|
||
|
{
|
||
|
g_source_remove (plug_in->priv->extension_source_id);
|
||
|
plug_in->priv->extension_source_id = 0;
|
||
|
}
|
||
|
|
||
|
if (plug_in->priv->temp_procedures)
|
||
|
{
|
||
|
g_list_free_full (plug_in->priv->temp_procedures, g_object_unref);
|
||
|
plug_in->priv->temp_procedures = NULL;
|
||
|
}
|
||
|
|
||
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_finalize (GObject *object)
|
||
|
{
|
||
|
PikaPlugIn *plug_in = PIKA_PLUG_IN (object);
|
||
|
GList *list;
|
||
|
|
||
|
g_clear_pointer (&plug_in->priv->translation_domain_name, g_free);
|
||
|
g_clear_object (&plug_in->priv->translation_domain_path);
|
||
|
|
||
|
g_clear_pointer (&plug_in->priv->help_domain_name, g_free);
|
||
|
g_clear_object (&plug_in->priv->help_domain_uri);
|
||
|
|
||
|
for (list = plug_in->priv->menu_branches; list; list = g_list_next (list))
|
||
|
{
|
||
|
PikaPlugInMenuBranch *branch = list->data;
|
||
|
|
||
|
g_free (branch->menu_path);
|
||
|
g_free (branch->menu_label);
|
||
|
g_slice_free (PikaPlugInMenuBranch, branch);
|
||
|
}
|
||
|
|
||
|
g_clear_pointer (&plug_in->priv->menu_branches, g_list_free);
|
||
|
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->displays, TRUE);
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->images, TRUE);
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->items, TRUE);
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->resources, TRUE);
|
||
|
|
||
|
pika_plug_in_destroy_hashes (plug_in);
|
||
|
|
||
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_set_property (GObject *object,
|
||
|
guint property_id,
|
||
|
const GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
PikaPlugIn *plug_in = PIKA_PLUG_IN (object);
|
||
|
|
||
|
switch (property_id)
|
||
|
{
|
||
|
case PROP_READ_CHANNEL:
|
||
|
plug_in->priv->read_channel = g_value_get_boxed (value);
|
||
|
break;
|
||
|
|
||
|
case PROP_WRITE_CHANNEL:
|
||
|
plug_in->priv->write_channel = g_value_get_boxed (value);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_get_property (GObject *object,
|
||
|
guint property_id,
|
||
|
GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
PikaPlugIn *plug_in = PIKA_PLUG_IN (object);
|
||
|
|
||
|
switch (property_id)
|
||
|
{
|
||
|
case PROP_READ_CHANNEL:
|
||
|
g_value_set_boxed (value, plug_in->priv->read_channel);
|
||
|
break;
|
||
|
|
||
|
case PROP_WRITE_CHANNEL:
|
||
|
g_value_set_boxed (value, plug_in->priv->write_channel);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
pika_plug_in_real_set_i18n (PikaPlugIn *plug_in,
|
||
|
const gchar *procedure_name,
|
||
|
gchar **gettext_domain,
|
||
|
gchar **catalog_dir)
|
||
|
{
|
||
|
/* Default to enabling localization by gettext. It will have the good
|
||
|
* side-effect of warning plug-in developers of the existence of the
|
||
|
* ability through stderr if the catalog directory is missing.
|
||
|
*/
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* public functions */
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_set_help_domain:
|
||
|
* @plug_in: A #PikaPlugIn.
|
||
|
* @domain_name: The XML namespace of the plug-in's help pages.
|
||
|
* @domain_uri: The root URI of the plug-in's help pages.
|
||
|
*
|
||
|
* Set a help domain and path for the @plug_in.
|
||
|
*
|
||
|
* This function registers user documentation for the calling plug-in
|
||
|
* with the PIKA help system. The @domain_uri parameter points to the
|
||
|
* root directory where the plug-in help is installed. For each
|
||
|
* supported language there should be a file called 'pika-help.xml'
|
||
|
* that maps the help IDs to the actual help files.
|
||
|
*
|
||
|
* This function can only be called in the
|
||
|
* [vfunc@PlugIn.query_procedures] function of a plug-in.
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
**/
|
||
|
void
|
||
|
pika_plug_in_set_help_domain (PikaPlugIn *plug_in,
|
||
|
const gchar *domain_name,
|
||
|
GFile *domain_uri)
|
||
|
{
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
g_return_if_fail (domain_name != NULL);
|
||
|
g_return_if_fail (G_IS_FILE (domain_uri));
|
||
|
|
||
|
g_free (plug_in->priv->help_domain_name);
|
||
|
plug_in->priv->help_domain_name = g_strdup (domain_name);
|
||
|
|
||
|
g_set_object (&plug_in->priv->help_domain_uri, domain_uri);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_add_menu_branch:
|
||
|
* @plug_in: A #PikaPlugIn
|
||
|
* @menu_path: The sub-menu's menu path.
|
||
|
* @menu_label: The menu label of the sub-menu.
|
||
|
*
|
||
|
* Add a new sub-menu to the PIKA menus.
|
||
|
*
|
||
|
* This function installs a sub-menu which does not belong to any
|
||
|
* procedure at the location @menu_path.
|
||
|
*
|
||
|
* For translations of @menu_label to work properly, @menu_label
|
||
|
* should only be marked for translation but passed to this function
|
||
|
* untranslated, for example using N_("Submenu"). PIKA will look up
|
||
|
* the translation in the textdomain registered for the plug-in.
|
||
|
*
|
||
|
* See also: pika_procedure_add_menu_path().
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
**/
|
||
|
void
|
||
|
pika_plug_in_add_menu_branch (PikaPlugIn *plug_in,
|
||
|
const gchar *menu_path,
|
||
|
const gchar *menu_label)
|
||
|
{
|
||
|
PikaPlugInMenuBranch *branch;
|
||
|
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
g_return_if_fail (menu_path != NULL);
|
||
|
g_return_if_fail (menu_label != NULL);
|
||
|
|
||
|
branch = g_slice_new (PikaPlugInMenuBranch);
|
||
|
|
||
|
branch->menu_path = g_strdup (menu_path);
|
||
|
branch->menu_label = g_strdup (menu_label);
|
||
|
|
||
|
plug_in->priv->menu_branches = g_list_append (plug_in->priv->menu_branches,
|
||
|
branch);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_add_temp_procedure:
|
||
|
* @plug_in: A #PikaPlugIn
|
||
|
* @procedure: A #PikaProcedure of type %PIKA_PDB_PROC_TYPE_TEMPORARY.
|
||
|
*
|
||
|
* This function adds a temporary procedure to @plug_in. It is usually
|
||
|
* called from a %PIKA_PDB_PROC_TYPE_EXTENSION procedure's
|
||
|
* [vfunc@Procedure.run].
|
||
|
*
|
||
|
* A temporary procedure is a procedure which is only available while
|
||
|
* one of your plug-in's "real" procedures is running.
|
||
|
*
|
||
|
* The procedure's type _must_ be
|
||
|
* %PIKA_PDB_PROC_TYPE_TEMPORARY or the function will fail.
|
||
|
*
|
||
|
* NOTE: Normally, plug-in communication is triggered by the plug-in
|
||
|
* and the PIKA core only responds to the plug-in's requests. You must
|
||
|
* explicitly enable receiving of temporary procedure run requests
|
||
|
* using either [method@PlugIn.extension_enable] or
|
||
|
* [method@PlugIn.extension_process]. See their respective documentation
|
||
|
* for details.
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
**/
|
||
|
void
|
||
|
pika_plug_in_add_temp_procedure (PikaPlugIn *plug_in,
|
||
|
PikaProcedure *procedure)
|
||
|
{
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
g_return_if_fail (PIKA_IS_PROCEDURE (procedure));
|
||
|
g_return_if_fail (pika_procedure_get_proc_type (procedure) ==
|
||
|
PIKA_PDB_PROC_TYPE_TEMPORARY);
|
||
|
|
||
|
plug_in->priv->temp_procedures =
|
||
|
g_list_prepend (plug_in->priv->temp_procedures,
|
||
|
g_object_ref (procedure));
|
||
|
|
||
|
PIKA_PROCEDURE_GET_CLASS (procedure)->install (procedure);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_remove_temp_procedure:
|
||
|
* @plug_in: A #PikaPlugIn
|
||
|
* @procedure_name: The name of a [class@Procedure] added to @plug_in.
|
||
|
*
|
||
|
* This function removes a temporary procedure from @plug_in by the
|
||
|
* procedure's @procedure_name.
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
**/
|
||
|
void
|
||
|
pika_plug_in_remove_temp_procedure (PikaPlugIn *plug_in,
|
||
|
const gchar *procedure_name)
|
||
|
{
|
||
|
PikaProcedure *procedure;
|
||
|
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
g_return_if_fail (pika_is_canonical_identifier (procedure_name));
|
||
|
|
||
|
procedure = pika_plug_in_get_temp_procedure (plug_in, procedure_name);
|
||
|
|
||
|
if (procedure)
|
||
|
{
|
||
|
PIKA_PROCEDURE_GET_CLASS (procedure)->uninstall (procedure);
|
||
|
|
||
|
plug_in->priv->temp_procedures =
|
||
|
g_list_remove (plug_in->priv->temp_procedures,
|
||
|
procedure);
|
||
|
g_object_unref (procedure);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_get_temp_procedures:
|
||
|
* @plug_in: A plug-in
|
||
|
*
|
||
|
* This function retrieves the list of temporary procedure of @plug_in as
|
||
|
* added with [method@PlugIn.add_temp_procedure].
|
||
|
*
|
||
|
* Returns: (transfer none) (element-type PikaProcedure): The list of
|
||
|
* procedures.
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
**/
|
||
|
GList *
|
||
|
pika_plug_in_get_temp_procedures (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), NULL);
|
||
|
|
||
|
return plug_in->priv->temp_procedures;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_get_temp_procedure:
|
||
|
* @plug_in: A #PikaPlugIn
|
||
|
* @procedure_name: The name of a [class@Procedure] added to @plug_in.
|
||
|
*
|
||
|
* This function retrieves a temporary procedure from @plug_in by the
|
||
|
* procedure's @procedure_name.
|
||
|
*
|
||
|
* Returns: (nullable) (transfer none): The procedure if registered, or %NULL.
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
**/
|
||
|
PikaProcedure *
|
||
|
pika_plug_in_get_temp_procedure (PikaPlugIn *plug_in,
|
||
|
const gchar *procedure_name)
|
||
|
{
|
||
|
GList *list;
|
||
|
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), NULL);
|
||
|
g_return_val_if_fail (pika_is_canonical_identifier (procedure_name), NULL);
|
||
|
|
||
|
for (list = plug_in->priv->temp_procedures; list; list = g_list_next (list))
|
||
|
{
|
||
|
PikaProcedure *procedure = list->data;
|
||
|
|
||
|
if (! strcmp (procedure_name, pika_procedure_get_name (procedure)))
|
||
|
return procedure;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_extension_enable:
|
||
|
* @plug_in: A plug-in
|
||
|
*
|
||
|
* Enables asynchronous processing of messages from the main PIKA
|
||
|
* application.
|
||
|
*
|
||
|
* Normally, a plug-in is not called by PIKA except for the call to
|
||
|
* the procedure it implements. All subsequent communication is
|
||
|
* triggered by the plug-in and all messages sent from PIKA to the
|
||
|
* plug-in are just answers to requests the plug-in made.
|
||
|
*
|
||
|
* If the plug-in however registered temporary procedures using
|
||
|
* [method@PlugIn.add_temp_procedure], it needs to be able to receive
|
||
|
* requests to execute them. Usually this will be done by running
|
||
|
* [method@PlugIn.extension_process] in an endless loop.
|
||
|
*
|
||
|
* If the plug-in cannot use [method@PlugIn.extension_process], i.e. if
|
||
|
* it has a GUI and is hanging around in a [struct@GLib.MainLoop], it
|
||
|
* must call [method@PlugIn.extension_enable].
|
||
|
*
|
||
|
* Note that the plug-in does not need to be a
|
||
|
* [const@PDBProcType.EXTENSION] to register temporary procedures.
|
||
|
*
|
||
|
* See also: [method@PlugIn.add_temp_procedure].
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
**/
|
||
|
void
|
||
|
pika_plug_in_extension_enable (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
|
||
|
if (! plug_in->priv->extension_source_id)
|
||
|
{
|
||
|
plug_in->priv->extension_source_id =
|
||
|
g_io_add_watch (plug_in->priv->read_channel, G_IO_IN | G_IO_PRI,
|
||
|
pika_plug_in_extension_read,
|
||
|
plug_in);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_extension_process:
|
||
|
* @plug_in: A plug-in.
|
||
|
* @timeout: The timeout (in ms) to use for the select() call.
|
||
|
*
|
||
|
* Processes one message sent by PIKA and returns.
|
||
|
*
|
||
|
* Call this function in an endless loop after calling
|
||
|
* pika_procedure_extension_ready() to process requests for running
|
||
|
* temporary procedures.
|
||
|
*
|
||
|
* See [method@PlugIn.extension_enable] for an asynchronous way of
|
||
|
* doing the same if running an endless loop is not an option.
|
||
|
*
|
||
|
* See also: [method@PlugIn.add_temp_procedure].
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
**/
|
||
|
void
|
||
|
pika_plug_in_extension_process (PikaPlugIn *plug_in,
|
||
|
guint timeout)
|
||
|
{
|
||
|
#ifndef G_OS_WIN32
|
||
|
|
||
|
gint select_val;
|
||
|
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
|
||
|
do
|
||
|
{
|
||
|
fd_set readfds;
|
||
|
struct timeval tv;
|
||
|
struct timeval *tvp;
|
||
|
|
||
|
if (timeout)
|
||
|
{
|
||
|
tv.tv_sec = timeout / 1000;
|
||
|
tv.tv_usec = (timeout % 1000) * 1000;
|
||
|
tvp = &tv;
|
||
|
}
|
||
|
else
|
||
|
tvp = NULL;
|
||
|
|
||
|
FD_ZERO (&readfds);
|
||
|
FD_SET (g_io_channel_unix_get_fd (plug_in->priv->read_channel),
|
||
|
&readfds);
|
||
|
|
||
|
if ((select_val = select (FD_SETSIZE, &readfds, NULL, NULL, tvp)) > 0)
|
||
|
{
|
||
|
pika_plug_in_single_message (plug_in);
|
||
|
}
|
||
|
else if (select_val == -1 && errno != EINTR)
|
||
|
{
|
||
|
perror ("pika_plug_in_extension_process");
|
||
|
pika_quit ();
|
||
|
}
|
||
|
}
|
||
|
while (select_val == -1 && errno == EINTR);
|
||
|
|
||
|
#else
|
||
|
|
||
|
/* Zero means infinite wait for us, but g_poll and
|
||
|
* g_io_channel_win32_poll use -1 to indicate
|
||
|
* infinite wait.
|
||
|
*/
|
||
|
GPollFD pollfd;
|
||
|
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
|
||
|
if (timeout == 0)
|
||
|
timeout = -1;
|
||
|
|
||
|
g_io_channel_win32_make_pollfd (plug_in->priv->read_channel, G_IO_IN,
|
||
|
&pollfd);
|
||
|
|
||
|
if (g_io_channel_win32_poll (&pollfd, 1, timeout) == 1)
|
||
|
{
|
||
|
pika_plug_in_single_message (plug_in);
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_set_pdb_error_handler:
|
||
|
* @plug_in: A plug-in
|
||
|
* @handler: Who is responsible for handling procedure call errors.
|
||
|
*
|
||
|
* Sets an error handler for procedure calls.
|
||
|
*
|
||
|
* This procedure changes the way that errors in procedure calls are
|
||
|
* handled. By default PIKA will raise an error dialog if a procedure
|
||
|
* call made by a plug-in fails. Using this procedure the plug-in can
|
||
|
* change this behavior. If the error handler is set to
|
||
|
* %PIKA_PDB_ERROR_HANDLER_PLUGIN, then the plug-in is responsible for
|
||
|
* calling pika_pdb_get_last_error() and handling the error whenever
|
||
|
* one if its procedure calls fails. It can do this by displaying the
|
||
|
* error message or by forwarding it in its own return values.
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
**/
|
||
|
void
|
||
|
pika_plug_in_set_pdb_error_handler (PikaPlugIn *plug_in,
|
||
|
PikaPDBErrorHandler handler)
|
||
|
{
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
|
||
|
_pika_plug_in_set_pdb_error_handler (handler);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_plug_in_get_pdb_error_handler:
|
||
|
* @plug_in: A plug-in
|
||
|
*
|
||
|
* Retrieves the active error handler for procedure calls.
|
||
|
*
|
||
|
* This procedure retrieves the currently active error handler for
|
||
|
* procedure calls made by the calling plug-in. See
|
||
|
* pika_plugin_set_pdb_error_handler() for details.
|
||
|
*
|
||
|
* Returns: Who is responsible for handling procedure call errors.
|
||
|
*
|
||
|
* Since: 3.0
|
||
|
**/
|
||
|
PikaPDBErrorHandler
|
||
|
pika_plug_in_get_pdb_error_handler (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in),
|
||
|
PIKA_PDB_ERROR_HANDLER_INTERNAL);
|
||
|
|
||
|
return _pika_plug_in_get_pdb_error_handler ();
|
||
|
}
|
||
|
|
||
|
|
||
|
/* internal functions */
|
||
|
|
||
|
void
|
||
|
_pika_plug_in_query (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
|
||
|
if (PIKA_PLUG_IN_GET_CLASS (plug_in)->init_procedures)
|
||
|
gp_has_init_write (plug_in->priv->write_channel, plug_in);
|
||
|
|
||
|
if (PIKA_PLUG_IN_GET_CLASS (plug_in)->query_procedures)
|
||
|
{
|
||
|
GList *procedures =
|
||
|
PIKA_PLUG_IN_GET_CLASS (plug_in)->query_procedures (plug_in);
|
||
|
|
||
|
pika_plug_in_register (plug_in, procedures);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
_pika_plug_in_init (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
|
||
|
if (PIKA_PLUG_IN_GET_CLASS (plug_in)->init_procedures)
|
||
|
{
|
||
|
GList *procedures =
|
||
|
PIKA_PLUG_IN_GET_CLASS (plug_in)->init_procedures (plug_in);
|
||
|
|
||
|
pika_plug_in_register (plug_in, procedures);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
_pika_plug_in_run (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
|
||
|
g_io_add_watch (plug_in->priv->read_channel,
|
||
|
G_IO_ERR | G_IO_HUP,
|
||
|
pika_plug_in_io_error_handler,
|
||
|
NULL);
|
||
|
|
||
|
pika_plug_in_loop (plug_in);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
_pika_plug_in_quit (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
|
||
|
if (PIKA_PLUG_IN_GET_CLASS (plug_in)->quit)
|
||
|
PIKA_PLUG_IN_GET_CLASS (plug_in)->quit (plug_in);
|
||
|
|
||
|
_pika_shm_close ();
|
||
|
|
||
|
gp_quit_write (plug_in->priv->write_channel, plug_in);
|
||
|
}
|
||
|
|
||
|
GIOChannel *
|
||
|
_pika_plug_in_get_read_channel (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), NULL);
|
||
|
|
||
|
return plug_in->priv->read_channel;
|
||
|
}
|
||
|
|
||
|
GIOChannel *
|
||
|
_pika_plug_in_get_write_channel (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), NULL);
|
||
|
|
||
|
return plug_in->priv->write_channel;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
_pika_plug_in_read_expect_msg (PikaPlugIn *plug_in,
|
||
|
PikaWireMessage *msg,
|
||
|
gint type)
|
||
|
{
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
|
||
|
while (TRUE)
|
||
|
{
|
||
|
if (! pika_wire_read_msg (plug_in->priv->read_channel, msg, NULL))
|
||
|
pika_quit ();
|
||
|
|
||
|
if (msg->type == type)
|
||
|
return; /* up to the caller to call wire_destroy() */
|
||
|
|
||
|
if (msg->type == GP_TEMP_PROC_RUN || msg->type == GP_QUIT)
|
||
|
{
|
||
|
pika_plug_in_process_message (plug_in, msg);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_error ("unexpected message: %d", msg->type);
|
||
|
}
|
||
|
|
||
|
pika_wire_destroy (msg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
_pika_plug_in_set_i18n (PikaPlugIn *plug_in,
|
||
|
const gchar *procedure_name,
|
||
|
gchar **gettext_domain,
|
||
|
gchar **catalog_dir)
|
||
|
{
|
||
|
gboolean use_gettext;
|
||
|
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), FALSE);
|
||
|
g_return_val_if_fail (gettext_domain && *gettext_domain == NULL, FALSE);
|
||
|
g_return_val_if_fail (catalog_dir && *catalog_dir == NULL, FALSE);
|
||
|
|
||
|
if (! plug_in->priv->translation_domain_path ||
|
||
|
! plug_in->priv->translation_domain_name)
|
||
|
pika_plug_in_init_i18n (plug_in);
|
||
|
|
||
|
if (! PIKA_PLUG_IN_GET_CLASS (plug_in)->set_i18n)
|
||
|
{
|
||
|
use_gettext = FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
gchar *utf8_catalog_dir = NULL;
|
||
|
|
||
|
use_gettext = PIKA_PLUG_IN_GET_CLASS (plug_in)->set_i18n (plug_in,
|
||
|
procedure_name,
|
||
|
gettext_domain,
|
||
|
&utf8_catalog_dir);
|
||
|
if (use_gettext)
|
||
|
{
|
||
|
gboolean reserved = FALSE;
|
||
|
|
||
|
if (*gettext_domain == NULL)
|
||
|
{
|
||
|
*gettext_domain = g_strdup (plug_in->priv->translation_domain_name);
|
||
|
}
|
||
|
else if (g_strcmp0 (*gettext_domain, GETTEXT_PACKAGE "-std-plug-ins") == 0 ||
|
||
|
g_strcmp0 (*gettext_domain, GETTEXT_PACKAGE "-script-fu") == 0 ||
|
||
|
g_strcmp0 (*gettext_domain, GETTEXT_PACKAGE "-python") == 0)
|
||
|
{
|
||
|
/* We special-case these 3 domains as the only ones where
|
||
|
* it is allowed to set an absolute system dir (actually
|
||
|
* set by the lib itself; devs must set NULL). See docs of
|
||
|
* set_i18n() method.
|
||
|
*/
|
||
|
if (utf8_catalog_dir != NULL)
|
||
|
g_printerr ("[%s] Do not set a catalog directory with set_i18n() with reserved domain: %s\n",
|
||
|
procedure_name, *gettext_domain);
|
||
|
|
||
|
*catalog_dir = g_strdup (pika_locale_directory ());
|
||
|
reserved = TRUE;
|
||
|
}
|
||
|
|
||
|
if (utf8_catalog_dir != NULL && *catalog_dir == NULL)
|
||
|
{
|
||
|
GError *error = NULL;
|
||
|
|
||
|
/* The passed-on catalog directory is in UTF-8 because this is
|
||
|
* usually hardcoded (in which case it's easier to request a
|
||
|
* specific encoding, chosen at development time, rather than the
|
||
|
* "OS encoding", depending on runtime).
|
||
|
* But now we want to transform it to the encoding used for
|
||
|
* filenames by GLib.
|
||
|
*/
|
||
|
*catalog_dir = g_filename_from_utf8 (utf8_catalog_dir, -1, NULL, NULL, &error);
|
||
|
|
||
|
if (*catalog_dir == NULL)
|
||
|
g_printerr ("[%s] provided catalog directory is not proper UTF-8: %s\n",
|
||
|
procedure_name, error ? error->message : "(N/A)");
|
||
|
|
||
|
g_clear_error (&error);
|
||
|
}
|
||
|
|
||
|
if (*catalog_dir && ! reserved)
|
||
|
{
|
||
|
if (g_path_is_absolute (*catalog_dir))
|
||
|
{
|
||
|
g_printerr ("[%s] The catalog directory set by set_i18n() is not relative: %s\n",
|
||
|
procedure_name, *catalog_dir);
|
||
|
g_printerr ("[%s] Localization disabled\n", procedure_name);
|
||
|
|
||
|
use_gettext = FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
gchar *rootdir = g_path_get_dirname (pika_get_progname ());
|
||
|
GFile *root_file = g_file_new_for_path (rootdir);
|
||
|
GFile *catalog_file;
|
||
|
GFile *parent;
|
||
|
|
||
|
catalog_file = g_file_resolve_relative_path (root_file, *catalog_dir);
|
||
|
|
||
|
/* Verify that the catalog is a subdir of the plug-in folder.
|
||
|
* We do not want to allow plug-ins to look outside their own
|
||
|
* realm.
|
||
|
*/
|
||
|
parent = g_file_dup (catalog_file);
|
||
|
do
|
||
|
{
|
||
|
if (g_file_equal (parent, root_file))
|
||
|
break;
|
||
|
g_clear_object (&parent);
|
||
|
}
|
||
|
while ((parent = g_file_get_parent (parent)));
|
||
|
|
||
|
if (parent == NULL)
|
||
|
{
|
||
|
g_printerr ("[%s] The catalog directory set by set_i18n() is not a subdirectory: %s\n",
|
||
|
procedure_name, *catalog_dir);
|
||
|
g_printerr ("[%s] Localization disabled\n", procedure_name);
|
||
|
|
||
|
use_gettext = FALSE;
|
||
|
}
|
||
|
|
||
|
g_free (rootdir);
|
||
|
g_object_unref (root_file);
|
||
|
g_clear_object (&parent);
|
||
|
g_object_unref (catalog_file);
|
||
|
}
|
||
|
}
|
||
|
else if (! *catalog_dir)
|
||
|
{
|
||
|
*catalog_dir = g_file_get_path (plug_in->priv->translation_domain_path);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
g_clear_pointer (&utf8_catalog_dir, g_free);
|
||
|
}
|
||
|
|
||
|
if (use_gettext && ! g_file_test (*catalog_dir, G_FILE_TEST_IS_DIR))
|
||
|
{
|
||
|
g_printerr ("[%s] The catalog directory does not exist: %s\n",
|
||
|
procedure_name, *catalog_dir);
|
||
|
g_printerr ("[%s] Override method set_i18n() for the plug-in to customize or disable localization.\n",
|
||
|
procedure_name);
|
||
|
g_printerr ("[%s] Localization disabled\n", procedure_name);
|
||
|
|
||
|
use_gettext = FALSE;
|
||
|
}
|
||
|
|
||
|
if (! use_gettext)
|
||
|
{
|
||
|
g_clear_pointer (gettext_domain, g_free);
|
||
|
g_clear_pointer (catalog_dir, g_free);
|
||
|
}
|
||
|
|
||
|
return use_gettext;
|
||
|
}
|
||
|
|
||
|
PikaProcedure *
|
||
|
_pika_plug_in_create_procedure (PikaPlugIn *plug_in,
|
||
|
const gchar *procedure_name)
|
||
|
{
|
||
|
gchar *gettext_domain = NULL;
|
||
|
gchar *catalog_dir = NULL;
|
||
|
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), NULL);
|
||
|
g_return_val_if_fail (pika_is_canonical_identifier (procedure_name), NULL);
|
||
|
|
||
|
if (_pika_plug_in_set_i18n (plug_in, procedure_name, &gettext_domain, &catalog_dir))
|
||
|
{
|
||
|
pika_bind_text_domain (gettext_domain, catalog_dir);
|
||
|
#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
|
||
|
bind_textdomain_codeset (gettext_domain, "UTF-8");
|
||
|
#endif
|
||
|
textdomain (gettext_domain);
|
||
|
|
||
|
g_free (gettext_domain);
|
||
|
g_free (catalog_dir);
|
||
|
}
|
||
|
|
||
|
if (PIKA_PLUG_IN_GET_CLASS (plug_in)->create_procedure)
|
||
|
return PIKA_PLUG_IN_GET_CLASS (plug_in)->create_procedure (plug_in,
|
||
|
procedure_name);
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* private functions */
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_register (PikaPlugIn *plug_in,
|
||
|
GList *procedures)
|
||
|
{
|
||
|
GList *list;
|
||
|
|
||
|
for (list = procedures; list; list = g_list_next (list))
|
||
|
{
|
||
|
const gchar *name = list->data;
|
||
|
PikaProcedure *procedure;
|
||
|
|
||
|
procedure = _pika_plug_in_create_procedure (plug_in, name);
|
||
|
if (procedure)
|
||
|
{
|
||
|
PIKA_PROCEDURE_GET_CLASS (procedure)->install (procedure);
|
||
|
g_object_unref (procedure);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_warning ("Plug-in failed to create procedure '%s'\n", name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
g_list_free_full (procedures, g_free);
|
||
|
|
||
|
if (plug_in->priv->help_domain_name)
|
||
|
{
|
||
|
_pika_plug_in_help_register (plug_in->priv->help_domain_name,
|
||
|
plug_in->priv->help_domain_uri);
|
||
|
}
|
||
|
|
||
|
for (list = plug_in->priv->menu_branches; list; list = g_list_next (list))
|
||
|
{
|
||
|
PikaPlugInMenuBranch *branch = list->data;
|
||
|
|
||
|
_pika_plug_in_menu_branch_register (branch->menu_path,
|
||
|
branch->menu_label);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
pika_plug_in_write (GIOChannel *channel,
|
||
|
const guint8 *buf,
|
||
|
gulong count,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
PikaPlugIn *plug_in = user_data;
|
||
|
|
||
|
while (count > 0)
|
||
|
{
|
||
|
gulong bytes;
|
||
|
|
||
|
if ((plug_in->priv->write_buffer_index + count) >= WRITE_BUFFER_SIZE)
|
||
|
{
|
||
|
bytes = WRITE_BUFFER_SIZE - plug_in->priv->write_buffer_index;
|
||
|
memcpy (&plug_in->priv->write_buffer[plug_in->priv->write_buffer_index],
|
||
|
buf, bytes);
|
||
|
plug_in->priv->write_buffer_index += bytes;
|
||
|
|
||
|
if (! pika_wire_flush (channel, plug_in))
|
||
|
return FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bytes = count;
|
||
|
memcpy (&plug_in->priv->write_buffer[plug_in->priv->write_buffer_index],
|
||
|
buf, bytes);
|
||
|
plug_in->priv->write_buffer_index += bytes;
|
||
|
}
|
||
|
|
||
|
buf += bytes;
|
||
|
count -= bytes;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
pika_plug_in_flush (GIOChannel *channel,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
PikaPlugIn *plug_in = user_data;
|
||
|
|
||
|
if (plug_in->priv->write_buffer_index > 0)
|
||
|
{
|
||
|
gsize count = 0;
|
||
|
|
||
|
while (count != plug_in->priv->write_buffer_index)
|
||
|
{
|
||
|
GIOStatus status;
|
||
|
gsize bytes;
|
||
|
GError *error = NULL;
|
||
|
|
||
|
do
|
||
|
{
|
||
|
bytes = 0;
|
||
|
status = g_io_channel_write_chars (channel,
|
||
|
&plug_in->priv->write_buffer[count],
|
||
|
(plug_in->priv->write_buffer_index - count),
|
||
|
&bytes,
|
||
|
&error);
|
||
|
}
|
||
|
while (status == G_IO_STATUS_AGAIN);
|
||
|
|
||
|
if (status != G_IO_STATUS_NORMAL)
|
||
|
{
|
||
|
if (error)
|
||
|
{
|
||
|
g_warning ("%s: pika_flush(): error: %s",
|
||
|
g_get_prgname (), error->message);
|
||
|
g_error_free (error);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_warning ("%s: pika_flush(): error", g_get_prgname ());
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
count += bytes;
|
||
|
}
|
||
|
|
||
|
plug_in->priv->write_buffer_index = 0;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
pika_plug_in_io_error_handler (GIOChannel *channel,
|
||
|
GIOCondition cond,
|
||
|
gpointer data)
|
||
|
{
|
||
|
g_printerr ("%s: fatal error: PIKA crashed\n", pika_get_progname ());
|
||
|
pika_quit ();
|
||
|
|
||
|
/* never reached */
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_loop (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
while (TRUE)
|
||
|
{
|
||
|
PikaWireMessage msg;
|
||
|
|
||
|
if (! pika_wire_read_msg (plug_in->priv->read_channel, &msg, NULL))
|
||
|
return;
|
||
|
|
||
|
switch (msg.type)
|
||
|
{
|
||
|
case GP_QUIT:
|
||
|
pika_wire_destroy (&msg);
|
||
|
return;
|
||
|
|
||
|
case GP_CONFIG:
|
||
|
_pika_config (msg.data);
|
||
|
break;
|
||
|
|
||
|
case GP_TILE_REQ:
|
||
|
case GP_TILE_ACK:
|
||
|
case GP_TILE_DATA:
|
||
|
g_warning ("unexpected tile message received (should not happen)");
|
||
|
break;
|
||
|
|
||
|
case GP_PROC_RUN:
|
||
|
pika_plug_in_proc_run (plug_in, msg.data);
|
||
|
pika_wire_destroy (&msg);
|
||
|
return;
|
||
|
|
||
|
case GP_PROC_RETURN:
|
||
|
g_warning ("unexpected proc return message received (should not happen)");
|
||
|
break;
|
||
|
|
||
|
case GP_TEMP_PROC_RUN:
|
||
|
g_warning ("unexpected temp proc run message received (should not happen");
|
||
|
break;
|
||
|
|
||
|
case GP_TEMP_PROC_RETURN:
|
||
|
g_warning ("unexpected temp proc return message received (should not happen");
|
||
|
break;
|
||
|
|
||
|
case GP_PROC_INSTALL:
|
||
|
g_warning ("unexpected proc install message received (should not happen)");
|
||
|
break;
|
||
|
|
||
|
case GP_HAS_INIT:
|
||
|
g_warning ("unexpected has init message received (should not happen)");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pika_wire_destroy (&msg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_single_message (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
PikaWireMessage msg;
|
||
|
|
||
|
/* Run a temp function */
|
||
|
if (! pika_wire_read_msg (plug_in->priv->read_channel, &msg, NULL))
|
||
|
pika_quit ();
|
||
|
|
||
|
pika_plug_in_process_message (plug_in, &msg);
|
||
|
|
||
|
pika_wire_destroy (&msg);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_process_message (PikaPlugIn *plug_in,
|
||
|
PikaWireMessage *msg)
|
||
|
{
|
||
|
switch (msg->type)
|
||
|
{
|
||
|
case GP_QUIT:
|
||
|
pika_quit ();
|
||
|
break;
|
||
|
case GP_CONFIG:
|
||
|
_pika_config (msg->data);
|
||
|
break;
|
||
|
case GP_TILE_REQ:
|
||
|
case GP_TILE_ACK:
|
||
|
case GP_TILE_DATA:
|
||
|
g_warning ("unexpected tile message received (should not happen)");
|
||
|
break;
|
||
|
case GP_PROC_RUN:
|
||
|
g_warning ("unexpected proc run message received (should not happen)");
|
||
|
break;
|
||
|
case GP_PROC_RETURN:
|
||
|
g_warning ("unexpected proc return message received (should not happen)");
|
||
|
break;
|
||
|
case GP_TEMP_PROC_RUN:
|
||
|
pika_plug_in_temp_proc_run (plug_in, msg->data);
|
||
|
break;
|
||
|
case GP_TEMP_PROC_RETURN:
|
||
|
g_warning ("unexpected temp proc return message received (should not happen)");
|
||
|
break;
|
||
|
case GP_PROC_INSTALL:
|
||
|
g_warning ("unexpected proc install message received (should not happen)");
|
||
|
break;
|
||
|
case GP_HAS_INIT:
|
||
|
g_warning ("unexpected has init message received (should not happen)");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_proc_run (PikaPlugIn *plug_in,
|
||
|
GPProcRun *proc_run)
|
||
|
{
|
||
|
GPProcReturn proc_return;
|
||
|
PikaProcedure *procedure;
|
||
|
|
||
|
procedure = _pika_plug_in_create_procedure (plug_in, proc_run->name);
|
||
|
|
||
|
if (procedure)
|
||
|
{
|
||
|
pika_plug_in_proc_run_internal (plug_in,
|
||
|
proc_run, procedure,
|
||
|
&proc_return);
|
||
|
g_object_unref (procedure);
|
||
|
}
|
||
|
|
||
|
if (! gp_proc_return_write (plug_in->priv->write_channel,
|
||
|
&proc_return, plug_in))
|
||
|
pika_quit ();
|
||
|
|
||
|
_pika_gp_params_free (proc_return.params, proc_return.n_params, TRUE);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_temp_proc_run (PikaPlugIn *plug_in,
|
||
|
GPProcRun *proc_run)
|
||
|
{
|
||
|
GPProcReturn proc_return;
|
||
|
PikaProcedure *procedure;
|
||
|
|
||
|
procedure = pika_plug_in_get_temp_procedure (plug_in, proc_run->name);
|
||
|
|
||
|
if (procedure)
|
||
|
{
|
||
|
pika_plug_in_proc_run_internal (plug_in,
|
||
|
proc_run, procedure,
|
||
|
&proc_return);
|
||
|
}
|
||
|
|
||
|
if (! gp_temp_proc_return_write (plug_in->priv->write_channel,
|
||
|
&proc_return, plug_in))
|
||
|
pika_quit ();
|
||
|
|
||
|
_pika_gp_params_free (proc_return.params, proc_return.n_params, TRUE);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_proc_run_internal (PikaPlugIn *plug_in,
|
||
|
GPProcRun *proc_run,
|
||
|
PikaProcedure *procedure,
|
||
|
GPProcReturn *proc_return)
|
||
|
{
|
||
|
PikaValueArray *arguments;
|
||
|
PikaValueArray *return_values = NULL;
|
||
|
gchar *gettext_domain = NULL;
|
||
|
gchar *catalog_dir = NULL;
|
||
|
|
||
|
if (_pika_plug_in_set_i18n (plug_in, pika_procedure_get_name (procedure),
|
||
|
&gettext_domain, &catalog_dir))
|
||
|
{
|
||
|
pika_bind_text_domain (gettext_domain, catalog_dir);
|
||
|
#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
|
||
|
bind_textdomain_codeset (gettext_domain, "UTF-8");
|
||
|
#endif
|
||
|
textdomain (gettext_domain);
|
||
|
|
||
|
g_free (gettext_domain);
|
||
|
g_free (catalog_dir);
|
||
|
}
|
||
|
|
||
|
pika_plug_in_push_procedure (plug_in, procedure);
|
||
|
|
||
|
arguments = _pika_gp_params_to_value_array (NULL,
|
||
|
NULL, 0,
|
||
|
proc_run->params,
|
||
|
proc_run->n_params,
|
||
|
FALSE);
|
||
|
|
||
|
return_values = pika_procedure_run (procedure, arguments);
|
||
|
|
||
|
pika_value_array_unref (arguments);
|
||
|
|
||
|
proc_return->name = proc_run->name;
|
||
|
proc_return->n_params = pika_value_array_length (return_values);
|
||
|
proc_return->params = _pika_value_array_to_gp_params (return_values, TRUE);
|
||
|
|
||
|
pika_value_array_unref (return_values);
|
||
|
|
||
|
pika_plug_in_pop_procedure (plug_in, procedure);
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
pika_plug_in_extension_read (GIOChannel *channel,
|
||
|
GIOCondition condition,
|
||
|
gpointer data)
|
||
|
{
|
||
|
PikaPlugIn *plug_in = data;
|
||
|
|
||
|
pika_plug_in_single_message (plug_in);
|
||
|
|
||
|
return G_SOURCE_CONTINUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* procedure stack / display-, image-, item-cache */
|
||
|
|
||
|
PikaProcedure *
|
||
|
_pika_plug_in_get_procedure (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), NULL);
|
||
|
g_return_val_if_fail (plug_in->priv->procedure_stack != NULL, NULL);
|
||
|
|
||
|
return plug_in->priv->procedure_stack->data;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_push_procedure (PikaPlugIn *plug_in,
|
||
|
PikaProcedure *procedure)
|
||
|
{
|
||
|
plug_in->priv->procedure_stack =
|
||
|
g_list_prepend (plug_in->priv->procedure_stack, procedure);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_pop_procedure (PikaPlugIn *plug_in,
|
||
|
PikaProcedure *procedure)
|
||
|
{
|
||
|
plug_in->priv->procedure_stack =
|
||
|
g_list_remove (plug_in->priv->procedure_stack, procedure);
|
||
|
|
||
|
_pika_procedure_destroy_proxies (procedure);
|
||
|
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->displays, FALSE);
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->images, FALSE);
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->items, FALSE);
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->resources, FALSE);
|
||
|
|
||
|
if (! plug_in->priv->procedure_stack)
|
||
|
{
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->displays, TRUE);
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->images, TRUE);
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->items, TRUE);
|
||
|
pika_plug_in_destroy_proxies (plug_in->priv->resources, TRUE);
|
||
|
|
||
|
pika_plug_in_destroy_hashes (plug_in);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
PikaDisplay *
|
||
|
_pika_plug_in_get_display (PikaPlugIn *plug_in,
|
||
|
gint32 display_id)
|
||
|
{
|
||
|
PikaDisplay *display = NULL;
|
||
|
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), NULL);
|
||
|
|
||
|
if (G_UNLIKELY (! plug_in->priv->displays))
|
||
|
plug_in->priv->displays =
|
||
|
g_hash_table_new_full (g_direct_hash,
|
||
|
g_direct_equal,
|
||
|
NULL,
|
||
|
(GDestroyNotify) g_object_unref);
|
||
|
|
||
|
display = g_hash_table_lookup (plug_in->priv->displays,
|
||
|
GINT_TO_POINTER (display_id));
|
||
|
|
||
|
if (! display)
|
||
|
{
|
||
|
display = g_object_new (PIKA_TYPE_DISPLAY,
|
||
|
"id", display_id,
|
||
|
NULL);
|
||
|
|
||
|
g_hash_table_insert (plug_in->priv->displays,
|
||
|
GINT_TO_POINTER (display_id),
|
||
|
g_object_ref (display) /* add debug ref */);
|
||
|
}
|
||
|
|
||
|
return display;
|
||
|
}
|
||
|
|
||
|
PikaImage *
|
||
|
_pika_plug_in_get_image (PikaPlugIn *plug_in,
|
||
|
gint32 image_id)
|
||
|
{
|
||
|
PikaImage *image = NULL;
|
||
|
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), NULL);
|
||
|
|
||
|
if (G_UNLIKELY (! plug_in->priv->images))
|
||
|
plug_in->priv->images =
|
||
|
g_hash_table_new_full (g_direct_hash,
|
||
|
g_direct_equal,
|
||
|
NULL,
|
||
|
(GDestroyNotify) g_object_unref);
|
||
|
|
||
|
image = g_hash_table_lookup (plug_in->priv->images,
|
||
|
GINT_TO_POINTER (image_id));
|
||
|
|
||
|
if (! image)
|
||
|
{
|
||
|
image = g_object_new (PIKA_TYPE_IMAGE,
|
||
|
"id", image_id,
|
||
|
NULL);
|
||
|
|
||
|
g_hash_table_insert (plug_in->priv->images,
|
||
|
GINT_TO_POINTER (image_id),
|
||
|
g_object_ref (image) /* add debug ref */);
|
||
|
}
|
||
|
|
||
|
return image;
|
||
|
}
|
||
|
|
||
|
PikaItem *
|
||
|
_pika_plug_in_get_item (PikaPlugIn *plug_in,
|
||
|
gint32 item_id)
|
||
|
{
|
||
|
PikaItem *item = NULL;
|
||
|
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), NULL);
|
||
|
|
||
|
if (G_UNLIKELY (! plug_in->priv->items))
|
||
|
plug_in->priv->items =
|
||
|
g_hash_table_new_full (g_direct_hash,
|
||
|
g_direct_equal,
|
||
|
NULL,
|
||
|
(GDestroyNotify) g_object_unref);
|
||
|
|
||
|
item = g_hash_table_lookup (plug_in->priv->items,
|
||
|
GINT_TO_POINTER (item_id));
|
||
|
|
||
|
if (! item)
|
||
|
{
|
||
|
if (pika_item_id_is_text_layer (item_id))
|
||
|
{
|
||
|
item = g_object_new (PIKA_TYPE_TEXT_LAYER,
|
||
|
"id", item_id,
|
||
|
NULL);
|
||
|
}
|
||
|
else if (pika_item_id_is_layer (item_id))
|
||
|
{
|
||
|
item = g_object_new (PIKA_TYPE_LAYER,
|
||
|
"id", item_id,
|
||
|
NULL);
|
||
|
}
|
||
|
else if (pika_item_id_is_layer_mask (item_id))
|
||
|
{
|
||
|
item = g_object_new (PIKA_TYPE_LAYER_MASK,
|
||
|
"id", item_id,
|
||
|
NULL);
|
||
|
}
|
||
|
else if (pika_item_id_is_selection (item_id))
|
||
|
{
|
||
|
item = g_object_new (PIKA_TYPE_SELECTION,
|
||
|
"id", item_id,
|
||
|
NULL);
|
||
|
}
|
||
|
else if (pika_item_id_is_channel (item_id))
|
||
|
{
|
||
|
item = g_object_new (PIKA_TYPE_CHANNEL,
|
||
|
"id", item_id,
|
||
|
NULL);
|
||
|
}
|
||
|
else if (pika_item_id_is_vectors (item_id))
|
||
|
{
|
||
|
item = g_object_new (PIKA_TYPE_VECTORS,
|
||
|
"id", item_id,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
if (item)
|
||
|
g_hash_table_insert (plug_in->priv->items,
|
||
|
GINT_TO_POINTER (item_id),
|
||
|
g_object_ref (item) /* add debug ref */);
|
||
|
}
|
||
|
|
||
|
return item;
|
||
|
}
|
||
|
|
||
|
PikaResource *
|
||
|
_pika_plug_in_get_resource (PikaPlugIn *plug_in,
|
||
|
gint32 resource_id)
|
||
|
{
|
||
|
PikaResource *resource = NULL;
|
||
|
|
||
|
g_return_val_if_fail (PIKA_IS_PLUG_IN (plug_in), NULL);
|
||
|
|
||
|
if (G_UNLIKELY (! plug_in->priv->resources))
|
||
|
plug_in->priv->resources =
|
||
|
g_hash_table_new_full (g_direct_hash,
|
||
|
g_direct_equal,
|
||
|
NULL,
|
||
|
(GDestroyNotify) g_object_unref);
|
||
|
|
||
|
resource = g_hash_table_lookup (plug_in->priv->resources,
|
||
|
GINT_TO_POINTER (resource_id));
|
||
|
|
||
|
if (! resource)
|
||
|
{
|
||
|
if (pika_resource_id_is_brush (resource_id))
|
||
|
{
|
||
|
resource = g_object_new (PIKA_TYPE_BRUSH,
|
||
|
"id", resource_id,
|
||
|
NULL);
|
||
|
}
|
||
|
else if (pika_resource_id_is_pattern (resource_id))
|
||
|
{
|
||
|
resource = g_object_new (PIKA_TYPE_PATTERN,
|
||
|
"id", resource_id,
|
||
|
NULL);
|
||
|
}
|
||
|
else if (pika_resource_id_is_gradient (resource_id))
|
||
|
{
|
||
|
resource = g_object_new (PIKA_TYPE_GRADIENT,
|
||
|
"id", resource_id,
|
||
|
NULL);
|
||
|
}
|
||
|
else if (pika_resource_id_is_palette (resource_id))
|
||
|
{
|
||
|
resource = g_object_new (PIKA_TYPE_PALETTE,
|
||
|
"id", resource_id,
|
||
|
NULL);
|
||
|
}
|
||
|
else if (pika_resource_id_is_font (resource_id))
|
||
|
{
|
||
|
resource = g_object_new (PIKA_TYPE_FONT,
|
||
|
"id", resource_id,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
if (resource)
|
||
|
g_hash_table_insert (plug_in->priv->resources,
|
||
|
GINT_TO_POINTER (resource_id),
|
||
|
g_object_ref (resource) /* add debug ref */);
|
||
|
}
|
||
|
|
||
|
return resource;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_destroy_hashes (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
g_clear_pointer (&plug_in->priv->displays, g_hash_table_unref);
|
||
|
g_clear_pointer (&plug_in->priv->images, g_hash_table_unref);
|
||
|
g_clear_pointer (&plug_in->priv->items, g_hash_table_unref);
|
||
|
g_clear_pointer (&plug_in->priv->resources, g_hash_table_unref);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_destroy_proxies (GHashTable *hash_table,
|
||
|
gboolean destroy_all)
|
||
|
{
|
||
|
GHashTableIter iter;
|
||
|
gpointer key, value;
|
||
|
|
||
|
if (! hash_table)
|
||
|
return;
|
||
|
|
||
|
g_hash_table_iter_init (&iter, hash_table);
|
||
|
|
||
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
||
|
{
|
||
|
GObject *object = value;
|
||
|
|
||
|
if (object->ref_count == 2)
|
||
|
{
|
||
|
/* this is the normal case for an unused proxy */
|
||
|
|
||
|
g_object_unref (object);
|
||
|
g_hash_table_iter_remove (&iter);
|
||
|
}
|
||
|
else if (object->ref_count == 1)
|
||
|
{
|
||
|
/* this is debug code, a plug-in MUST NOT unref a proxy, we
|
||
|
* only catch one unref here, more then one will crash right
|
||
|
* in this function
|
||
|
*/
|
||
|
|
||
|
gint id;
|
||
|
|
||
|
g_object_get (object, "id", &id, NULL);
|
||
|
|
||
|
g_printerr ("%s: ERROR: %s proxy with ID %d was unrefed "
|
||
|
"by plug-in, it MUST NOT do that!\n",
|
||
|
G_STRFUNC, G_OBJECT_TYPE_NAME (object), id);
|
||
|
|
||
|
g_hash_table_iter_remove (&iter);
|
||
|
}
|
||
|
else if (destroy_all)
|
||
|
{
|
||
|
/* this is debug code, a plug-in MUST NOT ref a proxy */
|
||
|
|
||
|
gint id;
|
||
|
|
||
|
g_object_get (object, "id", &id, NULL);
|
||
|
|
||
|
g_printerr ("%s: ERROR: %s proxy with ID %d was refed "
|
||
|
"by plug-in, it MUST NOT do that!\n",
|
||
|
G_STRFUNC, G_OBJECT_TYPE_NAME (object), id);
|
||
|
|
||
|
#if 0
|
||
|
/* the code used to do this, but it appears that some bindings
|
||
|
* keep references around until later, let's keep this for
|
||
|
* reference and as a reminder to figure if we can do anything
|
||
|
* generic about it that works for all bindings.
|
||
|
*/
|
||
|
while (object->ref_count > 1)
|
||
|
g_object_unref (object);
|
||
|
#else
|
||
|
g_object_unref (object);
|
||
|
#endif
|
||
|
g_hash_table_iter_remove (&iter);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_plug_in_init_i18n (PikaPlugIn *plug_in)
|
||
|
{
|
||
|
gchar *rootdir = g_path_get_dirname (pika_get_progname ());
|
||
|
GFile *root_file = g_file_new_for_path (rootdir);
|
||
|
GFile *catalog_file = NULL;
|
||
|
|
||
|
g_return_if_fail (PIKA_IS_PLUG_IN (plug_in));
|
||
|
|
||
|
/* Default domain name is the program directory name. */
|
||
|
g_free (plug_in->priv->translation_domain_name);
|
||
|
plug_in->priv->translation_domain_name = g_path_get_basename (rootdir);
|
||
|
|
||
|
/* Default catalog path is the locale/ directory under the root
|
||
|
* directory.
|
||
|
*/
|
||
|
catalog_file = g_file_resolve_relative_path (root_file, "locale");
|
||
|
g_set_object (&plug_in->priv->translation_domain_path, catalog_file);
|
||
|
|
||
|
g_free (rootdir);
|
||
|
g_object_unref (root_file);
|
||
|
g_object_unref (catalog_file);
|
||
|
}
|