Move document encoding conversion with BOM support to encodings.[ch]
[geany-mirror.git] / src / win32.c
blob7b7b284fd91ef629829be8e68576c61502b17c3d
1 /*
2 * win32.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2011 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
60 #define CMDSIZE 32768
62 struct _geany_win32_spawn
64 HANDLE hChildStdinRd;
65 HANDLE hChildStdinWr;
66 HANDLE hChildStdoutRd;
67 HANDLE hChildStdoutWr;
68 HANDLE hChildStderrRd;
69 HANDLE hChildStderrWr;
70 HANDLE hInputFile;
71 HANDLE hStdout;
72 HANDLE hStderr;
73 HANDLE processId;
74 DWORD dwExitCode;
76 typedef struct _geany_win32_spawn geany_win32_spawn;
78 static gboolean GetContentFromHandle(HANDLE hFile, gchar **content, GError **error);
79 static HANDLE GetTempFileHandle(GError **error);
80 static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline, const TCHAR *dir, GError **error);
81 static VOID ReadFromPipe(HANDLE hRead, HANDLE hWrite, HANDLE hFile, GError **error);
84 static wchar_t *get_file_filters(void)
86 gchar *string;
87 guint i, j, len;
88 static wchar_t title[4096];
89 GString *str = g_string_sized_new(100);
90 GString *all_patterns = g_string_sized_new(100);
91 GSList *node;
92 gchar *tmp;
94 /* create meta file filter "All files" */
95 g_string_append_printf(str, "%s\t*\t", _("All files"));
96 /* create meta file filter "All Source" (skip GEANY_FILETYPES_NONE) */
97 for (i = GEANY_FILETYPES_NONE + 1; i < filetypes_array->len; i++)
99 for (j = 0; filetypes[i]->pattern[j] != NULL; j++)
101 g_string_append(all_patterns, filetypes[i]->pattern[j]);
102 g_string_append_c(all_patterns, ';');
105 g_string_append_printf(str, "%s\t%s\t", _("All Source"), all_patterns->str);
106 g_string_free(all_patterns, TRUE);
107 /* add 'usual' filetypes */
108 foreach_slist(node, filetypes_by_title)
110 GeanyFiletype *ft = node->data;
112 if (G_UNLIKELY(ft->id == GEANY_FILETYPES_NONE))
113 continue;
114 tmp = g_strjoinv(";", ft->pattern);
115 g_string_append_printf(str, "%s\t%s\t", ft->title, tmp);
116 g_free(tmp);
118 g_string_append_c(str, '\t'); /* the final \0 byte to mark the end of the string */
119 string = str->str;
120 g_string_free(str, FALSE);
122 /* replace all "\t"s by \0 */
123 len = strlen(string);
124 for (i = 0; i < len; i++)
126 if (string[i] == '\t')
127 string[i] = '\0';
129 MultiByteToWideChar(CP_UTF8, 0, string, len, title, sizeof(title));
130 g_free(string);
132 return title;
136 static wchar_t *get_file_filter_all_files(void)
138 guint len;
139 static wchar_t title[4096];
140 gchar *filter;
142 /* create meta file filter "All files" */
143 filter = g_strdup_printf("%s\0*\0", _("All files"));
145 len = strlen(_("All files")) + 3;
146 MultiByteToWideChar(CP_UTF8, 0, filter, len, title, sizeof(title));
147 g_free(filter);
149 return title;
153 static wchar_t *get_filters(gboolean project_files)
155 gchar *string;
156 gint i, len;
157 static wchar_t title[1024];
159 if (project_files)
161 string = g_strconcat(_("Geany project files"), "\t", "*." GEANY_PROJECT_EXT, "\t",
162 filetypes[GEANY_FILETYPES_NONE]->title, "\t",
163 filetypes[GEANY_FILETYPES_NONE]->pattern[0], "\t", NULL);
165 else
167 string = g_strconcat(_("Executables"), "\t", "*.exe;*.bat;*.cmd", "\t",
168 filetypes[GEANY_FILETYPES_NONE]->title, "\t",
169 filetypes[GEANY_FILETYPES_NONE]->pattern[0], "\t", NULL);
172 /* replace all "\t"s by \0 */
173 len = strlen(string);
174 for (i = 0; i < len; i++)
176 if (string[i] == '\t') string[i] = '\0';
178 MultiByteToWideChar(CP_UTF8, 0, string, len, title, sizeof(title));
179 g_free(string);
181 return title;
185 /* Converts the given UTF-8 filename or directory name into something usable for Windows and
186 * returns the directory part of the given filename. */
187 static wchar_t *get_dir_for_path(const gchar *utf8_filename)
189 static wchar_t w_dir[MAX_PATH];
190 gchar *result;
192 if (g_file_test(utf8_filename, G_FILE_TEST_IS_DIR))
193 result = (gchar*) utf8_filename;
194 else
195 result = g_path_get_dirname(utf8_filename);
197 MultiByteToWideChar(CP_UTF8, 0, result, -1, w_dir, sizeof(w_dir));
199 if (result != utf8_filename)
200 g_free(result);
202 return w_dir;
206 /* Callback function for setting the initial directory of the folder open dialog. This could also
207 * be done with BROWSEINFO.pidlRoot and SHParseDisplayName but SHParseDisplayName is not available
208 * on systems below Windows XP. So, we go the hard way by creating a callback which will set up the
209 * folder when the dialog is initialised. Yeah, I like Windows. */
210 INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)
212 switch (uMsg)
214 case BFFM_INITIALIZED:
216 SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, pData);
217 break;
219 case BFFM_SELCHANGED:
221 /* set the status window to the currently selected path. */
222 static wchar_t szDir[MAX_PATH];
223 if (SHGetPathFromIDListW((LPITEMIDLIST) lp, szDir))
225 SendMessageW(hwnd, BFFM_SETSTATUSTEXTW, 0, (LPARAM) szDir);
227 break;
230 return 0;
234 /* Shows a folder selection dialog.
235 * initial_dir is expected in UTF-8
236 * The selected folder name is returned. */
237 gchar *win32_show_folder_dialog(GtkWidget *parent, const gchar *title, const gchar *initial_dir)
239 BROWSEINFOW bi;
240 LPCITEMIDLIST pidl;
241 gchar *result = NULL;
242 wchar_t fname[MAX_PATH];
243 wchar_t w_title[512];
245 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, sizeof(w_title));
247 if (parent == NULL)
248 parent = main_widgets.window;
250 memset(&bi, 0, sizeof bi);
251 bi.hwndOwner = GDK_WINDOW_HWND(parent->window);
252 bi.pidlRoot = NULL;
253 bi.lpszTitle = w_title;
254 bi.lpfn = BrowseCallbackProc;
255 bi.lParam = (LPARAM) get_dir_for_path(initial_dir);
256 bi.ulFlags = BIF_DONTGOBELOWDOMAIN | BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT;
258 pidl = SHBrowseForFolderW(&bi);
260 /* convert the strange Windows folder list item something into an usual path string ;-) */
261 if (pidl != 0)
263 if (SHGetPathFromIDListW(pidl, fname))
265 result = g_malloc0(MAX_PATH * 2);
266 WideCharToMultiByte(CP_UTF8, 0, fname, -1, result, MAX_PATH * 2, NULL, NULL);
268 /* SHBrowseForFolder() probably leaks memory here, but how to free the allocated memory? */
270 return result;
274 /* Shows a file open dialog.
275 * If allow_new_file is set, the file to be opened doesn't have to exist.
276 * initial_dir is expected in UTF-8
277 * The selected file name is returned.
278 * If project_file_filter is set, the file open dialog will have a file filter for Geany project
279 * files, a filter for executables otherwise. */
280 gchar *win32_show_project_open_dialog(GtkWidget *parent, const gchar *title,
281 const gchar *initial_dir, gboolean allow_new_file,
282 gboolean project_file_filter)
284 OPENFILENAMEW of;
285 gint retval;
286 wchar_t fname[MAX_PATH];
287 wchar_t w_title[512];
288 gchar *filename;
290 fname[0] = '\0';
292 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, sizeof(w_title));
294 if (parent == NULL)
295 parent = main_widgets.window;
297 /* initialise file dialog info struct */
298 memset(&of, 0, sizeof of);
299 #ifdef OPENFILENAME_SIZE_VERSION_400
300 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
301 #else
302 of.lStructSize = sizeof of;
303 #endif
304 of.hwndOwner = GDK_WINDOW_HWND(parent->window);
305 of.lpstrFilter = get_filters(project_file_filter);
307 of.lpstrCustomFilter = NULL;
308 of.nFilterIndex = 0;
309 of.lpstrFile = fname;
310 of.lpstrInitialDir = get_dir_for_path(initial_dir);
311 of.nMaxFile = 2048;
312 of.lpstrFileTitle = NULL;
313 of.lpstrTitle = w_title;
314 of.lpstrDefExt = L"";
315 of.Flags = OFN_PATHMUSTEXIST | OFN_EXPLORER | OFN_HIDEREADONLY;
316 if (! allow_new_file)
317 of.Flags |= OFN_FILEMUSTEXIST;
319 retval = GetOpenFileNameW(&of);
321 if (! retval)
323 if (CommDlgExtendedError())
325 gchar *error;
326 error = g_strdup_printf("File dialog box error (%x)", (int)CommDlgExtendedError());
327 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
328 g_free(error);
330 return NULL;
332 /* convert the resulting filename into UTF-8 (from whatever encoding it has at this moment) */
333 filename = g_malloc0(MAX_PATH * 2);
334 WideCharToMultiByte(CP_UTF8, 0, fname, -1, filename, MAX_PATH * 2, NULL, NULL);
336 return filename;
340 /* initial_dir can be NULL to use the current working directory.
341 * Returns: TRUE if the dialog was not cancelled. */
342 gboolean win32_show_document_open_dialog(GtkWindow *parent, const gchar *title, const gchar *initial_dir)
344 OPENFILENAMEW of;
345 gint retval;
346 guint x;
347 gchar tmp[MAX_PATH];
348 wchar_t fname[MAX_PATH];
349 wchar_t w_dir[MAX_PATH];
350 wchar_t w_title[512];
352 fname[0] = '\0';
354 if (initial_dir != NULL)
355 MultiByteToWideChar(CP_UTF8, 0, initial_dir, -1, w_dir, sizeof(w_dir));
357 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, sizeof(w_title));
359 /* initialise file dialog info struct */
360 memset(&of, 0, sizeof of);
361 #ifdef OPENFILENAME_SIZE_VERSION_400
362 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
363 #else
364 of.lStructSize = sizeof of;
365 #endif
366 of.hwndOwner = GDK_WINDOW_HWND(GTK_WIDGET(parent)->window);
367 of.lpstrFilter = get_file_filters();
369 of.lpstrCustomFilter = NULL;
370 of.nFilterIndex = GEANY_FILETYPES_NONE + 1;
371 of.lpstrFile = fname;
372 of.lpstrInitialDir = (initial_dir != NULL) ? w_dir : NULL;
373 of.nMaxFile = 2048;
374 of.lpstrFileTitle = NULL;
375 of.lpstrTitle = w_title;
376 of.lpstrDefExt = L"";
377 of.Flags = OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST | OFN_EXPLORER;
379 retval = GetOpenFileNameW(&of);
381 if (!retval)
383 if (CommDlgExtendedError())
385 gchar error[100];
386 g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
387 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
389 return FALSE;
392 x = of.nFileOffset - 1;
393 if (x != wcslen(fname))
394 { /* open a single file */
395 WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
396 document_open_file(tmp, of.Flags & OFN_READONLY, NULL, NULL);
398 else
399 { /* open multiple files */
400 gchar file_name[MAX_PATH];
401 gchar dir_name[MAX_PATH];
403 WideCharToMultiByte(CP_UTF8, 0, fname, of.nFileOffset,
404 dir_name, sizeof(dir_name), NULL, NULL);
405 for (; ;)
407 if (! fname[x])
409 if (! fname[x + 1])
410 break;
412 WideCharToMultiByte(CP_UTF8, 0, fname + x + 1, -1,
413 tmp, sizeof(tmp), NULL, NULL);
414 g_snprintf(file_name, 511, "%s\\%s", dir_name, tmp);
416 /* convert the resulting filename into UTF-8 */
417 document_open_file(file_name, of.Flags & OFN_READONLY, NULL, NULL);
419 x++;
422 return (retval != 0);
426 gchar *win32_show_document_save_as_dialog(GtkWindow *parent, const gchar *title,
427 const gchar *initial_file)
429 OPENFILENAMEW of;
430 gint retval;
431 gchar tmp[MAX_PATH];
432 wchar_t w_file[MAX_PATH];
433 wchar_t w_title[512];
434 guint x;
436 w_file[0] = '\0';
438 if (initial_file != NULL)
439 MultiByteToWideChar(CP_UTF8, 0, initial_file, -1, w_file, sizeof(w_file));
441 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, sizeof(w_title));
443 /* initialise file dialog info struct */
444 memset(&of, 0, sizeof of);
445 #ifdef OPENFILENAME_SIZE_VERSION_400
446 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
447 #else
448 of.lStructSize = sizeof of;
449 #endif
450 of.hwndOwner = GDK_WINDOW_HWND(GTK_WIDGET(parent)->window);
452 of.lpstrFilter = get_file_filter_all_files();
453 of.lpstrCustomFilter = NULL;
454 of.nFilterIndex = 0;
456 of.lpstrFile = w_file;
457 of.nMaxFile = 2048;
458 of.lpstrFileTitle = NULL;
459 of.lpstrTitle = w_title;
460 of.lpstrDefExt = L"";
461 of.Flags = OFN_FILEMUSTEXIST | OFN_EXPLORER;
462 retval = GetSaveFileNameW(&of);
464 if (! retval)
466 if (CommDlgExtendedError())
468 gchar *error = g_strdup_printf(
469 "File dialog box error (%x)", (gint) CommDlgExtendedError());
470 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
471 g_free(error);
473 return NULL;
476 WideCharToMultiByte(CP_UTF8, 0, w_file, -1, tmp, sizeof(tmp), NULL, NULL);
478 return g_strdup(tmp);
482 /* initial_dir can be NULL to use the current working directory.
483 * Returns: the selected filename */
484 gchar *win32_show_file_dialog(GtkWindow *parent, const gchar *title, const gchar *initial_file)
486 OPENFILENAMEW of;
487 gint retval;
488 gchar tmp[MAX_PATH];
489 wchar_t w_file[MAX_PATH];
490 wchar_t w_title[512];
491 guint x;
493 w_file[0] = '\0';
495 if (initial_file != NULL)
496 MultiByteToWideChar(CP_UTF8, 0, initial_file, -1, w_file, sizeof(w_file));
498 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, sizeof(w_title));
500 /* initialise file dialog info struct */
501 memset(&of, 0, sizeof of);
502 #ifdef OPENFILENAME_SIZE_VERSION_400
503 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
504 #else
505 of.lStructSize = sizeof of;
506 #endif
507 of.hwndOwner = GDK_WINDOW_HWND(GTK_WIDGET(parent)->window);
509 of.lpstrFile = w_file;
510 of.nMaxFile = 2048;
511 of.lpstrFileTitle = NULL;
512 of.lpstrTitle = w_title;
513 of.lpstrDefExt = L"";
514 of.Flags = OFN_FILEMUSTEXIST | OFN_EXPLORER;
515 retval = GetOpenFileNameW(&of);
517 if (! retval)
519 if (CommDlgExtendedError())
521 gchar *error = g_strdup_printf(
522 "File dialog box error (%x)", (gint) CommDlgExtendedError());
523 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
524 g_free(error);
526 return NULL;
529 WideCharToMultiByte(CP_UTF8, 0, w_file, -1, tmp, sizeof(tmp), NULL, NULL);
531 return g_strdup(tmp);
535 void win32_show_font_dialog(void)
537 gint retval;
538 CHOOSEFONT cf;
539 LOGFONT lf; /* logical font structure */
541 memset(&cf, 0, sizeof cf);
542 cf.lStructSize = sizeof cf;
543 cf.hwndOwner = GDK_WINDOW_HWND(main_widgets.window->window);
544 cf.lpLogFont = &lf;
545 cf.Flags = CF_APPLY | CF_NOSCRIPTSEL | CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
547 retval = ChooseFont(&cf);
549 if (retval)
551 gchar *editorfont = g_strdup_printf("%s %d", lf.lfFaceName, (cf.iPointSize / 10));
552 ui_set_editor_font(editorfont);
553 g_free(editorfont);
558 void win32_show_color_dialog(const gchar *colour)
560 CHOOSECOLOR cc;
561 static COLORREF acr_cust_clr[16];
562 static DWORD rgb_current;
563 gchar *hex = g_malloc0(12);
564 GeanyDocument *doc = document_get_current();
566 /* Initialize CHOOSECOLOR */
567 memset(&cc, 0, sizeof cc);
568 cc.lStructSize = sizeof(cc);
569 cc.hwndOwner = GDK_WINDOW_HWND(main_widgets.window->window);
570 cc.lpCustColors = (LPDWORD) acr_cust_clr;
571 cc.rgbResult = (colour != NULL) ? utils_strtod(colour, NULL, colour[0] == '#') : 0;
572 cc.Flags = CC_FULLOPEN | CC_RGBINIT;
574 if (ChooseColor(&cc))
576 rgb_current = cc.rgbResult;
577 g_snprintf(hex, 11, "#%02X%02X%02X",
578 (guint) (utils_scale_round(GetRValue(rgb_current), 255)),
579 (guint) (utils_scale_round(GetGValue(rgb_current), 255)),
580 (guint) (utils_scale_round(GetBValue(rgb_current), 255)));
582 editor_insert_color(doc->editor, hex);
584 g_free(hex);
588 void win32_show_pref_file_dialog(GtkEntry *item)
590 OPENFILENAMEW of;
591 gint retval, len;
592 wchar_t fname[MAX_PATH];
593 gchar tmp[MAX_PATH];
594 gchar **field, *filename;
596 fname[0] = '\0';
598 /* cut the options from the command line */
599 field = g_strsplit(gtk_entry_get_text(GTK_ENTRY(item)), " ", 2);
600 if (field[0])
602 filename = g_find_program_in_path(field[0]);
603 if (filename != NULL && g_file_test(filename, G_FILE_TEST_EXISTS))
605 MultiByteToWideChar(CP_UTF8, 0, filename, -1, fname, sizeof(fname));
606 g_free(filename);
610 /* initialize file dialog info struct */
611 memset(&of, 0, sizeof of);
612 #ifdef OPENFILENAME_SIZE_VERSION_400
613 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
614 #else
615 of.lStructSize = sizeof of;
616 #endif
617 of.hwndOwner = GDK_WINDOW_HWND(ui_widgets.prefs_dialog->window);
619 of.lpstrFilter = get_filters(FALSE);
620 of.lpstrCustomFilter = NULL;
621 of.nFilterIndex = 1;
623 of.lpstrFile = fname;
624 of.nMaxFile = 2048;
625 of.lpstrFileTitle = NULL;
626 /*of.lpstrInitialDir = g_get_home_dir();*/
627 of.lpstrInitialDir = NULL;
628 of.lpstrTitle = NULL;
629 of.lpstrDefExt = L"exe";
630 of.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_EXPLORER;
631 retval = GetOpenFileNameW(&of);
633 if (!retval)
635 if (CommDlgExtendedError())
637 gchar error[100];
638 g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
639 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
641 g_strfreev(field);
642 return;
645 len = WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
646 if ((of.nFileOffset - 1) != len)
648 if (g_strv_length(field) > 1)
649 /* add the command line args of the old command */
650 /** TODO this fails badly when the old command contained spaces, we need quoting here */
651 filename = g_strconcat(tmp, " ", field[1], NULL);
652 else
654 filename = g_strdup(tmp);
656 gtk_entry_set_text(GTK_ENTRY(item), filename);
657 g_free(filename);
659 g_strfreev(field);
663 /* Creates a native Windows message box of the given type and returns always TRUE
664 * or FALSE representing th pressed Yes or No button.
665 * If type is not GTK_MESSAGE_QUESTION, it returns always TRUE. */
666 gboolean win32_message_dialog(GtkWidget *parent, GtkMessageType type, const gchar *msg)
668 gboolean ret = TRUE;
669 gint rc;
670 guint t;
671 const gchar *title;
672 HWND parent_hwnd = NULL;
673 static wchar_t w_msg[512];
674 static wchar_t w_title[512];
676 switch (type)
678 case GTK_MESSAGE_ERROR:
680 t = MB_OK | MB_ICONERROR;
681 title = _("Error");
682 break;
684 case GTK_MESSAGE_QUESTION:
686 t = MB_YESNO | MB_ICONQUESTION;
687 title = _("Question");
688 break;
690 case GTK_MESSAGE_WARNING:
692 t = MB_OK | MB_ICONWARNING;
693 title = _("Warning");
694 break;
696 default:
698 t = MB_OK | MB_ICONINFORMATION;
699 title = _("Information");
700 break;
704 /* convert the Unicode chars to wide chars */
705 /** TODO test if LANG == C then possibly skip conversion => g_win32_getlocale() */
706 MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg));
707 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
709 if (parent != NULL)
710 parent_hwnd = GDK_WINDOW_HWND(parent->window);
711 else if (main_widgets.window != NULL)
712 parent_hwnd = GDK_WINDOW_HWND(main_widgets.window->window);
714 /* display the message box */
715 rc = MessageBoxW(parent_hwnd, w_msg, w_title, t);
717 if (type == GTK_MESSAGE_QUESTION && rc != IDYES)
718 ret = FALSE;
720 return ret;
724 /* Little wrapper for _waccess(), returns errno or 0 if there was no error */
725 gint win32_check_write_permission(const gchar *dir)
727 static wchar_t w_dir[MAX_PATH];
728 MultiByteToWideChar(CP_UTF8, 0, dir, -1, w_dir, sizeof w_dir);
729 if (_waccess(w_dir, R_OK | W_OK) != 0)
730 return errno;
731 else
732 return 0;
736 /* Special dialog to ask for an action when closing an unsaved file */
737 gint win32_message_dialog_unsaved(const gchar *msg)
739 static wchar_t w_msg[512];
740 static wchar_t w_title[512];
741 HWND parent_hwnd = NULL;
742 gint ret;
744 /* convert the Unicode chars to wide chars */
745 MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg));
746 MultiByteToWideChar(CP_UTF8, 0, _("Question"), -1, w_title, G_N_ELEMENTS(w_title));
748 if (main_widgets.window != NULL)
749 parent_hwnd = GDK_WINDOW_HWND(main_widgets.window->window);
751 ret = MessageBoxW(parent_hwnd, w_msg, w_title, MB_YESNOCANCEL | MB_ICONQUESTION);
752 switch (ret)
754 case IDYES: ret = GTK_RESPONSE_YES; break;
755 case IDNO: ret = GTK_RESPONSE_NO; break;
756 case IDCANCEL: ret = GTK_RESPONSE_CANCEL; break;
759 return ret;
763 /* Just a simple wrapper function to open a browser window */
764 void win32_open_browser(const gchar *uri)
766 if (strncmp(uri, "file://", 7) == 0)
768 uri += 7;
769 if (strchr(uri, ':') != NULL)
771 while (*uri == '/')
772 uri++;
775 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOWNORMAL);
779 /* Returns TRUE if the command, which child_pid refers to, returned with a successful exit code,
780 * otherwise FALSE. */
781 gboolean win32_get_exit_status(GPid child_pid)
783 DWORD exit_code;
784 GetExitCodeProcess(child_pid, &exit_code);
786 return (exit_code == 0);
790 static void debug_setup_console()
792 static const WORD MAX_CONSOLE_LINES = 500;
793 gint hConHandle;
794 glong lStdHandle;
795 CONSOLE_SCREEN_BUFFER_INFO coninfo;
796 FILE *fp;
798 /* allocate a console for this app */
799 AllocConsole();
801 /* set the screen buffer to be big enough to let us scroll text */
802 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
803 coninfo.dwSize.Y = MAX_CONSOLE_LINES;
804 SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
806 /* redirect unbuffered STDOUT to the console */
807 lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
808 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
809 fp = _fdopen(hConHandle, "w");
810 *stdout = *fp;
811 setvbuf(stdout, NULL, _IONBF, 0);
813 /* redirect unbuffered STDERR to the console */
814 lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
815 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
816 fp = _fdopen(hConHandle, "w");
817 *stderr = *fp;
818 setvbuf(stderr, NULL, _IONBF, 0);
822 void win32_init_debug_code(void)
824 if (app->debug_mode)
826 /* create a console window to get log messages on Windows */
827 /** TODO remove me? */
828 debug_setup_console();
829 /* Enable GLib process spawn debug mode when Geany was started with the debug flag */
830 g_setenv("G_SPAWN_WIN32_DEBUG", "1", FALSE);
835 gchar *win32_get_hostname(void)
837 gchar hostname[100];
838 DWORD size = sizeof(hostname);
840 if (GetComputerName(hostname, &size))
841 return g_strdup(hostname);
842 else
843 return g_strdup("localhost");
847 /* Process spawning implementation for Windows, by Pierre Joye.
848 * Don't call this function directly, use utils_spawn_[a]sync() instead. */
849 gboolean win32_spawn(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
850 gchar **std_out, gchar **std_err, gint *exit_status, GError **error)
852 TCHAR buffer[CMDSIZE]=TEXT("");
853 TCHAR cmdline[CMDSIZE] = TEXT("");
854 TCHAR* lpPart[CMDSIZE]={NULL};
855 DWORD retval = 0;
856 gint argc = 0, i;
857 gint cmdpos = 0;
859 SECURITY_ATTRIBUTES saAttr;
860 BOOL fSuccess;
861 geany_win32_spawn gw_spawn;
863 /* Temp file */
864 HANDLE hStdoutTempFile = NULL;
865 HANDLE hStderrTempFile = NULL;
867 gchar *stdout_content = NULL;
868 gchar *stderr_content = NULL;
870 while (argv[argc])
872 ++argc;
874 g_return_val_if_fail (std_out == NULL ||
875 !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
876 g_return_val_if_fail (std_err == NULL ||
877 !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
879 if (flags & G_SPAWN_SEARCH_PATH)
881 retval = SearchPath(NULL, argv[0], ".exe", sizeof(buffer), buffer, lpPart);
882 if (retval > 0)
883 g_snprintf(cmdline, sizeof(cmdline), "\"%s\"", buffer);
884 else
885 g_strlcpy(cmdline, argv[0], sizeof(cmdline));
886 cmdpos = 1;
889 for (i = cmdpos; i < argc; i++)
891 g_snprintf(cmdline, sizeof(cmdline), "%s %s", cmdline, argv[i]);
892 /*MessageBox(NULL, cmdline, cmdline, MB_OK);*/
895 if (std_err != NULL)
897 hStderrTempFile = GetTempFileHandle(error);
898 if (hStderrTempFile == INVALID_HANDLE_VALUE)
900 gchar *msg = g_win32_error_message(GetLastError());
901 geany_debug("win32_spawn: Second CreateFile failed (%d)", (gint) GetLastError());
902 if (error != NULL)
903 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
904 g_free(msg);
905 return FALSE;
909 if (std_out != NULL)
911 hStdoutTempFile = GetTempFileHandle(error);
912 if (hStdoutTempFile == INVALID_HANDLE_VALUE)
914 gchar *msg = g_win32_error_message(GetLastError());
915 geany_debug("win32_spawn: Second CreateFile failed (%d)", (gint) GetLastError());
916 if (error != NULL)
917 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
918 g_free(msg);
919 return FALSE;
923 /* Set the bInheritHandle flag so pipe handles are inherited. */
924 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
925 saAttr.bInheritHandle = TRUE;
926 saAttr.lpSecurityDescriptor = NULL;
928 /* Get the handle to the current STDOUT and STDERR. */
929 gw_spawn.hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
930 gw_spawn.hStderr = GetStdHandle(STD_ERROR_HANDLE);
931 gw_spawn.dwExitCode = 0;
933 /* Create a pipe for the child process's STDOUT. */
934 if (! CreatePipe(&(gw_spawn.hChildStdoutRd), &(gw_spawn.hChildStdoutWr), &saAttr, 0))
936 gchar *msg = g_win32_error_message(GetLastError());
937 geany_debug("win32_spawn: Stdout pipe creation failed (%d)", (gint) GetLastError());
938 if (error != NULL)
939 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
940 g_free(msg);
941 return FALSE;
944 /* Ensure that the read handle to the child process's pipe for STDOUT is not inherited.*/
945 SetHandleInformation(gw_spawn.hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
947 /* Create a pipe for the child process's STDERR. */
948 if (! CreatePipe(&(gw_spawn.hChildStderrRd), &(gw_spawn.hChildStderrWr), &saAttr, 0))
950 gchar *msg = g_win32_error_message(GetLastError());
951 geany_debug("win32_spawn: Stderr pipe creation failed");
952 if (error != NULL)
953 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
954 g_free(msg);
955 return FALSE;
958 /* Ensure that the read handle to the child process's pipe for STDOUT is not inherited.*/
959 SetHandleInformation(gw_spawn.hChildStderrRd, HANDLE_FLAG_INHERIT, 0);
961 /* Create a pipe for the child process's STDIN. */
962 if (! CreatePipe(&(gw_spawn.hChildStdinRd), &(gw_spawn.hChildStdinWr), &saAttr, 0))
964 gchar *msg = g_win32_error_message(GetLastError());
965 geany_debug("win32_spawn: Stdin pipe creation failed");
966 if (error != NULL)
967 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
968 g_free(msg);
969 return FALSE;
972 /* Ensure that the write handle to the child process's pipe for STDIN is not inherited. */
973 SetHandleInformation(gw_spawn.hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
975 /* Now create the child process. */
976 fSuccess = CreateChildProcess(&gw_spawn, cmdline, dir, error);
977 if (exit_status)
979 *exit_status = gw_spawn.dwExitCode;
982 if (! fSuccess)
984 geany_debug("win32_spawn: Create process failed");
985 if (error != NULL)
986 *error = g_error_new(G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "Create process failed");
987 return FALSE;
990 /* Read from pipe that is the standard output for child process. */
991 if (std_out != NULL)
993 ReadFromPipe(gw_spawn.hChildStdoutRd, gw_spawn.hChildStdoutWr, hStdoutTempFile, error);
994 if (! GetContentFromHandle(hStdoutTempFile, &stdout_content, error))
996 return FALSE;
998 *std_out = stdout_content;
1001 if (std_err != NULL)
1003 ReadFromPipe(gw_spawn.hChildStderrRd, gw_spawn.hChildStderrWr, hStderrTempFile, error);
1004 if (! GetContentFromHandle(hStderrTempFile, &stderr_content, error))
1006 return FALSE;
1008 *std_err = stderr_content;
1010 return TRUE;
1014 static gboolean GetContentFromHandle(HANDLE hFile, gchar **content, GError **error)
1016 DWORD filesize;
1017 gchar * buffer;
1018 DWORD dwRead;
1020 filesize = GetFileSize(hFile, NULL);
1021 if (filesize < 1)
1023 *content = NULL;
1024 return TRUE;
1027 buffer = g_malloc(filesize + 1);
1028 if (! buffer)
1030 gchar *msg = g_win32_error_message(GetLastError());
1031 geany_debug("GetContentFromHandle: Alloc failed");
1032 if (error != NULL)
1033 *error = g_error_new(G_SPAWN_ERROR, G_SPAWN_ERROR, "%s", msg);
1034 g_free(msg);
1035 return FALSE;
1038 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
1039 if (! ReadFile(hFile, buffer, filesize, &dwRead, NULL) || dwRead == 0)
1041 gchar *msg = g_win32_error_message(GetLastError());
1042 geany_debug("GetContentFromHandle: Cannot read tempfile");
1043 if (error != NULL)
1044 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
1045 g_free(msg);
1046 return FALSE;
1049 if (! CloseHandle(hFile))
1051 gchar *msg = g_win32_error_message(GetLastError());
1052 geany_debug("GetContentFromHandle: CloseHandle failed (%d)", (gint) GetLastError());
1053 if (error != NULL)
1054 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
1055 g_free(msg);
1056 g_free(buffer);
1057 *content = NULL;
1058 return FALSE;
1060 buffer[filesize] = '\0';
1061 *content = buffer;
1062 return TRUE;
1066 gchar *win32_expand_environment_variables(const gchar *str)
1068 gchar expCmdline[32768]; /* 32768 is the limit for ExpandEnvironmentStrings() */
1070 if (ExpandEnvironmentStrings((LPCTSTR) str, (LPTSTR) expCmdline, sizeof(expCmdline)) != 0)
1071 return g_strdup(expCmdline);
1072 else
1073 return g_strdup(str);
1077 static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline, const TCHAR *dir, GError **error)
1079 PROCESS_INFORMATION piProcInfo;
1080 STARTUPINFOW siStartInfo;
1081 BOOL bFuncRetn = FALSE;
1082 gchar *expandedCmdline;
1083 wchar_t w_commandline[CMDSIZE];
1084 wchar_t w_dir[MAX_PATH];
1086 /* Set up members of the PROCESS_INFORMATION structure. */
1087 ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
1089 /* Set up members of the STARTUPINFO structure.*/
1090 ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
1092 siStartInfo.cb = sizeof(STARTUPINFO);
1093 siStartInfo.hStdError = gw_spawn->hChildStderrWr;
1094 siStartInfo.hStdOutput = gw_spawn->hChildStdoutWr;
1095 siStartInfo.hStdInput = gw_spawn->hChildStdinRd;
1096 siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
1098 /* Expand environment variables like %blah%. */
1099 expandedCmdline = win32_expand_environment_variables(szCmdline);
1101 MultiByteToWideChar(CP_UTF8, 0, expandedCmdline, -1, w_commandline, sizeof(w_commandline));
1102 MultiByteToWideChar(CP_UTF8, 0, dir, -1, w_dir, sizeof(w_dir));
1104 /* Create the child process. */
1105 bFuncRetn = CreateProcessW(NULL,
1106 w_commandline, /* command line */
1107 NULL, /* process security attributes */
1108 NULL, /* primary thread security attributes */
1109 TRUE, /* handles are inherited */
1110 CREATE_NO_WINDOW, /* creation flags */
1111 NULL, /* use parent's environment */
1112 w_dir, /* use parent's current directory */
1113 &siStartInfo, /* STARTUPINFO pointer */
1114 &piProcInfo); /* receives PROCESS_INFORMATION */
1116 g_free(expandedCmdline);
1118 if (bFuncRetn == 0)
1120 gchar *msg = g_win32_error_message(GetLastError());
1121 geany_debug("CreateChildProcess: CreateProcess failed (%s)", msg);
1122 if (*error != NULL)
1123 *error = g_error_new(G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "%s", msg);
1124 g_free(msg);
1125 return FALSE;
1127 else
1129 gint i;
1130 DWORD dwStatus;
1132 for (i = 0; i < 2 && (dwStatus = WaitForSingleObject(piProcInfo.hProcess, 30*1000)) == WAIT_TIMEOUT; i++)
1134 geany_debug("CreateChildProcess: CreateProcess failed");
1135 TerminateProcess(piProcInfo.hProcess, WAIT_TIMEOUT); /* NOTE: This will not kill grandkids. */
1138 if (GetExitCodeProcess(piProcInfo.hProcess, &gw_spawn->dwExitCode) != 0)
1140 gchar *msg = g_win32_error_message(GetLastError());
1141 geany_debug("GetExitCodeProcess failed: %s", msg);
1142 if (error != NULL)
1143 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
1144 g_free(msg);
1146 CloseHandle(piProcInfo.hProcess);
1147 CloseHandle(piProcInfo.hThread);
1148 return bFuncRetn;
1150 return FALSE;
1154 static VOID ReadFromPipe(HANDLE hRead, HANDLE hWrite, HANDLE hFile, GError **error)
1156 DWORD dwRead, dwWritten;
1157 CHAR chBuf[BUFSIZE];
1159 /* Close the write end of the pipe before reading from the
1160 read end of the pipe. */
1161 if (! CloseHandle(hWrite))
1163 gchar *msg = g_win32_error_message(GetLastError());
1164 geany_debug("ReadFromPipe: Closing handle failed");
1165 if (error != NULL)
1166 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
1167 g_free(msg);
1168 return;
1171 /* Read output from the child process, and write to parent's STDOUT. */
1172 for (;;)
1174 if (! ReadFile(hRead, chBuf, BUFSIZE, &dwRead, NULL) || dwRead == 0)
1175 break;
1177 if (! WriteFile(hFile, chBuf, dwRead, &dwWritten, NULL))
1178 break;
1183 static HANDLE GetTempFileHandle(GError **error)
1185 /* Temp file */
1186 DWORD dwBufSize = BUFSIZE;
1187 UINT uRetVal;
1188 TCHAR szTempName[BUFSIZE];
1189 TCHAR lpPathBuffer[BUFSIZE];
1190 DWORD dwRetVal;
1191 HANDLE hTempFile;
1193 /* Get the temp path. */
1194 dwRetVal = GetTempPath(dwBufSize, /* length of the buffer*/
1195 lpPathBuffer); /* buffer for path */
1197 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1199 gchar *msg = g_win32_error_message(GetLastError());
1200 geany_debug("GetTempFileHandle: GetTempPath failed (%d)", (gint) GetLastError());
1201 if (error != NULL)
1202 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1203 g_free(msg);
1204 return NULL;
1207 /* Create a temporary file for STDOUT. */
1208 uRetVal = GetTempFileName(lpPathBuffer, /* directory for tmp files */
1209 TEXT("GEANY_VCDIFF_"), /* temp file name prefix */
1210 0, /* create unique name */
1211 szTempName); /* buffer for name */
1212 if (uRetVal == 0)
1214 gchar *msg = g_win32_error_message(GetLastError());
1215 geany_debug("GetTempFileName failed (%d)", (gint) GetLastError());
1216 if (error != NULL)
1217 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1218 g_free(msg);
1219 return NULL;
1222 hTempFile = CreateFile((LPTSTR) szTempName, /* file name */
1223 GENERIC_READ | GENERIC_WRITE, /* open r-w */
1224 0, /* do not share */
1225 NULL, /* default security */
1226 CREATE_ALWAYS, /* overwrite existing */
1227 FILE_ATTRIBUTE_NORMAL,/* normal file */
1228 NULL); /* no template */
1230 if (hTempFile == INVALID_HANDLE_VALUE)
1232 gchar *msg = g_win32_error_message(GetLastError());
1233 geany_debug("GetTempFileHandle: Second CreateFile failed (%d)", (gint) GetLastError());
1234 if (error != NULL)
1235 *error = g_error_new(G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1236 g_free(msg);
1237 return NULL;
1239 return hTempFile;
1243 /* From GDK (they got it from MS Knowledge Base article Q130698) */
1244 static gboolean resolve_link(HWND hWnd, wchar_t *link, gchar **lpszPath)
1246 WIN32_FILE_ATTRIBUTE_DATA wfad;
1247 HRESULT hres;
1248 IShellLinkW *pslW = NULL;
1249 IPersistFile *ppf = NULL;
1250 LPVOID pslWV = NULL;
1251 LPVOID ppfV = NULL;
1253 /* Check if the file is empty first because IShellLink::Resolve for some reason succeeds
1254 * with an empty file and returns an empty "link target". (#524151) */
1255 if (!GetFileAttributesExW(link, GetFileExInfoStandard, &wfad) ||
1256 (wfad.nFileSizeHigh == 0 && wfad.nFileSizeLow == 0))
1258 return FALSE;
1261 /* Assume failure to start with: */
1262 *lpszPath = 0;
1264 CoInitialize(NULL);
1266 hres = CoCreateInstance(
1267 &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, &pslWV);
1269 if (SUCCEEDED(hres))
1271 /* The IShellLink interface supports the IPersistFile interface.
1272 * Get an interface pointer to it. */
1273 pslW = (IShellLinkW*) pslWV;
1274 hres = pslW->lpVtbl->QueryInterface(pslW, &IID_IPersistFile, &ppfV);
1277 if (SUCCEEDED(hres))
1279 /* Load the file. */
1280 ppf = (IPersistFile*) ppfV;
1281 hres = ppf->lpVtbl->Load(ppf, link, STGM_READ);
1284 if (SUCCEEDED(hres))
1286 /* Resolve the link by calling the Resolve() interface function. */
1287 hres = pslW->lpVtbl->Resolve(pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI);
1290 if (SUCCEEDED(hres))
1292 wchar_t wtarget[MAX_PATH];
1294 hres = pslW->lpVtbl->GetPath(pslW, wtarget, MAX_PATH, NULL, 0);
1295 if (SUCCEEDED(hres))
1296 *lpszPath = g_utf16_to_utf8(wtarget, -1, NULL, NULL, NULL);
1299 if (ppf)
1300 ppf->lpVtbl->Release(ppf);
1302 if (pslW)
1303 pslW->lpVtbl->Release(pslW);
1305 return SUCCEEDED(hres);
1309 /* Checks whether file_name is a Windows shortcut. file_name is expected in UTF-8 encoding.
1310 * If file_name is a Windows shortcut, it returns the target in UTF-8 encoding.
1311 * If it is not a shortcut, it returns a newly allocated copy of file_name. */
1312 gchar *win32_get_shortcut_target(const gchar *file_name)
1314 gchar *path = NULL;
1315 wchar_t *wfilename = g_utf8_to_utf16(file_name, -1, NULL, NULL, NULL);
1317 resolve_link(GDK_WINDOW_HWND(main_widgets.window->window), wfilename, &path);
1318 g_free(wfilename);
1320 if (path == NULL)
1321 return g_strdup(file_name);
1322 else
1323 return path;
1327 void win32_set_working_directory(const gchar *dir)
1329 SetCurrentDirectory(dir);
1333 gchar *win32_get_installation_dir(void)
1335 #if GLIB_CHECK_VERSION(2, 16, 0)
1336 return g_win32_get_package_installation_directory_of_module(NULL);
1337 #else
1338 return g_win32_get_package_installation_directory(NULL, NULL);
1339 #endif
1343 #endif