diff --git a/src/build.c b/src/build.c index 6703b16ce5..09a218e89d 100644 --- a/src/build.c +++ b/src/build.c @@ -909,8 +909,8 @@ static void build_run_cmd(GeanyDocument *doc, guint cmdindex) } else { - geany_debug("spawn_async() failed: %s", error->message); - ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message); + ui_set_statusbar(TRUE, _("Cannot execute terminal command \"%s\": %s. " + "Check the path setting in Preferences."), tool_prefs.term_cmd, error->message); g_error_free(error); g_unlink(run_cmd); run_info[cmdindex].pid = (GPid) 0; diff --git a/src/callbacks.c b/src/callbacks.c index e758533b1f..11c4083bcd 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -1452,6 +1452,7 @@ void on_context_action1_activate(GtkMenuItem *menuitem, gpointer user_data) gchar *word, *command; GError *error = NULL; GeanyDocument *doc = document_get_current(); + const gchar *check_msg; g_return_if_fail(doc != NULL); @@ -1469,22 +1470,30 @@ void on_context_action1_activate(GtkMenuItem *menuitem, gpointer user_data) !EMPTY(doc->file_type->context_action_cmd)) { command = g_strdup(doc->file_type->context_action_cmd); + check_msg = _("Check the path setting in Filetype configuration."); } else { command = g_strdup(tool_prefs.context_action_cmd); + check_msg = _("Check the path setting in Preferences."); } /* substitute the wildcard %s and run the command if it is non empty */ if (G_LIKELY(!EMPTY(command))) { - utils_str_replace_all(&command, "%s", word); + gchar *command_line = g_strdup(command); - if (!spawn_async(NULL, command, NULL, NULL, NULL, &error)) + utils_str_replace_all(&command_line, "%s", word); + + if (!spawn_async(NULL, command_line, NULL, NULL, NULL, &error)) { - ui_set_statusbar(TRUE, "Context action command failed: %s", error->message); + /* G_SHELL_ERROR is parsing error, it may be caused by %s word with quotes */ + ui_set_statusbar(TRUE, _("Cannot execute context action command \"%s\": %s. %s"), + error->domain == G_SHELL_ERROR ? command_line : command, error->message, + check_msg); g_error_free(error); } + g_free(command_line); } g_free(word); g_free(command); diff --git a/src/printing.c b/src/printing.c index 79bb0eef36..535dc133f0 100644 --- a/src/printing.c +++ b/src/printing.c @@ -612,8 +612,9 @@ static void print_external(GeanyDocument *doc) #endif { dialogs_show_msgbox(GTK_MESSAGE_ERROR, - _("Printing of \"%s\" failed (return code: %s)."), - doc->file_name, error->message); + _("Cannot execute print command \"%s\": %s. " + "Check the path setting in Preferences."), + printing_prefs.external_print_cmd, error->message); g_error_free(error); } else diff --git a/src/search.c b/src/search.c index 7ae34ee7de..17aa6ad1d4 100644 --- a/src/search.c +++ b/src/search.c @@ -1715,8 +1715,8 @@ search_find_in_files(const gchar *utf8_search_text, const gchar *utf8_dir, const } else { - geany_debug("%s: spawn_with_callbacks() failed: %s", G_STRFUNC, error->message); - ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message); + ui_set_statusbar(TRUE, _("Cannot execute grep tool \"%s\": %s. " + "Check the path setting in Preferences."), tool_prefs.grep_cmd, error->message); g_error_free(error); } diff --git a/src/spawn.c b/src/spawn.c index bff20f8a11..e72f2fb9de 100644 --- a/src/spawn.c +++ b/src/spawn.c @@ -74,6 +74,23 @@ # define DEFAULT_IO_LENGTH 2048 #else # define DEFAULT_IO_LENGTH 4096 + +/* helper function that cuts glib citing of the original text on bad quoting: it may be long, + and only the caller knows whether it's UTF-8. Thought we lose the ' or " failed info. */ +static gboolean spawn_parse_argv(const gchar *command_line, gint *argcp, gchar ***argvp, + GError **error) +{ + GError *gerror = NULL; + + if (g_shell_parse_argv(command_line, argcp, argvp, &gerror)) + return TRUE; + + g_set_error_literal(error, gerror->domain, gerror->code, + gerror->code == G_SHELL_ERROR_BAD_QUOTING ? + _("Text ended before matching quote was found") : gerror->message); + g_error_free(gerror); + return FALSE; +} #endif #define G_IO_FAILURE (G_IO_ERR | G_IO_HUP | G_IO_NVAL) /* always used together */ @@ -104,7 +121,7 @@ static gchar *spawn_get_program_name(const gchar *command_line, GError **error) if (!*command_line) { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_EMPTY_STRING, + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_EMPTY_STRING, /* TL note: from glib */ _("Text was empty (or contained only whitespace)")); return FALSE; @@ -119,16 +136,14 @@ static gchar *spawn_get_program_name(const gchar *command_line, GError **error) /* Windows allows "foo.exe, but we may have extra arguments */ if ((s = strchr(command_line, '"')) == NULL) { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, - /* TL note: from glib */ - _("Text ended before matching quote was found for %c." - " (The text was '%s')"), '"', command_line); + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + _("Text ended before matching quote was found")); return FALSE; } if (!strchr(" \t", s[1])) /* strchr() catches s[1] == '\0' */ { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, _("A quoted Windows program name must be entirely inside the quotes")); return FALSE; } @@ -142,7 +157,7 @@ static gchar *spawn_get_program_name(const gchar *command_line, GError **error) if (quote && quote < s) { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, _("A quoted Windows program name must be entirely inside the quotes")); return FALSE; } @@ -165,10 +180,8 @@ static gchar *spawn_get_program_name(const gchar *command_line, GError **error) if (open_quote) { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, - /* TL note: from glib */ - _("Text ended before matching quote was found for %c." - " (The text was '%s')"), '"', command_line); + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + _("Text ended before matching quote was found")); g_free(program); return FALSE; } @@ -176,7 +189,7 @@ static gchar *spawn_get_program_name(const gchar *command_line, GError **error) int argc; char **argv; - if (!g_shell_parse_argv(command_line, &argc, &argv, error)) + if (!spawn_parse_argv(command_line, &argc, &argv, error)) return FALSE; /* empty string results in parse error, so argv[0] is not NULL */ @@ -237,8 +250,8 @@ gboolean spawn_check_command(const gchar *command_line, gboolean execute, GError if (!executable) { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_FAILED, /* or SPAWN error? */ - _("Program '%s' not found"), program); + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_FAILED, + _("Program not found")); g_free(program); return FALSE; } @@ -274,15 +287,14 @@ gboolean spawn_kill_process(GPid pid, GError **error) { gchar *message = g_win32_error_message(GetLastError()); - g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, - _("TerminateProcess() failed: %s"), message); + g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, message); g_free(message); return FALSE; } #else if (kill(pid, SIGTERM)) { - g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "%s", g_strerror(errno)); + g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, g_strerror(errno)); return FALSE; } #endif @@ -322,7 +334,7 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo if (!CreatePipe(&hpipe[pindex[i][0]], &hpipe[pindex[i][1]], NULL, 0)) { hpipe[pindex[i][0]] = hpipe[pindex[i][1]] = NULL; - failed = "CreatePipe"; + failed = "create pipe"; goto leave; } @@ -332,21 +344,21 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo if ((*fd[i] = _open_osfhandle((intptr_t) hpipe[i], mode[i])) == -1) { - failed = "_open_osfhandle"; + failed = "convert pipe handle to file descriptor"; message = g_strdup(g_strerror(errno)); goto leave; } } else if (!CloseHandle(hpipe[i])) { - failed = "CloseHandle"; + failed = "close pipe"; goto leave; } if (!SetHandleInformation(hpipe[i + 3], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) { - failed = "SetHandleInformation"; + failed = "set pipe handle to inheritable"; goto leave; } } @@ -359,7 +371,7 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo if (!CreateProcess(NULL, command_line, NULL, NULL, TRUE, pipe_io ? CREATE_NO_WINDOW : 0, environment, working_directory, &startup, &process)) { - failed = "CreateProcess"; + failed = ""; /* report the message only */ /* further errors will not be reported */ } else @@ -377,10 +389,24 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo if (failed) { if (!message) + { + size_t len; + message = g_win32_error_message(GetLastError()); + len = strlen(message); - failure = g_strdup_printf("%s() failed: %s", failed, message); - g_free(message); + /* unlike g_strerror(), the g_win32_error messages may include a final '.' */ + if (len > 0 && message[len - 1] == '.') + message[len - 1] = '\0'; + } + + if (*failed == '\0') + failure = message; + else + { + failure = g_strdup_printf("Failed to %s (%s)", failed, message); + g_free(message); + } } if (pipe_io) @@ -546,7 +572,7 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch if (failure) { - g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "%s", failure); + g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, failure); g_free(failure); return FALSE; } @@ -556,13 +582,14 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch int cl_argc; char **full_argv; gboolean spawned; + GError *gerror = NULL; if (command_line) { int argc = 0; char **cl_argv; - if (!g_shell_parse_argv(command_line, &cl_argc, &cl_argv, error)) + if (!spawn_parse_argv(command_line, &cl_argc, &cl_argv, error)) return FALSE; if (argv) @@ -577,7 +604,83 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch spawned = g_spawn_async_with_pipes(working_directory, full_argv, envp, G_SPAWN_SEARCH_PATH | (child_pid ? G_SPAWN_DO_NOT_REAP_CHILD : 0), NULL, NULL, - child_pid, stdin_fd, stdout_fd, stderr_fd, error); + child_pid, stdin_fd, stdout_fd, stderr_fd, &gerror); + + if (!spawned) + { + gint en = 0; + const gchar *message = gerror->message; + + /* try to cut glib citing of the program name or working directory: they may be long, + and only the caller knows whether they're UTF-8. We lose the exact chdir error. */ + switch (gerror->code) + { + #ifdef EACCES + case G_SPAWN_ERROR_ACCES : en = EACCES; break; + #endif + #ifdef EPERM + case G_SPAWN_ERROR_PERM : en = EPERM; break; + #endif + #ifdef E2BIG + case G_SPAWN_ERROR_TOO_BIG : en = E2BIG; break; + #endif + #ifdef ENOEXEC + case G_SPAWN_ERROR_NOEXEC : en = ENOEXEC; break; + #endif + #ifdef ENAMETOOLONG + case G_SPAWN_ERROR_NAMETOOLONG : en = ENAMETOOLONG; break; + #endif + #ifdef ENOENT + case G_SPAWN_ERROR_NOENT : en = ENOENT; break; + #endif + #ifdef ENOMEM + case G_SPAWN_ERROR_NOMEM : en = ENOMEM; break; + #endif + #ifdef ENOTDIR + case G_SPAWN_ERROR_NOTDIR : en = ENOTDIR; break; + #endif + #ifdef ELOOP + case G_SPAWN_ERROR_LOOP : en = ELOOP; break; + #endif + #ifdef ETXTBUSY + case G_SPAWN_ERROR_TXTBUSY : en = ETXTBUSY; break; + #endif + #ifdef EIO + case G_SPAWN_ERROR_IO : en = EIO; break; + #endif + #ifdef ENFILE + case G_SPAWN_ERROR_NFILE : en = ENFILE; break; + #endif + #ifdef EMFILE + case G_SPAWN_ERROR_MFILE : en = EMFILE; break; + #endif + #ifdef EINVAL + case G_SPAWN_ERROR_INVAL : en = EINVAL; break; + #endif + #ifdef EISDIR + case G_SPAWN_ERROR_ISDIR : en = EISDIR; break; + #endif + #ifdef ELIBBAD + case G_SPAWN_ERROR_LIBBAD : en = ELIBBAD; break; + #endif + case G_SPAWN_ERROR_CHDIR : + { + message = _("Failed to change to the working directory"); + break; + } + case G_SPAWN_ERROR_FAILED : + { + message = _("Unknown error executing child process"); + break; + } + } + + if (en) + message = g_strerror(en); + + g_set_error_literal(error, gerror->domain, gerror->code, message); + g_error_free(gerror); + } if (full_argv != argv) { diff --git a/src/templates.c b/src/templates.c index fe8fee236a..512edb04e9 100644 --- a/src/templates.c +++ b/src/templates.c @@ -617,7 +617,8 @@ static gchar *run_command(const gchar *command, const gchar *file_name, } else { - g_warning("templates_replace_command: %s", error->message); + g_warning(_("Cannot execute command \"%s\" from the template: %s. " + "Check the path in the template."), command, error->message); g_error_free(error); } diff --git a/src/tools.c b/src/tools.c index c8ebc9784c..0276aec9d6 100644 --- a/src/tools.c +++ b/src/tools.c @@ -239,8 +239,8 @@ void tools_execute_custom_command(GeanyDocument *doc, const gchar *command) } else { - geany_debug("spawn_sync() failed: %s", error->message); - ui_set_statusbar(TRUE, _("Custom command failed: %s"), error->message); + ui_set_statusbar(TRUE, _("Cannot execute custom command \"%s\": %s. " + "Check the path setting in Custom Commands."), command, error->message); g_error_free(error); }