Branch: refs/heads/master Author: Colomban Wendling ban@herbesfolles.org Committer: Colomban Wendling ban@herbesfolles.org Date: Sun, 13 Nov 2016 15:29:43 UTC Commit: 5f879126cecd443d2e1a4be4eacaf8ac1d43ffa8 https://github.com/geany/geany/commit/5f879126cecd443d2e1a4be4eacaf8ac1d43ff...
Log Message: ----------- Merge pull request #1095 from eht16/issue1076_win32_build_working_dir_locale
Windows: Improve executing external commands on non-ASCII paths, effectively fixing most known issues.
Modified Paths: -------------- geany.nsi.in src/Makefile.am src/build.c src/geany-run-helper.bat src/prefix.h src/spawn.c src/utils.c src/utils.h src/win32.c
Modified: geany.nsi.in 4 lines changed, 4 insertions(+), 0 deletions(-) =================================================================== @@ -137,6 +137,9 @@ Section "!Program Files" SEC01 File "gtk\bin\libstdc++-*.dll" File "gtk\bin\libwinpthread*.dll"
+ SetOutPath "$INSTDIR\libexec" + File /r "${RESOURCEDIR}\libexec*" + SetOutPath "$INSTDIR\data" File "${RESOURCEDIR}\data\GPL-2" File "${RESOURCEDIR}\data\filetype_extensions.conf" @@ -311,6 +314,7 @@ Section Uninstall RMDir /r "$INSTDIR\etc" RMDir /r "$INSTDIR\include" RMDir /r "$INSTDIR\lib" + RMDir /r "$INSTDIR\libexec" RMDir /r "$INSTDIR\share" RMDir "$INSTDIR"
Modified: src/Makefile.am 7 lines changed, 7 insertions(+), 0 deletions(-) =================================================================== @@ -138,6 +138,7 @@ AM_CPPFLAGS += \ -DGEANY_DATADIR="data" \ -DGEANY_DOCDIR="" \ -DGEANY_LIBDIR="" \ + -DGEANY_LIBEXECDIR="" \ -DGEANY_LOCALEDIR="" \ -DGEANY_PREFIX=""
@@ -162,6 +163,7 @@ AM_CPPFLAGS += \ -DGEANY_DATADIR=""$(datadir)"" \ -DGEANY_DOCDIR=""$(docdir)"" \ -DGEANY_LIBDIR=""$(libdir)"" \ + -DGEANY_LIBEXECDIR=""$(libexecdir)"" \ -DGEANY_LOCALEDIR=""$(localedir)"" \ -DGEANY_PREFIX=""$(prefix)""
@@ -182,6 +184,11 @@ signallist.i: $(glade_file) Makefile
CLEANFILES += signallist.i
+# install the run script +if MINGW +dist_pkglibexec_SCRIPTS = geany-run-helper.bat +endif + # Ubuntu ld has a bug so that libtool sees /usr/local/lib as a system path so # doesn't add RPATH, but ld requires explicit ldconfig there, unlike when # installing in /usr/lib. So, workaround this by calling it explicitly when
Modified: src/build.c 55 lines changed, 38 insertions(+), 17 deletions(-) =================================================================== @@ -79,9 +79,7 @@ typedef struct RunInfo
static RunInfo *run_info;
-#ifdef G_OS_WIN32 -static const gchar RUN_SCRIPT_CMD[] = "geany_run_script_XXXXXX.bat"; -#else +#ifndef G_OS_WIN32 static const gchar RUN_SCRIPT_CMD[] = "geany_run_script_XXXXXX.sh"; #endif
@@ -115,7 +113,9 @@ static guint build_items_count = 9;
static void build_exit_cb(GPid pid, gint status, gpointer user_data); static void build_iofunc(GString *string, GIOCondition condition, gpointer data); +#ifndef G_OS_WIN32 static gchar *build_create_shellscript(const gchar *working_dir, const gchar *cmd, gboolean autoclose, GError **error); +#endif static void build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *dir); static void set_stop_button(gboolean stop); static void run_exit_cb(GPid child_pid, gint status, gpointer user_data); @@ -750,13 +750,15 @@ static void build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *d msgwin_compiler_add(COLOR_BLUE, _("%s (in directory: %s)"), cmd, utf8_working_dir); g_free(utf8_working_dir);
+#ifdef G_OS_UNIX cmd_string = utils_get_locale_from_utf8(cmd); argv[2] = cmd_string; - -#ifdef G_OS_UNIX cmd = NULL; /* under Unix, use argv to start cmd via sh for compatibility */ #else + /* Expand environment variables like %blah%. */ + cmd_string = win32_expand_environment_variables(cmd); argv[0] = NULL; /* under Windows, run cmd directly */ + cmd = cmd_string; #endif
/* set the build info for the message window */ @@ -826,6 +828,17 @@ static gchar *prepare_run_cmd(GeanyDocument *doc, gchar **working_dir, guint cmd } #endif
+#ifdef G_OS_WIN32 + /* Expand environment variables like %blah%. */ + SETPTR(cmd_string, win32_expand_environment_variables(cmd_string)); + + gchar *helper = g_build_filename(utils_resource_dir(RESOURCE_DIR_LIBEXEC), "geany-run-helper", NULL); + /* escape helper appropriately */ + /* FIXME: check the Windows rules, but it should not matter too much here as \es and "es are not + * allowed in paths anyway */ + run_cmd = g_strdup_printf(""%s" "%s" %d %s", helper, *working_dir, autoclose ? 1 : 0, cmd_string); + g_free(helper); +#else run_cmd = build_create_shellscript(*working_dir, cmd_string, autoclose, &error); if (!run_cmd) { @@ -834,7 +847,7 @@ static gchar *prepare_run_cmd(GeanyDocument *doc, gchar **working_dir, guint cmd g_error_free(error); g_free(*working_dir); } - +#endif utils_free_pointers(3, cmd_string_utf8, working_dir_utf8, cmd_string, NULL); return run_cmd; } @@ -892,6 +905,21 @@ static void build_run_cmd(GeanyDocument *doc, guint cmdindex) gchar *locale_term_cmd = utils_get_locale_from_utf8(tool_prefs.term_cmd); GError *error = NULL;
+#ifdef G_OS_WIN32 + if (g_regex_match_simple("^[ "]*cmd([.]exe)?[" ]", locale_term_cmd, 0, 0)) + { + /* if passing an argument to cmd.exe, respect its quoting rules */ + GString *escaped_run_cmd = g_string_new(NULL); + for (gchar *p = run_cmd; *p; p++) + { + if (strchr("()%!^"<>&| ", *p)) // cmd.exe metacharacters + g_string_append_c(escaped_run_cmd, '^'); + g_string_append_c(escaped_run_cmd, *p); + } + SETPTR(run_cmd, g_string_free(escaped_run_cmd, FALSE)); + } +#endif + utils_str_replace_all(&locale_term_cmd, "%c", run_cmd);
if (spawn_async(working_dir, locale_term_cmd, NULL, NULL, &(run_info[cmdindex].pid), @@ -908,7 +936,9 @@ static void build_run_cmd(GeanyDocument *doc, guint cmdindex) "Check the Terminal setting in Preferences"), utf8_term_cmd, error->message); g_free(utf8_term_cmd); g_error_free(error); +#ifndef G_OS_WIN32 g_unlink(run_cmd); +#endif run_info[cmdindex].pid = (GPid) 0; } } @@ -1063,35 +1093,25 @@ static void run_exit_cb(GPid child_pid, gint status, gpointer user_data) /* write a little shellscript to call the executable (similar to anjuta_launcher but "internal") * working_dir and cmd are both in the locale encoding * it returns the full file name (including path) of the created script in the locale encoding */ +#ifndef G_OS_WIN32 static gchar *build_create_shellscript(const gchar *working_dir, const gchar *cmd, gboolean autoclose, GError **error) { gint fd; gchar *str, *fname; gboolean success = TRUE; -#ifdef G_OS_WIN32 - gchar *expanded_cmd; -#else gchar *escaped_dir; -#endif fd = g_file_open_tmp (RUN_SCRIPT_CMD, &fname, error); if (fd < 0) return NULL; close(fd);
-#ifdef G_OS_WIN32 - /* Expand environment variables like %blah%. */ - expanded_cmd = win32_expand_environment_variables(cmd); - str = g_strdup_printf("cd "%s"\n\n%s\n\n%s\ndel "%%0"\n\npause\n", working_dir, expanded_cmd, (autoclose) ? "" : "pause"); - g_free(expanded_cmd); -#else escaped_dir = g_shell_quote(working_dir); str = g_strdup_printf( "#!/bin/sh\n\nrm $0\n\ncd %s\n\n%s\n\necho "\n\n------------------\n(program exited with code: $?)" \ \n\n%s\n", escaped_dir, cmd, (autoclose) ? "" : "\necho "Press return to continue"\n#to be more compatible with shells like " "dash\ndummy_var=""\nread dummy_var"); g_free(escaped_dir); -#endif
if (!g_file_set_contents(fname, str, -1, error)) success = FALSE; @@ -1119,6 +1139,7 @@ static gchar *build_create_shellscript(const gchar *working_dir, const gchar *cm
return fname; } +#endif
typedef void Callback(GtkWidget *w, gpointer u);
Modified: src/geany-run-helper.bat 30 lines changed, 30 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,30 @@ +REM USAGE: geany-run-helper DIRECTORY AUTOCLOSE COMMAND... + +REM unnecessary, but we get the directory +cd %1 +shift +REM save autoclose option and remove it +set autoclose=%1 +shift + +REM spawn the child +REM it's tricky because shift doesn't affect %*, so hack it out +REM https://en.wikibooks.org/wiki/Windows_Batch_Scripting#Command-line_arguments +set SPAWN= +:argloop +if -%1-==-- goto argloop_end + set SPAWN=%SPAWN% %1 + shift +goto argloop +:argloop_end +%SPAWN% + +REM show the result +echo: +echo: +echo:------------------ +echo:(program exited with code: %ERRORLEVEL%) +echo: + +REM and if wanted, wait on the user +if not %autoclose%==1 pause
Modified: src/prefix.h 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -81,6 +81,7 @@ G_BEGIN_DECLS #define GEANY_PREFIX (br_thread_local_store (br_locate_prefix ((void *) ""))) #define GEANY_DATADIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/share"))) #define GEANY_LIBDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/lib"))) + #define GEANY_LIBEXECDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/libexec/geany"))) #define GEANY_DOCDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/share/doc/geany"))) #define GEANY_LOCALEDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/share/locale"))) #endif /* BR_NO_MACROS */
Modified: src/spawn.c 132 lines changed, 112 insertions(+), 20 deletions(-) =================================================================== @@ -307,11 +307,11 @@ gboolean spawn_kill_process(GPid pid, GError **error)
#ifdef G_OS_WIN32 -static gchar *spawn_create_process_with_pipes(char *command_line, const char *working_directory, - void *environment, HANDLE *hprocess, int *stdin_fd, int *stdout_fd, int *stderr_fd) +static gchar *spawn_create_process_with_pipes(wchar_t *w_command_line, const wchar_t *w_working_directory, + void *w_environment, HANDLE *hprocess, int *stdin_fd, int *stdout_fd, int *stderr_fd) { enum { WRITE_STDIN, READ_STDOUT, READ_STDERR, READ_STDIN, WRITE_STDOUT, WRITE_STDERR }; - STARTUPINFO startup; + STARTUPINFOW startup; PROCESS_INFORMATION process; HANDLE hpipe[6] = { NULL, NULL, NULL, NULL, NULL, NULL }; int *fd[3] = { stdin_fd, stdout_fd, stderr_fd }; @@ -372,8 +372,9 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo startup.hStdOutput = hpipe[WRITE_STDOUT]; startup.hStdError = hpipe[WRITE_STDERR];
- if (!CreateProcess(NULL, command_line, NULL, NULL, TRUE, pipe_io ? CREATE_NO_WINDOW : 0, - environment, working_directory, &startup, &process)) + if (!CreateProcessW(NULL, w_command_line, NULL, NULL, TRUE, + CREATE_UNICODE_ENVIRONMENT | (pipe_io ? CREATE_NO_WINDOW : 0), + w_environment, w_working_directory, &startup, &process)) { failed = ""; /* report the message only */ /* further errors will not be reported */ @@ -521,8 +522,10 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch
#ifdef G_OS_WIN32 GString *command; - GArray *environment; - gchar *failure; + GArray *w_environment; + wchar_t *w_working_directory = NULL; + wchar_t *w_command = NULL; + gboolean success = TRUE;
if (command_line) { @@ -553,7 +556,7 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch else command = g_string_new(NULL);
- environment = g_array_new(TRUE, FALSE, sizeof(char)); + w_environment = g_array_new(TRUE, FALSE, sizeof(wchar_t));
while (argv && *argv) spawn_append_argument(command, *argv++); @@ -562,26 +565,115 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch g_message("full spawn command line: %s", command->str); #endif
- while (envp && *envp) + while (envp && *envp && success) { - g_array_append_vals(environment, *envp, strlen(*envp) + 1); + glong w_entry_len; + wchar_t *w_entry; + gchar *tmp = NULL; + + // FIXME: remove this and rely on UTF-8 input + if (! g_utf8_validate(*envp, -1, NULL)) + { + tmp = g_locale_to_utf8(*envp, -1, NULL, NULL, NULL); + if (tmp) + *envp = tmp; + } + /* TODO: better error message */ + w_entry = g_utf8_to_utf16(*envp, -1, NULL, &w_entry_len, error); + + if (! w_entry) + success = FALSE; + else + { + /* copy the entry, including NUL terminator */ + g_array_append_vals(w_environment, w_entry, w_entry_len + 1); + g_free(w_entry); + } + + g_free(tmp); envp++; }
- failure = spawn_create_process_with_pipes(command->str, working_directory, - envp ? environment->data : NULL, child_pid, stdin_fd, stdout_fd, stderr_fd); + /* convert working directory into locale encoding */ + if (success && working_directory) + { + GError *gerror = NULL; + const gchar *utf8_working_directory; + gchar *tmp = NULL;
- g_string_free(command, TRUE); - g_array_free(environment, TRUE); + // FIXME: remove this and rely on UTF-8 input + if (! g_utf8_validate(working_directory, -1, NULL)) + { + tmp = g_locale_to_utf8(working_directory, -1, NULL, NULL, NULL); + if (tmp) + utf8_working_directory = tmp; + } + else + utf8_working_directory = working_directory;
- if (failure) + w_working_directory = g_utf8_to_utf16(utf8_working_directory, -1, NULL, NULL, &gerror); + if (! w_working_directory) + { + /* TODO use the code below post-1.28 as it introduces a new string + g_set_error(error, gerror->domain, gerror->code, + _("Failed to convert working directory into locale encoding: %s"), gerror->message); + */ + g_propagate_error(error, gerror); + success = FALSE; + } + g_free(tmp); + } + /* convert command into locale encoding */ + if (success) { - g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, failure); - g_free(failure); - return FALSE; + GError *gerror = NULL; + const gchar *utf8_cmd; + gchar *tmp = NULL; + + // FIXME: remove this and rely on UTF-8 input + if (! g_utf8_validate(command->str, -1, NULL)) + { + tmp = g_locale_to_utf8(command->str, -1, NULL, NULL, NULL); + if (tmp) + utf8_cmd = tmp; + } + else + utf8_cmd = command->str; + + w_command = g_utf8_to_utf16(utf8_cmd, -1, NULL, NULL, &gerror); + if (! w_command) + { + /* TODO use the code below post-1.28 as it introduces a new string + g_set_error(error, gerror->domain, gerror->code, + _("Failed to convert command into locale encoding: %s"), gerror->message); + */ + g_propagate_error(error, gerror); + success = FALSE; + } }
- return TRUE; + if (success) + { + gchar *failure; + + failure = spawn_create_process_with_pipes(w_command, w_working_directory, + envp ? w_environment->data : NULL, child_pid, stdin_fd, stdout_fd, stderr_fd); + + if (failure) + { + g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, failure); + g_free(failure); + } + + success = failure == NULL; + } + + g_string_free(command, TRUE); + g_array_free(w_environment, TRUE); + g_free(w_working_directory); + g_free(w_command); + + return success; #else /* G_OS_WIN32 */ int cl_argc; char **full_argv; @@ -655,7 +747,7 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch #ifdef ENFILE case G_SPAWN_ERROR_NFILE : en = ENFILE; break; #endif - #ifdef EMFILE + #ifdef EMFILE case G_SPAWN_ERROR_MFILE : en = EMFILE; break; #endif #ifdef EINVAL
Modified: src/utils.c 3 lines changed, 3 insertions(+), 0 deletions(-) =================================================================== @@ -2103,6 +2103,7 @@ const gchar *utils_resource_dir(GeanyResourceDirType type) resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL); resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL); resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL); + resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL); g_free(prefix); #else if (is_osx_bundle()) @@ -2115,6 +2116,7 @@ const gchar *utils_resource_dir(GeanyResourceDirType type) resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL); resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL); resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL); + resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL); g_free(prefix); # endif } @@ -2125,6 +2127,7 @@ const gchar *utils_resource_dir(GeanyResourceDirType type) resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL); resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL); resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL); + resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(GEANY_LIBEXECDIR, "geany", NULL); } #endif }
Modified: src/utils.h 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -221,6 +221,7 @@ typedef enum RESOURCE_DIR_DOC, RESOURCE_DIR_LOCALE, RESOURCE_DIR_PLUGIN, + RESOURCE_DIR_LIBEXEC,
RESOURCE_DIR_COUNT } GeanyResourceDirType;
Modified: src/win32.c 15 lines changed, 10 insertions(+), 5 deletions(-) =================================================================== @@ -891,14 +891,19 @@ void win32_init_debug_code(void) }
+/* expands environment placeholders in @str. input and output is in UTF-8 */ gchar *win32_expand_environment_variables(const gchar *str) { - gchar expCmdline[32768]; /* 32768 is the limit for ExpandEnvironmentStrings() */ + wchar_t *cmdline = g_utf8_to_utf16(str, -1, NULL, NULL, NULL); + wchar_t expCmdline[32768]; /* 32768 is the limit for ExpandEnvironmentStrings() */ + gchar *expanded = NULL;
- if (ExpandEnvironmentStrings((LPCTSTR) str, (LPTSTR) expCmdline, sizeof(expCmdline)) != 0) - return g_strdup(expCmdline); - else - return g_strdup(str); + if (cmdline && ExpandEnvironmentStringsW(cmdline, expCmdline, sizeof(expCmdline)) != 0) + expanded = g_utf16_to_utf8(expCmdline, -1, NULL, NULL, NULL); + + g_free(cmdline); + + return expanded ? expanded : g_strdup(str); }
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).