[geany/geany] 826f65: Merge pull request #441 from zhekov/spawn
Colomban Wendling
git-noreply at xxxxx
Fri May 15 16:54:23 UTC 2015
Branch: refs/heads/master
Author: Colomban Wendling <ban at herbesfolles.org>
Committer: Colomban Wendling <ban at herbesfolles.org>
Date: Fri, 15 May 2015 16:54:23 UTC
Commit: 826f6516d36ec7a7bed35116fda32d42449c1e5d
https://github.com/geany/geany/commit/826f6516d36ec7a7bed35116fda32d42449c1e5d
Log Message:
-----------
Merge pull request #441 from zhekov/spawn
Add a spawn module for Geany
Thanks a lot for Dimitar's hard work, commitment and endless patience!
Closes #274, #441, and https://sourceforge.net/p/geany/bugs/943/
Should also fix https://sourceforge.net/p/geany/bugs/898/
Modified Paths:
--------------
src/Makefile.am
src/build.c
src/callbacks.c
src/keyfile.c
src/makefile.win32
src/printing.c
src/search.c
src/spawn.c
src/spawn.h
src/templates.c
src/tools.c
src/utils.c
src/win32.c
src/win32.h
wscript
Modified: src/Makefile.am
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -89,6 +89,7 @@ libgeany_la_SOURCES = \
sciwrappers.c sciwrappers.h \
search.c search.h \
socket.c socket.h \
+ spawn.c spawn.h \
stash.c stash.h \
support.h \
symbols.c symbols.h \
Modified: src/build.c
291 lines changed, 47 insertions(+), 244 deletions(-)
===================================================================
@@ -44,6 +44,7 @@
#include "prefs.h"
#include "projectprivate.h"
#include "sciwrappers.h"
+#include "spawn.h"
#include "support.h"
#include "toolbar.h"
#include "ui_utils.h"
@@ -60,20 +61,8 @@
#include <errno.h>
#include <glib/gstdio.h>
-#ifdef G_OS_UNIX
-# include <sys/types.h>
-# include <sys/wait.h>
-# include <signal.h>
-#else
-# include <windows.h>
-#endif
-/* g_spawn_async_with_pipes doesn't work on Windows */
-#ifdef G_OS_WIN32
-#define SYNC_SPAWN
-#endif
-
/* Number of editor indicators to draw - limited as this can affect performance */
#define GEANY_BUILD_ERR_HIGHLIGHT_MAX 50
@@ -124,12 +113,10 @@ widgets;
static guint build_groups_count[GEANY_GBG_COUNT] = { 3, 4, 2 };
static guint build_items_count = 9;
-#ifndef SYNC_SPAWN
-static void build_exit_cb(GPid child_pid, gint status, gpointer user_data);
-static gboolean build_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer data);
-#endif
+static void build_exit_cb(GPid pid, gint status, gpointer user_data);
+static void build_iofunc(GString *string, GIOCondition condition, gpointer data);
static gchar *build_create_shellscript(const gchar *working_dir, const gchar *cmd, gboolean autoclose, GError **error);
-static GPid build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *dir);
+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);
static void on_set_build_commands_activate(GtkWidget *w, gpointer u);
@@ -137,7 +124,7 @@ static void on_build_next_error(GtkWidget *menuitem, gpointer user_data);
static void on_build_previous_error(GtkWidget *menuitem, gpointer user_data);
static void kill_process(GPid *pid);
static void show_build_result_message(gboolean failure);
-static void process_build_output_line(const gchar *line, gint color);
+static void process_build_output_line(gchar *msg, gint color);
static void show_build_commands_dialog(void);
static void on_build_menu_item(GtkWidget *w, gpointer user_data);
@@ -675,47 +662,6 @@ static void clear_all_errors(void)
}
-#ifdef SYNC_SPAWN
-static void parse_build_output(const gchar **output, gint status)
-{
- guint x, i, len;
- gchar *line, **lines;
-
- for (x = 0; x < 2; x++)
- {
- if (!EMPTY(output[x]))
- {
- lines = g_strsplit_set(output[x], "\r\n", -1);
- len = g_strv_length(lines);
-
- for (i = 0; i < len; i++)
- {
- if (!EMPTY(lines[i]))
- {
- line = lines[i];
- while (*line != '\0')
- { /* replace any control characters in the output */
- if (*line < 32)
- *line = 32;
- line++;
- }
- process_build_output_line(lines[i], COLOR_BLACK);
- }
- }
- g_strfreev(lines);
- }
- }
-
- show_build_result_message(status != 0);
- utils_beep();
-
- build_info.pid = 0;
- /* enable build items again */
- build_menu_update(NULL);
-}
-#endif
-
-
/* Replaces occurrences of %e and %p with the appropriate filenames and
* %l with current line number. %d and %p replacements should be in UTF8 */
static gchar *build_replace_placeholder(const GeanyDocument *doc, const gchar *src)
@@ -784,43 +730,26 @@ static gchar *build_replace_placeholder(const GeanyDocument *doc, const gchar *s
/* dir is the UTF-8 working directory to run cmd in. It can be NULL to use the
* idx document directory */
-static GPid build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *dir)
+static void build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *dir)
{
GError *error = NULL;
- gchar **argv;
+ gchar *argv[] = { "/bin/sh", "-c", (gchar *) cmd, NULL };
gchar *working_dir;
gchar *utf8_working_dir;
gchar *utf8_cmd_string;
-#ifdef SYNC_SPAWN
- gchar *output[2];
- gint status;
-#else
- gint stdout_fd;
- gint stderr_fd;
-#endif
- g_return_val_if_fail(doc == NULL || doc->is_valid, (GPid) -1);
+ g_return_if_fail(doc == NULL || doc->is_valid);
- if (!((doc != NULL && !EMPTY(doc->file_name)) || !EMPTY(dir)))
+ if ((doc == NULL || EMPTY(doc->file_name)) && EMPTY(dir))
{
geany_debug("Failed to run command with no working directory");
ui_set_statusbar(TRUE, _("Process failed, no working directory"));
- return (GPid) 1;
+ return;
}
clear_all_errors();
SETPTR(current_dir_entered, NULL);
-#ifdef G_OS_WIN32
- argv = g_strsplit(cmd, " ", 0);
-#else
- argv = g_new0(gchar *, 4);
- argv[0] = g_strdup("/bin/sh");
- argv[1] = g_strdup("-c");
- argv[2] = g_strdup(cmd);
- argv[3] = NULL;
-#endif
-
utf8_cmd_string = utils_get_utf8_from_locale(cmd);
utf8_working_dir = !EMPTY(dir) ? g_strdup(dir) : g_path_get_dirname(doc->file_name);
working_dir = utils_get_locale_from_utf8(utf8_working_dir);
@@ -831,53 +760,28 @@ static GPid build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *d
g_free(utf8_working_dir);
g_free(utf8_cmd_string);
+#ifdef G_OS_UNIX
+ cmd = NULL; /* under Unix, use argv to start cmd via sh for compatibility */
+#else
+ argv[0] = NULL; /* under Windows, run cmd directly */
+#endif
+
/* set the build info for the message window */
g_free(build_info.dir);
build_info.dir = g_strdup(working_dir);
build_info.file_type_id = (doc == NULL) ? GEANY_FILETYPES_NONE : doc->file_type->id;
build_info.message_count = 0;
-#ifdef SYNC_SPAWN
- if (! utils_spawn_sync(working_dir, argv, NULL, G_SPAWN_SEARCH_PATH,
- NULL, NULL, &output[0], &output[1], &status, &error))
-#else
- if (! g_spawn_async_with_pipes(working_dir, argv, NULL,
- G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL,
- &(build_info.pid), NULL, &stdout_fd, &stderr_fd, &error))
-#endif
+ if (!spawn_with_callbacks(working_dir, cmd, argv, NULL, 0, NULL, NULL, build_iofunc,
+ GINT_TO_POINTER(0), 0, build_iofunc, GINT_TO_POINTER(1), 0, build_exit_cb, NULL,
+ &build_info.pid, &error))
{
geany_debug("build command spawning failed: %s", error->message);
ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message);
- g_strfreev(argv);
g_error_free(error);
- g_free(working_dir);
- error = NULL;
- return (GPid) 0;
- }
-
-#ifdef SYNC_SPAWN
- parse_build_output((const gchar**) output, status);
- g_free(output[0]);
- g_free(output[1]);
-#else
- if (build_info.pid != 0)
- {
- g_child_watch_add(build_info.pid, (GChildWatchFunc) build_exit_cb, NULL);
- build_menu_update(doc);
- ui_progress_bar_start(NULL);
}
- /* use GIOChannels to monitor stdout and stderr */
- utils_set_up_io_channel(stdout_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- TRUE, build_iofunc, GINT_TO_POINTER(0));
- utils_set_up_io_channel(stderr_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- TRUE, build_iofunc, GINT_TO_POINTER(1));
-#endif
-
- g_strfreev(argv);
g_free(working_dir);
-
- return build_info.pid;
}
@@ -903,13 +807,10 @@ static gchar *prepare_run_cmd(GeanyDocument *doc, gchar **working_dir, guint cmd
working_dir_utf8 = build_replace_placeholder(doc, cmd_working_dir);
*working_dir = utils_get_locale_from_utf8(working_dir_utf8);
- /* only test whether working dir exists, don't change it or else Windows support will break
- * (gspawn-win32-helper.exe is used by GLib and must be in $PATH which means current working
- * dir where geany.exe was started from, so we can't change it) */
if (EMPTY(*working_dir) || ! g_file_test(*working_dir, G_FILE_TEST_EXISTS) ||
! g_file_test(*working_dir, G_FILE_TEST_IS_DIR))
{
- ui_set_statusbar(TRUE, _("Failed to change the working directory to \"%s\""),
+ ui_set_statusbar(TRUE, _("Invalid working directory \"%s\""),
!EMPTY(working_dir_utf8) ? working_dir_utf8 : "<NULL>" );
utils_free_pointers(3, cmd_string_utf8, working_dir_utf8, *working_dir, NULL);
return NULL;
@@ -945,18 +846,17 @@ static gchar *prepare_run_cmd(GeanyDocument *doc, gchar **working_dir, guint cmd
}
-static GPid build_run_cmd(GeanyDocument *doc, guint cmdindex)
+static void build_run_cmd(GeanyDocument *doc, guint cmdindex)
{
gchar *working_dir;
gchar *run_cmd = NULL;
- GError *error = NULL;
if (! DOC_VALID(doc) || doc->file_name == NULL)
- return (GPid) 0;
+ return;
run_cmd = prepare_run_cmd(doc, &working_dir, cmdindex);
if (run_cmd == NULL)
- return (GPid) 0;
+ return;
run_info[cmdindex].file_type_id = doc->file_type->id;
@@ -990,97 +890,48 @@ static GPid build_run_cmd(GeanyDocument *doc, guint cmdindex)
msgwin_show_hide(TRUE);
run_info[cmdindex].pid = 1;
-
g_free(vte_cmd);
}
else
#endif
{
- gchar *locale_term_cmd = NULL;
- gint argv_len, i;
- gchar **argv = NULL;
- gchar *script_path = NULL;
+ gchar *locale_term_cmd = utils_get_locale_from_utf8(tool_prefs.term_cmd);
+ GError *error = NULL;
- /* get the terminal path */
- locale_term_cmd = utils_get_locale_from_utf8(tool_prefs.term_cmd);
- /* split the term_cmd, so arguments will work too */
- if (!g_shell_parse_argv(locale_term_cmd, &argv_len, &argv, NULL))
- {
- ui_set_statusbar(TRUE,
- _("Could not parse terminal command \"%s\" "
- "(check Terminal tool setting in Preferences)"), tool_prefs.term_cmd);
- run_info[cmdindex].pid = (GPid) 1;
- g_unlink(run_cmd);
- goto free_strings;
- }
+ utils_str_replace_all(&locale_term_cmd, "%c", run_cmd);
- /* check that terminal exists (to prevent misleading error messages) */
- if (argv[0] != NULL)
+ if (spawn_async(working_dir, locale_term_cmd, NULL, NULL, &(run_info[cmdindex].pid),
+ &error))
{
- gchar *tmp = argv[0];
- /* g_find_program_in_path checks whether tmp exists and is executable */
- argv[0] = g_find_program_in_path(tmp);
- g_free(tmp);
- }
- if (argv[0] == NULL)
- {
- ui_set_statusbar(TRUE,
- _("Could not find terminal \"%s\" "
- "(check path for Terminal tool setting in Preferences)"), tool_prefs.term_cmd);
- run_info[cmdindex].pid = (GPid) 1;
- g_unlink(run_cmd);
- goto free_strings;
- }
-
- for (i = 0; i < argv_len; i++)
- {
- utils_str_replace_all(&(argv[i]), "%c", run_cmd);
+ g_child_watch_add(run_info[cmdindex].pid, (GChildWatchFunc) run_exit_cb,
+ (gpointer) &(run_info[cmdindex]));
+ build_menu_update(doc);
}
-
- if (! g_spawn_async(working_dir, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
- NULL, NULL, &(run_info[cmdindex].pid), &error))
+ else
{
- geany_debug("g_spawn_async() failed: %s", error->message);
+ geany_debug("spawn_async() failed: %s", error->message);
ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message);
g_error_free(error);
g_unlink(run_cmd);
- error = NULL;
run_info[cmdindex].pid = (GPid) 0;
}
-
- if (run_info[cmdindex].pid != 0)
- {
- g_child_watch_add(run_info[cmdindex].pid, (GChildWatchFunc) run_exit_cb,
- (gpointer)&(run_info[cmdindex]));
- build_menu_update(doc);
- }
- free_strings:
- g_strfreev(argv);
- g_free(locale_term_cmd);
- g_free(script_path);
}
g_free(working_dir);
g_free(run_cmd);
- return run_info[cmdindex].pid;
}
-static void process_build_output_line(const gchar *str, gint color)
+static void process_build_output_line(gchar *msg, gint color)
{
- gchar *msg, *tmp;
+ gchar *tmp;
gchar *filename;
gint line;
- msg = g_strdup(str);
-
g_strchomp(msg);
if (EMPTY(msg))
- {
- g_free(msg);
return;
- }
if (build_parse_make_dir(msg, &tmp))
{
@@ -1106,33 +957,17 @@ static void process_build_output_line(const gchar *str, gint color)
g_free(filename);
msgwin_compiler_add_string(color, msg);
- g_free(msg);
}
-#ifndef SYNC_SPAWN
-static gboolean build_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer data)
+static void build_iofunc(GString *string, GIOCondition condition, gpointer data)
{
- if (cond & (G_IO_IN | G_IO_PRI))
+ if (condition & (G_IO_IN | G_IO_PRI))
{
- gchar *msg;
- GIOStatus st;
-
- while ((st = g_io_channel_read_line(ioc, &msg, NULL, NULL, NULL)) == G_IO_STATUS_NORMAL && msg)
- {
- gint color = (GPOINTER_TO_INT(data)) ? COLOR_DARK_RED : COLOR_BLACK;
-
- process_build_output_line(msg, color);
- g_free(msg);
- }
- if (st == G_IO_STATUS_ERROR || st == G_IO_STATUS_EOF) return FALSE;
+ process_build_output_line(string->str,
+ (GPOINTER_TO_INT(data)) ? COLOR_DARK_RED : COLOR_BLACK);
}
- if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
- return FALSE;
-
- return TRUE;
}
-#endif
gboolean build_parse_make_dir(const gchar *string, gchar **prefix)
@@ -1205,40 +1040,16 @@ static void show_build_result_message(gboolean failure)
}
-#ifndef SYNC_SPAWN
static void build_exit_cb(GPid child_pid, gint status, gpointer user_data)
{
- gboolean failure = FALSE;
-
-#ifdef G_OS_WIN32
- failure = status;
-#else
- if (WIFEXITED(status))
- {
- if (WEXITSTATUS(status) != EXIT_SUCCESS)
- failure = TRUE;
- }
- else if (WIFSIGNALED(status))
- {
- /* the terminating signal: WTERMSIG (status)); */
- failure = TRUE;
- }
- else
- { /* any other failure occurred */
- failure = TRUE;
- }
-#endif
- show_build_result_message(failure);
-
+ show_build_result_message(!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS);
utils_beep();
- g_spawn_close_pid(child_pid);
build_info.pid = 0;
/* enable build items again */
build_menu_update(NULL);
ui_progress_bar_stop();
}
-#endif
static void run_exit_cb(GPid child_pid, gint status, gpointer user_data)
@@ -1790,26 +1601,18 @@ static void on_toolbutton_make_activate(GtkWidget *menuitem, gpointer user_data)
static void kill_process(GPid *pid)
{
- gint result;
-
-#ifdef G_OS_WIN32
- g_return_if_fail(*pid != NULL);
- result = TerminateProcess(*pid, 0);
- /* TerminateProcess() returns TRUE on success, for the check below we have to convert
- * it to FALSE (and vice versa) */
- result = ! result;
-#else
- g_return_if_fail(*pid > 1);
- result = kill(*pid, SIGTERM);
-#endif
+ GError *error = NULL;
- if (result != 0)
- ui_set_statusbar(TRUE, _("Process could not be stopped (%s)."), g_strerror(errno));
- else
+ if (spawn_kill_process(*pid, &error))
{
*pid = 0;
build_menu_update(NULL);
}
+ else
+ {
+ ui_set_statusbar(TRUE, _("Process could not be stopped (%s)."), error->message);
+ g_error_free(error);
+ }
}
Modified: src/callbacks.c
3 lines changed, 2 insertions(+), 1 deletions(-)
===================================================================
@@ -51,6 +51,7 @@
#include "printing.h"
#include "sciwrappers.h"
#include "sidebar.h"
+#include "spawn.h"
#ifdef HAVE_SOCKET
# include "socket.h"
#endif
@@ -1479,7 +1480,7 @@ void on_context_action1_activate(GtkMenuItem *menuitem, gpointer user_data)
{
utils_str_replace_all(&command, "%s", word);
- if (! g_spawn_command_line_async(command, &error))
+ if (!spawn_async(NULL, command, NULL, NULL, NULL, &error))
{
ui_set_statusbar(TRUE, "Context action command failed: %s", error->message);
g_error_free(error);
Modified: src/keyfile.c
13 lines changed, 7 insertions(+), 6 deletions(-)
===================================================================
@@ -941,19 +941,20 @@ static void load_dialog_prefs(GKeyFile *config)
/* printing */
tmp_string2 = g_find_program_in_path(GEANY_DEFAULT_TOOLS_PRINTCMD);
-#ifdef G_OS_WIN32
+
if (!EMPTY(tmp_string2))
{
- /* single quote paths on Win32 for g_spawn_command_line_async */
- tmp_string = g_strconcat("'", tmp_string2, "' '%f'", NULL);
+ #ifdef G_OS_WIN32
+ tmp_string = g_strconcat(GEANY_DEFAULT_TOOLS_PRINTCMD, " \"%f\"", NULL);
+ #else
+ tmp_string = g_strconcat(GEANY_DEFAULT_TOOLS_PRINTCMD, " '%f'", NULL);
+ #endif
}
else
{
tmp_string = g_strdup("");
}
-#else
- tmp_string = g_strconcat(tmp_string2, " %f", NULL);
-#endif
+
printing_prefs.external_print_cmd = utils_get_setting_string(config, "printing", "print_cmd", tmp_string);
g_free(tmp_string);
g_free(tmp_string2);
Modified: src/makefile.win32
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -67,7 +67,7 @@ OBJS = about.o build.o callbacks.o dialogs.o document.o editor.o encodings.o fil
geanyentryaction.o geanymenubuttonaction.o geanyobject.o geanywraplabel.o highlighting.o \
keybindings.o keyfile.o log.o main.o msgwindow.o navqueue.o notebook.o \
plugins.o pluginutils.o prefs.o printing.o project.o sciwrappers.o search.o \
- socket.o stash.o symbols.o templates.o toolbar.o tools.o sidebar.o \
+ socket.o spawn.o stash.o symbols.o templates.o toolbar.o tools.o sidebar.o \
ui_utils.o utils.o win32.o
.c.o:
Modified: src/printing.c
19 lines changed, 9 insertions(+), 10 deletions(-)
===================================================================
@@ -39,6 +39,7 @@
#include "highlighting.h"
#include "msgwindow.h"
#include "sciwrappers.h"
+#include "spawn.h"
#include "support.h"
#include "utils.h"
#include "ui_utils.h"
@@ -600,16 +601,15 @@ static void print_external(GeanyDocument *doc)
doc->file_name, cmdline))
{
GError *error = NULL;
-
-#ifdef G_OS_WIN32
- gchar *tmp_cmdline = g_strdup(cmdline);
-#else
/* /bin/sh -c emulates the system() call and makes complex commands possible
- * but only needed on non-win32 systems due to the lack of win32's shell capabilities */
- gchar *tmp_cmdline = g_strconcat("/bin/sh -c \"", cmdline, "\"", NULL);
-#endif
-
- if (! g_spawn_command_line_async(tmp_cmdline, &error))
+ * but only on non-win32 systems due to the lack of win32's shell capabilities */
+ #ifdef G_OS_UNIX
+ gchar *argv[] = { "/bin/sh", "-c", cmdline, NULL };
+
+ if (!spawn_async(NULL, NULL, argv, NULL, NULL, &error))
+ #else
+ if (!spawn_async(NULL, cmdline, NULL, NULL, NULL, &error))
+ #endif
{
dialogs_show_msgbox(GTK_MESSAGE_ERROR,
_("Printing of \"%s\" failed (return code: %s)."),
@@ -620,7 +620,6 @@ static void print_external(GeanyDocument *doc)
{
msgwin_status_add(_("File %s printed."), doc->file_name);
}
- g_free(tmp_cmdline);
}
g_free(cmdline);
}
Modified: src/search.c
175 lines changed, 63 insertions(+), 112 deletions(-)
===================================================================
@@ -37,6 +37,7 @@
#include "msgwindow.h"
#include "prefs.h"
#include "sciwrappers.h"
+#include "spawn.h"
#include "stash.h"
#include "support.h"
#include "toolbar.h"
@@ -49,11 +50,6 @@
#include <string.h>
#include <ctype.h>
-#ifdef G_OS_UNIX
-# include <sys/types.h>
-# include <sys/wait.h>
-#endif
-
#include <gdk/gdkkeysyms.h>
enum
@@ -149,10 +145,10 @@ static struct
fif_dlg = {NULL, NULL, NULL, NULL, NULL, NULL, {0, 0}};
-static gboolean search_read_io(GIOChannel *source, GIOCondition condition, gpointer data);
-static gboolean search_read_io_stderr(GIOChannel *source, GIOCondition condition, gpointer data);
+static void search_read_io(GString *string, GIOCondition condition, gpointer data);
+static void search_read_io_stderr(GString *string, GIOCondition condition, gpointer data);
-static void search_close_pid(GPid child_pid, gint status, gpointer user_data);
+static void search_finished(GPid child_pid, gint status, gpointer user_data);
static gchar **search_get_argv(const gchar **argv_prefix, const gchar *dir);
@@ -1619,21 +1615,17 @@ on_find_in_files_dialog_response(GtkDialog *dialog, gint response,
ui_set_statusbar(FALSE, _("Invalid directory for find in files."));
else if (!EMPTY(search_text))
{
- gchar *locale_dir;
GString *opts = get_grep_options();
const gchar *enc = (enc_idx == GEANY_ENCODING_UTF_8) ? NULL :
encodings_get_charset_from_index(enc_idx);
- locale_dir = utils_get_locale_from_utf8(utf8_dir);
-
- if (search_find_in_files(search_text, locale_dir, opts->str, enc))
+ if (search_find_in_files(search_text, utf8_dir, opts->str, enc))
{
ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(search_combo), search_text, 0);
ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(fif_dlg.files_combo), NULL, 0);
ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(dir_combo), utf8_dir, 0);
gtk_widget_hide(fif_dlg.dialog);
}
- g_free(locale_dir);
g_string_free(opts, TRUE);
}
else
@@ -1645,36 +1637,26 @@ on_find_in_files_dialog_response(GtkDialog *dialog, gint response,
static gboolean
-search_find_in_files(const gchar *utf8_search_text, const gchar *dir, const gchar *opts,
+search_find_in_files(const gchar *utf8_search_text, const gchar *utf8_dir, const gchar *opts,
const gchar *enc)
{
- gchar **argv_prefix, **argv, **opts_argv;
+ gchar **argv_prefix, **argv;
gchar *command_grep;
+ gchar *command_line, *dir;
gchar *search_text = NULL;
- gint opts_argv_len, i;
- GPid child_pid;
- gint stdout_fd;
- gint stderr_fd;
GError *error = NULL;
gboolean ret = FALSE;
gssize utf8_text_len;
- if (EMPTY(utf8_search_text) || ! dir) return TRUE;
+ if (EMPTY(utf8_search_text) || ! utf8_dir) return TRUE;
command_grep = g_find_program_in_path(tool_prefs.grep_cmd);
if (command_grep == NULL)
+ command_line = g_strdup_printf("%s %s --", tool_prefs.grep_cmd, opts);
+ else
{
- ui_set_statusbar(TRUE, _("Cannot execute grep tool '%s';"
- " check the path setting in Preferences."), tool_prefs.grep_cmd);
- return FALSE;
- }
-
- if (! g_shell_parse_argv(opts, &opts_argv_len, &opts_argv, &error))
- {
- ui_set_statusbar(TRUE, _("Cannot parse extra options: %s"), error->message);
- g_error_free(error);
+ command_line = g_strdup_printf("\"%s\" %s --", command_grep, opts);
g_free(command_grep);
- return FALSE;
}
/* convert the search text in the preferred encoding (if the text is not valid UTF-8. assume
@@ -1687,74 +1669,58 @@ search_find_in_files(const gchar *utf8_search_text, const gchar *dir, const gcha
if (search_text == NULL)
search_text = g_strdup(utf8_search_text);
- /* set grep command and options */
- argv_prefix = g_new0(gchar*, 1 + opts_argv_len + 3 + 1); /* last +1 for recursive arg */
-
- argv_prefix[0] = command_grep;
- for (i = 0; i < opts_argv_len; i++)
- {
- argv_prefix[i + 1] = g_strdup(opts_argv[i]);
- }
- g_strfreev(opts_argv);
-
- i++; /* correct for tool_prefs.grep_cmd */
- argv_prefix[i++] = g_strdup("--");
- argv_prefix[i++] = search_text;
+ argv_prefix = g_new(gchar*, 3);
+ argv_prefix[0] = search_text;
+ dir = utils_get_locale_from_utf8(utf8_dir);
/* finally add the arguments(files to be searched) */
- if (strstr(argv_prefix[1], "r")) /* recursive option set */
+ if (settings.fif_recursive) /* recursive option set */
{
/* Use '.' so we get relative paths in the output */
- argv_prefix[i++] = g_strdup(".");
- argv_prefix[i++] = NULL;
+ argv_prefix[1] = g_strdup(".");
+ argv_prefix[2] = NULL;
argv = argv_prefix;
}
else
{
- argv_prefix[i++] = NULL;
+ argv_prefix[1] = NULL;
argv = search_get_argv((const gchar**)argv_prefix, dir);
g_strfreev(argv_prefix);
- }
- if (argv == NULL) /* no files */
- {
- return FALSE;
+ if (argv == NULL) /* no files */
+ {
+ g_free(command_line);
+ return FALSE;
+ }
}
gtk_list_store_clear(msgwindow.store_msg);
gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_MESSAGE);
- if (! g_spawn_async_with_pipes(dir, (gchar**)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
- NULL, NULL, &child_pid,
- NULL, &stdout_fd, &stderr_fd, &error))
- {
- geany_debug("%s: g_spawn_async_with_pipes() failed: %s", G_STRFUNC, error->message);
- ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message);
- g_error_free(error);
- ret = FALSE;
+ /* we can pass 'enc' without strdup'ing it here because it's a global const string and
+ * always exits longer than the lifetime of this function */
+ if (spawn_with_callbacks(dir, command_line, argv, NULL, 0, NULL, NULL, search_read_io,
+ (gpointer) enc, 0, search_read_io_stderr, (gpointer) enc, 0, search_finished, NULL,
+ NULL, &error))
+ {
+ gchar *utf8_str;
+
+ ui_progress_bar_start(_("Searching..."));
+ msgwin_set_messages_dir(dir);
+ utf8_str = g_strdup_printf(_("%s %s -- %s (in directory: %s)"),
+ tool_prefs.grep_cmd, opts, utf8_search_text, utf8_dir);
+ msgwin_msg_add_string(COLOR_BLUE, -1, NULL, utf8_str);
+ g_free(utf8_str);
+ ret = TRUE;
}
else
{
- gchar *str, *utf8_str;
-
- ui_progress_bar_start(_("Searching..."));
-
- msgwin_set_messages_dir(dir);
- /* we can pass 'enc' without strdup'ing it here because it's a global const string and
- * always exits longer than the lifetime of this function */
- utils_set_up_io_channel(stdout_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- TRUE, search_read_io, (gpointer) enc);
- utils_set_up_io_channel(stderr_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- TRUE, search_read_io_stderr, (gpointer) enc);
- g_child_watch_add(child_pid, search_close_pid, NULL);
-
- str = g_strdup_printf(_("%s %s -- %s (in directory: %s)"),
- tool_prefs.grep_cmd, opts, utf8_search_text, dir);
- utf8_str = utils_get_utf8_from_locale(str);
- msgwin_msg_add_string(COLOR_BLUE, -1, NULL, utf8_str);
- utils_free_pointers(2, str, utf8_str, NULL);
- ret = TRUE;
+ geany_debug("%s: spawn_with_callbacks() failed: %s", G_STRFUNC, error->message);
+ ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message);
+ g_error_free(error);
}
+
+ utils_free_pointers(2, dir, command_line, NULL);
g_strfreev(argv);
return ret;
}
@@ -1837,61 +1803,47 @@ static gchar **search_get_argv(const gchar **argv_prefix, const gchar *dir)
}
-static gboolean read_fif_io(GIOChannel *source, GIOCondition condition, gchar *enc, gint msg_color)
+static void read_fif_io(gchar *msg, GIOCondition condition, gchar *enc, gint msg_color)
{
if (condition & (G_IO_IN | G_IO_PRI))
{
- gchar *msg, *utf8_msg;
- GIOStatus st;
+ gchar *utf8_msg = NULL;
- while ((st = g_io_channel_read_line(source, &msg, NULL, NULL, NULL)) != G_IO_STATUS_ERROR &&
- st != G_IO_STATUS_EOF && msg)
+ g_strstrip(msg);
+ /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */
+ if (enc != NULL)
{
- utf8_msg = NULL;
-
- g_strstrip(msg);
- /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */
- if (enc != NULL)
+ if (! g_utf8_validate(msg, -1, NULL))
{
- if (! g_utf8_validate(msg, -1, NULL))
- {
- utf8_msg = g_convert(msg, -1, "UTF-8", enc, NULL, NULL, NULL);
- }
- if (utf8_msg == NULL)
- utf8_msg = msg;
+ utf8_msg = g_convert(msg, -1, "UTF-8", enc, NULL, NULL, NULL);
}
- else
+ if (utf8_msg == NULL)
utf8_msg = msg;
+ }
+ else
+ utf8_msg = msg;
- msgwin_msg_add_string(msg_color, -1, NULL, utf8_msg);
+ msgwin_msg_add_string(msg_color, -1, NULL, utf8_msg);
- if (utf8_msg != msg)
- g_free(utf8_msg);
- g_free(msg);
- }
- if (st == G_IO_STATUS_ERROR || st == G_IO_STATUS_EOF)
- return FALSE;
+ if (utf8_msg != msg)
+ g_free(utf8_msg);
}
- if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
- return FALSE;
-
- return TRUE;
}
-static gboolean search_read_io(GIOChannel *source, GIOCondition condition, gpointer data)
+static void search_read_io(GString *string, GIOCondition condition, gpointer data)
{
- return read_fif_io(source, condition, data, COLOR_BLACK);
+ return read_fif_io(string->str, condition, data, COLOR_BLACK);
}
-static gboolean search_read_io_stderr(GIOChannel *source, GIOCondition condition, gpointer data)
+static void search_read_io_stderr(GString *string, GIOCondition condition, gpointer data)
{
- return read_fif_io(source, condition, data, COLOR_DARK_RED);
+ return read_fif_io(string->str, condition, data, COLOR_DARK_RED);
}
-static void search_close_pid(GPid child_pid, gint status, gpointer user_data)
+static void search_finished(GPid child_pid, gint status, gpointer user_data)
{
const gchar *msg = _("Search failed.");
#ifdef G_OS_UNIX
@@ -1932,7 +1884,6 @@ static void search_close_pid(GPid child_pid, gint status, gpointer user_data)
break;
}
utils_beep();
- g_spawn_close_pid(child_pid);
ui_progress_bar_stop();
}
Modified: src/spawn.c
1341 lines changed, 1341 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,1341 @@
+/*
+ * spawn.c - this file is part of Geany, a fast and lightweight IDE
+ *
+ * Copyright 2013 Dimitar Toshkov Zhekov <dimitar(dot)zhekov(at)gmail(dot)com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* An ongoing effort to improve the tool spawning situation under Windows.
+ * In particular:
+ * - There is no g_shell_parse_argv() for windows. It's not hard to write one,
+ * but the command line recreated by mscvrt may be wrong.
+ * - GLib converts the argument vector to UNICODE. For non-UTF8 arguments,
+ * the result is often "Invalid string in argument vector at %d: %s: Invalid
+ * byte sequence in conversion input" (YMMV). Our tools (make, grep, gcc, ...)
+ * are "ANSI", so converting to UNICODE and then back only causes problems.
+ * - For various reasons, GLib uses an intermediate program to start children
+ * (see gspawn-win32.c), the result being that the grandchildren output (such
+ * as make -> gcc) is not captured.
+ * - With non-blocking pipes, the g_io_add_watch() callbacks are never invoked,
+ * while with blocking pipes, g_io_channel_read_line() blocks.
+ * - Some other problems are explained in separate comments below.
+ *
+ * Even under Unix, using g_io_channel_read_line() is not a good idea, since it may
+ * buffer lines of unlimited length.
+ *
+ * This module does not depend on Geany when compiled for testing (-DSPAWN_TEST).
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <errno.h>
+#include <string.h>
+
+#include "spawn.h"
+
+#ifdef G_OS_WIN32
+# include <ctype.h> /* isspace() */
+# include <fcntl.h> /* _O_RDONLY, _O_WRONLY */
+# include <io.h> /* _open_osfhandle, _close */
+# include <windows.h>
+#else /* G_OS_WIN32 */
+# include <signal.h>
+#endif /* G_OS_WIN32 */
+
+#ifdef SPAWN_TEST
+# define _
+#else
+# include "support.h"
+#endif
+
+#ifdef G_OS_WIN32
+/* Each 4KB under Windows seem to come in 2 portions, so 2K + 2K is more
+ balanced than 4095 + 1. May be different on the latest Windows/glib? */
+# define DEFAULT_IO_LENGTH 2048
+#else
+# define DEFAULT_IO_LENGTH 4096
+#endif
+
+#define G_IO_FAILURE (G_IO_ERR | G_IO_HUP | G_IO_NVAL) /* always used together */
+
+
+/**
+ * Checks whether a command line is syntactically valid and extracts the program name from it.
+ *
+ * All OS:
+ * - any leading spaces, tabs and new lines are skipped
+ * - an empty command is invalid
+ * Unix:
+ * - the standard shell quoting and escaping rules are used, see @c g_shell_parse_argv()
+ * - as a consequence, an unqouted # at the start of an argument comments to the end of line
+ * Windows:
+ * - leading carriage returns are skipped too
+ * - a quoted program name must be entirely inside the quotes. No "C:\Foo\Bar".pdf or
+ * "C:\Foo\Bar".bat, which would be executed by Windows as C:\Foo\Bar.exe
+ * - an unquoted program name may not contain spaces. Foo Bar Qux will not be considered
+ * "Foo Bar.exe" Qux or "Foo Bar Qux.exe", depending on what executables exist, as
+ * Windows normally does.
+ * - the program name must be separated from the arguments by at least one space or tab
+ * - the standard Windows quoting and escaping rules are used: double quote is escaped with
+ * backslash, and any literal backslashes before a double quote must be duplicated.
+ *
+ * @param command_line the command line to check and get the program name from.
+ * @param error return location for error.
+ *
+ * @return allocated string with the program name on success, @c NULL on error.
+ *
+ * @since 1.25
+ **/
+GEANY_API_SYMBOL
+gchar *spawn_get_program_name(const gchar *command_line, GError **error)
+{
+ gchar *program;
+
+#ifdef G_OS_WIN32
+ gboolean open_quote = FALSE;
+ const gchar *s, *arguments;
+
+ g_return_val_if_fail(command_line != NULL, FALSE);
+
+ while (*command_line && strchr(" \t\r\n", *command_line))
+ command_line++;
+
+ if (!*command_line)
+ {
+ /* TL note: from glib */
+ g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_EMPTY_STRING,
+ _("Text was empty (or contained only whitespace)"));
+ return FALSE;
+ }
+
+ /* To prevent Windows from doing something weird, we want to be 100% sure that the
+ character after the program name is a delimiter, so we allow space and tab only. */
+
+ if (*command_line == '"')
+ {
+ command_line++;
+ /* Windows allows "foo.exe, but we may have extra arguments */
+ if ((s = strchr(command_line, '"')) == NULL)
+ {
+ /* TL note: from glib */
+ g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING,
+ _("Text ended before matching quote was found for %c."
+ " (The text was '%s')"), '"', command_line);
+ return FALSE;
+ }
+
+ if (!strchr(" \t", s[1])) /* strchr() catches s[1] == '\0' */
+ {
+ g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING,
+ _("A quoted Windows program name must be entirely inside the quotes"));
+ return FALSE;
+ }
+ }
+ else
+ {
+ const gchar *quote = strchr(command_line, '"');
+
+ /* strchr() catches *s == '\0', and the for body is empty */
+ for (s = command_line; !strchr(" \t", *s); s++);
+
+ if (quote && quote < s)
+ {
+ g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING,
+ _("A quoted Windows program name must be entirely inside the quotes"));
+ return FALSE;
+ }
+ }
+
+ program = g_strndup(command_line, s - command_line);
+ arguments = s + (*s == '"');
+
+ for (s = arguments; *s; s++)
+ {
+ if (*s == '"')
+ {
+ const char *slash;
+
+ for (slash = s; slash > arguments && slash[-1] == '\\'; slash--);
+ if ((s - slash) % 2 == 0)
+ open_quote ^= TRUE;
+ }
+ }
+
+ if (open_quote)
+ {
+ /* TL note: from glib */
+ g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING,
+ _("Text ended before matching quote was found for %c."
+ " (The text was '%s')"), '"', command_line);
+ g_free(program);
+ return FALSE;
+ }
+#else /* G_OS_WIN32 */
+ int argc;
+ char **argv;
+
+ if (!g_shell_parse_argv(command_line, &argc, &argv, error))
+ return FALSE;
+
+ /* empty string results in parse error, so argv[0] is not NULL */
+ program = g_strdup(argv[0]);
+ g_strfreev(argv);
+#endif /* G_OS_WIN32 */
+
+ return program;
+}
+
+
+/**
+ * Checks whether a command line is valid.
+ *
+ * Checks if @a command_line is syntactically valid using @c spawn_get_program_name().
+ *
+ * If @a execute is TRUE, also checks, using @c g_find_program_in_path(), if the program
+ * specified in @a command_line exists and is executable.
+ *
+ * @param command_line the command line to check.
+ * @param execute whether to check if the command line is really executable.
+ * @param error return location for error.
+ *
+ * @return @c TRUE on success, @c FALSE on error.
+ *
+ * @since 1.25
+ **/
+GEANY_API_SYMBOL
+gboolean spawn_check_command(const gchar *command_line, gboolean execute, GError **error)
+{
+ gchar *program = spawn_get_program_name(command_line, error);
+
+ if (!program)
+ return FALSE;
+
+ if (execute)
+ {
+ gchar *executable = g_find_program_in_path(program);
+
+ if (!executable)
+ {
+ g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_FAILED, /* or SPAWN error? */
+ _("Program '%s' not found"), program);
+ g_free(program);
+ return FALSE;
+ }
+
+ g_free(executable);
+ }
+
+ g_free(program);
+ return TRUE;
+}
+
+
+/**
+ * Kills a process.
+ *
+ * @param pid id of the process to kill.
+ * @param error return location for error.
+ *
+ * On Unix, sends a SIGTERM to the process.
+ *
+ * On Windows, terminates the process with exit code 255 (used sometimes as "generic"
+ * error code, or for programs terminated with Ctrl+C / Ctrl+Break).
+ *
+ * @return @c TRUE on success, @c FALSE on error.
+ *
+ * @since 1.25
+ **/
+GEANY_API_SYMBOL
+gboolean spawn_kill_process(GPid pid, GError **error)
+{
+#ifdef G_OS_WIN32
+ if (!TerminateProcess(pid, 255))
+ {
+ gchar *message = g_win32_error_message(GetLastError());
+
+ g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+ _("TerminateProcess() failed: %s"), 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));
+ return FALSE;
+ }
+#endif
+ return TRUE;
+}
+
+
+#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)
+{
+ enum { WRITE_STDIN, READ_STDOUT, READ_STDERR, READ_STDIN, WRITE_STDOUT, WRITE_STDERR };
+ STARTUPINFO startup;
+ PROCESS_INFORMATION process;
+ HANDLE hpipe[6] = { NULL, NULL, NULL, NULL, NULL, NULL };
+ int *fd[3] = { stdin_fd, stdout_fd, stderr_fd };
+ const char *failed; /* failed WIN32/CRTL function, if any */
+ gchar *message = NULL; /* glib WIN32/CTRL error message */
+ gchar *failure = NULL; /* full error text */
+ gboolean pipe_io;
+ int i;
+
+ ZeroMemory(&startup, sizeof startup);
+ startup.cb = sizeof startup;
+ pipe_io = stdin_fd || stdout_fd || stderr_fd;
+
+ if (pipe_io)
+ {
+ startup.dwFlags |= STARTF_USESTDHANDLES;
+
+ /* not all programs accept mixed NULL and non-NULL hStd*, so we create all */
+ for (i = 0; i < 3; i++)
+ {
+ static int pindex[3][2] = { { READ_STDIN, WRITE_STDIN },
+ { READ_STDOUT, WRITE_STDOUT }, { READ_STDERR, WRITE_STDERR } };
+
+ if (!CreatePipe(&hpipe[pindex[i][0]], &hpipe[pindex[i][1]], NULL, 0))
+ {
+ hpipe[pindex[i][0]] = hpipe[pindex[i][1]] = NULL;
+ failed = "CreatePipe";
+ goto leave;
+ }
+
+ if (fd[i])
+ {
+ static int mode[3] = { _O_WRONLY, _O_RDONLY, _O_RDONLY };
+
+ if ((*fd[i] = _open_osfhandle((intptr_t) hpipe[i], mode[i])) == -1)
+ {
+ failed = "_open_osfhandle";
+ message = g_strdup(g_strerror(errno));
+ goto leave;
+ }
+ }
+ else if (!CloseHandle(hpipe[i]))
+ {
+ failed = "CloseHandle";
+ goto leave;
+ }
+
+ if (!SetHandleInformation(hpipe[i + 3], HANDLE_FLAG_INHERIT,
+ HANDLE_FLAG_INHERIT))
+ {
+ failed = "SetHandleInformation";
+ goto leave;
+ }
+ }
+ }
+
+ startup.hStdInput = hpipe[READ_STDIN];
+ 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))
+ {
+ failed = "CreateProcess";
+ /* further errors will not be reported */
+ }
+ else
+ {
+ failed = NULL;
+ CloseHandle(process.hThread); /* we don't need this */
+ *hprocess = process.hProcess;
+ }
+
+leave:
+ if (failed)
+ {
+ if (!message)
+ message = g_win32_error_message(GetLastError());
+
+ failure = g_strdup_printf("%s() failed: %s", failed, message);
+ g_free(message);
+ }
+
+ if (pipe_io)
+ {
+ for (i = 0; i < 3; i++)
+ {
+ if (failed)
+ {
+ if (fd[i] && *fd[i] != -1)
+ _close(*fd[i]);
+ else if (hpipe[i])
+ CloseHandle(hpipe[i]);
+ }
+
+ if (hpipe[i + 3])
+ CloseHandle(hpipe[i + 3]);
+ }
+ }
+
+ return failure;
+}
+
+
+static void spawn_append_argument(GString *command, const char *text)
+{
+ const char *s;
+
+ if (command->len)
+ g_string_append_c(command, ' ');
+
+ for (s = text; *s; s++)
+ {
+ /* g_ascii_isspace() fails for '\v', and locale spaces (if any) will do no harm */
+ if (*s == '"' || isspace(*s))
+ break;
+ }
+
+ if (*text && !*s)
+ g_string_append(command, text);
+ else
+ {
+ g_string_append_c(command, '"');
+
+ for (s = text; *s; s++)
+ {
+ const char *slash;
+
+ for (slash = s; *slash == '\\'; slash++);
+
+ if (slash > s)
+ {
+ g_string_append_len(command, s, slash - s);
+
+ if (!*slash || *slash == '"')
+ {
+ g_string_append_len(command, s, slash - s);
+
+ if (!*slash)
+ break;
+ }
+
+ s = slash;
+ }
+
+ if (*s == '"')
+ g_string_append_c(command, '\\');
+
+ g_string_append_c(command, *s);
+ }
+
+ g_string_append_c(command, '"');
+ }
+}
+
+
+static void spawn_close_pid(GPid pid, G_GNUC_UNUSED gint status, G_GNUC_UNUSED gpointer data)
+{
+ g_spawn_close_pid(pid);
+}
+#endif /* G_OS_WIN32 */
+
+
+/**
+ * Executes a child program asynchronously and setups pipes.
+ *
+ * This is the low-level spawning function. Please use @c spawn_with_callbacks() unless
+ * you need to setup specific event source(s).
+ *
+ * A command line or an argument vector must be passed. If both are present, the argument
+ * vector is appended to the command line. An empty command line is not allowed.
+ *
+ * Under Windows, if the child is a console application, and at least one file descriptor is
+ * specified, the new child console (if any) will be hidden.
+ *
+ * If a @a child_pid is passed, it's your responsibility to invoke @c g_spawn_close_pid().
+ *
+ * @param working_directory child's current working directory, or @c NULL.
+ * @param command_line child program and arguments, or @c NULL.
+ * @param argv child's argument vector, or @c NULL.
+ * @param envp child's environment, or @c NULL.
+ * @param child_pid return location for child process ID, or @c NULL.
+ * @param stdin_fd return location for file descriptor to write to child's stdin, or @c NULL.
+ * @param stdout_fd return location for file descriptor to read child's stdout, or @c NULL.
+ * @param stderr_fd return location for file descriptor to read child's stderr, or @c NULL.
+ * @param error return location for error.
+ *
+ * @return @c TRUE on success, @c FALSE on error.
+ *
+ * @since 1.25
+ **/
+GEANY_API_SYMBOL
+gboolean spawn_async_with_pipes(const gchar *working_directory, const gchar *command_line,
+ gchar **argv, gchar **envp, GPid *child_pid, gint *stdin_fd, gint *stdout_fd,
+ gint *stderr_fd, GError **error)
+{
+ g_return_val_if_fail(command_line != NULL || argv != NULL, FALSE);
+
+#ifdef G_OS_WIN32
+ GString *command;
+ GArray *environment;
+ GPid pid;
+ gchar *failure;
+
+ if (command_line)
+ {
+ gchar *program = spawn_get_program_name(command_line, error);
+ const gchar *arguments;
+
+ if (!program)
+ return FALSE;
+
+ command = g_string_new(NULL);
+ arguments = strstr(command_line, program) + strlen(program);
+
+ if (*arguments == '"')
+ {
+ g_string_append(command, program);
+ arguments++;
+ }
+ else
+ {
+ /* quote the first token, to avoid Windows attemps to run two or more
+ unquoted tokens as a program until an existing file name is found */
+ g_string_printf(command, "\"%s\"", program);
+ }
+
+ g_string_append(command, arguments);
+ g_free(program);
+ }
+ else
+ command = g_string_new(NULL);
+
+ environment = g_array_new(TRUE, FALSE, sizeof(char));
+
+ while (argv && *argv)
+ spawn_append_argument(command, *argv++);
+
+#ifdef SPAWN_TEST
+ g_message("full spawn command line: %s\n", command->str);
+#endif
+
+ while (envp && *envp)
+ {
+ g_array_append_vals(environment, *envp, strlen(*envp) + 1);
+ envp++;
+ }
+
+ failure = spawn_create_process_with_pipes(command->str, working_directory,
+ envp ? environment->data : NULL, child_pid ? child_pid : &pid,
+ stdin_fd, stdout_fd, stderr_fd);
+
+ g_string_free(command, TRUE);
+ g_array_free(environment, TRUE);
+
+ if (failure)
+ {
+ g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "%s", failure);
+ g_free(failure);
+ return FALSE;
+ }
+ else if (!child_pid)
+ g_child_watch_add(pid, spawn_close_pid, NULL);
+
+ return TRUE;
+#else /* G_OS_WIN32 */
+ int cl_argc;
+ char **full_argv;
+ gboolean spawned;
+
+ if (command_line)
+ {
+ int argc = 0;
+ char **cl_argv;
+
+ if (!g_shell_parse_argv(command_line, &cl_argc, &cl_argv, error))
+ return FALSE;
+
+ if (argv)
+ for (argc = 0; argv[argc]; argc++);
+
+ full_argv = g_renew(gchar *, cl_argv, cl_argc + argc + 1);
+ memcpy(full_argv + cl_argc, argv, argc * sizeof(gchar *));
+ full_argv[cl_argc + argc] = NULL;
+ }
+ else
+ full_argv = argv;
+
+ 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);
+
+ if (full_argv != argv)
+ {
+ full_argv[cl_argc] = NULL;
+ g_strfreev(full_argv);
+ }
+
+ return spawned;
+#endif /* G_OS_WIN32 */
+}
+
+
+/**
+ * Executes a child asynchronously.
+ *
+ * See @c spawn_async_with_pipes() for a full description; this function simply calls
+ * @c g_spawn_async_with_pipes() without any pipes.
+ *
+ * @since 1.25
+ **/
+GEANY_API_SYMBOL
+gboolean spawn_async(const gchar *working_directory, const gchar *command_line, gchar **argv,
+ gchar **envp, GPid *child_pid, GError **error)
+{
+ return spawn_async_with_pipes(working_directory, command_line, argv, envp, child_pid,
+ NULL, NULL, NULL, error);
+}
+
+
+/*
+ * Spawn with callbacks - general event sequence:
+ *
+ * - Launch the child.
+ * - Setup any I/O callbacks and a child watch callback.
+ * - On sync execution, run a main loop.
+ * - Wait for the child to terminate.
+ * - Check for active I/O sources. If any, add a timeout source to watch them, they should
+ * become inactive real soon now that the child is dead. Otherwise, finalize immediately.
+ * - In the timeout source: check for active I/O sources and finalize if none.
+ */
+
+typedef struct _SpawnChannelData
+{
+ GIOChannel *channel; /* NULL if not created / already destroyed */
+ union
+ {
+ GIOFunc write;
+ SpawnReadFunc read;
+ } cb;
+ gpointer cb_data;
+ /* stdout/stderr only */
+ GString *buffer; /* NULL if recursive */
+ GString *line_buffer; /* NULL if char buffered */
+ gsize max_length;
+} SpawnChannelData;
+
+
+static void spawn_destroy_cb(gpointer data)
+{
+ SpawnChannelData *sc = (SpawnChannelData *) data;
+
+ g_io_channel_shutdown(sc->channel, FALSE, NULL);
+ sc->channel = NULL;
+
+ if (sc->buffer)
+ g_string_free(sc->buffer, TRUE);
+
+ if (sc->line_buffer)
+ g_string_free(sc->line_buffer, TRUE);
+}
+
+
+static gboolean spawn_write_cb(GIOChannel *channel, GIOCondition condition, gpointer data)
+{
+ SpawnChannelData *sc = (SpawnChannelData *) data;
+
+ if (!sc->cb.write(channel, condition, sc->cb_data))
+ return FALSE;
+
+ return !(condition & G_IO_FAILURE);
+}
+
+
+static gboolean spawn_read_cb(GIOChannel *channel, GIOCondition condition, gpointer data)
+{
+ SpawnChannelData *sc = (SpawnChannelData *) data;
+ GString *line_buffer = sc->line_buffer;
+ GString *buffer = sc->buffer ? sc->buffer : g_string_sized_new(sc->max_length);
+ GIOCondition input_cond = condition & (G_IO_IN | G_IO_PRI);
+ GIOCondition failure_cond = condition & G_IO_FAILURE;
+ /*
+ * - Normally, read only once. With IO watches, our data processing must be immediate,
+ * which may give the child time to emit more data, and a read loop may combine it into
+ * large stdout and stderr portions. Under Windows, looping blocks.
+ * - On failure, read in a loop. It won't block now, there will be no more data, and the
+ * IO watch is not guaranteed to be called again (under Windows this is the last call).
+ */
+ if (input_cond)
+ {
+ gsize chars_read;
+ GIOStatus status;
+
+ if (line_buffer)
+ {
+ gsize n = line_buffer->len;
+
+ while ((status = g_io_channel_read_chars(channel, line_buffer->str + n,
+ DEFAULT_IO_LENGTH, &chars_read, NULL)) == G_IO_STATUS_NORMAL)
+ {
+ g_string_set_size(line_buffer, n + chars_read);
+
+ while (n < line_buffer->len)
+ {
+ gsize line_len = 0;
+
+ if (n == sc->max_length)
+ line_len = n;
+ else if (strchr("\n", line_buffer->str[n])) /* '\n' or '\0' */
+ line_len = n + 1;
+ else if (n < line_buffer->len - 1 && line_buffer->str[n] == '\r')
+ line_len = n + 1 + (line_buffer->str[n + 1] == '\n');
+
+ if (!line_len)
+ n++;
+ else
+ {
+ g_string_append_len(buffer, line_buffer->str, line_len);
+ g_string_erase(line_buffer, 0, line_len);
+ /* input only, failures are reported separately below */
+ sc->cb.read(buffer, input_cond, sc->cb_data);
+ g_string_truncate(buffer, 0);
+ n = 0;
+ }
+ }
+
+ if (!failure_cond)
+ break;
+ }
+ }
+ else
+ {
+ while ((status = g_io_channel_read_chars(channel, buffer->str, sc->max_length,
+ &chars_read, NULL)) == G_IO_STATUS_NORMAL)
+ {
+ g_string_set_size(buffer, chars_read);
+ /* input only, failures are reported separately below */
+ sc->cb.read(buffer, input_cond, sc->cb_data);
+
+ if (!failure_cond)
+ break;
+ }
+ }
+
+ /* Under OSX, after child death, the read watches receive input conditions instead
+ of error conditions, so we convert the termination statuses into conditions.
+ Should not hurt the other OS. */
+ if (status == G_IO_STATUS_ERROR)
+ failure_cond |= G_IO_ERR;
+ else if (status == G_IO_STATUS_EOF)
+ failure_cond |= G_IO_HUP;
+ }
+
+ if (failure_cond) /* we must signal the callback */
+ {
+ if (line_buffer && line_buffer->len) /* flush the line buffer */
+ {
+ g_string_append_len(buffer, line_buffer->str, line_buffer->len);
+ /* all data may be from a previous call */
+ if (!input_cond)
+ input_cond = G_IO_IN;
+ }
+ else
+ {
+ input_cond = 0;
+ g_string_truncate(buffer, 0);
+ }
+
+ sc->cb.read(buffer, input_cond | failure_cond, sc->cb_data);
+ }
+
+ if (buffer != sc->buffer)
+ g_string_free(buffer, TRUE);
+
+ return !failure_cond;
+}
+
+
+typedef struct _SpawnWatcherData
+{
+ SpawnChannelData sc[3]; /* stdin, stdout, stderr */
+ GChildWatchFunc exit_cb;
+ gpointer exit_data;
+ GPid pid;
+ gint exit_status;
+ GMainContext *main_context; /* NULL if async execution */
+ GMainLoop *main_loop; /* NULL if async execution */
+} SpawnWatcherData;
+
+
+static void spawn_finalize(SpawnWatcherData *sw)
+{
+ if (sw->exit_cb)
+ sw->exit_cb(sw->pid, sw->exit_status, sw->exit_data);
+
+ if (sw->main_loop)
+ {
+ g_main_loop_quit(sw->main_loop);
+ g_main_loop_unref(sw->main_loop);
+ }
+
+ g_spawn_close_pid(sw->pid);
+ g_slice_free(SpawnWatcherData, sw);
+}
+
+
+static gboolean spawn_timeout_cb(gpointer data)
+{
+ SpawnWatcherData *sw = (SpawnWatcherData *) data;
+ int i;
+
+ for (i = 0; i < 3; i++)
+ if (sw->sc[i].channel)
+ return TRUE;
+
+ spawn_finalize(sw);
+ return FALSE;
+}
+
+
+static void spawn_watch_cb(GPid pid, gint status, gpointer data)
+{
+ SpawnWatcherData *sw = (SpawnWatcherData *) data;
+ int i;
+
+ sw->pid = pid;
+ sw->exit_status = status;
+
+ for (i = 0; i < 3; i++)
+ {
+ if (sw->sc[i].channel)
+ {
+ GSource *source = g_timeout_source_new(50);
+
+ g_source_set_callback(source, spawn_timeout_cb, data, NULL);
+ g_source_attach(source, sw->main_context);
+ g_source_unref(source);
+ return;
+ }
+ }
+
+ spawn_finalize(sw);
+}
+
+
+/**
+ * Executes a child program and setups callbacks.
+ *
+ * A command line or an argument vector must be passed. If both are present, the argument
+ * vector is appended to the command line. An empty command line is not allowed.
+ *
+ * The synchronous execution may not be combined with recursive callbacks.
+ *
+ * In line buffered mode, the child input is broken on '\n', "\r\n", '\r', '\0' and max length.
+ *
+ * All I/O callbacks are guaranteed to be invoked at least once with @c G_IO_ERR, @c G_IO_HUP
+ * or @c G_IO_NVAL set (except for a @a stdin_cb which returns @c FALSE before that). For the
+ * non-recursive callbacks, this is guaranteed to be the last call, and may be used to free any
+ * resources associated with the callback.
+ *
+ * The @a stdin_cb may write to @c channel only once per invocation, only if @c G_IO_OUT is
+ * set, and only a non-zero number of characters.
+ *
+ * @c stdout_cb and @c stderr_cb may modify the received strings in any way, but must not
+ * free them.
+ *
+ * The default max lengths are 24K for line buffered stdout, 8K for line buffered stderr,
+ * 4K for unbuffered input under Unix, and 2K for unbuffered input under Windows.
+ *
+ * @c exit_cb is always invoked last, after all I/O callbacks.
+ *
+ * The @a child_pid will be closed automatically, after @a exit_cb is invoked.
+ *
+ * @param working_directory child's current working directory, or @c NULL.
+ * @param command_line child program and arguments, or @c NULL.
+ * @param argv child's argument vector, or @c NULL.
+ * @param envp child's environment, or @c NULL.
+ * @param spawn_flags flags from SpawnFlags.
+ * @param stdin_cb callback to send data to childs's stdin, or @c NULL.
+ * @param stdin_data data to pass to @a stdin_cb.
+ * @param stdout_cb callback to receive child's stdout, or @c NULL.
+ * @param stdout_data data to pass to @a stdout_cb.
+ * @param stdout_max_length maximum data length to pass to stdout_cb, @c 0 = default.
+ * @param stderr_cb callback to receive child's stderr, or @c NULL.
+ * @param stderr_data data to pass to @a stderr_cb.
+ * @param stderr_max_length maximum data length to pass to stderr_cb, @c 0 = default.
+ * @param exit_cb callback to invoke when the child exits, or @c NULL.
+ * @param exit_data data to pass to @a exit_cb.
+ * @param child_pid return location for child process ID, or @c NULL.
+ * @param error return location for error.
+ *
+ * @return @c TRUE on success, @c FALSE on error.
+ *
+ * @since 1.25
+ **/
+GEANY_API_SYMBOL
+gboolean spawn_with_callbacks(const gchar *working_directory, const gchar *command_line,
+ gchar **argv, gchar **envp, SpawnFlags spawn_flags, GIOFunc stdin_cb, gpointer stdin_data,
+ SpawnReadFunc stdout_cb, gpointer stdout_data, gsize stdout_max_length,
+ SpawnReadFunc stderr_cb, gpointer stderr_data, gsize stderr_max_length,
+ GChildWatchFunc exit_cb, gpointer exit_data, GPid *child_pid, GError **error)
+{
+ GPid pid;
+ int pipe[3] = { -1, -1, -1 };
+
+ g_return_val_if_fail(!(spawn_flags & SPAWN_RECURSIVE) || !(spawn_flags & SPAWN_SYNC),
+ FALSE);
+
+ if (spawn_async_with_pipes(working_directory, command_line, argv, envp, &pid,
+ stdin_cb ? &pipe[0] : NULL, stdout_cb ? &pipe[1] : NULL,
+ stderr_cb ? &pipe[2] : NULL, error))
+ {
+ SpawnWatcherData *sw = g_slice_new0(SpawnWatcherData);
+ gpointer cb_data[3] = { stdin_data, stdout_data, stderr_data };
+ GSource *source;
+ int i;
+
+ sw->main_context = spawn_flags & SPAWN_SYNC ? g_main_context_new() : NULL;
+
+ if (child_pid)
+ *child_pid = pid;
+
+ for (i = 0; i < 3; i++)
+ {
+ SpawnChannelData *sc = &sw->sc[i];
+ GIOCondition condition;
+ GSourceFunc callback;
+
+ if (pipe[i] == -1)
+ continue;
+
+ #ifdef G_OS_WIN32
+ sc->channel = g_io_channel_win32_new_fd(pipe[i]);
+ #else
+ sc->channel = g_io_channel_unix_new(pipe[i]);
+ g_io_channel_set_flags(sc->channel, G_IO_FLAG_NONBLOCK, NULL);
+ #endif
+ g_io_channel_set_encoding(sc->channel, NULL, NULL);
+ /* we have our own buffers, and GIO buffering blocks under Windows */
+ g_io_channel_set_buffered(sc->channel, FALSE);
+ sc->cb_data = cb_data[i];
+
+ if (i == 0)
+ {
+ sc->cb.write = stdin_cb;
+ condition = G_IO_OUT | G_IO_FAILURE;
+ callback = (GSourceFunc) spawn_write_cb;
+ }
+ else
+ {
+ gboolean line_buffered = !(spawn_flags &
+ ((SPAWN_STDOUT_UNBUFFERED >> 1) << i));
+
+ condition = G_IO_IN | G_IO_PRI | G_IO_FAILURE;
+ callback = (GSourceFunc) spawn_read_cb;
+
+ if (i == 1)
+ {
+ sc->cb.read = stdout_cb;
+ sc->max_length = stdout_max_length ? stdout_max_length :
+ line_buffered ? 24576 : DEFAULT_IO_LENGTH;
+ }
+ else
+ {
+ sc->cb.read = stderr_cb;
+ sc->max_length = stderr_max_length ? stderr_max_length :
+ line_buffered ? 8192 : DEFAULT_IO_LENGTH;
+ }
+
+ if (line_buffered)
+ {
+ sc->line_buffer = g_string_sized_new(sc->max_length +
+ DEFAULT_IO_LENGTH);
+ }
+ }
+
+ source = g_io_create_watch(sc->channel, condition);
+ g_io_channel_unref(sc->channel);
+
+ if (spawn_flags & (SPAWN_STDIN_RECURSIVE << i))
+ g_source_set_can_recurse(source, TRUE);
+ else if (i) /* to avoid new string on each call */
+ sc->buffer = g_string_sized_new(sc->max_length);
+
+ g_source_set_callback(source, callback, sc, spawn_destroy_cb);
+ g_source_attach(source, sw->main_context);
+ g_source_unref(source);
+ }
+
+ sw->exit_cb = exit_cb;
+ sw->exit_data = exit_data;
+ source = g_child_watch_source_new(pid);
+ g_source_set_callback(source, (GSourceFunc) spawn_watch_cb, sw, NULL);
+ g_source_attach(source, sw->main_context);
+ g_source_unref(source);
+
+ if (spawn_flags & SPAWN_SYNC)
+ {
+ sw->main_loop = g_main_loop_new(sw->main_context, FALSE);
+ g_main_context_unref(sw->main_context);
+ g_main_loop_run(sw->main_loop);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/**
+ * Writes (a portion of) the data pointed by @a data->ptr to the @a channel.
+ *
+ * If @c G_IO_OUT in @a condition is set, and the @a data->size is > 0, attempts to write
+ * @a data->ptr (or a portion of it, depending on the size) to the @a channel. On success,
+ * increases ptr and decreases size with the number of characters written.
+ *
+ * This function may converted to @c GIOFunc and passed to @c spawn_with_callbacks() as
+ * @c stdin_cb, together with a @c SpawnWriteData for @c stdin_data. As with any other
+ * callback data, make sure that @c stdin_data exists while the child is being executed.
+ * (For example, on asynchronous execution, you can allocate the data in the heap, and free
+ * it in your @c spawn_with_callbacks() @c exit_cb callback.)
+ *
+ * @return @c TRUE if the remaining size is > 0 and @a condition does not indicate any error,
+ * @c FALSE otherwise.
+ *
+ * @since 1.25
+ **/
+GEANY_API_SYMBOL
+gboolean spawn_write_data(GIOChannel *channel, GIOCondition condition, SpawnWriteData *data)
+{
+ if ((condition & G_IO_OUT) && data->size)
+ {
+ gsize chars_written = 0;
+
+ g_io_channel_write_chars(channel, data->ptr, data->size < DEFAULT_IO_LENGTH ?
+ data->size : DEFAULT_IO_LENGTH, &chars_written, NULL);
+
+ /* "This can be nonzero even if the return value is not G_IO_STATUS_NORMAL." */
+ if (chars_written)
+ {
+ data->ptr += chars_written;
+ data->size -= chars_written;
+ }
+ }
+
+ return data->size > 0 && !(condition & G_IO_FAILURE);
+}
+
+
+static void spawn_append_gstring_cb(GString *string, GIOCondition condition, gpointer data)
+{
+ if (condition & (G_IO_IN | G_IO_PRI))
+ g_string_append_len((GString *) data, string->str, string->len);
+}
+
+
+/**
+ * Convinience @c GChildWatchFunc callback that copies the child exit status into a gint
+ * pointed by @a exit_status.
+ *
+ * @since 1.25
+ **/
+GEANY_API_SYMBOL
+void spawn_get_exit_status_cb(G_GNUC_UNUSED GPid pid, gint status, gpointer exit_status)
+{
+ *(gint *) exit_status = status;
+}
+
+
+/**
+ * Executes a child synchronously.
+ *
+ * A command line or an argument vector must be passed. If both are present, the argument
+ * vector is appended to the command line. An empty command line is not allowed.
+ *
+ * The @a stdin_data is sent to the child with @c spawn_write_data().
+ *
+ * All output from the child, including the nul characters, is stored in @a stdout_data and
+ * @a stderr_data (if non-NULL). Any existing data in these strings will be erased.
+ *
+ * @param working_directory child's current working directory, or @c NULL.
+ * @param command_line child program and arguments, or @c NULL.
+ * @param argv child's argument vector, or @c NULL.
+ * @param envp child's environment, or @c NULL.
+ * @param stdin_data data to send to childs's stdin, or @c NULL.
+ * @param stdout_data GString location to receive the child's stdout, or NULL.
+ * @param stderr_data GString location to receive the child's stderr, or NULL.
+ * @param exit_status return location for the child exit code, or NULL.
+ * @param error return location for error.
+ *
+ * @return @c TRUE on success, @c FALSE on error.
+ *
+ * @since 1.25
+ **/
+GEANY_API_SYMBOL
+gboolean spawn_sync(const gchar *working_directory, const gchar *command_line, gchar **argv,
+ gchar **envp, SpawnWriteData *stdin_data, GString *stdout_data, GString *stderr_data,
+ gint *exit_status, GError **error)
+{
+ g_string_truncate(stdout_data, 0);
+ g_string_truncate(stderr_data, 0);
+
+ return spawn_with_callbacks(working_directory, command_line, argv, envp, SPAWN_SYNC |
+ SPAWN_UNBUFFERED, stdin_data ? (GIOFunc) spawn_write_data : NULL, stdin_data,
+ stdout_data ? spawn_append_gstring_cb : NULL, stdout_data, 0,
+ stderr_data ? spawn_append_gstring_cb : NULL, stderr_data, 0,
+ exit_status ? spawn_get_exit_status_cb : NULL, exit_status, NULL, error);
+}
+
+
+/* tests, not part of the API */
+#ifdef SPAWN_TEST
+#include <stdio.h>
+
+
+static gboolean read_line(const char *prompt, char *buffer, size_t size)
+{
+ fputs(prompt, stderr);
+ *buffer = '\0';
+
+ if (fgets(buffer, size, stdin))
+ {
+ char *s = strchr(buffer, '\n');
+
+ if (s)
+ *s = '\0';
+ }
+
+ return *buffer;
+}
+
+
+static GString *read_string(const char *prompt)
+{
+ char buffer[0x1000]; /* larger portions for spawn < file */
+ GString *string = g_string_sized_new(sizeof buffer);
+
+ while (read_line(prompt, buffer, sizeof buffer))
+ {
+ if (string->len)
+ g_string_append_c(string, '\n');
+
+ g_string_append(string, buffer);
+ }
+
+ if (!string->len)
+ {
+ g_string_free(string, TRUE);
+ string = NULL;
+ }
+
+ return string;
+}
+
+
+static void print_cb(GString *string, GIOCondition condition, gpointer data)
+{
+ if (condition & (G_IO_IN | G_IO_PRI))
+ {
+ gsize i;
+
+ printf("%s: ", (const gchar *) data);
+ /*fputs(string->str, stdout);*/
+ for (i = 0; i < string->len; i++)
+ {
+ unsigned char c = (unsigned char) string->str[i];
+ printf(c >= ' ' && c < 0x80 ? "%c" : "\\x%02x", c);
+ }
+ putchar('\n');
+ }
+}
+
+
+static void print_status(gint status)
+{
+ fputs("finished, ", stderr);
+
+ if (WIFEXITED(status))
+ fprintf(stderr, "exit code %d\n", WEXITSTATUS(status));
+ else
+ fputs("abnormal termination\n", stderr);
+}
+
+
+static void exit_cb(GPid pid, gint status, G_GNUC_UNUSED gpointer data)
+{
+ fprintf(stderr, "process %u ", (guint) pid);
+ print_status(status);
+}
+
+
+static void watch_cb(GPid pid, gint status, gpointer data)
+{
+ g_spawn_close_pid(pid);
+ exit_cb(pid, status, NULL);
+ g_main_loop_quit((GMainLoop *) data);
+}
+
+
+int main(int argc, char **argv)
+{
+ char *test_type;
+
+ if (argc != 2)
+ {
+ fputs("usage: spawn <test-type>\n", stderr);
+ return 1;
+ }
+
+ test_type = argv[1];
+
+ if (!strcmp(test_type, "syntax") || !strcmp(test_type, "syntexec"))
+ {
+ char command_line[0x100];
+
+ while (read_line("command line: ", command_line, sizeof command_line))
+ {
+ GError *error = NULL;
+
+ if (spawn_check_command(command_line, argv[1][4] == 'e', &error))
+ fputs("valid\n", stderr);
+ else
+ {
+ fprintf(stderr, "error: %s\n", error->message);
+ g_error_free(error);
+ }
+ }
+ }
+ else if (!strcmp(test_type, "execute"))
+ {
+ char command_line[0x100];
+
+ while (read_line("command line: ", command_line, sizeof command_line))
+ {
+ char working_directory[0x100];
+ char args[4][0x100];
+ char envs[4][0x100];
+ char *argv[] = { args[0], args[1], args[2], args[3], NULL };
+ char *envp[] = { envs[0], envs[1], envs[2], envs[3], NULL };
+ int i;
+ GPid pid;
+ GError *error = NULL;
+
+ read_line("working directory: ", working_directory, sizeof working_directory);
+
+ fputs("up to 4 arguments\n", stderr);
+ for (i = 0; i < 4 && read_line("argument: ", args[i], sizeof args[i]); i++);
+ argv[i] = NULL;
+
+ fputs("up to 4 variables, or empty line for parent environment\n", stderr);
+ for (i = 0; i < 4 && read_line("variable: ", envs[i], sizeof envs[i]); i++);
+ envp[i] = NULL;
+
+ if (spawn_async_with_pipes(*working_directory ? working_directory : NULL,
+ *command_line ? command_line : NULL, argv, i ? envp : NULL, &pid, NULL,
+ NULL, NULL, &error))
+ {
+ GMainLoop *loop = g_main_loop_new(NULL, TRUE);
+
+ g_child_watch_add(pid, watch_cb, loop);
+ g_main_loop_run(loop);
+ g_main_loop_unref(loop);
+ }
+ else
+ {
+ fprintf(stderr, "error: %s\n", error->message);
+ g_error_free(error);
+ }
+ }
+ }
+ else if (!strcmp(test_type, "redirect") || !strcmp(test_type, "redinput"))
+ {
+ char command_line[0x100];
+ gboolean output = test_type[4] == 'r';
+
+ while (read_line("command line: ", command_line, sizeof command_line))
+ {
+ GString *stdin_text = read_string("text to send: ");
+ SpawnWriteData stdin_data;
+ GError *error = NULL;
+
+ if (stdin_text)
+ {
+ stdin_data.ptr = stdin_text->str;
+ stdin_data.size = stdin_text->len;
+ }
+
+ if (!spawn_with_callbacks(NULL, command_line, NULL, NULL, SPAWN_SYNC,
+ stdin_text ? (GIOFunc) spawn_write_data : NULL, &stdin_data,
+ output ? print_cb : NULL, "stdout", 0, output ? print_cb : NULL,
+ "stderr", 0, exit_cb, NULL, NULL, &error))
+ {
+ fprintf(stderr, "error: %s\n", error->message);
+ g_error_free(error);
+ }
+
+ if (stdin_text)
+ g_string_free(stdin_text, TRUE);
+ }
+ }
+ else if (!strcmp(test_type, "capture"))
+ {
+ char command_line[0x100];
+
+ while (read_line("command line: ", command_line, sizeof command_line))
+ {
+ GString *stdin_text = read_string("text to send: ");
+ SpawnWriteData stdin_data = { NULL, 0 };
+ GString *stdout_data = g_string_sized_new(0x10000); /* may grow */
+ GString *stderr_data = g_string_sized_new(0x1000); /* may grow */
+ gint exit_status;
+ GError *error = NULL;
+
+ if (stdin_text)
+ {
+ stdin_data.ptr = stdin_text->str;
+ stdin_data.size = stdin_text->len;
+ }
+
+ if (spawn_sync(NULL, command_line, NULL, NULL, &stdin_data, stdout_data,
+ stderr_data, &exit_status, &error))
+ {
+ printf("stdout: %s\n", stdout_data->str);
+ printf("stderr: %s\n", stderr_data->str);
+ print_status(exit_status);
+ }
+ else
+ {
+ fprintf(stderr, "error: %s\n", error->message);
+ g_error_free(error);
+ }
+
+ if (stdin_text)
+ g_string_free(stdin_text, TRUE);
+
+ g_string_free(stdout_data, TRUE);
+ g_string_free(stderr_data, TRUE);
+ }
+ }
+ else
+ {
+ fprintf(stderr, "spawn: unknown test type '%s'", argv[1]);
+ return 1;
+ }
+
+ return 0;
+}
+#endif /* SPAWN_TEST */
Modified: src/spawn.h
107 lines changed, 107 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,107 @@
+/*
+ * spawn.h - this file is part of Geany, a fast and lightweight IDE
+ *
+ * Copyright 2013 Dimitar Toshkov Zhekov <dimitar(dot)zhekov(at)gmail(dot)com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#ifndef GEANY_SPAWN_H
+#define GEANY_SPAWN_H 1
+
+#include <glib.h>
+
+#ifdef G_OS_WIN32
+# define WIFEXITED(status) TRUE
+# define WEXITSTATUS(status) (status)
+# define WIFSIGNALLED(status) FALSE
+#else
+# include <sys/types.h>
+# include <sys/wait.h>
+#endif
+
+gchar *spawn_get_program_name(const gchar *command_line, GError **error);
+
+gboolean spawn_check_command(const gchar *command_line, gboolean execute, GError **error);
+
+gboolean spawn_kill_process(GPid pid, GError **error);
+
+gboolean spawn_async_with_pipes(const gchar *working_directory, const gchar *command_line,
+ gchar **argv, gchar **envp, GPid *child_pid, gint *stdin_fd, gint *stdout_fd,
+ gint *stderr_fd, GError **error);
+
+gboolean spawn_async(const gchar *working_directory, const gchar *command_line, gchar **argv,
+ gchar **envp, GPid *child_pid, GError **error);
+
+/** Flags passed to @c spawn_with_callbacks(), which see. */
+typedef enum
+{
+ SPAWN_ASYNC = 0x00, /**< Asynchronous execution [default]. */
+ SPAWN_SYNC = 0x01, /**< Synchronous execution. */
+ /* buffering modes */
+ SPAWN_LINE_BUFFERED = 0x00, /**< stdout/stderr are line buffered [default]. */
+ SPAWN_STDOUT_UNBUFFERED = 0x02, /**< stdout is not buffered. */
+ SPAWN_STDERR_UNBUFFERED = 0x04, /**< stderr is not buffered. */
+ SPAWN_UNBUFFERED = 0x06, /**< stdout/stderr are not buffered. */
+ /* recursive modes */
+ SPAWN_STDIN_RECURSIVE = 0x08, /**< The stdin callback is recursive. */
+ SPAWN_STDOUT_RECURSIVE = 0x10, /**< The stdout callback is recursive. */
+ SPAWN_STDERR_RECURSIVE = 0x20, /**< The stderr callback is recursive. */
+ SPAWN_RECURSIVE = 0x38 /**< All callbacks are recursive. */
+} SpawnFlags;
+
+/**
+ * Specifies the type of function passed to @c spawn_with_callbacks() as stdout or stderr
+ * callback.
+ *
+ * In unbuffered mode, the @a string may contain nuls, while in line buffered mode, it may
+ * contain only a single nul as a line termination character at @a string->len - 1. In all
+ * cases, the @a string will be terminated with a nul character that is not part of the data
+ * at @a string->len.
+ *
+ * If @c G_IO_IN or @c G_IO_PRI are set, the @a string will contain at least one character.
+ *
+ * @param string contains the child data if @c G_IO_IN or @c G_IO_PRI are set.
+ * @param condition the I/O condition which has been satisfied.
+ * @param data the passed to @c spawn_with_callbacks() with the callback.
+ */
+typedef void (*SpawnReadFunc)(GString *string, GIOCondition condition, gpointer data);
+
+gboolean spawn_with_callbacks(const gchar *working_directory, const gchar *command_line,
+ gchar **argv, gchar **envp, SpawnFlags spawn_flags, GIOFunc stdin_cb, gpointer stdin_data,
+ SpawnReadFunc stdout_cb, gpointer stdout_data, gsize stdout_max_length,
+ SpawnReadFunc stderr_cb, gpointer stderr_data, gsize stderr_max_length,
+ GChildWatchFunc exit_cb, gpointer exit_data, GPid *child_pid, GError **error);
+
+/**
+ * A simple structure used by @c spawn_write_data() to write data to a channel.
+ * See @c spawn_write_data() for more information.
+ */
+typedef struct _SpawnWriteData
+{
+ const gchar *ptr; /**< Pointer to the data. May be NULL if the size is 0. */
+ gsize size; /**< Size of the data. */
+} SpawnWriteData;
+
+gboolean spawn_write_data(GIOChannel *channel, GIOCondition condition, SpawnWriteData *data);
+
+void spawn_get_exit_status_cb(GPid pid, gint status, gpointer exit_status);
+
+gboolean spawn_sync(const gchar *working_directory, const gchar *command_line, gchar **argv,
+ gchar **envp, SpawnWriteData *stdin_data, GString *stdout_data, GString *stderr_data,
+ gint *exit_status, GError **error);
+
+#endif /* GEANY_SPAWN_H */
Modified: src/templates.c
44 lines changed, 22 insertions(+), 22 deletions(-)
===================================================================
@@ -37,6 +37,7 @@
#include "geany.h"
#include "geanymenubuttonaction.h"
#include "geanyobject.h"
+#include "spawn.h"
#include "support.h"
#include "toolbar.h"
#include "ui_utils.h"
@@ -595,33 +596,32 @@ static void templates_replace_default_dates(GString *text)
static gchar *run_command(const gchar *command, const gchar *file_name,
const gchar *file_type, const gchar *func_name)
{
+ GString *output = g_string_new(NULL);
gchar *result = NULL;
- gchar **argv;
+ GError *error = NULL;
+ gchar **env;
- if (g_shell_parse_argv(command, NULL, &argv, NULL))
- {
- GError *error = NULL;
- gchar **env;
+ file_name = (file_name != NULL) ? file_name : "";
+ file_type = (file_type != NULL) ? file_type : "";
+ func_name = (func_name != NULL) ? func_name : "";
- file_name = (file_name != NULL) ? file_name : "";
- file_type = (file_type != NULL) ? file_type : "";
- func_name = (func_name != NULL) ? func_name : "";
+ env = utils_copy_environment(NULL,
+ "GEANY_FILENAME", file_name,
+ "GEANY_FILETYPE", file_type,
+ "GEANY_FUNCNAME", func_name,
+ NULL);
- env = utils_copy_environment(NULL,
- "GEANY_FILENAME", file_name,
- "GEANY_FILETYPE", file_type,
- "GEANY_FUNCNAME", func_name,
- NULL);
- if (! utils_spawn_sync(NULL, argv, env, G_SPAWN_SEARCH_PATH,
- NULL, NULL, &result, NULL, NULL, &error))
- {
- g_warning("templates_replace_command: %s", error->message);
- g_error_free(error);
- result = NULL;
- }
- g_strfreev(argv);
- g_strfreev(env);
+ if (spawn_sync(NULL, command, NULL, env, NULL, output, NULL, NULL, &error))
+ {
+ result = g_string_free(output, FALSE);
}
+ else
+ {
+ g_warning("templates_replace_command: %s", error->message);
+ g_error_free(error);
+ }
+
+ g_strfreev(env);
return result;
}
Modified: src/tools.c
264 lines changed, 36 insertions(+), 228 deletions(-)
===================================================================
@@ -33,6 +33,7 @@
#include "document.h"
#include "keybindings.h"
#include "sciwrappers.h"
+#include "spawn.h"
#include "support.h"
#include "ui_utils.h"
#include "utils.h"
@@ -45,12 +46,6 @@
#include <string.h>
#include <errno.h>
-#ifdef G_OS_UNIX
-# include <sys/types.h>
-# include <sys/wait.h>
-# include <signal.h>
-#endif
-
enum
{
@@ -76,26 +71,6 @@ struct cc_dialog
GtkWidget *button_down;
};
-/* data required by the custom command callbacks */
-struct cc_data
-{
- const gchar *command; /* command launched */
- GeanyDocument *doc; /* document in which replace the selection */
- GString *buffer; /* buffer holding stdout content, or NULL */
- gboolean error; /* whether and error occurred */
- gboolean finished; /* whether the command has finished */
-};
-
-
-static gboolean cc_exists_command(const gchar *command)
-{
- gchar *path = g_find_program_in_path(command);
-
- g_free(path);
-
- return path != NULL;
-}
-
/* update STATUS and TOOLTIP columns according to cmd */
static void cc_dialog_update_row_status(GtkListStore *store, GtkTreeIter *iter, const gchar *cmd)
@@ -103,19 +78,9 @@ static void cc_dialog_update_row_status(GtkListStore *store, GtkTreeIter *iter,
GError *err = NULL;
const gchar *stock_id = GTK_STOCK_NO;
gchar *tooltip = NULL;
- gint argc;
- gchar **argv;
- if (EMPTY(cmd))
+ if (EMPTY(cmd) || spawn_check_command(cmd, TRUE, &err))
stock_id = GTK_STOCK_YES;
- else if (g_shell_parse_argv(cmd, &argc, &argv, &err))
- {
- if (argc > 0 && cc_exists_command(argv[0]))
- stock_id = GTK_STOCK_YES;
- else
- tooltip = g_strdup_printf(_("Invalid command: %s"), _("Command not found"));
- g_strfreev(argv);
- }
else
{
tooltip = g_strdup_printf(_("Invalid command: %s"), err->message);
@@ -227,218 +192,61 @@ static void cc_on_dialog_move_down_clicked(GtkButton *button, struct cc_dialog *
}
-static gboolean cc_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer user_data)
+/* Executes command (which should include all necessary command line args) and passes the current
+ * selection through the standard input of command. The whole output of command replaces the
+ * current selection. */
+void tools_execute_custom_command(GeanyDocument *doc, const gchar *command)
{
- struct cc_data *data = user_data;
-
- if (cond & (G_IO_IN | G_IO_PRI))
- {
- gchar *msg = NULL;
- GIOStatus rv;
- GError *err = NULL;
-
- if (! data->buffer)
- data->buffer = g_string_sized_new(256);
-
- do
- {
- rv = g_io_channel_read_line(ioc, &msg, NULL, NULL, &err);
- if (msg != NULL)
- {
- g_string_append(data->buffer, msg);
- g_free(msg);
- }
- if (G_UNLIKELY(err != NULL))
- {
- geany_debug("%s: %s", G_STRFUNC, err->message);
- g_error_free(err);
- err = NULL;
- }
- } while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
-
- if (G_UNLIKELY(rv != G_IO_STATUS_EOF))
- { /* Something went wrong? */
- g_warning("%s: %s\n", G_STRFUNC, "Incomplete command output");
- }
- }
- return FALSE;
-}
-
+ GError *error = NULL;
+ gchar *sel;
+ SpawnWriteData input;
+ GString *output;
+ GString *errors;
+ gint status;
+
+ g_return_if_fail(doc != NULL && command != NULL);
+
+ if (! sci_has_selection(doc->editor->sci))
+ editor_select_lines(doc->editor, FALSE);
-static gboolean cc_iofunc_err(GIOChannel *ioc, GIOCondition cond, gpointer user_data)
-{
- struct cc_data *data = user_data;
+ sel = sci_get_selection_contents(doc->editor->sci);
+ input.ptr = sel;
+ input.size = strlen(sel);
+ output = g_string_sized_new(256);
+ errors = g_string_new(NULL);
+ ui_set_statusbar(TRUE, _("Passing data and executing custom command: %s"), command);
- if (cond & (G_IO_IN | G_IO_PRI))
+ if (spawn_sync(NULL, command, NULL, NULL, &input, output, errors, &status, &error))
{
- gchar *msg = NULL;
- GString *str = g_string_sized_new(256);
- GIOStatus rv;
-
- do
- {
- rv = g_io_channel_read_line(ioc, &msg, NULL, NULL, NULL);
- if (msg != NULL)
- {
- g_string_append(str, msg);
- g_free(msg);
- }
- } while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
-
- if (!EMPTY(str->str))
+ if (errors->len > 0)
{
- g_warning("%s: %s\n", data->command, str->str);
+ g_warning("%s: %s\n", command, errors->str);
ui_set_statusbar(TRUE,
_("The executed custom command returned an error. "
"Your selection was not changed. Error message: %s"),
- str->str);
- data->error = TRUE;
-
+ errors->str);
}
- g_string_free(str, TRUE);
- }
- data->finished = TRUE;
- return FALSE;
-}
-
-
-static gboolean cc_replace_sel_cb(gpointer user_data)
-{
- struct cc_data *data = user_data;
-
- if (! data->finished)
- { /* keep this function in the main loop until cc_iofunc_err() has finished */
- return TRUE;
- }
-
- if (! data->error && data->buffer != NULL && DOC_VALID(data->doc))
- { /* Command completed successfully */
- sci_replace_sel(data->doc->editor->sci, data->buffer->str);
- }
-
- if (data->buffer)
- g_string_free(data->buffer, TRUE);
- g_slice_free1(sizeof *data, data);
-
- return FALSE;
-}
-
-
-/* check whether the executed command failed and if so do nothing.
- * If it returned with a successful exit code, replace the selection. */
-static void cc_exit_cb(GPid child_pid, gint status, gpointer user_data)
-{
- struct cc_data *data = user_data;
-
- /* if there was already an error, skip further checks */
- if (! data->error)
- {
-#ifdef G_OS_UNIX
- if (WIFEXITED(status))
+ else if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS)
{
- if (WEXITSTATUS(status) != EXIT_SUCCESS)
- data->error = TRUE;
- }
- else if (WIFSIGNALED(status))
- { /* the terminating signal: WTERMSIG (status)); */
- data->error = TRUE;
- }
- else
- { /* any other failure occurred */
- data->error = TRUE;
- }
-#else
- data->error = ! win32_get_exit_status(child_pid);
-#endif
-
- if (data->error)
- { /* here we are sure data->error was set due to an unsuccessful exit code
- * and so we add an error message */
/* TODO maybe include the exit code in the error message */
ui_set_statusbar(TRUE,
_("The executed custom command exited with an unsuccessful exit code."));
}
- }
-
- g_idle_add(cc_replace_sel_cb, data);
- g_spawn_close_pid(child_pid);
-}
-
-
-/* Executes command (which should include all necessary command line args) and passes the current
- * selection through the standard input of command. The whole output of command replaces the
- * current selection. */
-void tools_execute_custom_command(GeanyDocument *doc, const gchar *command)
-{
- GError *error = NULL;
- GPid pid;
- gchar **argv;
- gint stdin_fd;
- gint stdout_fd;
- gint stderr_fd;
-
- g_return_if_fail(DOC_VALID(doc) && command != NULL);
-
- if (! sci_has_selection(doc->editor->sci))
- editor_select_lines(doc->editor, FALSE);
-
- if (!g_shell_parse_argv(command, NULL, &argv, &error))
- {
- ui_set_statusbar(TRUE, _("Custom command failed: %s"), error->message);
- g_error_free(error);
- return;
- }
- ui_set_statusbar(TRUE, _("Passing data and executing custom command: %s"), command);
-
- if (g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
- NULL, NULL, &pid, &stdin_fd, &stdout_fd, &stderr_fd, &error))
- {
- gchar *sel;
- gint remaining, wrote;
- struct cc_data *data = g_slice_alloc(sizeof *data);
-
- data->error = FALSE;
- data->finished = FALSE;
- data->buffer = NULL;
- data->doc = doc;
- data->command = command;
-
- g_child_watch_add(pid, cc_exit_cb, data);
-
- /* use GIOChannel to monitor stdout */
- utils_set_up_io_channel(stdout_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- FALSE, cc_iofunc, data);
- /* copy program's stderr to Geany's stdout to help error tracking */
- utils_set_up_io_channel(stderr_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- FALSE, cc_iofunc_err, data);
-
- /* get selection */
- sel = sci_get_selection_contents(doc->editor->sci);
-
- /* write data to the command */
- remaining = strlen(sel);
- do
- {
- wrote = write(stdin_fd, sel, remaining);
- if (G_UNLIKELY(wrote < 0))
- {
- g_warning("%s: %s: %s\n", G_STRFUNC, "Failed sending data to command",
- g_strerror(errno));
- break;
- }
- remaining -= wrote;
- } while (remaining > 0);
- close(stdin_fd);
- g_free(sel);
+ else
+ { /* Command completed successfully */
+ sci_replace_sel(doc->editor->sci, output->str);
+ }
}
else
{
- geany_debug("g_spawn_async_with_pipes() failed: %s", error->message);
+ geany_debug("spawn_sync() failed: %s", error->message);
ui_set_statusbar(TRUE, _("Custom command failed: %s"), error->message);
g_error_free(error);
}
- g_strfreev(argv);
+ g_string_free(output, TRUE);
+ g_string_free(errors, TRUE);
+ g_free(sel);
}
Modified: src/utils.c
80 lines changed, 24 insertions(+), 56 deletions(-)
===================================================================
@@ -34,6 +34,7 @@
#include "document.h"
#include "prefs.h"
#include "sciwrappers.h"
+#include "spawn.h"
#include "support.h"
#include "templates.h"
#include "ui_utils.h"
@@ -76,29 +77,21 @@ void utils_open_browser(const gchar *uri)
g_return_if_fail(uri != NULL);
win32_open_browser(uri);
#else
- gboolean again = TRUE;
+ gchar *argv[2] = { (gchar *) uri, NULL };
g_return_if_fail(uri != NULL);
- while (again)
+ while (!spawn_async(NULL, tool_prefs.browser_cmd, argv, NULL, NULL, NULL))
{
- gchar *cmdline = g_strconcat(tool_prefs.browser_cmd, " \"", uri, "\"", NULL);
+ gchar *new_cmd = dialogs_show_input(_("Select Browser"), GTK_WINDOW(main_widgets.window),
+ _("Failed to spawn the configured browser command. "
+ "Please correct it or enter another one."),
+ tool_prefs.browser_cmd);
- if (g_spawn_command_line_async(cmdline, NULL))
- again = FALSE;
- else
- {
- gchar *new_cmd = dialogs_show_input(_("Select Browser"), GTK_WINDOW(main_widgets.window),
- _("Failed to spawn the configured browser command. "
- "Please correct it or enter another one."),
- tool_prefs.browser_cmd);
+ if (new_cmd == NULL) /* user canceled */
+ break;
- if (new_cmd == NULL) /* user canceled */
- again = FALSE;
- else
- SETPTR(tool_prefs.browser_cmd, new_cmd);
- }
- g_free(cmdline);
+ SETPTR(tool_prefs.browser_cmd, new_cmd);
}
#endif
}
@@ -1651,15 +1644,14 @@ const gchar *utils_get_default_dir_utf8(void)
/**
- * Wraps g_spawn_sync() and internally calls this function on Unix-like
- * systems. On Win32 platforms, it uses the Windows API.
+ * Wraps @c spawn_sync(), which see.
*
* @param dir The child's current working directory, or @a NULL to inherit parent's.
* @param argv The child's argument vector.
* @param env The child's environment, or @a NULL to inherit parent's.
- * @param flags Flags from GSpawnFlags.
- * @param child_setup A function to run in the child just before exec().
- * @param user_data The user data for child_setup.
+ * @param flags Ignored.
+ * @param child_setup Ignored.
+ * @param user_data Ignored.
* @param std_out The return location for child output, or @a NULL.
* @param std_err The return location for child error messages, or @a NULL.
* @param exit_status The child exit status, as returned by waitpid(), or @a NULL.
@@ -1672,40 +1664,29 @@ gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFla
GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out,
gchar **std_err, gint *exit_status, GError **error)
{
- gboolean result;
-
- if (argv == NULL)
- {
- g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "argv must not be NULL");
- return FALSE;
- }
+ GString *output = std_out ? g_string_new(NULL) : NULL;
+ GString *errors = std_err ? g_string_new(NULL) : NULL;
+ gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
if (std_out)
- *std_out = NULL;
+ *std_out = g_string_free(output, !result);
if (std_err)
- *std_err = NULL;
-
-#ifdef G_OS_WIN32
- result = win32_spawn(dir, argv, env, flags, std_out, std_err, exit_status, error);
-#else
- result = g_spawn_sync(dir, argv, env, flags, NULL, NULL, std_out, std_err, exit_status, error);
-#endif
+ *std_err = g_string_free(errors, !result);
return result;
}
/**
- * Wraps g_spawn_async() and internally calls this function on Unix-like
- * systems. On Win32 platforms, it uses the Windows API.
+ * Wraps @c spawn_async(), which see.
*
* @param dir The child's current working directory, or @a NULL to inherit parent's.
* @param argv The child's argument vector.
* @param env The child's environment, or @a NULL to inherit parent's.
- * @param flags Flags from GSpawnFlags.
- * @param child_setup A function to run in the child just before exec().
- * @param user_data The user data for child_setup.
+ * @param flags Ignored.
+ * @param child_setup Ignored.
+ * @param user_data Ignored.
* @param child_pid The return location for child process ID, or NULL.
* @param error The return location for error or @a NULL.
*
@@ -1716,20 +1697,7 @@ gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFl
GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
GError **error)
{
- gboolean result;
-
- if (argv == NULL)
- {
- g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "argv must not be NULL");
- return FALSE;
- }
-
-#ifdef G_OS_WIN32
- result = win32_spawn(dir, argv, env, flags, NULL, NULL, NULL, error);
-#else
- result = g_spawn_async(dir, argv, env, flags, NULL, NULL, child_pid, error);
-#endif
- return result;
+ return spawn_async(dir, NULL, argv, env, child_pid, error);
}
Modified: src/win32.c
493 lines changed, 0 insertions(+), 493 deletions(-)
===================================================================
@@ -57,31 +57,6 @@
#include <glib/gstdio.h>
#include <gdk/gdkwin32.h>
-#define BUFSIZE 4096
-#define CMDSIZE 32768
-
-struct _geany_win32_spawn
-{
- HANDLE hChildStdinRd;
- HANDLE hChildStdinWr;
- HANDLE hChildStdoutRd;
- HANDLE hChildStdoutWr;
- HANDLE hChildStderrRd;
- HANDLE hChildStderrWr;
- HANDLE hInputFile;
- HANDLE hStdout;
- HANDLE hStderr;
- HANDLE processId;
- DWORD dwExitCode;
-};
-typedef struct _geany_win32_spawn geany_win32_spawn;
-
-static gboolean GetContentFromHandle(HANDLE hFile, gchar **content, GError **error);
-static HANDLE GetTempFileHandle(GError **error);
-static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline,
- const TCHAR *dir, GError **error);
-static VOID ReadFromPipe(HANDLE hRead, HANDLE hWrite, HANDLE hFile, GError **error);
-
/* The timer handle used to refresh windows below modal native dialogs. If
* ever more than one dialog can be shown at a time, this needs to be changed
@@ -837,17 +812,6 @@ void win32_open_browser(const gchar *uri)
}
-/* Returns TRUE if the command, which child_pid refers to, returned with a successful exit code,
- * otherwise FALSE. */
-gboolean win32_get_exit_status(GPid child_pid)
-{
- DWORD exit_code;
- GetExitCodeProcess(child_pid, &exit_code);
-
- return (exit_code == 0);
-}
-
-
static FILE *open_std_handle(DWORD handle, const char *mode)
{
HANDLE lStdHandle;
@@ -929,299 +893,7 @@ void win32_init_debug_code(void)
/* create a console window to get log messages on Windows,
* especially useful when generating tags files */
debug_setup_console();
- /* Enable GLib process spawn debug mode when Geany was started with the debug flag */
- g_setenv("G_SPAWN_WIN32_DEBUG", "1", FALSE);
- }
-}
-
-
-static gchar *create_temp_file(void)
-{
- gchar *name;
- gint fd;
-
- fd = g_file_open_tmp("tmp_XXXXXX", &name, NULL);
- if (fd == -1)
- name = NULL;
- else
- close(fd);
-
- return name;
-}
-
-
-/* Sometimes this blocks for 30s before aborting when there are several
- * pages of (error) output and sometimes hangs - see the FIXME.
- * Also gw_spawn.dwExitCode seems to be not set properly. */
-/* Process spawning implementation for Windows, by Pierre Joye.
- * Don't call this function directly, use utils_spawn_[a]sync() instead. */
-static
-gboolean _broken_win32_spawn(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
- gchar **std_out, gchar **std_err, gint *exit_status, GError **error)
-{
- TCHAR buffer[CMDSIZE]=TEXT("");
- TCHAR cmdline[CMDSIZE] = TEXT("");
- TCHAR* lpPart[CMDSIZE]={NULL};
- DWORD retval = 0;
- gint argc = 0, i;
- gint cmdpos = 0;
-
- SECURITY_ATTRIBUTES saAttr;
- BOOL fSuccess;
- geany_win32_spawn gw_spawn;
-
- /* Temp file */
- HANDLE hStdoutTempFile = NULL;
- HANDLE hStderrTempFile = NULL;
-
- gchar *stdout_content = NULL;
- gchar *stderr_content = NULL;
-
- while (argv[argc])
- {
- ++argc;
- }
- g_return_val_if_fail (std_out == NULL ||
- !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
- g_return_val_if_fail (std_err == NULL ||
- !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
-
- if (flags & G_SPAWN_SEARCH_PATH)
- {
- retval = SearchPath(NULL, argv[0], ".exe", sizeof(buffer), buffer, lpPart);
- if (retval > 0)
- g_snprintf(cmdline, sizeof(cmdline), "\"%s\"", buffer);
- else
- g_strlcpy(cmdline, argv[0], sizeof(cmdline));
- cmdpos = 1;
- }
-
- for (i = cmdpos; i < argc; i++)
- {
- g_snprintf(cmdline, sizeof(cmdline), "%s %s", cmdline, argv[i]);
- /*MessageBox(NULL, cmdline, cmdline, MB_OK);*/
- }
-
- if (std_err != NULL)
- {
- hStderrTempFile = GetTempFileHandle(error);
- if (hStderrTempFile == INVALID_HANDLE_VALUE)
- {
- gchar *msg = g_win32_error_message(GetLastError());
- geany_debug("win32_spawn: Second CreateFile failed (%d)", (gint) GetLastError());
- g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
- g_free(msg);
- return FALSE;
- }
- }
-
- if (std_out != NULL)
- {
- hStdoutTempFile = GetTempFileHandle(error);
- if (hStdoutTempFile == INVALID_HANDLE_VALUE)
- {
- gchar *msg = g_win32_error_message(GetLastError());
- geany_debug("win32_spawn: Second CreateFile failed (%d)", (gint) GetLastError());
- g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
- g_free(msg);
- return FALSE;
- }
- }
-
- /* Set the bInheritHandle flag so pipe handles are inherited. */
- saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
- saAttr.bInheritHandle = TRUE;
- saAttr.lpSecurityDescriptor = NULL;
-
- /* Get the handle to the current STDOUT and STDERR. */
- gw_spawn.hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
- gw_spawn.hStderr = GetStdHandle(STD_ERROR_HANDLE);
- gw_spawn.dwExitCode = 0;
-
- /* Create a pipe for the child process's STDOUT. */
- if (! CreatePipe(&(gw_spawn.hChildStdoutRd), &(gw_spawn.hChildStdoutWr), &saAttr, 0))
- {
- gchar *msg = g_win32_error_message(GetLastError());
- geany_debug("win32_spawn: Stdout pipe creation failed (%d)", (gint) GetLastError());
- g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
- g_free(msg);
- return FALSE;
- }
-
- /* Ensure that the read handle to the child process's pipe for STDOUT is not inherited.*/
- SetHandleInformation(gw_spawn.hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
-
- /* Create a pipe for the child process's STDERR. */
- if (! CreatePipe(&(gw_spawn.hChildStderrRd), &(gw_spawn.hChildStderrWr), &saAttr, 0))
- {
- gchar *msg = g_win32_error_message(GetLastError());
- geany_debug("win32_spawn: Stderr pipe creation failed");
- g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
- g_free(msg);
- return FALSE;
- }
-
- /* Ensure that the read handle to the child process's pipe for STDOUT is not inherited.*/
- SetHandleInformation(gw_spawn.hChildStderrRd, HANDLE_FLAG_INHERIT, 0);
-
- /* Create a pipe for the child process's STDIN. */
- if (! CreatePipe(&(gw_spawn.hChildStdinRd), &(gw_spawn.hChildStdinWr), &saAttr, 0))
- {
- gchar *msg = g_win32_error_message(GetLastError());
- geany_debug("win32_spawn: Stdin pipe creation failed");
- g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
- g_free(msg);
- return FALSE;
- }
-
- /* Ensure that the write handle to the child process's pipe for STDIN is not inherited. */
- SetHandleInformation(gw_spawn.hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
-
- /* Now create the child process. */
- fSuccess = CreateChildProcess(&gw_spawn, cmdline, dir, error);
- if (exit_status)
- {
- *exit_status = gw_spawn.dwExitCode;
- }
-
- if (! fSuccess)
- {
- geany_debug("win32_spawn: Create process failed");
- return FALSE;
- }
-
- /* Read from pipe that is the standard output for child process. */
- if (std_out != NULL)
- {
- ReadFromPipe(gw_spawn.hChildStdoutRd, gw_spawn.hChildStdoutWr, hStdoutTempFile, error);
- if (! GetContentFromHandle(hStdoutTempFile, &stdout_content, error))
- {
- return FALSE;
- }
- *std_out = stdout_content;
- }
-
- if (std_err != NULL)
- {
- ReadFromPipe(gw_spawn.hChildStderrRd, gw_spawn.hChildStderrWr, hStderrTempFile, error);
- if (! GetContentFromHandle(hStderrTempFile, &stderr_content, error))
- {
- return FALSE;
- }
- *std_err = stderr_content;
- }
- return TRUE;
-}
-
-
-/* Note: g_spawn is broken for receiving both stdio and stderr e.g. when
- * running make and there are compile errors. See glib/giowin32.c header
- * comment about Windows bugs, e.g. #338943 */
-/* Simple replacement for _broken_win32_spawn().
- * flags is ignored, G_SPAWN_SEARCH_PATH is implied.
- * Don't call this function directly, use utils_spawn_[a]sync() instead.
- * Adapted from tm_workspace_create_global_tags(). */
-gboolean win32_spawn(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
- gchar **std_out, gchar **std_err, gint *exit_status, GError **error)
-{
- gint ret;
- gboolean fail;
- gchar *tmp_file = create_temp_file();
- gchar *tmp_errfile = create_temp_file();
- gchar *command;
- gchar *locale_command;
-
- if (env != NULL)
- {
- return _broken_win32_spawn(dir, argv, env, flags, std_out, std_err,
- exit_status, error);
- }
- if (!tmp_file || !tmp_errfile)
- {
- g_warning("%s: Could not create temporary files!", G_STRFUNC);
- return FALSE;
- }
- command = g_strjoinv(" ", argv);
- SETPTR(command, g_strdup_printf("cmd.exe /S /C \"%s >%s 2>%s\"",
- command, tmp_file, tmp_errfile));
- locale_command = g_locale_from_utf8(command, -1, NULL, NULL, NULL);
- if (! locale_command)
- locale_command = g_strdup(command);
- geany_debug("WIN32: actually running command:\n%s", command);
- g_chdir(dir);
- errno = 0;
- ret = system(locale_command);
- /* the command can return -1 as an exit code, so check errno also */
- fail = ret == -1 && errno;
- if (!fail)
- {
- if (std_out != NULL)
- g_file_get_contents(tmp_file, std_out, NULL, NULL);
- if (std_err != NULL)
- g_file_get_contents(tmp_errfile, std_err, NULL, NULL);
- }
- else if (error)
- g_set_error_literal(error, G_SPAWN_ERROR, errno, g_strerror(errno));
-
- g_free(command);
- g_free(locale_command);
- g_unlink(tmp_file);
- g_free(tmp_file);
- g_unlink(tmp_errfile);
- g_free(tmp_errfile);
- if (exit_status)
- *exit_status = ret;
-
- return !fail;
-}
-
-
-static gboolean GetContentFromHandle(HANDLE hFile, gchar **content, GError **error)
-{
- DWORD filesize;
- gchar * buffer;
- DWORD dwRead;
-
- filesize = GetFileSize(hFile, NULL);
- if (filesize < 1)
- {
- *content = NULL;
- return TRUE;
- }
-
- buffer = g_malloc(filesize + 1);
- if (! buffer)
- {
- gchar *msg = g_win32_error_message(GetLastError());
- geany_debug("GetContentFromHandle: Alloc failed");
- g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR, "%s", msg);
- g_free(msg);
- return FALSE;
- }
-
- SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
- if (! ReadFile(hFile, buffer, filesize, &dwRead, NULL) || dwRead == 0)
- {
- gchar *msg = g_win32_error_message(GetLastError());
- geany_debug("GetContentFromHandle: Cannot read tempfile");
- g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
- g_free(msg);
- return FALSE;
- }
-
- if (! CloseHandle(hFile))
- {
- gchar *msg = g_win32_error_message(GetLastError());
- geany_debug("GetContentFromHandle: CloseHandle failed (%d)", (gint) GetLastError());
- g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
- g_free(msg);
- g_free(buffer);
- *content = NULL;
- return FALSE;
}
- buffer[filesize] = '\0';
- *content = buffer;
- return TRUE;
}
@@ -1236,171 +908,6 @@ gchar *win32_expand_environment_variables(const gchar *str)
}
-static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline,
- const TCHAR *dir, GError **error)
-{
- PROCESS_INFORMATION piProcInfo;
- STARTUPINFOW siStartInfo;
- BOOL bFuncRetn = FALSE;
- gchar *expandedCmdline;
- wchar_t w_commandline[CMDSIZE];
- wchar_t w_dir[MAX_PATH];
-
- /* Set up members of the PROCESS_INFORMATION structure. */
- ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
-
- /* Set up members of the STARTUPINFO structure.*/
- ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
-
- siStartInfo.cb = sizeof(STARTUPINFO);
- siStartInfo.hStdError = gw_spawn->hChildStderrWr;
- siStartInfo.hStdOutput = gw_spawn->hChildStdoutWr;
- siStartInfo.hStdInput = gw_spawn->hChildStdinRd;
- siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
-
- /* Expand environment variables like %blah%. */
- expandedCmdline = win32_expand_environment_variables(szCmdline);
-
- MultiByteToWideChar(CP_UTF8, 0, expandedCmdline, -1, w_commandline, G_N_ELEMENTS(w_commandline));
- MultiByteToWideChar(CP_UTF8, 0, dir, -1, w_dir, G_N_ELEMENTS(w_dir));
-
- /* Create the child process. */
- bFuncRetn = CreateProcessW(NULL,
- w_commandline, /* command line */
- NULL, /* process sec@@ Diff output truncated at 100000 characters. @@
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
More information about the Commits
mailing list