/* * win32.c - this file is part of Geany, a fast and lightweight IDE * * Copyright 2005-2009 Enrico Tröger * Copyright 2006-2009 Nick Treleaven * * 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. * * $Id$ */ /* * Special functions for the win32 platform, to provide native dialogs. */ #include "geany.h" #ifdef G_OS_WIN32 #define VC_EXTRALEAN #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include #include "win32.h" #include "document.h" #include "support.h" #include "utils.h" #include "ui_utils.h" #include "sciwrappers.h" #include "dialogs.h" #include "filetypes.h" #include "project.h" #include "editor.h" #define BUFSIZE 4096 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); static wchar_t *get_file_filters(void) { gchar *string; gint i, j, len; static wchar_t title[1024]; GString *str = g_string_sized_new(100); GString *all_patterns = g_string_sized_new(100); gchar *tmp; for (i = 0; i < filetypes_array->len; i++) { tmp = g_strjoinv(";", filetypes[i]->pattern); g_string_append_printf(str, "%s\t%s\t", filetypes[i]->title, tmp); g_free(tmp); } /* create meta file filter "All Source" */ for (i = 0; i < filetypes_array->len; i++) { for (j = 0; filetypes[i]->pattern[j] != NULL; j++) { g_string_append(all_patterns, filetypes[i]->pattern[j]); g_string_append_c(all_patterns, ';'); } } g_string_append_printf(str, "%s\t%s\t", _("All Source"), all_patterns->str); g_string_free(all_patterns, TRUE); g_string_append_c(str, '\t'); /* the final \0 byte to mark the end of the string */ string = str->str; g_string_free(str, FALSE); /* replace all "\t"s by \0 */ len = strlen(string); for(i = 0; i < len; i++) { if (string[i] == '\t') string[i] = '\0'; } MultiByteToWideChar(CP_UTF8, 0, string, len, title, sizeof(title)); g_free(string); return title; } static wchar_t *get_filters(gboolean project_files) { gchar *string; gint i, len; static wchar_t title[1024]; if (project_files) { string = g_strconcat(_("Geany project files"), "\t", "*." GEANY_PROJECT_EXT, "\t", filetypes[GEANY_FILETYPES_NONE]->title, "\t", filetypes[GEANY_FILETYPES_NONE]->pattern[0], "\t", NULL); } else { string = g_strconcat(_("Executables"), "\t", "*.exe;*.bat;*.cmd", "\t", filetypes[GEANY_FILETYPES_NONE]->title, "\t", filetypes[GEANY_FILETYPES_NONE]->pattern[0], "\t", NULL); } /* replace all "\t"s by \0 */ len = strlen(string); for(i = 0; i < len; i++) { if (string[i] == '\t') string[i] = '\0'; } MultiByteToWideChar(CP_UTF8, 0, string, len, title, sizeof(title)); g_free(string); return title; } /* Converts the given UTF-8 filename or directory name into something usable for Windows and * returns the directory part of the given filename. */ static wchar_t *get_dir_for_path(const gchar *utf8_filename) { static wchar_t w_dir[MAX_PATH]; gchar *result; if (g_file_test(utf8_filename, G_FILE_TEST_IS_DIR)) result = (gchar*) utf8_filename; else result = g_path_get_dirname(utf8_filename); MultiByteToWideChar(CP_UTF8, 0, result, -1, w_dir, sizeof(w_dir)); if (result != utf8_filename) g_free(result); return w_dir; } /* Callback function for setting the initial directory of the folder open dialog. This could also * be done with BROWSEINFO.pidlRoot and SHParseDisplayName but SHParseDisplayName is not available * on systems below Windows XP. So, we go the hard way by creating a callback which will set up the * folder when the dialog is initialised. Yeah, I like Windows. */ INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) { switch(uMsg) { case BFFM_INITIALIZED: { SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, pData); break; } case BFFM_SELCHANGED: { /* set the status window to the currently selected path. */ static wchar_t szDir[MAX_PATH]; if (SHGetPathFromIDListW((LPITEMIDLIST) lp, szDir)) { SendMessageW(hwnd, BFFM_SETSTATUSTEXTW, 0, (LPARAM) szDir); } break; } } return 0; } /* Shows a folder selection dialog. * initial_dir is expected in UTF-8 * The selected folder name is returned. */ gchar *win32_show_project_folder_dialog(GtkWidget *parent, const gchar *title, const gchar *initial_dir) { BROWSEINFOW bi; LPCITEMIDLIST pidl; gchar *result = NULL; wchar_t fname[MAX_PATH]; wchar_t w_title[512]; MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, sizeof(w_title)); if (parent == NULL) parent = main_widgets.window; memset(&bi, 0, sizeof bi); bi.hwndOwner = GDK_WINDOW_HWND(parent->window); bi.pidlRoot = NULL; bi.lpszTitle = w_title; bi.lpfn = BrowseCallbackProc; bi.lParam = (LPARAM) get_dir_for_path(initial_dir); bi.ulFlags = BIF_DONTGOBELOWDOMAIN | BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT; pidl = SHBrowseForFolderW(&bi); /* convert the strange Windows folder list item something into an usual path string ;-) */ if (pidl != 0) { if (SHGetPathFromIDListW(pidl, fname)) { result = g_malloc0(MAX_PATH * 2); WideCharToMultiByte(CP_UTF8, 0, fname, -1, result, MAX_PATH * 2, NULL, NULL); } /* SHBrowseForFolder() probably leaks memory here, but how to free the allocated memory? */ } return result; } /* Shows a file open dialog. * If allow_new_file is set, the file to be opened doesn't have to exist. * initial_dir is expected in UTF-8 * The selected file name is returned. * If project_file_filter is set, the file open dialog will have a file filter for Geany project * files, a filter for executables otherwise. */ gchar *win32_show_project_open_dialog(GtkWidget *parent, const gchar *title, const gchar *initial_dir, gboolean allow_new_file, gboolean project_file_filter) { OPENFILENAMEW of; gint retval; wchar_t fname[MAX_PATH]; wchar_t w_title[512]; gchar *filename; fname[0] = '\0'; MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, sizeof(w_title)); if (parent == NULL) parent = main_widgets.window; /* initialise file dialog info struct */ memset(&of, 0, sizeof of); #ifdef OPENFILENAME_SIZE_VERSION_400 of.lStructSize = OPENFILENAME_SIZE_VERSION_400; #else of.lStructSize = sizeof of; #endif of.hwndOwner = GDK_WINDOW_HWND(parent->window); of.lpstrFilter = get_filters(project_file_filter); of.lpstrCustomFilter = NULL; of.nFilterIndex = 0; of.lpstrFile = fname; of.lpstrInitialDir = get_dir_for_path(initial_dir); of.nMaxFile = 2048; of.lpstrFileTitle = NULL; of.lpstrTitle = w_title; of.lpstrDefExt = L""; of.Flags = OFN_PATHMUSTEXIST | OFN_EXPLORER | OFN_HIDEREADONLY; if (! allow_new_file) of.Flags |= OFN_FILEMUSTEXIST; retval = GetOpenFileNameW(&of); if (! retval) { if (CommDlgExtendedError()) { gchar *error; error = g_strdup_printf("File dialog box error (%x)", (int)CommDlgExtendedError()); win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error); g_free(error); } return NULL; } /* convert the resulting filename into UTF-8 (from whatever encoding it has at this moment) */ filename = g_malloc0(MAX_PATH * 2); WideCharToMultiByte(CP_UTF8, 0, fname, -1, filename, MAX_PATH * 2, NULL, NULL); return filename; } /* initial_dir can be NULL to use the current working directory. * Returns: TRUE if the dialog was not cancelled. */ gboolean win32_show_file_dialog(gboolean file_open, const gchar *initial_dir) { OPENFILENAMEW of; gint retval; gchar tmp[MAX_PATH]; wchar_t fname[MAX_PATH]; wchar_t w_dir[MAX_PATH]; fname[0] = '\0'; g_message("%d",MAX_PATH); MultiByteToWideChar(CP_UTF8, 0, initial_dir, -1, w_dir, sizeof(w_dir)); /* initialise file dialog info struct */ memset(&of, 0, sizeof of); #ifdef OPENFILENAME_SIZE_VERSION_400 of.lStructSize = OPENFILENAME_SIZE_VERSION_400; #else of.lStructSize = sizeof of; #endif of.hwndOwner = GDK_WINDOW_HWND(main_widgets.window->window); of.lpstrFilter = get_file_filters(); of.lpstrCustomFilter = NULL; of.nFilterIndex = GEANY_FILETYPES_NONE + 1; of.lpstrFile = fname; of.lpstrInitialDir = w_dir; of.nMaxFile = 2048; of.lpstrFileTitle = NULL; of.lpstrTitle = NULL; of.lpstrDefExt = L""; if (file_open) { of.Flags = OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST | OFN_EXPLORER; retval = GetOpenFileNameW(&of); } else { of.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; retval = GetSaveFileNameW(&of); } if (!retval) { if (CommDlgExtendedError()) { gchar error[100]; g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError()); win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error); } return FALSE; } if (file_open) { gint x; x = of.nFileOffset - 1; if (x != wcslen(fname)) { /* open a single file */ WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL); document_open_file(tmp, of.Flags & OFN_READONLY, NULL, NULL); } else { /* open multiple files */ gchar file_name[MAX_PATH]; gchar dir_name[MAX_PATH]; WideCharToMultiByte(CP_UTF8, 0, fname, of.nFileOffset, dir_name, sizeof(dir_name), NULL, NULL); for (; ;) { if (! fname[x]) { if (! fname[x+1]) break; WideCharToMultiByte(CP_UTF8, 0, fname + x + 1, -1, tmp, sizeof(tmp), NULL, NULL); g_snprintf(file_name, 511, "%s\\%s", dir_name, tmp); /* convert the resulting filename into UTF-8 */ document_open_file(file_name, of.Flags & OFN_READONLY, NULL, NULL); } x++; } } } else { GeanyDocument *doc = document_get_current(); WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL); document_save_file_as(doc, tmp); } return (retval != 0); } void win32_show_font_dialog(void) { gint retval; CHOOSEFONT cf; LOGFONT lf; /* logical font structure */ memset(&cf, 0, sizeof cf); cf.lStructSize = sizeof cf; cf.hwndOwner = GDK_WINDOW_HWND(main_widgets.window->window); cf.lpLogFont = &lf; cf.Flags = CF_APPLY | CF_NOSCRIPTSEL | CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS; retval = ChooseFont(&cf); if (retval) { gchar *editorfont = g_strdup_printf("%s %d", lf.lfFaceName, (cf.iPointSize / 10)); ui_set_editor_font(editorfont); g_free(editorfont); } } void win32_show_color_dialog(const gchar *colour) { CHOOSECOLOR cc; static COLORREF acr_cust_clr[16]; static DWORD rgb_current; gchar *hex = g_malloc0(12); GeanyDocument *doc = document_get_current(); /* Initialize CHOOSECOLOR */ memset(&cc, 0, sizeof cc); cc.lStructSize = sizeof(cc); cc.hwndOwner = GDK_WINDOW_HWND(main_widgets.window->window); cc.lpCustColors = (LPDWORD) acr_cust_clr; cc.rgbResult = (colour != NULL) ? utils_strtod(colour, NULL, colour[0] == '#') : 0; cc.Flags = CC_FULLOPEN | CC_RGBINIT; if (ChooseColor(&cc)) { rgb_current = cc.rgbResult; g_snprintf(hex, 11, "#%02X%02X%02X", (guint) (utils_scale_round(GetRValue(rgb_current), 255)), (guint) (utils_scale_round(GetGValue(rgb_current), 255)), (guint) (utils_scale_round(GetBValue(rgb_current), 255))); editor_insert_color(doc->editor, hex); } g_free(hex); } void win32_show_pref_file_dialog(GtkEntry *item) { OPENFILENAMEW of; gint retval, len; wchar_t fname[MAX_PATH]; gchar tmp[MAX_PATH]; gchar **field, *filename; fname[0] = '\0'; /* cut the options from the command line */ field = g_strsplit(gtk_entry_get_text(GTK_ENTRY(item)), " ", 2); if (field[0]) { filename = g_find_program_in_path(field[0]); if (filename != NULL && g_file_test(filename, G_FILE_TEST_EXISTS)) { MultiByteToWideChar(CP_UTF8, 0, filename, -1, fname, sizeof(fname)); g_free(filename); } } /* initialize file dialog info struct */ memset(&of, 0, sizeof of); #ifdef OPENFILENAME_SIZE_VERSION_400 of.lStructSize = OPENFILENAME_SIZE_VERSION_400; #else of.lStructSize = sizeof of; #endif of.hwndOwner = GDK_WINDOW_HWND(ui_widgets.prefs_dialog->window); of.lpstrFilter = get_filters(FALSE); of.lpstrCustomFilter = NULL; of.nFilterIndex = 1; of.lpstrFile = fname; of.nMaxFile = 2048; of.lpstrFileTitle = NULL; /*of.lpstrInitialDir = g_get_home_dir();*/ of.lpstrInitialDir = NULL; of.lpstrTitle = NULL; of.lpstrDefExt = L"exe"; of.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_EXPLORER; retval = GetOpenFileNameW(&of); if (!retval) { if (CommDlgExtendedError()) { gchar error[100]; g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError()); win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error); } g_strfreev(field); return; } len = WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL); if ((of.nFileOffset - 1) != len) { if (g_strv_length(field) > 1) /* add the command line args of the old command */ /** TODO this fails badly when the old command contained spaces, we need quoting here */ filename = g_strconcat(tmp, " ", field[1], NULL); else { filename = g_strdup(tmp); } gtk_entry_set_text(GTK_ENTRY(item), filename); g_free(filename); } g_strfreev(field); } /* Creates a native Windows message box of the given type and returns always TRUE * or FALSE representing th pressed Yes or No button. * If type is not GTK_MESSAGE_QUESTION, it returns always TRUE. */ gboolean win32_message_dialog(GtkWidget *parent, GtkMessageType type, const gchar *msg) { gboolean ret = TRUE; gint rc; guint t; const gchar *title; HWND parent_hwnd = NULL; static wchar_t w_msg[512]; static wchar_t w_title[512]; switch (type) { case GTK_MESSAGE_ERROR: { t = MB_OK | MB_ICONERROR; title = _("Error"); break; } case GTK_MESSAGE_QUESTION: { t = MB_YESNO | MB_ICONQUESTION; title = _("Question"); break; } case GTK_MESSAGE_WARNING: { t = MB_OK | MB_ICONWARNING; title = _("Warning"); break; } default: { t = MB_OK | MB_ICONINFORMATION; title = _("Information"); break; } } /* convert the Unicode chars to wide chars */ /** TODO test if LANG == C then possibly skip conversion => g_win32_getlocale() */ MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg)); MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title)); if (parent != NULL) parent_hwnd = GDK_WINDOW_HWND(parent->window); else if (main_widgets.window != NULL) parent_hwnd = GDK_WINDOW_HWND(main_widgets.window->window); /* display the message box */ rc = MessageBoxW(parent_hwnd, w_msg, w_title, t); if (type == GTK_MESSAGE_QUESTION && rc != IDYES) ret = FALSE; return ret; } /* Little wrapper for _waccess(), returns errno or 0 if there was no error */ gint win32_check_write_permission(const gchar *dir) { static wchar_t w_dir[MAX_PATH]; MultiByteToWideChar(CP_UTF8, 0, dir, -1, w_dir, sizeof w_dir); if (_waccess(w_dir, R_OK | W_OK) != 0) return errno; else return 0; } /* Special dialog to ask for an action when closing an unsaved file */ gint win32_message_dialog_unsaved(const gchar *msg) { static wchar_t w_msg[512]; static wchar_t w_title[512]; HWND parent_hwnd = NULL; gint ret; /* convert the Unicode chars to wide chars */ MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg)); MultiByteToWideChar(CP_UTF8, 0, _("Question"), -1, w_title, G_N_ELEMENTS(w_title)); if (main_widgets.window != NULL) parent_hwnd = GDK_WINDOW_HWND(main_widgets.window->window); ret = MessageBoxW(parent_hwnd, w_msg, w_title, MB_YESNOCANCEL | MB_ICONQUESTION); switch(ret) { case IDYES: ret = GTK_RESPONSE_YES; break; case IDNO: ret = GTK_RESPONSE_NO; break; case IDCANCEL: ret = GTK_RESPONSE_CANCEL; break; } return ret; } /* Just a simple wrapper function to open a browser window */ void win32_open_browser(const gchar *uri) { ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOWNORMAL); } /* 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 void debug_setup_console() { static const WORD MAX_CONSOLE_LINES = 500; gint hConHandle; glong lStdHandle; CONSOLE_SCREEN_BUFFER_INFO coninfo; FILE *fp; /* allocate a console for this app */ AllocConsole(); /* set the screen buffer to be big enough to let us scroll text */ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); coninfo.dwSize.Y = MAX_CONSOLE_LINES; SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); /* redirect unbuffered STDOUT to the console */ lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen(hConHandle, "w"); *stdout = *fp; setvbuf(stdout, NULL, _IONBF, 0); /* redirect unbuffered STDERR to the console */ lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen(hConHandle, "w"); *stderr = *fp; setvbuf(stderr, NULL, _IONBF, 0); } void win32_init_debug_code(void) { if (app->debug_mode) { /* create a console window to get log messages on Windows */ /** TODO remove me? */ 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); } } gchar *win32_get_hostname(void) { gchar hostname[100]; DWORD size = sizeof(hostname); if (GetComputerName(hostname, &size)) return g_strdup(hostname); else return g_strdup("localhost"); } /* Process spawning implementation for Windows, by Pierre Joye. * Don't call this function directly, use utils_spawn_[a]sync() instead. */ gboolean win32_spawn(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags, gchar **std_out, gchar **std_err, gint *exit_status, GError **error) { TCHAR buffer[MAX_PATH]=TEXT(""); TCHAR cmdline[MAX_PATH] = TEXT(""); TCHAR* lpPart[MAX_PATH]={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", MAX_PATH, buffer, lpPart); if (retval > 0) g_snprintf(cmdline, MAX_PATH, "\"%s\"", buffer); else g_strlcpy(cmdline, argv[0], sizeof(cmdline)); cmdpos = 1; } for (i = cmdpos; i < argc; i++) { g_snprintf(cmdline, MAX_PATH, "%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()); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, 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()); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, 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()); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, 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"); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, 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"); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, 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"); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "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; } 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(sizeof(gchar*) * (filesize+1)); if (!buffer) { gchar *msg = g_win32_error_message(GetLastError()); geany_debug("GetContentFromHandle: Alloc failed"); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_SPAWN_ERROR, 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"); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_FAILED, msg); g_free(msg); return FALSE; } if (!CloseHandle (hFile)) { gchar *msg = g_win32_error_message(GetLastError()); geany_debug("GetContentFromHandle: CloseHandle failed (%d)", (gint) GetLastError()); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_FAILED, msg); g_free(msg); g_free(buffer); *content = NULL; return FALSE; } buffer[filesize] = '\0'; *content = buffer; return TRUE; } static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline, const TCHAR *dir, GError **error) { PROCESS_INFORMATION piProcInfo; STARTUPINFO siStartInfo; BOOL bFuncRetn = FALSE; /* 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; /* Create the child process. */ bFuncRetn = CreateProcess(NULL, szCmdline, /* command line */ NULL, /* process security attributes */ NULL, /* primary thread security attributes */ TRUE, /* handles are inherited */ CREATE_NO_WINDOW, /* creation flags */ NULL, /* use parent's environment */ dir, /* use parent's current directory */ &siStartInfo, /* STARTUPINFO pointer */ &piProcInfo); /* receives PROCESS_INFORMATION */ if (bFuncRetn == 0) { gchar *msg = g_win32_error_message(GetLastError()); geany_debug("CreateChildProcess: CreateProcess failed (%s)", msg); if (*error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, msg); g_free(msg); return FALSE; } else { int i; DWORD dwStatus; for (i = 0; i < 2 && (dwStatus = WaitForSingleObject(piProcInfo.hProcess, 30*1000)) == WAIT_TIMEOUT; i++) { geany_debug("CreateChildProcess: CreateProcess failed"); TerminateProcess(piProcInfo.hProcess, WAIT_TIMEOUT); /* NOTE: This will not kill grandkids. */ } if (GetExitCodeProcess(piProcInfo.hProcess, &gw_spawn->dwExitCode) != 0) { gchar *msg = g_win32_error_message(GetLastError()); geany_debug("GetExitCodeProcess failed: %s", msg); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_FAILED, msg); g_free(msg); } CloseHandle(piProcInfo.hProcess); CloseHandle(piProcInfo.hThread); return bFuncRetn; } return FALSE; } static VOID ReadFromPipe(HANDLE hRead, HANDLE hWrite, HANDLE hFile, GError **error) { DWORD dwRead, dwWritten; CHAR chBuf[BUFSIZE]; /* Close the write end of the pipe before reading from the read end of the pipe. */ if (!CloseHandle(hWrite)) { gchar *msg = g_win32_error_message(GetLastError()); geany_debug("ReadFromPipe: Closing handle failed"); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, msg); g_free(msg); return; } /* Read output from the child process, and write to parent's STDOUT. */ for (;;) { if( !ReadFile(hRead, chBuf, BUFSIZE, &dwRead, NULL) || dwRead == 0) break; if (!WriteFile(hFile, chBuf, dwRead, &dwWritten, NULL)) break; } } static HANDLE GetTempFileHandle(GError **error) { /* Temp file */ DWORD dwBufSize=BUFSIZE; UINT uRetVal; TCHAR szTempName[BUFSIZE]; TCHAR lpPathBuffer[BUFSIZE]; DWORD dwRetVal; HANDLE hTempFile; /* Get the temp path. */ dwRetVal = GetTempPath(dwBufSize, /* length of the buffer*/ lpPathBuffer); /* buffer for path */ if (dwRetVal > dwBufSize || (dwRetVal == 0)) { gchar *msg = g_win32_error_message(GetLastError()); geany_debug("GetTempFileHandle: GetTempPath failed (%d)", (gint) GetLastError()); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, msg); g_free(msg); return NULL; } /* Create a temporary file for STDOUT. */ uRetVal = GetTempFileName(lpPathBuffer, /* directory for tmp files */ TEXT("GEANY_VCDIFF_"), /* temp file name prefix */ 0, /* create unique name */ szTempName); /* buffer for name */ if (uRetVal == 0) { gchar *msg = g_win32_error_message(GetLastError()); geany_debug("GetTempFileName failed (%d)", (gint) GetLastError()); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, msg); g_free(msg); return NULL; } hTempFile = CreateFile((LPTSTR) szTempName, /* file name */ GENERIC_READ | GENERIC_WRITE, /* open r-w */ 0, /* do not share */ NULL, /* default security */ CREATE_ALWAYS, /* overwrite existing */ FILE_ATTRIBUTE_NORMAL,/* normal file */ NULL); /* no template */ if (hTempFile == INVALID_HANDLE_VALUE) { gchar *msg = g_win32_error_message(GetLastError()); geany_debug("GetTempFileHandle: Second CreateFile failed (%d)", (gint) GetLastError()); if (error != NULL) *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, msg); g_free(msg); return NULL; } return hTempFile; } /* From GDK (they got it from MS Knowledge Base article Q130698) */ static gboolean resolve_link(HWND hWnd, wchar_t *link, gchar **lpszPath) { WIN32_FILE_ATTRIBUTE_DATA wfad; HRESULT hres; IShellLinkW *pslW = NULL; IPersistFile *ppf = NULL; LPVOID pslWV = NULL; LPVOID ppfV = NULL; /* Check if the file is empty first because IShellLink::Resolve for some reason succeeds * with an empty file and returns an empty "link target". (#524151) */ if (!GetFileAttributesExW(link, GetFileExInfoStandard, &wfad) || (wfad.nFileSizeHigh == 0 && wfad.nFileSizeLow == 0)) { return FALSE; } /* Assume failure to start with: */ *lpszPath = 0; CoInitialize(NULL); hres = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, &pslWV); if (SUCCEEDED(hres)) { /* The IShellLink interface supports the IPersistFile interface. * Get an interface pointer to it. */ pslW = (IShellLinkW*) pslWV; hres = pslW->lpVtbl->QueryInterface(pslW, &IID_IPersistFile, &ppfV); } if (SUCCEEDED(hres)) { /* Load the file. */ ppf = (IPersistFile*) ppfV; hres = ppf->lpVtbl->Load(ppf, link, STGM_READ); } if (SUCCEEDED(hres)) { /* Resolve the link by calling the Resolve() interface function. */ hres = pslW->lpVtbl->Resolve(pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI); } if (SUCCEEDED(hres)) { wchar_t wtarget[MAX_PATH]; hres = pslW->lpVtbl->GetPath(pslW, wtarget, MAX_PATH, NULL, 0); if (SUCCEEDED(hres)) *lpszPath = g_utf16_to_utf8(wtarget, -1, NULL, NULL, NULL); } if (ppf) ppf->lpVtbl->Release(ppf); if (pslW) pslW->lpVtbl->Release(pslW); return SUCCEEDED(hres); } /* Checks whether file_name is a Windows shortcut. file_name is expected in UTF-8 encoding. * If file_name is a Windows shortcut, it returns the target in UTF-8 encoding. * If it is not a shortcut, it returns a newly allocated copy of file_name. */ gchar *win32_get_shortcut_target(const gchar *file_name) { gchar *path = NULL; wchar_t *wfilename = g_utf8_to_utf16(file_name, -1, NULL, NULL, NULL); resolve_link(GDK_WINDOW_HWND(main_widgets.window->window), wfilename, &path); g_free(wfilename); if (path == NULL) return g_strdup(file_name); else return path; } void win32_set_working_directory(const gchar *dir) { SetCurrentDirectory(dir); } gchar *win32_get_installation_dir(void) { #if GLIB_CHECK_VERSION(2, 16, 0) return g_win32_get_package_installation_directory_of_module(NULL); #else return g_win32_get_package_installation_directory(NULL, NULL); #endif } #endif