Try to fix Gtk warning when using Tools->Reload Configuration.
[geany-mirror.git] / src / win32.c
blobf27412170346b6009b90b9619b512d61fb761e44
1 /*
2 * win32.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2009 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2009 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * $Id$
25 * Special functions for the win32 platform, to provide native dialogs.
28 #include "geany.h"
30 #ifdef G_OS_WIN32
32 #define VC_EXTRALEAN
33 #define WIN32_LEAN_AND_MEAN
34 #include <windows.h>
35 #include <commdlg.h>
36 #include <shlobj.h>
37 #include <io.h>
38 #include <fcntl.h>
40 #include <string.h>
41 #include <ctype.h>
42 #include <math.h>
43 #include <stdlib.h>
45 #include <gdk/gdkwin32.h>
47 #include "win32.h"
49 #include "document.h"
50 #include "support.h"
51 #include "utils.h"
52 #include "ui_utils.h"
53 #include "sciwrappers.h"
54 #include "dialogs.h"
55 #include "filetypes.h"
56 #include "project.h"
57 #include "editor.h"
59 #define BUFSIZE 4096
61 struct _geany_win32_spawn
63 HANDLE hChildStdinRd;
64 HANDLE hChildStdinWr;
65 HANDLE hChildStdoutRd;
66 HANDLE hChildStdoutWr;
67 HANDLE hChildStderrRd;
68 HANDLE hChildStderrWr;
69 HANDLE hInputFile;
70 HANDLE hStdout;
71 HANDLE hStderr;
72 HANDLE processId;
73 DWORD dwExitCode;
75 typedef struct _geany_win32_spawn geany_win32_spawn;
77 static gboolean GetContentFromHandle(HANDLE hFile, gchar **content, GError **error);
78 static HANDLE GetTempFileHandle(GError **error);
79 static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline, const TCHAR *dir, GError **error);
80 static VOID ReadFromPipe(HANDLE hRead, HANDLE hWrite, HANDLE hFile, GError **error);
83 static wchar_t *get_file_filters(void)
85 gchar *string;
86 guint i, j, len;
87 static wchar_t title[1024];
88 GString *str = g_string_sized_new(100);
89 GString *all_patterns = g_string_sized_new(100);
90 gchar *tmp;
92 for (i = 0; i < filetypes_array->len; i++)
94 tmp = g_strjoinv(";", filetypes[i]->pattern);
95 g_string_append_printf(str, "%s\t%s\t", filetypes[i]->title, tmp);
96 g_free(tmp);
98 /* create meta file filter "All Source" */
99 for (i = 0; i < filetypes_array->len; i++)
101 for (j = 0; filetypes[i]->pattern[j] != NULL; j++)
103 g_string_append(all_patterns, filetypes[i]->pattern[j]);
104 g_string_append_c(all_patterns, ';');
107 g_string_append_printf(str, "%s\t%s\t", _("All Source"), all_patterns->str);
108 g_string_free(all_patterns, TRUE);
110 g_string_append_c(str, '\t'); /* the final \0 byte to mark the end of the string */
111 string = str->str;
112 g_string_free(str, FALSE);
114 /* replace all "\t"s by \0 */
115 len = strlen(string);
116 for (i = 0; i < len; i++)
118 if (string[i] == '\t') string[i] = '\0';
120 MultiByteToWideChar(CP_UTF8, 0, string, len, title, sizeof(title));
121 g_free(string);
123 return title;
127 static wchar_t *get_filters(gboolean project_files)
129 gchar *string;
130 gint i, len;
131 static wchar_t title[1024];
133 if (project_files)
135 string = g_strconcat(_("Geany project files"), "\t", "*." GEANY_PROJECT_EXT, "\t",
136 filetypes[GEANY_FILETYPES_NONE]->title, "\t",
137 filetypes[GEANY_FILETYPES_NONE]->pattern[0], "\t", NULL);
139 else
141 string = g_strconcat(_("Executables"), "\t", "*.exe;*.bat;*.cmd", "\t",
142 filetypes[GEANY_FILETYPES_NONE]->title, "\t",
143 filetypes[GEANY_FILETYPES_NONE]->pattern[0], "\t", NULL);
146 /* replace all "\t"s by \0 */
147 len = strlen(string);
148 for (i = 0; i < len; i++)
150 if (string[i] == '\t') string[i] = '\0';
152 MultiByteToWideChar(CP_UTF8, 0, string, len, title, sizeof(title));
153 g_free(string);
155 return title;
159 /* Converts the given UTF-8 filename or directory name into something usable for Windows and
160 * returns the directory part of the given filename. */
161 static wchar_t *get_dir_for_path(const gchar *utf8_filename)
163 static wchar_t w_dir[MAX_PATH];
164 gchar *result;
166 if (g_file_test(utf8_filename, G_FILE_TEST_IS_DIR))
167 result = (gchar*) utf8_filename;
168 else
169 result = g_path_get_dirname(utf8_filename);
171 MultiByteToWideChar(CP_UTF8, 0, result, -1, w_dir, sizeof(w_dir));
173 if (result != utf8_filename)
174 g_free(result);
176 return w_dir;
180 /* Callback function for setting the initial directory of the folder open dialog. This could also
181 * be done with BROWSEINFO.pidlRoot and SHParseDisplayName but SHParseDisplayName is not available
182 * on systems below Windows XP. So, we go the hard way by creating a callback which will set up the
183 * folder when the dialog is initialised. Yeah, I like Windows. */
184 INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)
186 switch (uMsg)
188 case BFFM_INITIALIZED:
190 SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, pData);
191 break;
193 case BFFM_SELCHANGED:
195 /* set the status window to the currently selected path. */
196 static wchar_t szDir[MAX_PATH];
197 if (SHGetPathFromIDListW((LPITEMIDLIST) lp, szDir))
199 SendMessageW(hwnd, BFFM_SETSTATUSTEXTW, 0, (LPARAM) szDir);
201 break;
204 return 0;
208 /* Shows a folder selection dialog.
209 * initial_dir is expected in UTF-8
210 * The selected folder name is returned. */
211 gchar *win32_show_project_folder_dialog(GtkWidget *parent, const gchar *title,
212 const gchar *initial_dir)
214 BROWSEINFOW bi;
215 LPCITEMIDLIST pidl;
216 gchar *result = NULL;
217 wchar_t fname[MAX_PATH];
218 wchar_t w_title[512];
220 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, sizeof(w_title));
222 if (parent == NULL)
223 parent = main_widgets.window;
225 memset(&bi, 0, sizeof bi);
226 bi.hwndOwner = GDK_WINDOW_HWND(parent->window);
227 bi.pidlRoot = NULL;
228 bi.lpszTitle = w_title;
229 bi.lpfn = BrowseCallbackProc;
230 bi.lParam = (LPARAM) get_dir_for_path(initial_dir);
231 bi.ulFlags = BIF_DONTGOBELOWDOMAIN | BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT;
233 pidl = SHBrowseForFolderW(&bi);
235 /* convert the strange Windows folder list item something into an usual path string ;-) */
236 if (pidl != 0)
238 if (SHGetPathFromIDListW(pidl, fname))
240 result = g_malloc0(MAX_PATH * 2);
241 WideCharToMultiByte(CP_UTF8, 0, fname, -1, result, MAX_PATH * 2, NULL, NULL);
243 /* SHBrowseForFolder() probably leaks memory here, but how to free the allocated memory? */
245 return result;
249 /* Shows a file open dialog.
250 * If allow_new_file is set, the file to be opened doesn't have to exist.
251 * initial_dir is expected in UTF-8
252 * The selected file name is returned.
253 * If project_file_filter is set, the file open dialog will have a file filter for Geany project
254 * files, a filter for executables otherwise. */
255 gchar *win32_show_project_open_dialog(GtkWidget *parent, const gchar *title,
256 const gchar *initial_dir, gboolean allow_new_file,
257 gboolean project_file_filter)
259 OPENFILENAMEW of;
260 gint retval;
261 wchar_t fname[MAX_PATH];
262 wchar_t w_title[512];
263 gchar *filename;
265 fname[0] = '\0';
267 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, sizeof(w_title));
269 if (parent == NULL)
270 parent = main_widgets.window;
272 /* initialise file dialog info struct */
273 memset(&of, 0, sizeof of);
274 #ifdef OPENFILENAME_SIZE_VERSION_400
275 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
276 #else
277 of.lStructSize = sizeof of;
278 #endif
279 of.hwndOwner = GDK_WINDOW_HWND(parent->window);
280 of.lpstrFilter = get_filters(project_file_filter);
282 of.lpstrCustomFilter = NULL;
283 of.nFilterIndex = 0;
284 of.lpstrFile = fname;
285 of.lpstrInitialDir = get_dir_for_path(initial_dir);
286 of.nMaxFile = 2048;
287 of.lpstrFileTitle = NULL;
288 of.lpstrTitle = w_title;
289 of.lpstrDefExt = L"";
290 of.Flags = OFN_PATHMUSTEXIST | OFN_EXPLORER | OFN_HIDEREADONLY;
291 if (! allow_new_file)
292 of.Flags |= OFN_FILEMUSTEXIST;
294 retval = GetOpenFileNameW(&of);
296 if (! retval)
298 if (CommDlgExtendedError())
300 gchar *error;
301 error = g_strdup_printf("File dialog box error (%x)", (int)CommDlgExtendedError());
302 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
303 g_free(error);
305 return NULL;
307 /* convert the resulting filename into UTF-8 (from whatever encoding it has at this moment) */
308 filename = g_malloc0(MAX_PATH * 2);
309 WideCharToMultiByte(CP_UTF8, 0, fname, -1, filename, MAX_PATH * 2, NULL, NULL);
311 return filename;
315 /* initial_dir can be NULL to use the current working directory.
316 * Returns: TRUE if the dialog was not cancelled. */
317 gboolean win32_show_file_dialog(gboolean file_open, const gchar *initial_dir)
319 OPENFILENAMEW of;
320 gint retval;
321 gchar tmp[MAX_PATH];
322 wchar_t fname[MAX_PATH];
323 wchar_t w_dir[MAX_PATH];
325 fname[0] = '\0';
327 if (initial_dir != NULL)
328 MultiByteToWideChar(CP_UTF8, 0, initial_dir, -1, w_dir, sizeof(w_dir));
330 /* initialise file dialog info struct */
331 memset(&of, 0, sizeof of);
332 #ifdef OPENFILENAME_SIZE_VERSION_400
333 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
334 #else
335 of.lStructSize = sizeof of;
336 #endif
337 of.hwndOwner = GDK_WINDOW_HWND(main_widgets.window->window);
338 of.lpstrFilter = get_file_filters();
340 of.lpstrCustomFilter = NULL;
341 of.nFilterIndex = GEANY_FILETYPES_NONE + 1;
342 of.lpstrFile = fname;
343 of.lpstrInitialDir = (initial_dir != NULL) ? w_dir : NULL;
344 of.nMaxFile = 2048;
345 of.lpstrFileTitle = NULL;
346 of.lpstrTitle = NULL;
347 of.lpstrDefExt = L"";
348 if (file_open)
350 of.Flags = OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST | OFN_EXPLORER;
351 retval = GetOpenFileNameW(&of);
353 else
355 of.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
356 retval = GetSaveFileNameW(&of);
359 if (!retval)
361 if (CommDlgExtendedError())
363 gchar error[100];
364 g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
365 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
367 return FALSE;
370 if (file_open)
372 guint x;
374 x = of.nFileOffset - 1;
375 if (x != wcslen(fname))
376 { /* open a single file */
377 WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
378 document_open_file(tmp, of.Flags & OFN_READONLY, NULL, NULL);
380 else
381 { /* open multiple files */
382 gchar file_name[MAX_PATH];
383 gchar dir_name[MAX_PATH];
385 WideCharToMultiByte(CP_UTF8, 0, fname, of.nFileOffset,
386 dir_name, sizeof(dir_name), NULL, NULL);
387 for (; ;)
389 if (! fname[x])
391 if (! fname[x + 1])
392 break;
394 WideCharToMultiByte(CP_UTF8, 0, fname + x + 1, -1,
395 tmp, sizeof(tmp), NULL, NULL);
396 g_snprintf(file_name, 511, "%s\\%s", dir_name, tmp);
398 /* convert the resulting filename into UTF-8 */
399 document_open_file(file_name, of.Flags & OFN_READONLY, NULL, NULL);
401 x++;
405 else
407 GeanyDocument *doc = document_get_current();
409 WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
410 document_save_file_as(doc, tmp);
412 return (retval != 0);
416 void win32_show_font_dialog(void)
418 gint retval;
419 CHOOSEFONT cf;
420 LOGFONT lf; /* logical font structure */
422 memset(&cf, 0, sizeof cf);
423 cf.lStructSize = sizeof cf;
424 cf.hwndOwner = GDK_WINDOW_HWND(main_widgets.window->window);
425 cf.lpLogFont = &lf;
426 cf.Flags = CF_APPLY | CF_NOSCRIPTSEL | CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
428 retval = ChooseFont(&cf);
430 if (retval)
432 gchar *editorfont = g_strdup_printf("%s %d", lf.lfFaceName, (cf.iPointSize / 10));
433 ui_set_editor_font(editorfont);
434 g_free(editorfont);
439 void win32_show_color_dialog(const gchar *colour)
441 CHOOSECOLOR cc;
442 static COLORREF acr_cust_clr[16];
443 static DWORD rgb_current;
444 gchar *hex = g_malloc0(12);
445 GeanyDocument *doc = document_get_current();
447 /* Initialize CHOOSECOLOR */
448 memset(&cc, 0, sizeof cc);
449 cc.lStructSize = sizeof(cc);
450 cc.hwndOwner = GDK_WINDOW_HWND(main_widgets.window->window);
451 cc.lpCustColors = (LPDWORD) acr_cust_clr;
452 cc.rgbResult = (colour != NULL) ? utils_strtod(colour, NULL, colour[0] == '#') : 0;
453 cc.Flags = CC_FULLOPEN | CC_RGBINIT;
455 if (ChooseColor(&cc))
457 rgb_current = cc.rgbResult;
458 g_snprintf(hex, 11, "#%02X%02X%02X",
459 (guint) (utils_scale_round(GetRValue(rgb_current), 255)),
460 (guint) (utils_scale_round(GetGValue(rgb_current), 255)),
461 (guint) (utils_scale_round(GetBValue(rgb_current), 255)));
463 editor_insert_color(doc->editor, hex);
465 g_free(hex);
469 void win32_show_pref_file_dialog(GtkEntry *item)
471 OPENFILENAMEW of;
472 gint retval, len;
473 wchar_t fname[MAX_PATH];
474 gchar tmp[MAX_PATH];
475 gchar **field, *filename;
477 fname[0] = '\0';
479 /* cut the options from the command line */
480 field = g_strsplit(gtk_entry_get_text(GTK_ENTRY(item)), " ", 2);
481 if (field[0])
483 filename = g_find_program_in_path(field[0]);
484 if (filename != NULL && g_file_test(filename, G_FILE_TEST_EXISTS))
486 MultiByteToWideChar(CP_UTF8, 0, filename, -1, fname, sizeof(fname));
487 g_free(filename);
491 /* initialize file dialog info struct */
492 memset(&of, 0, sizeof of);
493 #ifdef OPENFILENAME_SIZE_VERSION_400
494 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
495 #else
496 of.lStructSize = sizeof of;
497 #endif
498 of.hwndOwner = GDK_WINDOW_HWND(ui_widgets.prefs_dialog->window);
500 of.lpstrFilter = get_filters(FALSE);
501 of.lpstrCustomFilter = NULL;
502 of.nFilterIndex = 1;
504 of.lpstrFile = fname;
505 of.nMaxFile = 2048;
506 of.lpstrFileTitle = NULL;
507 /*of.lpstrInitialDir = g_get_home_dir();*/
508 of.lpstrInitialDir = NULL;
509 of.lpstrTitle = NULL;
510 of.lpstrDefExt = L"exe";
511 of.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_EXPLORER;
512 retval = GetOpenFileNameW(&of);
514 if (!retval)
516 if (CommDlgExtendedError())
518 gchar error[100];
519 g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
520 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
522 g_strfreev(field);
523 return;
526 len = WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
527 if ((of.nFileOffset - 1) != len)
529 if (g_strv_length(field) > 1)
530 /* add the command line args of the old command */
531 /** TODO this fails badly when the old command contained spaces, we need quoting here */
532 filename = g_strconcat(tmp, " ", field[1], NULL);
533 else
535 filename = g_strdup(tmp);
537 gtk_entry_set_text(GTK_ENTRY(item), filename);
538 g_free(filename);
540 g_strfreev(field);
544 /* Creates a native Windows message box of the given type and returns always TRUE
545 * or FALSE representing th pressed Yes or No button.
546 * If type is not GTK_MESSAGE_QUESTION, it returns always TRUE. */
547 gboolean win32_message_dialog(GtkWidget *parent, GtkMessageType type, const gchar *msg)
549 gboolean ret = TRUE;
550 gint rc;
551 guint t;
552 const gchar *title;
553 HWND parent_hwnd = NULL;
554 static wchar_t w_msg[512];
555 static wchar_t w_title[512];
557 switch (type)
559 case GTK_MESSAGE_ERROR:
561 t = MB_OK | MB_ICONERROR;
562 title = _("Error");
563 break;
565 case GTK_MESSAGE_QUESTION:
567 t = MB_YESNO | MB_ICONQUESTION;
568 title = _("Question");
569 break;
571 case GTK_MESSAGE_WARNING:
573 t = MB_OK | MB_ICONWARNING;
574 title = _("Warning");
575 break;
577 default:
579 t = MB_OK | MB_ICONINFORMATION;
580 title = _("Information");
581 break;
585 /* convert the Unicode chars to wide chars */
586 /** TODO test if LANG == C then possibly skip conversion => g_win32_getlocale() */
587 MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg));
588 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
590 if (parent != NULL)
591 parent_hwnd = GDK_WINDOW_HWND(parent->window);
592 else if (main_widgets.window != NULL)
593 parent_hwnd = GDK_WINDOW_HWND(main_widgets.window->window);
595 /* display the message box */
596 rc = MessageBoxW(parent_hwnd, w_msg, w_title, t);
598 if (type == GTK_MESSAGE_QUESTION && rc != IDYES)
599 ret = FALSE;
601 return ret;
605 /* Little wrapper for _waccess(), returns errno or 0 if there was no error */
606 gint win32_check_write_permission(const gchar *dir)
608 static wchar_t w_dir[MAX_PATH];
609 MultiByteToWideChar(CP_UTF8, 0, dir, -1, w_dir, sizeof w_dir);
610 if (_waccess(w_dir, R_OK | W_OK) != 0)
611 return errno;
612 else
613 return 0;
617 /* Special dialog to ask for an action when closing an unsaved file */
618 gint win32_message_dialog_unsaved(const gchar *msg)
620 static wchar_t w_msg[512];
621 static wchar_t w_title[512];
622 HWND parent_hwnd = NULL;
623 gint ret;
625 /* convert the Unicode chars to wide chars */
626 MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg));
627 MultiByteToWideChar(CP_UTF8, 0, _("Question"), -1, w_title, G_N_ELEMENTS(w_title));
629 if (main_widgets.window != NULL)
630 parent_hwnd = GDK_WINDOW_HWND(main_widgets.window->window);
632 ret = MessageBoxW(parent_hwnd, w_msg, w_title, MB_YESNOCANCEL | MB_ICONQUESTION);
633 switch (ret)
635 case IDYES: ret = GTK_RESPONSE_YES; break;
636 case IDNO: ret = GTK_RESPONSE_NO; break;
637 case IDCANCEL: ret = GTK_RESPONSE_CANCEL; break;
640 return ret;
644 /* Just a simple wrapper function to open a browser window */
645 void win32_open_browser(const gchar *uri)
647 if (strncmp(uri, "file://", 7) == 0)
649 uri += 7;
650 while (*uri == '/')
651 uri++;
653 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOWNORMAL);
657 /* Returns TRUE if the command, which child_pid refers to, returned with a successful exit code,
658 * otherwise FALSE. */
659 gboolean win32_get_exit_status(GPid child_pid)
661 DWORD exit_code;
662 GetExitCodeProcess(child_pid, &exit_code);
664 return (exit_code == 0);
668 static void debug_setup_console()
670 static const WORD MAX_CONSOLE_LINES = 500;
671 gint hConHandle;
672 glong lStdHandle;
673 CONSOLE_SCREEN_BUFFER_INFO coninfo;
674 FILE *fp;
676 /* allocate a console for this app */
677 AllocConsole();
679 /* set the screen buffer to be big enough to let us scroll text */
680 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
681 coninfo.dwSize.Y = MAX_CONSOLE_LINES;
682 SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
684 /* redirect unbuffered STDOUT to the console */
685 lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
686 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
687 fp = _fdopen(hConHandle, "w");
688 *stdout = *fp;
689 setvbuf(stdout, NULL, _IONBF, 0);
691 /* redirect unbuffered STDERR to the console */
692 lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
693 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
694 fp = _fdopen(hConHandle, "w");
695 *stderr = *fp;
696 setvbuf(stderr, NULL, _IONBF, 0);
700 void win32_init_debug_code(void)
702 if (app->debug_mode)
704 /* create a console window to get log messages on Windows */
705 /** TODO remove me? */
706 debug_setup_console();
707 /* Enable GLib process spawn debug mode when Geany was started with the debug flag */
708 g_setenv("G_SPAWN_WIN32_DEBUG", "1", FALSE);
713 gchar *win32_get_hostname(void)
715 gchar hostname[100];
716 DWORD size = sizeof(hostname);
718 if (GetComputerName(hostname, &size))
719 return g_strdup(hostname);
720 else
721 return g_strdup("localhost");
725 /* Process spawning implementation for Windows, by Pierre Joye.
726 * Don't call this function directly, use utils_spawn_[a]sync() instead. */
727 gboolean win32_spawn(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
728 gchar **std_out, gchar **std_err, gint *exit_status, GError **error)
730 TCHAR buffer[MAX_PATH]=TEXT("");
731 TCHAR cmdline[MAX_PATH] = TEXT("");
732 TCHAR* lpPart[MAX_PATH]={NULL};
733 DWORD retval = 0;
734 gint argc = 0, i;
735 gint cmdpos = 0;
737 SECURITY_ATTRIBUTES saAttr;
738 BOOL fSuccess;
739 geany_win32_spawn gw_spawn;
741 /* Temp file */
742 HANDLE hStdoutTempFile = NULL;
743 HANDLE hStderrTempFile = NULL;
745 gchar *stdout_content = NULL;
746 gchar *stderr_content = NULL;
748 while (argv[argc])
750 ++argc;
752 g_return_val_if_fail (std_out == NULL ||
753 !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
754 g_return_val_if_fail (std_err == NULL ||
755 !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
757 if (flags & G_SPAWN_SEARCH_PATH)
759 retval = SearchPath(NULL, argv[0], ".exe", MAX_PATH, buffer, lpPart);
760 if (retval > 0)
761 g_snprintf(cmdline, MAX_PATH, "\"%s\"", buffer);
762 else
763 g_strlcpy(cmdline, argv[0], sizeof(cmdline));
764 cmdpos = 1;
767 for (i = cmdpos; i < argc; i++)
769 g_snprintf(cmdline, MAX_PATH, "%s %s", cmdline, argv[i]);
770 /*MessageBox(NULL, cmdline, cmdline, MB_OK);*/
773 if (std_err != NULL)
775 hStderrTempFile = GetTempFileHandle(error);
776 if (hStderrTempFile == INVALID_HANDLE_VALUE)
778 gchar *msg = g_win32_error_message(GetLastError());
779 geany_debug("win32_spawn: Second CreateFile failed (%d)", (gint) GetLastError());
780 if (error != NULL)
781 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
782 g_free(msg);
783 return FALSE;
787 if (std_out != NULL)
789 hStdoutTempFile = GetTempFileHandle(error);
790 if (hStdoutTempFile == INVALID_HANDLE_VALUE)
792 gchar *msg = g_win32_error_message(GetLastError());
793 geany_debug("win32_spawn: Second CreateFile failed (%d)", (gint) GetLastError());
794 if (error != NULL)
795 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
796 g_free(msg);
797 return FALSE;
801 /* Set the bInheritHandle flag so pipe handles are inherited. */
802 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
803 saAttr.bInheritHandle = TRUE;
804 saAttr.lpSecurityDescriptor = NULL;
806 /* Get the handle to the current STDOUT and STDERR. */
807 gw_spawn.hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
808 gw_spawn.hStderr = GetStdHandle(STD_ERROR_HANDLE);
809 gw_spawn.dwExitCode = 0;
811 /* Create a pipe for the child process's STDOUT. */
812 if (! CreatePipe(&(gw_spawn.hChildStdoutRd), &(gw_spawn.hChildStdoutWr), &saAttr, 0))
814 gchar *msg = g_win32_error_message(GetLastError());
815 geany_debug("win32_spawn: Stdout pipe creation failed (%d)", (gint) GetLastError());
816 if (error != NULL)
817 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
818 g_free(msg);
819 return FALSE;
822 /* Ensure that the read handle to the child process's pipe for STDOUT is not inherited.*/
823 SetHandleInformation(gw_spawn.hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
825 /* Create a pipe for the child process's STDERR. */
826 if (! CreatePipe(&(gw_spawn.hChildStderrRd), &(gw_spawn.hChildStderrWr), &saAttr, 0))
828 gchar *msg = g_win32_error_message(GetLastError());
829 geany_debug("win32_spawn: Stderr pipe creation failed");
830 if (error != NULL)
831 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
832 g_free(msg);
833 return FALSE;
836 /* Ensure that the read handle to the child process's pipe for STDOUT is not inherited.*/
837 SetHandleInformation(gw_spawn.hChildStderrRd, HANDLE_FLAG_INHERIT, 0);
839 /* Create a pipe for the child process's STDIN. */
840 if (! CreatePipe(&(gw_spawn.hChildStdinRd), &(gw_spawn.hChildStdinWr), &saAttr, 0))
842 gchar *msg = g_win32_error_message(GetLastError());
843 geany_debug("win32_spawn: Stdin pipe creation failed");
844 if (error != NULL)
845 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
846 g_free(msg);
847 return FALSE;
850 /* Ensure that the write handle to the child process's pipe for STDIN is not inherited. */
851 SetHandleInformation(gw_spawn.hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
853 /* Now create the child process. */
854 fSuccess = CreateChildProcess(&gw_spawn, cmdline, dir, error);
855 if (exit_status)
857 *exit_status = gw_spawn.dwExitCode;
860 if (! fSuccess)
862 geany_debug("win32_spawn: Create process failed");
863 if (error != NULL)
864 *error = g_error_new(G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "Create process failed");
865 return FALSE;
868 /* Read from pipe that is the standard output for child process. */
869 if (std_out != NULL)
871 ReadFromPipe(gw_spawn.hChildStdoutRd, gw_spawn.hChildStdoutWr, hStdoutTempFile, error);
872 if (! GetContentFromHandle(hStdoutTempFile, &stdout_content, error))
874 return FALSE;
876 *std_out = stdout_content;
879 if (std_err != NULL)
881 ReadFromPipe(gw_spawn.hChildStderrRd, gw_spawn.hChildStderrWr, hStderrTempFile, error);
882 if (! GetContentFromHandle(hStderrTempFile, &stderr_content, error))
884 return FALSE;
886 *std_err = stderr_content;
888 return TRUE;
892 static gboolean GetContentFromHandle(HANDLE hFile, gchar **content, GError **error)
894 DWORD filesize;
895 gchar * buffer;
896 DWORD dwRead;
898 filesize = GetFileSize(hFile, NULL);
899 if (filesize < 1)
901 *content = NULL;
902 return TRUE;
905 buffer = g_malloc(filesize + 1);
906 if (! buffer)
908 gchar *msg = g_win32_error_message(GetLastError());
909 geany_debug("GetContentFromHandle: Alloc failed");
910 if (error != NULL)
911 *error = g_error_new(G_SPAWN_ERROR, G_SPAWN_ERROR, "%s", msg);
912 g_free(msg);
913 return FALSE;
916 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
917 if (! ReadFile(hFile, buffer, filesize, &dwRead, NULL) || dwRead == 0)
919 gchar *msg = g_win32_error_message(GetLastError());
920 geany_debug("GetContentFromHandle: Cannot read tempfile");
921 if (error != NULL)
922 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
923 g_free(msg);
924 return FALSE;
927 if (! CloseHandle(hFile))
929 gchar *msg = g_win32_error_message(GetLastError());
930 geany_debug("GetContentFromHandle: CloseHandle failed (%d)", (gint) GetLastError());
931 if (error != NULL)
932 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
933 g_free(msg);
934 g_free(buffer);
935 *content = NULL;
936 return FALSE;
938 buffer[filesize] = '\0';
939 *content = buffer;
940 return TRUE;
944 gchar *win32_expand_environment_variables(const gchar *str)
946 gchar expCmdline[32768]; /* 32768 is the limit for ExpandEnvironmentStrings() */
948 if (ExpandEnvironmentStrings((LPCTSTR) str, (LPTSTR) expCmdline, sizeof(expCmdline)) != 0)
949 return g_strdup(expCmdline);
950 else
951 return g_strdup(str);
955 static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline, const TCHAR *dir, GError **error)
957 PROCESS_INFORMATION piProcInfo;
958 STARTUPINFO siStartInfo;
959 BOOL bFuncRetn = FALSE;
960 gchar *expandedCmdline;
962 /* Set up members of the PROCESS_INFORMATION structure. */
963 ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
965 /* Set up members of the STARTUPINFO structure.*/
966 ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
968 siStartInfo.cb = sizeof(STARTUPINFO);
969 siStartInfo.hStdError = gw_spawn->hChildStderrWr;
970 siStartInfo.hStdOutput = gw_spawn->hChildStdoutWr;
971 siStartInfo.hStdInput = gw_spawn->hChildStdinRd;
972 siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
974 /* Expand environment variables like %blah%. */
975 expandedCmdline = win32_expand_environment_variables(szCmdline);
977 /* Create the child process. */
978 bFuncRetn = CreateProcess(NULL,
979 expandedCmdline, /* command line */
980 NULL, /* process security attributes */
981 NULL, /* primary thread security attributes */
982 TRUE, /* handles are inherited */
983 CREATE_NO_WINDOW, /* creation flags */
984 NULL, /* use parent's environment */
985 dir, /* use parent's current directory */
986 &siStartInfo, /* STARTUPINFO pointer */
987 &piProcInfo); /* receives PROCESS_INFORMATION */
989 g_free(expandedCmdline);
991 if (bFuncRetn == 0)
993 gchar *msg = g_win32_error_message(GetLastError());
994 geany_debug("CreateChildProcess: CreateProcess failed (%s)", msg);
995 if (*error != NULL)
996 *error = g_error_new(G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "%s", msg);
997 g_free(msg);
998 return FALSE;
1000 else
1002 gint i;
1003 DWORD dwStatus;
1005 for (i = 0; i < 2 && (dwStatus = WaitForSingleObject(piProcInfo.hProcess, 30*1000)) == WAIT_TIMEOUT; i++)
1007 geany_debug("CreateChildProcess: CreateProcess failed");
1008 TerminateProcess(piProcInfo.hProcess, WAIT_TIMEOUT); /* NOTE: This will not kill grandkids. */
1011 if (GetExitCodeProcess(piProcInfo.hProcess, &gw_spawn->dwExitCode) != 0)
1013 gchar *msg = g_win32_error_message(GetLastError());
1014 geany_debug("GetExitCodeProcess failed: %s", msg);
1015 if (error != NULL)
1016 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
1017 g_free(msg);
1019 CloseHandle(piProcInfo.hProcess);
1020 CloseHandle(piProcInfo.hThread);
1021 return bFuncRetn;
1023 return FALSE;
1027 static VOID ReadFromPipe(HANDLE hRead, HANDLE hWrite, HANDLE hFile, GError **error)
1029 DWORD dwRead, dwWritten;
1030 CHAR chBuf[BUFSIZE];
1032 /* Close the write end of the pipe before reading from the
1033 read end of the pipe. */
1034 if (! CloseHandle(hWrite))
1036 gchar *msg = g_win32_error_message(GetLastError());
1037 geany_debug("ReadFromPipe: Closing handle failed");
1038 if (error != NULL)
1039 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
1040 g_free(msg);
1041 return;
1044 /* Read output from the child process, and write to parent's STDOUT. */
1045 for (;;)
1047 if (! ReadFile(hRead, chBuf, BUFSIZE, &dwRead, NULL) || dwRead == 0)
1048 break;
1050 if (! WriteFile(hFile, chBuf, dwRead, &dwWritten, NULL))
1051 break;
1056 static HANDLE GetTempFileHandle(GError **error)
1058 /* Temp file */
1059 DWORD dwBufSize = BUFSIZE;
1060 UINT uRetVal;
1061 TCHAR szTempName[BUFSIZE];
1062 TCHAR lpPathBuffer[BUFSIZE];
1063 DWORD dwRetVal;
1064 HANDLE hTempFile;
1066 /* Get the temp path. */
1067 dwRetVal = GetTempPath(dwBufSize, /* length of the buffer*/
1068 lpPathBuffer); /* buffer for path */
1070 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1072 gchar *msg = g_win32_error_message(GetLastError());
1073 geany_debug("GetTempFileHandle: GetTempPath failed (%d)", (gint) GetLastError());
1074 if (error != NULL)
1075 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1076 g_free(msg);
1077 return NULL;
1080 /* Create a temporary file for STDOUT. */
1081 uRetVal = GetTempFileName(lpPathBuffer, /* directory for tmp files */
1082 TEXT("GEANY_VCDIFF_"), /* temp file name prefix */
1083 0, /* create unique name */
1084 szTempName); /* buffer for name */
1085 if (uRetVal == 0)
1087 gchar *msg = g_win32_error_message(GetLastError());
1088 geany_debug("GetTempFileName failed (%d)", (gint) GetLastError());
1089 if (error != NULL)
1090 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1091 g_free(msg);
1092 return NULL;
1095 hTempFile = CreateFile((LPTSTR) szTempName, /* file name */
1096 GENERIC_READ | GENERIC_WRITE, /* open r-w */
1097 0, /* do not share */
1098 NULL, /* default security */
1099 CREATE_ALWAYS, /* overwrite existing */
1100 FILE_ATTRIBUTE_NORMAL,/* normal file */
1101 NULL); /* no template */
1103 if (hTempFile == INVALID_HANDLE_VALUE)
1105 gchar *msg = g_win32_error_message(GetLastError());
1106 geany_debug("GetTempFileHandle: Second CreateFile failed (%d)", (gint) GetLastError());
1107 if (error != NULL)
1108 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1109 g_free(msg);
1110 return NULL;
1112 return hTempFile;
1116 /* From GDK (they got it from MS Knowledge Base article Q130698) */
1117 static gboolean resolve_link(HWND hWnd, wchar_t *link, gchar **lpszPath)
1119 WIN32_FILE_ATTRIBUTE_DATA wfad;
1120 HRESULT hres;
1121 IShellLinkW *pslW = NULL;
1122 IPersistFile *ppf = NULL;
1123 LPVOID pslWV = NULL;
1124 LPVOID ppfV = NULL;
1126 /* Check if the file is empty first because IShellLink::Resolve for some reason succeeds
1127 * with an empty file and returns an empty "link target". (#524151) */
1128 if (!GetFileAttributesExW(link, GetFileExInfoStandard, &wfad) ||
1129 (wfad.nFileSizeHigh == 0 && wfad.nFileSizeLow == 0))
1131 return FALSE;
1134 /* Assume failure to start with: */
1135 *lpszPath = 0;
1137 CoInitialize(NULL);
1139 hres = CoCreateInstance(
1140 &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, &pslWV);
1142 if (SUCCEEDED(hres))
1144 /* The IShellLink interface supports the IPersistFile interface.
1145 * Get an interface pointer to it. */
1146 pslW = (IShellLinkW*) pslWV;
1147 hres = pslW->lpVtbl->QueryInterface(pslW, &IID_IPersistFile, &ppfV);
1150 if (SUCCEEDED(hres))
1152 /* Load the file. */
1153 ppf = (IPersistFile*) ppfV;
1154 hres = ppf->lpVtbl->Load(ppf, link, STGM_READ);
1157 if (SUCCEEDED(hres))
1159 /* Resolve the link by calling the Resolve() interface function. */
1160 hres = pslW->lpVtbl->Resolve(pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI);
1163 if (SUCCEEDED(hres))
1165 wchar_t wtarget[MAX_PATH];
1167 hres = pslW->lpVtbl->GetPath(pslW, wtarget, MAX_PATH, NULL, 0);
1168 if (SUCCEEDED(hres))
1169 *lpszPath = g_utf16_to_utf8(wtarget, -1, NULL, NULL, NULL);
1172 if (ppf)
1173 ppf->lpVtbl->Release(ppf);
1175 if (pslW)
1176 pslW->lpVtbl->Release(pslW);
1178 return SUCCEEDED(hres);
1182 /* Checks whether file_name is a Windows shortcut. file_name is expected in UTF-8 encoding.
1183 * If file_name is a Windows shortcut, it returns the target in UTF-8 encoding.
1184 * If it is not a shortcut, it returns a newly allocated copy of file_name. */
1185 gchar *win32_get_shortcut_target(const gchar *file_name)
1187 gchar *path = NULL;
1188 wchar_t *wfilename = g_utf8_to_utf16(file_name, -1, NULL, NULL, NULL);
1190 resolve_link(GDK_WINDOW_HWND(main_widgets.window->window), wfilename, &path);
1191 g_free(wfilename);
1193 if (path == NULL)
1194 return g_strdup(file_name);
1195 else
1196 return path;
1200 void win32_set_working_directory(const gchar *dir)
1202 SetCurrentDirectory(dir);
1206 gchar *win32_get_installation_dir(void)
1208 #if GLIB_CHECK_VERSION(2, 16, 0)
1209 return g_win32_get_package_installation_directory_of_module(NULL);
1210 #else
1211 return g_win32_get_package_installation_directory(NULL, NULL);
1212 #endif
1216 #endif