diff --git a/docs/reference/gio/gio-sections-common.txt b/docs/reference/gio/gio-sections-common.txt index 2f3973956..7fe47f659 100644 --- a/docs/reference/gio/gio-sections-common.txt +++ b/docs/reference/gio/gio-sections-common.txt @@ -3368,6 +3368,8 @@ g_application_command_line_print g_application_command_line_printerr g_application_command_line_print_literal g_application_command_line_printerr_literal + +g_application_command_line_done G_TYPE_APPLICATION_COMMAND_LINE G_APPLICATION_COMMAND_LINE diff --git a/gio/gapplication.c b/gio/gapplication.c index 7465c9a54..602b265f1 100644 --- a/gio/gapplication.c +++ b/gio/gapplication.c @@ -1076,13 +1076,19 @@ g_application_call_command_line (GApplication *application, { GApplicationCommandLine *cmdline; GVariant *v; + gint handler_exit_status; 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_signal_emit (application, g_application_signals[SIGNAL_COMMAND_LINE], 0, cmdline, &handler_exit_status); + + /* For consistency with remote invocations */ + g_application_command_line_set_exit_status (cmdline, handler_exit_status); + *exit_status = g_application_command_line_get_exit_status (cmdline); + g_object_unref (cmdline); } } diff --git a/gio/gapplicationcommandline.c b/gio/gapplicationcommandline.c index 324f54ef6..75ad912f7 100644 --- a/gio/gapplicationcommandline.c +++ b/gio/gapplicationcommandline.c @@ -61,10 +61,11 @@ * for an example. * * The exit status of the originally-invoked process may be set and - * messages can be printed to stdout or stderr of that process. The - * life-cycle of the originally-invoked process is tied to the lifecycle - * of this object (ie: the process exits when the last reference is - * dropped). + * messages can be printed to stdout or stderr of that process. + * + * For remote invocation, the originally-invoked process exits when + * [method@Gio.ApplicationCommandLine.done] method is called. This method is + * also automatically called when the object is disposed. * * The main use for `GApplicationCommandLine` (and the * [signal@Gio.Application::command-line] signal) is 'Emacs server' like use cases: @@ -238,6 +239,7 @@ struct _GApplicationCommandLinePrivate gchar **environ; gint exit_status; + gboolean done; }; G_DEFINE_TYPE_WITH_PRIVATE (GApplicationCommandLine, g_application_command_line, G_TYPE_OBJECT) @@ -300,6 +302,11 @@ g_application_command_line_real_get_stdin (GApplicationCommandLine *cmdline) #endif } +static void +g_application_command_line_real_done (GApplicationCommandLine *cmdline) +{ +} + static void g_application_command_line_get_property (GObject *object, guint prop_id, @@ -359,6 +366,16 @@ g_application_command_line_set_property (GObject *object, } } +static void +g_application_command_line_dispose (GObject *object) +{ + GApplicationCommandLine *cmdline = G_APPLICATION_COMMAND_LINE (object); + + g_application_command_line_done (cmdline); + + G_OBJECT_CLASS (g_application_command_line_parent_class)->dispose (object); +} + static void g_application_command_line_finalize (GObject *object) { @@ -412,12 +429,15 @@ g_application_command_line_class_init (GApplicationCommandLineClass *class) object_class->get_property = g_application_command_line_get_property; object_class->set_property = g_application_command_line_set_property; object_class->finalize = g_application_command_line_finalize; + object_class->dispose = g_application_command_line_dispose; object_class->constructed = g_application_command_line_constructed; class->printerr_literal = g_application_command_line_real_printerr_literal; class->print_literal = g_application_command_line_real_print_literal; class->get_stdin = g_application_command_line_real_get_stdin; + class->done = g_application_command_line_real_done; + g_object_class_install_property (object_class, PROP_ARGUMENTS, g_param_spec_variant ("arguments", P_("Commandline arguments"), @@ -799,6 +819,9 @@ g_application_command_line_printerr (GApplicationCommandLine *cmdline, * always zero. If the application use count is zero, though, the exit * status of the local #GApplicationCommandLine is used. * + * This method is a no-op if g_application_command_line_done() has + * been called. + * * Since: 2.28 **/ void @@ -807,6 +830,9 @@ g_application_command_line_set_exit_status (GApplicationCommandLine *cmdline, { g_return_if_fail (G_IS_APPLICATION_COMMAND_LINE (cmdline)); + if (cmdline->priv->done) + return; + cmdline->priv->exit_status = exit_status; } @@ -890,3 +916,38 @@ g_application_command_line_create_file_for_arg (GApplicationCommandLine *cmdline return g_file_new_for_commandline_arg (arg); } + +/** + * g_application_command_line_done: + * @cmdline: a #GApplicationCommandLine + * + * Signals that command line processing is completed. + * + * For remote invocation, it causes the invoking process to terminate. + * + * For local invocation, it does nothing. + * + * This method should be called in the [signal@Gio.Application::command-line] + * handler, after the exit status is set and all messages are printed. + * + * After this call, g_application_command_line_set_exit_status() has no effect. + * Subsequent calls to this method are no-ops. + * + * This method is automatically called when the #GApplicationCommandLine + * object is disposed — so you can omit the call in non-garbage collected + * languages. + * + * Since: 2.80 + **/ +void +g_application_command_line_done (GApplicationCommandLine *cmdline) +{ + g_return_if_fail (G_IS_APPLICATION_COMMAND_LINE (cmdline)); + + if (cmdline->priv->done) + return; + + G_APPLICATION_COMMAND_LINE_GET_CLASS (cmdline)->done (cmdline); + + cmdline->priv->done = TRUE; +} diff --git a/gio/gapplicationcommandline.h b/gio/gapplicationcommandline.h index b6fdcd893..51133c4e9 100644 --- a/gio/gapplicationcommandline.h +++ b/gio/gapplicationcommandline.h @@ -67,8 +67,9 @@ struct _GApplicationCommandLineClass void (* printerr_literal) (GApplicationCommandLine *cmdline, const gchar *message); GInputStream * (* get_stdin) (GApplicationCommandLine *cmdline); + void (* done) (GApplicationCommandLine *cmdline); - gpointer padding[11]; + gpointer padding[10]; }; GIO_AVAILABLE_IN_ALL @@ -126,6 +127,9 @@ GIO_AVAILABLE_IN_2_36 GFile * g_application_command_line_create_file_for_arg (GApplicationCommandLine *cmdline, const gchar *arg); +GIO_AVAILABLE_IN_2_80 +void g_application_command_line_done (GApplicationCommandLine *cmdline); + G_END_DECLS #endif /* __G_APPLICATION_COMMAND_LINE_H__ */ diff --git a/gio/gapplicationimpl-dbus.c b/gio/gapplicationimpl-dbus.c index ac6644d88..f9bf28c40 100644 --- a/gio/gapplicationimpl-dbus.c +++ b/gio/gapplicationimpl-dbus.c @@ -970,18 +970,26 @@ g_dbus_command_line_get_stdin (GApplicationCommandLine *cmdline) static void g_dbus_command_line_finalize (GObject *object) { - GApplicationCommandLine *cmdline = G_APPLICATION_COMMAND_LINE (object); GDBusCommandLine *gdbcl = (GDBusCommandLine *) object; + + g_object_unref (gdbcl->invocation); + + G_OBJECT_CLASS (g_dbus_command_line_parent_class) + ->finalize (object); +} + +static void +g_dbus_command_line_done (GApplicationCommandLine *cmdline) +{ + GDBusCommandLine *gdbcl = (GDBusCommandLine *) cmdline; gint status; status = g_application_command_line_get_exit_status (cmdline); g_dbus_method_invocation_return_value (gdbcl->invocation, g_variant_new ("(i)", status)); - g_object_unref (gdbcl->invocation); - G_OBJECT_CLASS (g_dbus_command_line_parent_class) - ->finalize (object); + G_APPLICATION_COMMAND_LINE_CLASS (g_dbus_command_line_parent_class)->done (cmdline); } static void @@ -998,6 +1006,7 @@ g_dbus_command_line_class_init (GApplicationCommandLineClass *class) class->printerr_literal = g_dbus_command_line_printerr_literal; class->print_literal = g_dbus_command_line_print_literal; class->get_stdin = g_dbus_command_line_get_stdin; + class->done = g_dbus_command_line_done; } static GApplicationCommandLine * diff --git a/gio/tests/application-command-line.c b/gio/tests/application-command-line.c index 185d49f58..6104cb610 100644 --- a/gio/tests/application-command-line.c +++ b/gio/tests/application-command-line.c @@ -76,6 +76,15 @@ test_basic_properties (void) g_object_get (cl, "is-remote", &is_remote, NULL); g_assert_false (is_remote); + /* exit status */ + g_assert_cmpint (g_application_command_line_get_exit_status (cl), ==, 0); + g_application_command_line_set_exit_status (cl, 1); + g_assert_cmpint (g_application_command_line_get_exit_status (cl), ==, 1); + + g_application_command_line_done (cl); + g_application_command_line_set_exit_status (cl, 2); + g_assert_cmpint (g_application_command_line_get_exit_status (cl), ==, 1); + g_clear_object (&cl); } diff --git a/gio/tests/gapplication.c b/gio/tests/gapplication.c index b0584eb5f..29c69896f 100644 --- a/gio/tests/gapplication.c +++ b/gio/tests/gapplication.c @@ -832,6 +832,47 @@ test_help (void) g_test_trap_assert_stdout ("*Application options*"); } +static gint +command_line_done_callback (GApplication *app, + GApplicationCommandLine *command_line, + gpointer user_data) +{ + gboolean *called = user_data; + + *called = TRUE; + + g_application_command_line_set_exit_status (command_line, 42); + g_application_command_line_done (command_line); + + return 0; +} + +/* Test whether 'command-line' handler return value is ignored + * after g_application_command_line_done() + */ +static void +test_command_line_done (void) +{ + char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); + const gchar *const argv[] = { binpath, "arg", NULL }; + GApplication *app; + gboolean called = FALSE; + int status; + gulong command_line_id; + + app = g_application_new ("org.gtk.TestApplication", G_APPLICATION_HANDLES_COMMAND_LINE); + command_line_id = g_signal_connect (app, "command-line", G_CALLBACK (command_line_done_callback), &called); + + status = g_application_run (app, G_N_ELEMENTS (argv) - 1, (gchar **) argv); + + g_signal_handler_disconnect (app, command_line_id); + g_object_unref (app); + g_free (binpath); + + g_assert_true (called); + g_assert_cmpint (status, ==, 42); +} + static void test_busy (void) { @@ -1242,8 +1283,7 @@ dbus_startup_reply_cb (GObject *source_object, reply = g_dbus_connection_send_message_with_reply_finish (connection, result, &local_error); g_assert_no_error (local_error); - /* Nothing to check on the reply for now. */ - g_clear_object (&reply); + g_object_set_data_full (G_OBJECT (app), "dbus-command-line-reply", g_steal_pointer (&reply), g_object_unref); /* Release the app in an idle callback, so there’s time to process other * pending sources first. */ @@ -1563,6 +1603,80 @@ test_dbus_command_line (void) g_clear_object (&bus); } +static gint +dbus_command_line_done_cb (GApplication *app, + GApplicationCommandLine *command_line, + gpointer user_data) +{ + guint *n_command_lines = user_data; + + *n_command_lines = *n_command_lines + 1; + + if (*n_command_lines == 1) + return 0; + + g_object_set_data_full (G_OBJECT (app), "command-line", g_object_ref (command_line), g_object_unref); + + g_application_command_line_set_exit_status (command_line, 42); + g_application_command_line_done (command_line); + + return 1; /* ignored - after g_application_command_line_done () */ +} + +static void +test_dbus_command_line_done (void) +{ + GTestDBus *bus = NULL; + GVariantBuilder builder; + GDBusMessage *message = NULL; + GDBusMessage *reply = NULL; + GApplication *app = NULL; + guint n_command_lines = 0; + gint exit_status; + + g_test_summary ("Test that GDBusCommandLine.done() works"); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("aay")); + g_variant_builder_add (&builder, "^ay", "test-program"); + g_variant_builder_add (&builder, "^ay", "/path/to/something"); + + message = g_dbus_message_new_method_call ("org.gtk.TestApplication.CommandLine", + "/org/gtk/TestApplication/CommandLine", + "org.gtk.Application", + "CommandLine"); + g_dbus_message_set_body (message, g_variant_new ("(oaaya{sv})", + "/my/org/gtk/private/CommandLine", + &builder, NULL)); + + bus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (bus); + + app = g_application_new ("org.gtk.TestApplication.CommandLine", G_APPLICATION_HANDLES_COMMAND_LINE); + g_signal_connect (app, "activate", G_CALLBACK (dbus_activate_noop_cb), NULL); + g_signal_connect (app, "command-line", G_CALLBACK (dbus_command_line_done_cb), &n_command_lines); + g_signal_connect (app, "startup", G_CALLBACK (dbus_startup_cb), message); + + g_application_hold (app); + exit_status = g_application_run (app, 0, NULL); + + g_assert_cmpuint (n_command_lines, ==, 2); + g_assert_cmpint (exit_status, ==, 0); + + reply = g_object_get_data (G_OBJECT (app), "dbus-command-line-reply"); + g_variant_get (g_dbus_message_get_body (reply), "(i)", &exit_status); + g_assert_cmpint (exit_status, ==, 42); + + g_signal_handlers_disconnect_by_func (app, G_CALLBACK (dbus_activate_noop_cb), NULL); + g_signal_handlers_disconnect_by_func (app, G_CALLBACK (dbus_command_line_done_cb), &n_command_lines); + g_signal_handlers_disconnect_by_func (app, G_CALLBACK (dbus_startup_cb), message); + + g_clear_object (&app); + g_clear_object (&message); + + g_test_dbus_down (bus); + g_clear_object (&bus); +} + static void dbus_activate_action_cb (GSimpleAction *action, GVariant *parameter, @@ -1710,6 +1824,7 @@ main (int argc, char **argv) /* g_test_add_func ("/gapplication/remote-command-line", test_remote_command_line); */ g_test_add_func ("/gapplication/resource-path", test_resource_path); g_test_add_func ("/gapplication/test-help", test_help); + g_test_add_func ("/gapplication/command-line-done", test_command_line_done); g_test_add_func ("/gapplication/test-busy", test_busy); g_test_add_func ("/gapplication/test-handle-local-options1", test_handle_local_options_success); g_test_add_func ("/gapplication/test-handle-local-options2", test_handle_local_options_failure); @@ -1720,6 +1835,7 @@ main (int argc, char **argv) g_test_add_func ("/gapplication/dbus/activate", test_dbus_activate); g_test_add_func ("/gapplication/dbus/open", test_dbus_open); g_test_add_func ("/gapplication/dbus/command-line", test_dbus_command_line); + g_test_add_func ("/gapplication/dbus/command-line-done", test_dbus_command_line_done); g_test_add_func ("/gapplication/dbus/activate-action", test_dbus_activate_action); return g_test_run ();