Commit 0e671286 authored by Allison Karlitskaya's avatar Allison Karlitskaya

GApplication: parse command line options

Add support for parsing command line options with GApplication.

You can add GOptionGroup and GOptionEntry using two new APIs:
g_application_add_option_group() and
g_application_add_main_option_entries().

Also add a "handle-local-options" signal that allows handling of
commandline arguments in the local process without having to override
local_command_line.

As a special feature, you can have a %NULL @arg_data in a GOptionEntry
which will cause the argument to be stored in a GVariantDict.  This
dictionary is available for inspection and modification by the
"handle-local-options" signal and can be forwarded to the primary
instance in cases of command line invocation (where it can be fetched
using g_application_command_line_get_options()).

https://bugzilla.gnome.org/show_bug.cgi?id=721977
parent 7f362330
......@@ -3020,6 +3020,9 @@ g_application_send_notification
g_application_withdraw_notification
<SUBSECTION>
g_application_run
g_application_add_main_option_entries
g_application_add_option_group
<SUBSECTION>
g_application_set_default
g_application_get_default
<SUBSECTION>
......
<
......@@ -248,6 +248,12 @@ struct _GApplicationPrivate
GApplicationImpl *impl;
GNotificationBackend *notifications;
/* GOptionContext support */
GOptionGroup *main_options;
GSList *option_groups;
GHashTable *packed_options;
gboolean options_parsed;
};
enum
......@@ -269,6 +275,7 @@ enum
SIGNAL_OPEN,
SIGNAL_ACTION,
SIGNAL_COMMAND_LINE,
SIGNAL_HANDLE_LOCAL_OPTIONS,
NR_SIGNALS
};
......@@ -367,6 +374,352 @@ g_application_exported_actions_new (GApplication *application)
return G_ACTION_GROUP (actions);
}
/* Command line option handling {{{1 */
static void
free_option_entry (gpointer data)
{
GOptionEntry *entry = data;
switch (entry->arg)
{
case G_OPTION_ARG_STRING:
case G_OPTION_ARG_FILENAME:
g_free (*(gchar **) entry->arg_data);
break;
case G_OPTION_ARG_STRING_ARRAY:
case G_OPTION_ARG_FILENAME_ARRAY:
g_strfreev (*(gchar ***) entry->arg_data);
break;
default:
/* most things require no free... */
break;
}
/* ...except for the space that we allocated for it ourselves */
g_free (entry->arg_data);
g_slice_free (GOptionEntry, entry);
}
static void
g_application_pack_option_entries (GApplication *application,
GVariantDict *dict)
{
GHashTableIter iter;
gpointer item;
g_hash_table_iter_init (&iter, application->priv->packed_options);
while (g_hash_table_iter_next (&iter, NULL, &item))
{
GOptionEntry *entry = item;
GVariant *value = NULL;
switch (entry->arg)
{
case G_OPTION_ARG_NONE:
if (*(gboolean *) entry->arg_data != 2)
value = g_variant_new_boolean (*(gboolean *) entry->arg_data);
break;
case G_OPTION_ARG_STRING:
if (*(gchar **) entry->arg_data)
value = g_variant_new_string (*(gchar **) entry->arg_data);
break;
case G_OPTION_ARG_INT:
if (*(gint32 *) entry->arg_data)
value = g_variant_new_int32 (*(gint32 *) entry->arg_data);
break;
case G_OPTION_ARG_FILENAME:
if (*(gchar **) entry->arg_data)
value = g_variant_new_bytestring (*(gchar **) entry->arg_data);
break;
case G_OPTION_ARG_STRING_ARRAY:
if (*(gchar ***) entry->arg_data)
value = g_variant_new_strv (*(const gchar ***) entry->arg_data, -1);
break;
case G_OPTION_ARG_FILENAME_ARRAY:
if (*(gchar ***) entry->arg_data)
value = g_variant_new_bytestring_array (*(const gchar ***) entry->arg_data, -1);
break;
case G_OPTION_ARG_DOUBLE:
if (*(gdouble *) entry->arg_data)
value = g_variant_new_double (*(gdouble *) entry->arg_data);
break;
case G_OPTION_ARG_INT64:
if (*(gint64 *) entry->arg_data)
value = g_variant_new_int64 (*(gint64 *) entry->arg_data);
break;
default:
g_assert_not_reached ();
}
if (value)
g_variant_dict_insert_value (dict, entry->long_name, value);
}
}
static GVariantDict *
g_application_parse_command_line (GApplication *application,
gchar ***arguments,
GError **error)
{
gboolean become_service = FALSE;
GVariantDict *dict = NULL;
GOptionContext *context;
/* Due to the memory management of GOptionGroup we can only parse
* options once. That's because once you add a group to the
* GOptionContext there is no way to get it back again. This is fine:
* local_command_line() should never get invoked more than once
* anyway. Add a sanity check just to be sure.
*/
g_return_val_if_fail (!application->priv->options_parsed, NULL);
context = g_option_context_new (NULL);
/* Add the main option group, if it exists */
if (application->priv->main_options)
{
/* This consumes the main_options */
g_option_context_set_main_group (context, application->priv->main_options);
application->priv->main_options = NULL;
}
/* Add any other option groups if they exist. Adding them to the
* context will consume them, so we free the list as we go...
*/
while (application->priv->option_groups)
{
g_option_context_add_group (context, application->priv->option_groups->data);
application->priv->option_groups = g_slist_delete_link (application->priv->option_groups,
application->priv->option_groups);
}
/* If the application has not registered local options and it has
* G_APPLICATION_HANDLES_COMMAND_LINE then we have to assume that
* their primary instance commandline handler may want to deal with
* the arguments. We must therefore ignore them.
*/
if (application->priv->main_options == NULL && (application->priv->flags & G_APPLICATION_HANDLES_COMMAND_LINE))
g_option_context_set_ignore_unknown_options (context, TRUE);
/* In the case that we are not explicitly marked as a service or a
* launcher then we want to add the "--gapplication-service" option to
* allow the process to be made into a service.
*/
if ((application->priv->flags & (G_APPLICATION_IS_SERVICE | G_APPLICATION_IS_LAUNCHER)) == 0)
{
GOptionGroup *option_group;
GOptionEntry entries[] = {
{ "gapplication-service", '\0', 0, G_OPTION_ARG_NONE, &become_service,
N_("Enter GApplication service mode (use from D-Bus service files)") },
{ NULL }
};
option_group = g_option_group_new ("gapplication",
_("GApplication options"), _("Show GApplication options"),
NULL, NULL);
g_option_group_set_translation_domain (option_group, GETTEXT_PACKAGE);
g_option_group_add_entries (option_group, entries);
g_option_context_add_group (context, option_group);
}
/* Now we parse... */
if (!g_option_context_parse_strv (context, arguments, error))
goto out;
/* Check for --gapplication-service */
if (become_service)
application->priv->flags |= G_APPLICATION_IS_SERVICE;
dict = g_variant_dict_new (NULL);
if (application->priv->packed_options)
{
g_application_pack_option_entries (application, dict);
g_hash_table_unref (application->priv->packed_options);
application->priv->packed_options = NULL;
}
out:
/* Make sure we don't run again */
application->priv->options_parsed = TRUE;
g_option_context_free (context);
return dict;
}
static void
add_packed_option (GApplication *application,
GOptionEntry *entry)
{
switch (entry->arg)
{
case G_OPTION_ARG_NONE:
entry->arg_data = g_new (gboolean, 1);
*(gboolean *) entry->arg_data = 2;
break;
case G_OPTION_ARG_INT:
entry->arg_data = g_new0 (gint, 1);
break;
case G_OPTION_ARG_STRING:
case G_OPTION_ARG_FILENAME:
case G_OPTION_ARG_STRING_ARRAY:
case G_OPTION_ARG_FILENAME_ARRAY:
entry->arg_data = g_new0 (gpointer, 1);
break;
case G_OPTION_ARG_INT64:
entry->arg_data = g_new0 (gint64, 1);
break;
case G_OPTION_ARG_DOUBLE:
entry->arg_data = g_new0 (gdouble, 1);
break;
default:
g_return_if_reached ();
}
if (!application->priv->packed_options)
application->priv->packed_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_option_entry);
g_hash_table_insert (application->priv->packed_options,
g_strdup (entry->long_name),
g_slice_dup (GOptionEntry, entry));
}
/**
* g_application_add_main_option_entries:
* @application: a #GApplication
* @entries: a %NULL-terminated list of #GOptionEntrys
*
* Adds main option entries to be handled by @application.
*
* This function is comparable to g_option_context_add_main_entries().
*
* After the commandline arguments are parsed, the
* #GApplication::handle-local-options signal will be emitted. At this
* point, the application can inspect the values pointed to by @arg_data
* in the given #GOptionEntrys.
*
* Unlike #GOptionContext, #GApplication supports giving a %NULL
* @arg_data for a non-callback #GOptionEntry. This results in the
* argument in question being packed into a #GVariantDict which is also
* passed to #GApplication::handle-local-options, where it can be
* inspected and modified. If %G_APPLICATION_HANDLES_COMMAND_LINE is
* set, then the resulting dictionary is sent to the primary instance,
* where g_application_command_line_get_options_dict() will return it.
* This "packing" is done according to the type of the argument --
* booleans for normal flags, strings for strings, bytestrings for
* filenames, etc. The packing only occurs if the flag is given (ie: we
* do not pack a "false" #GVariant in the case that a flag is missing).
*
* In general, it is recommended that all commandline arguments are
* parsed locally. The options dictionary should then be used to
* transmit the result of the parsing to the primary instance, where
* g_variant_dict_lookup() can be used. For local options, it is
* possible to either use @arg_data in the usual way, or to consult (and
* potentially remove) the option from the options dictionary.
*
* This function is new in GLib 2.40. Before then, the only real choice
* was to send all of the commandline arguments (options and all) to the
* primary instance for handling. #GApplication ignored them completely
* on the local side. Calling this function "opts in" to the new
* behaviour, and in particular, means that unrecognised options will be
* treated as errors. Unrecognised options have never been ignored when
* %G_APPLICATION_HANDLES_COMMAND_LINE is unset.
*
* If #GApplication::handle-local-options needs to see the list of
* filenames, then the use of %G_OPTION_REMAINING is recommended. If
* @arg_data is %NULL then %G_OPTION_REMAINING can be used as a key into
* the options dictionary. If you do use %G_OPTION_REMAINING then you
* need to handle these arguments for yourself because once they are
* consumed, they will no longer be visible to the default handling
* (which treats them as filenames to be opened).
*
* Since: 2.40
*/
void
g_application_add_main_option_entries (GApplication *application,
const GOptionEntry *entries)
{
gint i;
g_return_if_fail (G_IS_APPLICATION (application));
g_return_if_fail (entries != NULL);
if (!application->priv->main_options)
application->priv->main_options = g_option_group_new (NULL, NULL, NULL, NULL, NULL);
for (i = 0; entries[i].long_name; i++)
{
GOptionEntry my_entries[2] = { entries[i], { NULL } };
if (!my_entries[0].arg_data)
add_packed_option (application, &my_entries[0]);
g_option_group_add_entries (application->priv->main_options, my_entries);
}
}
/**
* g_application_add_option_group:
* @application: the #GApplication
* @group: a #GOptionGroup
*
* Adds a #GOptionGroup to the commandline handling of @application.
*
* This function is comparable to g_option_context_add_group().
*
* Unlike g_application_add_main_option_entries(), this function does
* not deal with %NULL @arg_data and never transmits options to the
* primary instance.
*
* The reason for that is because, by the time the options arrive at the
* primary instance, it is typically too late to do anything with them.
* Taking the GTK option group as an example: GTK will already have been
* initialised by the time the #GApplication::command-line handler runs.
* In the case that this is not the first-running instance of the
* application, the existing instance may already have been running for
* a very long time.
*
* This means that the options from #GOptionGroup are only really usable
* in the case that the instance of the application being run is the
* first instance. Passing options like <literal>--display=</literal>
* or <literal>--gdk-debug=</literal> on future runs will have no effect
* on the existing primary instance.
*
* Calling this function will cause the options in the supplied option
* group to be parsed, but it does not cause you to be "opted in" to the
* new functionality whereby unrecognised options are rejected even if
* %G_APPLICATION_HANDLES_COMMAND_LINE was given.
*
* Since: 2.40
**/
void
g_application_add_option_group (GApplication *application,
GOptionGroup *group)
{
g_return_if_fail (G_IS_APPLICATION (application));
g_return_if_fail (group != NULL);
application->priv->option_groups = g_slist_prepend (application->priv->option_groups, group);
}
/* vfunc defaults {{{1 */
static void
g_application_real_before_emit (GApplication *application,
......@@ -459,64 +812,137 @@ g_application_real_command_line (GApplication *application,
return 1;
}
static gint
g_application_real_handle_local_options (GApplication *application,
GVariantDict *options)
{
return -1;
}
static GVariant *
get_platform_data (GApplication *application,
GVariant *options)
{
GVariantBuilder *builder;
GVariant *result;
builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
{
gchar *cwd = g_get_current_dir ();
g_variant_builder_add (builder, "{sv}", "cwd",
g_variant_new_bytestring (cwd));
g_free (cwd);
}
if (application->priv->flags & G_APPLICATION_SEND_ENVIRONMENT)
{
GVariant *array;
gchar **envp;
envp = g_get_environ ();
array = g_variant_new_bytestring_array ((const gchar **) envp, -1);
g_strfreev (envp);
g_variant_builder_add (builder, "{sv}", "environ", array);
}
if (options)
g_variant_builder_add (builder, "{sv}", "options", options);
G_APPLICATION_GET_CLASS (application)->
add_platform_data (application, builder);
result = g_variant_builder_end (builder);
g_variant_builder_unref (builder);
return result;
}
static void
g_application_call_command_line (GApplication *application,
const gchar * const *arguments,
GVariant *options,
gint *exit_status)
{
if (application->priv->is_remote)
{
GVariant *platform_data;
platform_data = get_platform_data (application, options);
*exit_status = g_application_impl_command_line (application->priv->impl, arguments, platform_data);
}
else
{
GApplicationCommandLine *cmdline;
GVariant *v;
v = g_variant_new_bytestring_array ((const gchar **) arguments, -1);
cmdline = g_object_new (G_TYPE_APPLICATION_COMMAND_LINE,
"arguments", v,
"options", options,
NULL);
g_signal_emit (application, g_application_signals[SIGNAL_COMMAND_LINE], 0, cmdline, exit_status);
g_object_unref (cmdline);
}
}
static gboolean
g_application_real_local_command_line (GApplication *application,
gchar ***arguments,
int *exit_status)
{
if ((application->priv->flags & (G_APPLICATION_IS_SERVICE | G_APPLICATION_IS_LAUNCHER)) == 0)
GError *error = NULL;
GVariantDict *options;
gint n_args;
options = g_application_parse_command_line (application, arguments, &error);
if (!options)
{
if ((*arguments)[0] && (*arguments)[1] && g_str_equal ((*arguments)[1], "--gapplication-service"))
{
GError *error = NULL;
g_printerr ("%s\n", error->message);
*exit_status = 1;
return TRUE;
}
if ((*arguments)[2])
g_warning ("Additional arguments following --gapplication-service are ignored");
g_signal_emit (application, g_application_signals[SIGNAL_HANDLE_LOCAL_OPTIONS], 0, options, exit_status);
application->priv->flags |= G_APPLICATION_IS_SERVICE;
if (!g_application_register (application, NULL, &error))
{
g_warning ("%s", error->message);
g_error_free (error);
*exit_status = 1;
}
else
*exit_status = 0;
if (*exit_status >= 0)
{
g_variant_dict_unref (options);
return TRUE;
}
return TRUE;
}
if (!g_application_register (application, NULL, &error))
{
g_printerr ("Failed to register: %s\n", error->message);
g_variant_dict_unref (options);
g_error_free (error);
*exit_status = 1;
return TRUE;
}
if ((application->priv->flags & G_APPLICATION_HANDLES_COMMAND_LINE) &&
!(application->priv->flags & G_APPLICATION_IS_SERVICE))
return FALSE;
n_args = g_strv_length (*arguments);
else
if (application->priv->flags & G_APPLICATION_IS_SERVICE)
{
GError *error = NULL;
gint n_args;
if (!g_application_register (application, NULL, &error))
if ((*exit_status = n_args > 1))
{
g_printerr ("Failed to register: %s\n", error->message);
g_error_free (error);
g_printerr ("GApplication service mode takes no arguments.\n");
application->priv->flags &= ~G_APPLICATION_IS_SERVICE;
*exit_status = 1;
return TRUE;
}
n_args = g_strv_length (*arguments);
if (application->priv->flags & G_APPLICATION_IS_SERVICE)
{
if ((*exit_status = n_args > 1))
{
g_printerr ("GApplication service mode takes no arguments.\n");
application->priv->flags &= ~G_APPLICATION_IS_SERVICE;
}
return TRUE;
}
else
*exit_status = 0;
}
else if (application->priv->flags & G_APPLICATION_HANDLES_COMMAND_LINE)
{
g_application_call_command_line (application,
(const gchar **) *arguments,
g_variant_dict_end (options),
exit_status);
}
else
{
if (n_args <= 1)
{
g_application_activate (application);
......@@ -551,9 +977,11 @@ g_application_real_local_command_line (GApplication *application,
*exit_status = 0;
}
}
return TRUE;
}
g_variant_dict_unref (options);
return TRUE;
}
static void
......@@ -699,6 +1127,12 @@ g_application_finalize (GObject *object)
{
GApplication *application = G_APPLICATION (object);
g_slist_free_full (application->priv->option_groups, (GDestroyNotify) g_option_group_free);
if (application->priv->main_options)
g_option_group_free (application->priv->main_options);
if (application->priv->packed_options)
g_hash_table_unref (application->priv->packed_options);
if (application->priv->impl)
g_application_impl_destroy (application->priv->impl);
g_free (application->priv->id);
......@@ -736,6 +1170,20 @@ g_application_init (GApplication *application)
G_CALLBACK (g_action_group_action_removed), application);
}
static gboolean
g_application_handle_local_options_accumulator (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer dummy)
{
gint value;
value = g_value_get_int (handler_return);
g_value_set_int (return_accu, value);
return value >= 0;
}
static void
g_application_class_init (GApplicationClass *class)
{
......@@ -754,6 +1202,7 @@ g_application_class_init (GApplicationClass *class)
class->open = g_application_real_open;
class->command_line = g_application_real_command_line;
class->local_command_line = g_application_real_local_command_line;
class->handle_local_options = g_application_real_handle_local_options;
class->add_platform_data = g_application_real_add_platform_data;
class->dbus_register = g_application_real_dbus_register;
class->dbus_unregister = g_application_real_dbus_unregister;
......@@ -870,42 +1319,67 @@ g_application_class_init (GApplicationClass *class)
g_signal_accumulator_first_wins, NULL,
NULL,
G_TYPE_INT, 1, G_TYPE_APPLICATION_COMMAND_LINE);
}
static GVariant *
get_platform_data (GApplication *application)
{
GVariantBuilder *builder;
GVariant *result;
builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
{
gchar *cwd = g_get_current_dir ();
g_variant_builder_add (builder, "{sv}", "cwd",
g_variant_new_bytestring (cwd));
g_free (cwd);
}
if (application->priv->flags & G_APPLICATION_SEND_ENVIRONMENT)
{
GVariant *array;
gchar **envp;
envp = g_get_environ ();
array = g_variant_new_bytestring_array ((const gchar **) envp, -1);
g_strfreev (envp);
g_variant_builder_add (builder, "{sv}", "environ", array);
}
G_APPLICATION_GET_CLASS (application)->
add_platform_data (application, builder);
result = g_variant_builder_end (builder);
g_variant_builder_unref (builder);
/**
* GApplication::handle-local-options:
* @application: the application
* @options: the options dictionary
*
* The ::handle-local-options signal is emitted on the local instance
* after the parsing of the commandline options has occurred.
*
* You can add options to be recognised during commandline option
* parsing using g_application_add_main_option_entries() and
* g_application_add_option_group().
*
* Signal handlers can inspect @options (along with values pointed to
* from the @arg_data of an installed #GOptionEntrys) in order to
* decide to perform certain actions, including direct local handling
* (which may be useful for options like --version).
*
* If the options have been "handled" then a non-negative value should
* be returned. In this case, the return value is the exit status: 0
* for success and a positive value for failure. -1 means to continue
* normal processing.
*
* In the event that the application is marked
* %G_APPLICATION_HANDLES_COMMAND_LINE the "normal processing" will
* send the @option dictionary to the primary instance where it can be
* read with g_application_command_line_get_options(). The signal
* handler can modify the dictionary before returning, and the
* modified dictionary will be sent.
*
* In the event that %G_APPLICATION_HANDLES_COMMAND_LINE is not set,
* "normal processing" will treat the remaining uncollected command
* line arguments as filenames or URIs. If there are no arguments,
* the application is activated by g_application_activate(). One or
* more arguments results in a call to g_application_open().
*
* If you want to handle the local commandline arguments for yourself
* by converting them to calls to g_application_open() or
* g_action_group_activate_action() then you must be sure to register
* the application first. You should probably not call
* g_application_activate() for yourself, however: just return -1 and
* allow the default handler to do it for you. This will ensure that
* the <literal>--gapplication-service</literal> switch works properly
* (ie: no activation in that case).
*
* Note that this signal is emitted from the default implementation of
* local_command_line(). If you override that function and don't
* chain up then this signal will never be emitted.
*
* You can override local_command_line() if you need more powerful
* capabilities than what is provided here, but this should not
* normally be required.
*
* Since: 2.40
**/
g_application_signals[SIGNAL_HANDLE_LOCAL_OPTIONS] =
g_signal_new ("handle-local-options", G_TYPE_APPLICATION, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GApplicationClass, handle_local_options),
g_application_handle_local_options_accumulator, NULL, NULL,
G_TYPE_INT, 1, G_TYPE_VARIANT_DICT);
return result;
}
/* Application ID validity {{{1 */
......@@ -1441,7 +1915,7 @@ g_application_activate (GApplication *application)
if (application->priv->is_remote)
g_application_impl_activate (application->priv->impl,
get_platform_data (application));
get_platform_data (application, NULL));
else
g_signal_emit (application, g_application_signals[SIGNAL_ACTIVATE], 0);
......@@ -1485,7 +1959,7 @@ g_application_open (GApplication *application,
if (application->priv->is_remote)
g_application_impl_open (application->priv->impl,
files, n_files, hint,
get_platform_data (application));
get_platform_data (application, NULL));
else
g_signal_emit (application, g_application_signals[SIGNAL_OPEN],
......@@ -1509,49 +1983,28 @@ g_application_open (GApplication *application,
* g_win32_get_command_line() is called internally (for proper support
* of Unicode commandline arguments).
*
* First, the local_command_line() virtual function is invoked.
* This function always runs on the local instance. It gets passed a pointer
* to a %NULL-terminated copy of the command line and is expected to
* remove the arguments that it handled (shifting up remaining
* arguments). See <xref linkend="gapplication-example-cmdline2"/> for
* an example of parsing @argv manually. Alternatively, you may use the
* #GOptionContext API, but you must use g_option_context_parse_strv()
* in order to avoid memory leaks and encoding mismatches.