X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=bus%2Fat-spi-bus-launcher.c;h=d180a1562f2c645cb0c43fb4adcdf6812a0a3fb3;hb=3627a21a62fe61592d92968e3dc67a78fde63bf0;hp=930097930e85b7142ae94f28a5f1dd7f92a37276;hpb=f0b979c8d448ff45c93afb9670f7decf6a2e3a04;p=platform%2Fupstream%2Fat-spi2-core.git diff --git a/bus/at-spi-bus-launcher.c b/bus/at-spi-bus-launcher.c index 9300979..d180a15 100644 --- a/bus/at-spi-bus-launcher.c +++ b/bus/at-spi-bus-launcher.c @@ -1,6 +1,6 @@ /* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- - * - * at-spi-bus-launcher: Manage the a11y bus as a child process + * + * at-spi-bus-launcher: Manage the a11y bus as a child process * * Copyright 2011 Red Hat, Inc. * @@ -27,10 +27,48 @@ #include #include #include +#include #include +#ifdef HAVE_X11 #include #include +#endif + +//TODO: move to vconf/vconf-internal-setting-keys.h? +#define VCONFKEY_SETAPPL_ACCESSIBILITY_UNIVERSAL_SWITCH "db/setting/accessibility/universal-switch" + +#define APP_CONTROL_OPERATION_SCREEN_READ "http://tizen.org/appcontrol/operation/read_screen" +#define APP_CONTROL_OPERATION_UNIVERSAL_SWITCH "http://tizen.org/appcontrol/operation/universal_switch" +#include +#include + +//uncomment if you want debug +//#ifndef TIZEN_ENGINEER_MODE +//#define TIZEN_ENGINEER_MODE +//#endif +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#define LOG_TAG "ATSPI_BUS_LAUNCHER" + +#include +#include + +//uncomment this if you want log suring startup +//seems like dlog is not working at startup time +#define ATSPI_BUS_LAUNCHER_LOG_TO_FILE + +#ifdef ATSPI_BUS_LAUNCHER_LOG_TO_FILE +FILE *log_file; +#ifdef LOGD +#undef LOGD +#endif +#define LOGD(arg...) do {if (log_file) {fprintf(log_file, ##arg);fprintf(log_file, "\n"); fflush(log_file);}} while(0) +#endif + +static gboolean _launch_process_repeat_until_success(gpointer user_data); typedef enum { A11Y_BUS_STATE_IDLE = 0, @@ -40,11 +78,28 @@ typedef enum { } A11yBusState; typedef struct { + const char * name; + const char * app_control_operation; + const char * vconf_key; + int launch_repeats; + int pid; +} A11yBusClient; + +typedef struct { GMainLoop *loop; gboolean launch_immediately; + gboolean a11y_enabled; + gboolean screen_reader_enabled; + GHashTable *client_watcher_id; GDBusConnection *session_bus; + GSettings *a11y_schema; + GSettings *interface_schema; + + A11yBusClient screen_reader; + A11yBusClient universal_switch; A11yBusState state; + /* -1 == error, 0 == pending, > 0 == running */ int a11y_bus_pid; char *a11y_bus_address; @@ -61,6 +116,10 @@ static const gchar introspection_xml[] = " " " " " " + "" + "" + "" + "" ""; static GDBusNodeInfo *introspection_data = NULL; @@ -78,7 +137,7 @@ setup_bus_child (gpointer data) #ifdef __linux #include prctl (PR_SET_PDEATHSIG, 15); -#endif +#endif } /** @@ -110,7 +169,7 @@ on_bus_exited (GPid pid, gpointer data) { A11yBusLauncher *app = data; - + app->a11y_bus_pid = -1; app->state = A11Y_BUS_STATE_ERROR; if (app->a11y_launch_error_message == NULL) @@ -123,9 +182,9 @@ on_bus_exited (GPid pid, app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status)); } g_main_loop_quit (app->loop); -} +} -static void +static gboolean ensure_a11y_bus (A11yBusLauncher *app) { GPid pid; @@ -134,13 +193,13 @@ ensure_a11y_bus (A11yBusLauncher *app) GError *error = NULL; if (app->a11y_bus_pid != 0) - return; - + return FALSE; + argv[1] = g_strdup_printf ("--config-file=%s/at-spi2/accessibility.conf", SYSCONFDIR); if (pipe (app->pipefd) < 0) g_error ("Failed to create pipe: %s", strerror (errno)); - + if (!g_spawn_async (NULL, argv, NULL, @@ -163,7 +222,7 @@ ensure_a11y_bus (A11yBusLauncher *app) app->state = A11Y_BUS_STATE_READING_ADDRESS; app->a11y_bus_pid = pid; - g_debug ("Launched a11y bus, child is %ld", (long) pid); + LOGD("Launched a11y bus, child is %ld", (long) pid); if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf))) { app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno)); @@ -176,8 +235,9 @@ ensure_a11y_bus (A11yBusLauncher *app) /* Trim the trailing newline */ app->a11y_bus_address = g_strchomp (g_strdup (addr_buf)); - g_debug ("a11y bus address: %s", app->a11y_bus_address); + LOGD("a11y bus address: %s", app->a11y_bus_address); +#ifdef HAVE_X11 { Display *display = XOpenDisplay (NULL); if (display) @@ -188,17 +248,23 @@ ensure_a11y_bus (A11yBusLauncher *app) bus_address_atom, XA_STRING, 8, PropModeReplace, (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address)); + XFlush (display); + XCloseDisplay (display); } - XFlush (display); - XCloseDisplay (display); } +#endif + + if (argv[1]) g_free(argv[1]); + + return TRUE; - return; - error: + if (argv[1]) g_free(argv[1]); close (app->pipefd[0]); close (app->pipefd[1]); app->state = A11Y_BUS_STATE_ERROR; + + return FALSE; } static void @@ -226,13 +292,208 @@ handle_method_call (GDBusConnection *connection, } } -static const GDBusInterfaceVTable interface_vtable = +static GVariant * +handle_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + A11yBusLauncher *app = user_data; + + if (g_strcmp0 (property_name, "IsEnabled") == 0) + return g_variant_new ("b", app->a11y_enabled); + else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0) + return g_variant_new ("b", app->screen_reader_enabled); + else + return NULL; +} + +static void +handle_a11y_enabled_change (A11yBusLauncher *app, gboolean enabled, + gboolean notify_gsettings) +{ + GVariantBuilder builder; + GVariantBuilder invalidated_builder; + + if (enabled == app->a11y_enabled) + return; + + app->a11y_enabled = enabled; + + if (notify_gsettings && app->interface_schema) + { + g_settings_set_boolean (app->interface_schema, "toolkit-accessibility", + enabled); + g_settings_sync (); + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as")); + g_variant_builder_add (&builder, "{sv}", "IsEnabled", + g_variant_new_boolean (enabled)); + + g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new ("(sa{sv}as)", "org.a11y.Status", + &builder, + &invalidated_builder), + NULL); + g_variant_builder_clear (&builder); + g_variant_builder_clear (&invalidated_builder); +} + +static void +handle_screen_reader_enabled_change (A11yBusLauncher *app, gboolean enabled, + gboolean notify_gsettings) +{ + GVariantBuilder builder; + GVariantBuilder invalidated_builder; + + if (enabled == app->screen_reader_enabled) + return; + + app->screen_reader_enabled = enabled; + + if (notify_gsettings && app->a11y_schema) + { + g_settings_set_boolean (app->a11y_schema, "screen-reader-enabled", + enabled); + g_settings_sync (); + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as")); + g_variant_builder_add (&builder, "{sv}", "ScreenReaderEnabled", + g_variant_new_boolean (enabled)); + + g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new ("(sa{sv}as)", "org.a11y.Status", + &builder, + &invalidated_builder), + NULL); + g_variant_builder_clear (&builder); + g_variant_builder_clear (&invalidated_builder); +} + +static gboolean +is_client_connected(A11yBusLauncher *app) +{ + guint watchers = g_hash_table_size(app->client_watcher_id); + LOGD("clients connected: %d", watchers); + return watchers > 0; +} + +static void +remove_client_watch(A11yBusLauncher *app, + const gchar *sender) +{ + LOGD("Remove client watcher for %s", sender); + guint watcher_id = GPOINTER_TO_UINT(g_hash_table_lookup(app->client_watcher_id, sender)); + if (watcher_id) + g_bus_unwatch_name(watcher_id); + + g_hash_table_remove(app->client_watcher_id, sender); + if (!is_client_connected(app)) + handle_a11y_enabled_change (app, FALSE, TRUE); +} + +static void +on_client_name_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + A11yBusLauncher *app = user_data; + remove_client_watch(app, name); +} + +static void +add_client_watch(A11yBusLauncher *app, + const gchar *sender) +{ + LOGD("Add client watcher for %s", sender); + + if (g_hash_table_contains(app->client_watcher_id, sender)) + { + LOGI("Watcher for %s already registered", sender); + return; + } + + guint watcher_id = g_bus_watch_name(G_BUS_TYPE_SESSION, + sender, + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, + on_client_name_vanished, + app, + NULL); + + g_hash_table_insert(app->client_watcher_id, g_strdup(sender), GUINT_TO_POINTER(watcher_id)); + handle_a11y_enabled_change (app, TRUE, TRUE); +} + +static gboolean +handle_set_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data) +{ + A11yBusLauncher *app = user_data; + const gchar *type = g_variant_get_type_string (value); + gboolean enabled; + + if (g_strcmp0 (type, "b") != 0) + { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "org.a11y.Status.%s expects a boolean but got %s", property_name, type); + return FALSE; + } + + enabled = g_variant_get_boolean (value); + + if (g_strcmp0 (property_name, "IsEnabled") == 0) + { + if (enabled) + add_client_watch(app, sender); + else + remove_client_watch(app, sender); + return TRUE; + } + else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0) + { + handle_screen_reader_enabled_change (app, enabled, TRUE); + return TRUE; + } + else + { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "Unknown property '%s'", property_name); + return FALSE; + } +} + +static const GDBusInterfaceVTable bus_vtable = { handle_method_call, - NULL, + NULL, /* handle_get_property, */ NULL /* handle_set_property */ }; +static const GDBusInterfaceVTable status_vtable = +{ + NULL, /* handle_method_call */ + handle_get_property, + handle_set_property +}; + static void on_bus_acquired (GDBusConnection *connection, const gchar *name, @@ -241,7 +502,7 @@ on_bus_acquired (GDBusConnection *connection, A11yBusLauncher *app = user_data; GError *error; guint registration_id; - + if (connection == NULL) { g_main_loop_quit (app->loop); @@ -263,12 +524,23 @@ on_bus_acquired (GDBusConnection *connection, registration_id = g_dbus_connection_register_object (connection, "/org/a11y/bus", introspection_data->interfaces[0], - &interface_vtable, + &bus_vtable, _global_app, NULL, &error); if (registration_id == 0) - g_error ("%s", error->message); + { + g_error ("%s", error->message); + g_clear_error (&error); + } + + g_dbus_connection_register_object (connection, + "/org/a11y/bus", + introspection_data->interfaces[1], + &status_vtable, + _global_app, + NULL, + NULL); } static void @@ -307,7 +579,7 @@ on_sigterm_pipe (GIOChannel *channel, gpointer data) { A11yBusLauncher *app = data; - + g_main_loop_quit (app->loop); return FALSE; @@ -330,49 +602,354 @@ init_sigterm_handling (A11yBusLauncher *app) } static gboolean -is_a11y_using_corba (void) +already_running () { - char *gconf_argv[] = { "gconftool-2", "--get", "/desktop/gnome/interface/at-spi-corba", NULL }; - char *stdout = NULL; - int estatus; +#ifdef HAVE_X11 + Atom AT_SPI_BUS; + Atom actual_type; + Display *bridge_display; + int actual_format; + unsigned char *data = NULL; + unsigned long nitems; + unsigned long leftover; gboolean result = FALSE; - if (!g_spawn_sync (NULL, gconf_argv, NULL, - G_SPAWN_SEARCH_PATH, NULL, NULL, &stdout, NULL, &estatus, NULL)) - goto out; - if (estatus != 0) - goto out; - if (g_str_has_prefix (stdout, "true")) - result = TRUE; - out: - g_free (stdout); + bridge_display = XOpenDisplay (NULL); + if (!bridge_display) + return FALSE; + + AT_SPI_BUS = XInternAtom (bridge_display, "AT_SPI_BUS", False); + XGetWindowProperty (bridge_display, + XDefaultRootWindow (bridge_display), + AT_SPI_BUS, 0L, + (long) BUFSIZ, False, + (Atom) 31, &actual_type, &actual_format, + &nitems, &leftover, &data); + + if (data) + { + GDBusConnection *bus; + bus = g_dbus_connection_new_for_address_sync ((const gchar *)data, 0, + NULL, NULL, NULL); + if (bus != NULL) + { + result = TRUE; + g_object_unref (bus); + } + } + + XCloseDisplay (bridge_display); return result; +#else + return FALSE; +#endif +} + +static GSettings * +get_schema (const gchar *name) +{ + const char * const *schemas = NULL; + gint i; + + schemas = g_settings_list_schemas (); + for (i = 0; schemas[i]; i++) + { + if (!strcmp (schemas[i], name)) + return g_settings_new (schemas[i]); + } + + return NULL; +} + +static void +gsettings_key_changed (GSettings *gsettings, const gchar *key, void *user_data) +{ + gboolean new_val = g_settings_get_boolean (gsettings, key); + + if (!strcmp (key, "toolkit-accessibility")) + handle_a11y_enabled_change (_global_app, new_val, FALSE); + else if (!strcmp (key, "screen-reader-enabled")) + handle_screen_reader_enabled_change (_global_app, new_val, FALSE); +} + +static int +_process_dead_tracker (int pid, void *data) +{ + A11yBusLauncher *app = data; + + if (app->screen_reader.pid > 0 && pid == app->screen_reader.pid) + { + LOGE("screen reader is dead, pid: %d, restarting", pid); + app->screen_reader.pid = 0; + g_timeout_add_seconds (2, _launch_process_repeat_until_success, &app->screen_reader); + } + + if (app->universal_switch.pid > 0 && pid == app->universal_switch.pid) + { + LOGE("universal switch is dead, pid: %d, restarting", pid); + app->universal_switch.pid = 0; + g_timeout_add_seconds (2, _launch_process_repeat_until_success, &app->universal_switch); + } + return 0; +} + +static void +_register_process_dead_tracker () +{ + if(_global_app->screen_reader.pid > 0 || _global_app->universal_switch.pid > 0) { + LOGD("registering process dead tracker"); + aul_listen_app_dead_signal(_process_dead_tracker, _global_app); + } else { + LOGD("unregistering process dead tracker"); + aul_listen_app_dead_signal(NULL, NULL); + } +} + + +static gboolean +_launch_client(A11yBusClient *client, gboolean by_vconf_change) +{ + LOGD("Launching %s", client->name); + + bundle *kb = NULL; + gboolean ret = FALSE; + + kb = bundle_create(); + + if (kb == NULL) + { + LOGD("Can't create bundle"); + return FALSE; + } + + if (by_vconf_change) + { + if (bundle_add_str(kb, "by_vconf_change", "yes") != BUNDLE_ERROR_NONE) + { + LOGD("Can't add information to bundle"); + } + } + + int operation_error = appsvc_set_operation(kb, client->app_control_operation); + LOGD("appsvc_set_operation: %i", operation_error); + + client->pid = appsvc_run_service(kb, 0, NULL, NULL); + + if (client->pid > 0) + { + LOGD("Process launched with pid: %i", client->pid); + _register_process_dead_tracker(); + ret = TRUE; + } + else + { + LOGD("Can't start %s - error code: %i", client->name, client->pid); + } + + bundle_free(kb); + return ret; +} + +static gboolean +_launch_process_repeat_until_success(gpointer user_data) { + A11yBusClient *client = user_data; + + if (client->launch_repeats > 100 || client->pid > 0) + { + //do not try anymore + return FALSE; + } + + gboolean ret = _launch_client(client, FALSE); + + if (ret) + { + //we managed to + client->launch_repeats = 0; + return FALSE; + } + client->launch_repeats++; + //try again + return TRUE; +} + +static gboolean +_terminate_process(int pid) +{ + int ret; + int ret_aul; + if (pid <= 0) + return FALSE; + + int status = aul_app_get_status_bypid(pid); + + if (status < 0) + { + LOGD("App with pid %d already terminated", pid); + return TRUE; + } + + LOGD("terminate process with pid %d", pid); + ret_aul = aul_terminate_pid(pid); + if (ret_aul >= 0) + { + LOGD("Terminating with aul_terminate_pid: return is %d", ret_aul); + return TRUE; + } + else + LOGD("aul_terminate_pid failed: return is %d", ret_aul); + + LOGD("Unable to terminate process using aul api. Sending SIGTERM signal"); + ret = kill(pid, SIGTERM); + if (!ret) + { + return TRUE; + } + + LOGD("Unable to terminate process: %d with api or signal.", pid); + return FALSE; +} + +static gboolean +_terminate_client(A11yBusClient *client) +{ + LOGD("Terminating %s", client->name); + int pid = client->pid; + client->pid = 0; + _register_process_dead_tracker(); + gboolean ret = _terminate_process(pid); + return ret; +} + +void vconf_client_cb(keynode_t *node, void *user_data) +{ + A11yBusClient *client = user_data; + int client_needed = vconf_keynode_get_bool(node); + LOGD("vconf_keynode_get_bool(node): %i", client_needed); + if (client_needed < 0) + return; + + //check if process really exists (e.g didn't crash) + if (client->pid > 0) + { + int err = kill(client->pid,0); + //process doesn't exist + if (err == ESRCH) + client->pid = 0; + } + + LOGD("client_needed: %i, client->pid: %i", client_needed, client->pid); + if (!client_needed && (client->pid > 0)) + _terminate_client(client); + else if (client_needed && (client->pid <= 0)) + _launch_client(client, TRUE); +} + + +static gboolean register_client(A11yBusClient *client) +{ + gboolean client_needed = FALSE; + + if(!client->vconf_key) { + LOGE("Vconf_key missing for client: %s \n", client->vconf_key); + return FALSE; + } + + int ret = vconf_get_bool(client->vconf_key, &client_needed); + if (ret != 0) + { + LOGD("Could not read %s key value.\n", client->vconf_key); + return FALSE; + } + ret = vconf_notify_key_changed(client->vconf_key, vconf_client_cb, client); + if(ret != 0) + { + LOGD("Could not add information level callback\n"); + return FALSE; + } + + if (client_needed) + g_timeout_add_seconds(2,_launch_process_repeat_until_success, client); + return TRUE; } int main (int argc, char **argv) { - GError *error = NULL; - GMainLoop *loop; - GDBusConnection *session_bus; - int name_owner_id; +#ifdef ATSPI_BUS_LAUNCHER_LOG_TO_FILE + log_file = fopen("/tmp/at-spi-bus-launcher.log", "a"); +#endif - g_type_init (); + LOGD("Starting atspi bus launcher"); + gboolean a11y_set = FALSE; + gboolean screen_reader_set = FALSE; + gint i; - if (is_a11y_using_corba ()) - return 0; + if (already_running ()) + { + LOGD("atspi bus launcher is already running"); + return 0; + } _global_app = g_slice_new0 (A11yBusLauncher); _global_app->loop = g_main_loop_new (NULL, FALSE); - _global_app->launch_immediately = (argc == 2 && strcmp (argv[1], "--launch-immediately") == 0); + _global_app->client_watcher_id = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + _global_app->screen_reader.name = "screen-reader"; + _global_app->screen_reader.app_control_operation = APP_CONTROL_OPERATION_SCREEN_READ; + _global_app->screen_reader.vconf_key = VCONFKEY_SETAPPL_ACCESSIBILITY_TTS; + + _global_app->universal_switch.name = "universal-switch"; + _global_app->universal_switch.app_control_operation = APP_CONTROL_OPERATION_UNIVERSAL_SWITCH; + _global_app->universal_switch.vconf_key = VCONFKEY_SETAPPL_ACCESSIBILITY_UNIVERSAL_SWITCH; + + for (i = 1; i < argc; i++) + { + if (!strcmp (argv[i], "--launch-immediately")) + _global_app->launch_immediately = TRUE; + else if (sscanf (argv[i], "--a11y=%d", &_global_app->a11y_enabled) == 2) + a11y_set = TRUE; + else if (sscanf (argv[i], "--screen-reader=%d", + &_global_app->screen_reader_enabled) == 2) + screen_reader_set = TRUE; + else + g_error ("usage: %s [--launch-immediately] [--a11y=0|1] [--screen-reader=0|1]", argv[0]); + } + + _global_app->interface_schema = get_schema ("org.gnome.desktop.interface"); + _global_app->a11y_schema = get_schema ("org.gnome.desktop.a11y.applications"); + + if (!a11y_set) + { + _global_app->a11y_enabled = _global_app->interface_schema + ? g_settings_get_boolean (_global_app->interface_schema, "toolkit-accessibility") + : _global_app->launch_immediately; + } + + if (!screen_reader_set) + { + _global_app->screen_reader_enabled = _global_app->a11y_schema + ? g_settings_get_boolean (_global_app->a11y_schema, "screen-reader-enabled") + : FALSE; + } + + if (_global_app->interface_schema) + g_signal_connect (_global_app->interface_schema, + "changed::toolkit-accessibility", + G_CALLBACK (gsettings_key_changed), _global_app); + + if (_global_app->a11y_schema) + g_signal_connect (_global_app->a11y_schema, + "changed::screen-reader-enabled", + G_CALLBACK (gsettings_key_changed), _global_app); init_sigterm_handling (_global_app); introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); g_assert (introspection_data != NULL); - name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + g_bus_own_name (G_BUS_TYPE_SESSION, "org.a11y.Bus", G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, on_bus_acquired, @@ -381,15 +958,19 @@ main (int argc, _global_app, NULL); + register_client (&_global_app->screen_reader); + register_client (&_global_app->universal_switch); + g_main_loop_run (_global_app->loop); if (_global_app->a11y_bus_pid > 0) kill (_global_app->a11y_bus_pid, SIGTERM); - /* Clear the X property if our bus is gone; in the case where e.g. + /* Clear the X property if our bus is gone; in the case where e.g. * GDM is launching a login on an X server it was using before, * we don't want early login processes to pick up the stale address. */ +#ifdef HAVE_X11 { Display *display = XOpenDisplay (NULL); if (display) @@ -403,6 +984,7 @@ main (int argc, XCloseDisplay (display); } } +#endif if (_global_app->a11y_launch_error_message) {