Handling command line options in GApplication

Background

In GApplication it's possible that the instance of the application that is started from the command line is not the instance that will ultimately respond to the request. This is due to application uniqueness: if the application is already running, the request must be forwarded. In the case that the application has been split into a launcher and service then this forwarding happens every time. See HowDoI/DBusApplicationLaunching about that.

In the following discussion, when speaking about local command line handling, we will talk about the local instance which is the instance that was directly invoked from the command line and the primary instance which is the one that will respond to the request. We use these terms even in the case that they are the same process (ie: in the case that the application is not split into service/launcher and was not already running). When speaking of processing performed on the primary instance, we will use the term remote instance to refer to the process that was executed via the command line, even in the case that this is the same process as the primary instance.

Default processing

If you do not handle the handle-local-options signal and do not set the G_APPLICATION_HANDLES_COMMAND_LINE flag then GApplication processes arguments in a way that is reasonable for most applications:

  • an invocation with no command line arguments results in the application being activated (ie: the 'activate' signal is emitted or your 'activate' method is called)
  • an invocation with one or more filenames or URIs on the command line will result in the 'open' signal being emitted (or 'open' method being called)
  • if --gapplication-service is given as the sole command line argument, GApplication will set the G_APPLICATION_IS_SERVICE flag and run in service mode. See HowDoI/DBusApplicationLaunching for information about that.

  • if the application enables it, --gapplication-app-id can be used to override the app id

  • if using GtkApplication then Gtk-specific commandline arguments will be handled in the usual way

  • --help is supported

  • all other commandline flags are rejected

Adding custom commandline options

GApplication supports parsing of additional commandline options if they are specified using g_application_add_main_option_entries(). The ideal place to call this is from the _init() function of your application or (if not subclassing), after g_application_new() has returned.

g_application_add_main_option_entries() takes a pointer to an array of GOptionEntry structures. When a particular commandline option is encountered, the arg_data field of the corresponding GOptionEntry is set to the result of parsing this option. If arg_data is NULL then the option will be stored in the options dictionary that is passed to the handle-local-options signal handler (or virtual function).

You can also specify additional commandline options with g_application_add_main_option() or g_application_add_option_group().

The handle-local-options handler is expected to handle the commandline options. There are a number of things that can be done from this handler:

  • handle an option locally and exit (either with success or error status), The typical example for this is --version

  • treat an option as a request to perform an action on the primary instance
  • treat an option as a request to open one or more files on the primary instance
  • inspect the options, find them uninteresting, and resume normal processing

The return value of the handle-local-options handler will determine what GApplication does next. If the return value is -1 then the default processing proceeds (see above). If a non-negative value is returned then this is taken to mean that the options have been handled locally and the process should exit (with the returned value as the exit status).

Using G_APPLICATION_HANDLES_COMMAND_LINE

Normally, when the application is launched for the second time, the communication of the local instance and the primary instance is short and simple -- usually just a request to show a new window or open some files. The local instance typically exits immediately.

G_APPLICATION_HANDLES_COMMAND_LINE allows for a more complex interaction between the two sides. If in doubt, you should not use G_APPLICATION_HANDLES_COMMAND_LINE. There are a number of situations under which its use may be necessary:

  • your application needs to print data from the primary instance to the stdout/stderr of the terminal of the remote instance
  • the primary instance of your application needs to control the duration of the remote instance
  • the primary instance of your application needs to return a particular exit status from the remote instance
  • the primary instance of your application needs access to the environment variables or file descriptors from the remote instance (such as stdin)
  • you may find it more convenient to pass pre-parsed commandline options to the primary instance in this way (although action parameters should provide a sufficiently convenient method of accomplishing the same thing with less overhead)
  • when porting your application to GApplication you find that it is easier to proceed in this way temporarily

A good example of this type of application is a text editor that might be used from the $EDITOR environment variable. If invoked from git commit, the remote instance must not exit until after the user is done editing the file but it still needs to exit even if the user has opened other windows and still has them open.

If G_APPLICATION_HANDLES_COMMAND_LINE is set then after the handle-local-options handler returns then instead of interpreting the remaining commandline arguments as a list of files, the arguments are passed to the primary instance via the command-line signal. The options array, as constructed during parsing of the commandline options and possibly modified from handle-local-options, is passed along to the primary instance, where it can be accessed using g_application_command_line_get_options_dict().

It is possible to have all processing done from the primary instance (by using GOptionContext inside of the command-line handler) but this is strongly discouraged. GOptionContext is very much designed around the assumption that it will only ever be run once, on argc and argv, and this is not well matched with the fact that command-line could be invoked multiple times. Additionally, it is more elegant to report errors in the commandline parsing directly from the local instance, without communication with the primary instance. Finally, it is better to have the options registered with the local instance is that the --help output will list them. All of that said, if you do not use g_application_add_main_option_entries() and you have set G_APPLICATION_HANDLES_COMMAND_LINE then any unknown options will be ignored and forwarded to the command-line signal on the primary instance.

Example Program

The following example is a little long, because it does not only technically show how to use command line options, but also how to structure your code while doing so.

#include <gtk/gtk.h>

/*************************************************
 *
 *   Could go in file: cmdparams.h
 *
 *************************************************/

/**
 * Struct for options
 **/
typedef struct {
  gboolean switch_option;
} AppOptions;


/*************************************************
 *
 *   Could go in file: cmdparams.c
 *
 *************************************************/

void
g_application_init_cmd_parameters(GApplication *app, AppOptions *options)
{
  const GOptionEntry cmd_params[] =
  {
    {
      .long_name = "my_switch_option",
      .short_name = 'm',
      .flags = G_OPTION_FLAG_NONE,     // see `GOptionFlags`
      .arg = G_OPTION_ARG_NONE,        // type of option (see `GOptionArg`)
      .arg_data = &(options->switch_option),// store data here
      .description = "<my description>",
      .arg_description = NULL,
    },
    {NULL}
  };

  g_application_add_main_option_entries (G_APPLICATION (app), cmd_params);
}



/*************************************************
 *
 *   Could go in file: myapplication.c
 *
 *************************************************/

// Application options are global here. In production they
//  might be a member of of your GtkApplication subclass.
//   (which is a lot of work, though)
static AppOptions app_options;

/**
 * Here is, where the application actually starts.
 **/
void
init_application(GtkApplication *app,
                 AppOptions     *p_options)
{
  // example use of options
  if (p_options->switch_option == TRUE)
    g_print ("Switch is ON\n");
  else
    g_print ("Switch is OFF\n");


  // Do stuff like creating window, ...
}


GtkApplication *
my_application_new()
{
  GtkApplication * app = gtk_application_new (NULL, G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (init_application), &app_options);

  // init command line options
  g_application_init_cmd_parameters (G_APPLICATION (app), &app_options);

  return app;
}


/*************************************************
 *
 *   Could go in file: main.c
 *
 *************************************************/

int
main(int argc, char *argv[])
{
  GtkApplication * app;
  int status;

  app = my_application_new ();
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
  return status;
}

HowDoI/GtkApplication/CommandLine (last edited 2021-04-13 10:53:42 by DarkTrick)