Merge pull request #464 from eht16/undeprecate_plugins
[geany-mirror.git] / src / win32.c
blob834dfd3b43497f3ff0782ff1cc8ab1e49054c678
1 /*
2 * win32.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2012 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 along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 * Special functions for the win32 platform, to provide native dialogs.
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
30 #include "win32.h"
32 #ifdef G_OS_WIN32
34 #include "dialogs.h"
35 #include "document.h"
36 #include "editor.h"
37 #include "filetypes.h"
38 #include "project.h"
39 #include "support.h"
40 #include "ui_utils.h"
41 #include "utils.h"
43 #include <ctype.h>
44 #include <fcntl.h>
45 #include <io.h>
46 #include <math.h>
47 #include <stdlib.h>
48 #include <string.h>
50 #define VC_EXTRALEAN
51 #define WIN32_LEAN_AND_MEAN
52 #include <windows.h>
53 #include <commdlg.h>
54 #include <shellapi.h>
55 #include <shlobj.h>
57 #include <glib/gstdio.h>
58 #include <gdk/gdkwin32.h>
60 #define BUFSIZE 4096
61 #define CMDSIZE 32768
63 struct _geany_win32_spawn
65 HANDLE hChildStdinRd;
66 HANDLE hChildStdinWr;
67 HANDLE hChildStdoutRd;
68 HANDLE hChildStdoutWr;
69 HANDLE hChildStderrRd;
70 HANDLE hChildStderrWr;
71 HANDLE hInputFile;
72 HANDLE hStdout;
73 HANDLE hStderr;
74 HANDLE processId;
75 DWORD dwExitCode;
77 typedef struct _geany_win32_spawn geany_win32_spawn;
79 static gboolean GetContentFromHandle(HANDLE hFile, gchar **content, GError **error);
80 static HANDLE GetTempFileHandle(GError **error);
81 static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline,
82 const TCHAR *dir, GError **error);
83 static VOID ReadFromPipe(HANDLE hRead, HANDLE hWrite, HANDLE hFile, GError **error);
86 /* The timer handle used to refresh windows below modal native dialogs. If
87 * ever more than one dialog can be shown at a time, this needs to be changed
88 * to be for specific dialogs. */
89 static UINT_PTR dialog_timer = 0;
92 G_INLINE_FUNC void win32_dialog_reset_timer(HWND hwnd)
94 if (G_UNLIKELY(dialog_timer != 0))
96 KillTimer(hwnd, dialog_timer);
97 dialog_timer = 0;
102 VOID CALLBACK
103 win32_dialog_update_main_window(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
105 gint i;
107 /* Pump the main window loop a bit, but not enough to lock-up.
108 * The typical `while(gtk_events_pending()) gtk_main_iteration();`
109 * loop causes the entire operating system to lock-up. */
110 for (i = 0; i < 4 && gtk_events_pending(); i++)
111 gtk_main_iteration();
113 /* Cancel any pending timers since we just did an update */
114 win32_dialog_reset_timer(hwnd);
118 G_INLINE_FUNC UINT_PTR win32_dialog_queue_main_window_redraw(HWND dlg, UINT msg,
119 WPARAM wParam, LPARAM lParam, gboolean postpone)
121 switch (msg)
123 /* Messages that likely mean the window below a dialog needs to be re-drawn. */
124 case WM_WINDOWPOSCHANGED:
125 case WM_MOVE:
126 case WM_SIZE:
127 case WM_THEMECHANGED:
128 if (postpone)
130 win32_dialog_reset_timer(dlg);
131 dialog_timer = SetTimer(dlg, 0, 33 /* around 30fps */, win32_dialog_update_main_window);
133 else
134 win32_dialog_update_main_window(dlg, msg, wParam, lParam);
135 break;
137 return 0; /* always let the default proc handle it */
141 /* This function is called for OPENFILENAME lpfnHook function and it establishes
142 * a timer that is reset each time which will update the main window loop eventually. */
143 UINT_PTR CALLBACK win32_dialog_explorer_hook_proc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam)
145 return win32_dialog_queue_main_window_redraw(dlg, msg, wParam, lParam, TRUE);
149 /* This function is called for old-school win32 dialogs that accept a proper
150 * lpfnHook function for all messages, it doesn't use a timer. */
151 UINT_PTR CALLBACK win32_dialog_hook_proc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam)
153 return win32_dialog_queue_main_window_redraw(dlg, msg, wParam, lParam, FALSE);
157 static wchar_t *get_file_filters(void)
159 gchar *string;
160 guint i, j, len;
161 static wchar_t title[4096];
162 GString *str = g_string_sized_new(100);
163 GString *all_patterns = g_string_sized_new(100);
164 GSList *node;
165 gchar *tmp;
167 /* create meta file filter "All files" */
168 g_string_append_printf(str, "%s\t*\t", _("All files"));
169 /* create meta file filter "All Source" (skip GEANY_FILETYPES_NONE) */
170 for (i = GEANY_FILETYPES_NONE + 1; i < filetypes_array->len; i++)
172 for (j = 0; filetypes[i]->pattern[j] != NULL; j++)
174 g_string_append(all_patterns, filetypes[i]->pattern[j]);
175 g_string_append_c(all_patterns, ';');
178 g_string_append_printf(str, "%s\t%s\t", _("All Source"), all_patterns->str);
179 g_string_free(all_patterns, TRUE);
180 /* add 'usual' filetypes */
181 foreach_slist(node, filetypes_by_title)
183 GeanyFiletype *ft = node->data;
185 if (G_UNLIKELY(ft->id == GEANY_FILETYPES_NONE))
186 continue;
187 tmp = g_strjoinv(";", ft->pattern);
188 g_string_append_printf(str, "%s\t%s\t", ft->title, tmp);
189 g_free(tmp);
191 g_string_append_c(str, '\t'); /* the final \0 byte to mark the end of the string */
192 string = str->str;
193 g_string_free(str, FALSE);
195 /* replace all "\t"s by \0 */
196 len = strlen(string);
197 g_strdelimit(string, "\t", '\0');
198 g_assert(string[len - 1] == 0x0);
199 MultiByteToWideChar(CP_UTF8, 0, string, len, title, G_N_ELEMENTS(title));
200 g_free(string);
202 return title;
206 static wchar_t *get_file_filter_all_files(void)
208 guint len;
209 static wchar_t title[4096];
210 gchar *filter;
212 /* create meta file filter "All files" */
213 filter = g_strdup_printf("%s\t*\t", _("All files"));
215 len = strlen(filter);
216 g_strdelimit(filter, "\t", '\0');
217 g_assert(filter[len - 1] == 0x0);
218 MultiByteToWideChar(CP_UTF8, 0, filter, len, title, G_N_ELEMENTS(title));
219 g_free(filter);
221 return title;
225 static wchar_t *get_filters(gboolean project_files)
227 gchar *string;
228 gint len;
229 static wchar_t title[1024];
231 if (project_files)
233 string = g_strconcat(_("Geany project files"), "\t", "*." GEANY_PROJECT_EXT, "\t",
234 _("All files"), "\t", "*", "\t", NULL);
236 else
238 string = g_strconcat(_("Executables"), "\t", "*.exe;*.bat;*.cmd", "\t",
239 _("All files"), "\t", "*", "\t", NULL);
242 /* replace all "\t"s by \0 */
243 len = strlen(string);
244 g_strdelimit(string, "\t", '\0');
245 g_assert(string[len - 1] == 0x0);
246 MultiByteToWideChar(CP_UTF8, 0, string, len, title, G_N_ELEMENTS(title));
247 g_free(string);
249 return title;
253 /* Converts the given UTF-8 filename or directory name into something usable for Windows and
254 * returns the directory part of the given filename. */
255 static wchar_t *get_dir_for_path(const gchar *utf8_filename)
257 static wchar_t w_dir[MAX_PATH];
258 gchar *result;
260 if (g_file_test(utf8_filename, G_FILE_TEST_IS_DIR))
261 result = (gchar*) utf8_filename;
262 else
263 result = g_path_get_dirname(utf8_filename);
265 MultiByteToWideChar(CP_UTF8, 0, result, -1, w_dir, G_N_ELEMENTS(w_dir));
267 if (result != utf8_filename)
268 g_free(result);
270 return w_dir;
274 /* Callback function for setting the initial directory of the folder open dialog. This could also
275 * be done with BROWSEINFO.pidlRoot and SHParseDisplayName but SHParseDisplayName is not available
276 * on systems below Windows XP. So, we go the hard way by creating a callback which will set up the
277 * folder when the dialog is initialised. Yeah, I like Windows. */
278 INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)
280 win32_dialog_hook_proc(hwnd, uMsg, lp, pData);
281 switch (uMsg)
283 case BFFM_INITIALIZED:
285 SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, pData);
286 break;
288 case BFFM_SELCHANGED:
290 /* set the status window to the currently selected path. */
291 static wchar_t szDir[MAX_PATH];
292 if (SHGetPathFromIDListW((LPITEMIDLIST) lp, szDir))
294 SendMessageW(hwnd, BFFM_SETSTATUSTEXTW, 0, (LPARAM) szDir);
296 break;
299 return 0;
303 /* Shows a folder selection dialog.
304 * initial_dir is expected in UTF-8
305 * The selected folder name is returned. */
306 gchar *win32_show_folder_dialog(GtkWidget *parent, const gchar *title, const gchar *initial_dir)
308 BROWSEINFOW bi;
309 LPCITEMIDLIST pidl;
310 gchar *result = NULL;
311 wchar_t fname[MAX_PATH];
312 wchar_t w_title[512];
314 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
316 if (parent == NULL)
317 parent = main_widgets.window;
319 memset(&bi, 0, sizeof bi);
320 bi.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(parent));
321 bi.pidlRoot = NULL;
322 bi.lpszTitle = w_title;
323 bi.lpfn = BrowseCallbackProc;
324 bi.lParam = (LPARAM) get_dir_for_path(initial_dir);
325 bi.ulFlags = BIF_DONTGOBELOWDOMAIN | BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT;
327 pidl = SHBrowseForFolderW(&bi);
329 /* convert the strange Windows folder list item something into an usual path string ;-) */
330 if (pidl != 0)
332 if (SHGetPathFromIDListW(pidl, fname))
334 result = g_malloc0(MAX_PATH * 2);
335 WideCharToMultiByte(CP_UTF8, 0, fname, -1, result, MAX_PATH * 2, NULL, NULL);
337 /* SHBrowseForFolder() probably leaks memory here, but how to free the allocated memory? */
339 return result;
343 /* Shows a file open dialog.
344 * If allow_new_file is set, the file to be opened doesn't have to exist.
345 * initial_dir is expected in UTF-8
346 * The selected file name is returned.
347 * If project_file_filter is set, the file open dialog will have a file filter for Geany project
348 * files, a filter for executables otherwise. */
349 gchar *win32_show_project_open_dialog(GtkWidget *parent, const gchar *title,
350 const gchar *initial_dir, gboolean allow_new_file,
351 gboolean project_file_filter)
353 OPENFILENAMEW of;
354 gint retval;
355 wchar_t fname[MAX_PATH];
356 wchar_t w_title[512];
357 gchar *filename;
359 fname[0] = '\0';
361 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
363 if (parent == NULL)
364 parent = main_widgets.window;
366 /* initialise file dialog info struct */
367 memset(&of, 0, sizeof of);
368 #ifdef OPENFILENAME_SIZE_VERSION_400
369 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
370 #else
371 of.lStructSize = sizeof of;
372 #endif
373 of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(parent));
374 of.lpstrFilter = get_filters(project_file_filter);
376 of.lpstrCustomFilter = NULL;
377 of.nFilterIndex = 0;
378 of.lpstrFile = fname;
379 of.lpstrInitialDir = get_dir_for_path(initial_dir);
380 of.nMaxFile = 2048;
381 of.lpstrFileTitle = NULL;
382 of.lpstrTitle = w_title;
383 of.lpstrDefExt = L"";
384 of.Flags = OFN_PATHMUSTEXIST | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_ENABLEHOOK;
385 of.lpfnHook = win32_dialog_explorer_hook_proc;
386 if (! allow_new_file)
387 of.Flags |= OFN_FILEMUSTEXIST;
389 retval = GetOpenFileNameW(&of);
391 if (! retval)
393 if (CommDlgExtendedError())
395 gchar *error;
396 error = g_strdup_printf("File dialog box error (%x)", (int)CommDlgExtendedError());
397 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
398 g_free(error);
400 return NULL;
402 /* convert the resulting filename into UTF-8 (from whatever encoding it has at this moment) */
403 filename = g_malloc0(MAX_PATH * 2);
404 WideCharToMultiByte(CP_UTF8, 0, fname, -1, filename, MAX_PATH * 2, NULL, NULL);
406 return filename;
410 /* initial_dir can be NULL to use the current working directory.
411 * Returns: TRUE if the dialog was not cancelled. */
412 gboolean win32_show_document_open_dialog(GtkWindow *parent, const gchar *title, const gchar *initial_dir)
414 OPENFILENAMEW of;
415 gint retval;
416 guint x;
417 gchar tmp[MAX_PATH];
418 wchar_t fname[MAX_PATH];
419 wchar_t w_dir[MAX_PATH];
420 wchar_t w_title[512];
422 fname[0] = '\0';
424 if (initial_dir != NULL)
425 MultiByteToWideChar(CP_UTF8, 0, initial_dir, -1, w_dir, G_N_ELEMENTS(w_dir));
427 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
429 /* initialise file dialog info struct */
430 memset(&of, 0, sizeof of);
431 #ifdef OPENFILENAME_SIZE_VERSION_400
432 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
433 #else
434 of.lStructSize = sizeof of;
435 #endif
436 of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(parent)));
437 of.lpstrFilter = get_file_filters();
439 of.lpstrCustomFilter = NULL;
440 of.nFilterIndex = GEANY_FILETYPES_NONE + 1;
441 of.lpstrFile = fname;
442 of.lpstrInitialDir = (initial_dir != NULL) ? w_dir : NULL;
443 of.nMaxFile = 2048;
444 of.lpstrFileTitle = NULL;
445 of.lpstrTitle = w_title;
446 of.lpstrDefExt = L"";
447 of.Flags = OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_ENABLEHOOK;
448 of.lpfnHook = win32_dialog_explorer_hook_proc;
450 retval = GetOpenFileNameW(&of);
452 if (!retval)
454 if (CommDlgExtendedError())
456 gchar error[100];
457 g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
458 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
460 return FALSE;
463 x = of.nFileOffset - 1;
464 if (x != wcslen(fname))
465 { /* open a single file */
466 WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
467 document_open_file(tmp, of.Flags & OFN_READONLY, NULL, NULL);
469 else
470 { /* open multiple files */
471 gchar file_name[MAX_PATH];
472 gchar dir_name[MAX_PATH];
474 WideCharToMultiByte(CP_UTF8, 0, fname, of.nFileOffset,
475 dir_name, sizeof(dir_name), NULL, NULL);
476 for (; ;)
478 if (! fname[x])
480 if (! fname[x + 1])
481 break;
483 WideCharToMultiByte(CP_UTF8, 0, fname + x + 1, -1,
484 tmp, sizeof(tmp), NULL, NULL);
485 g_snprintf(file_name, 511, "%s\\%s", dir_name, tmp);
487 /* convert the resulting filename into UTF-8 */
488 document_open_file(file_name, of.Flags & OFN_READONLY, NULL, NULL);
490 x++;
493 return (retval != 0);
497 gchar *win32_show_document_save_as_dialog(GtkWindow *parent, const gchar *title,
498 GeanyDocument *doc)
500 OPENFILENAMEW of;
501 gint retval;
502 gchar tmp[MAX_PATH];
503 wchar_t w_file[MAX_PATH];
504 wchar_t w_title[512];
505 int n;
507 w_file[0] = '\0';
509 /* Convert the name of the file for of.lpstrFile */
510 n = MultiByteToWideChar(CP_UTF8, 0, DOC_FILENAME(doc), -1, w_file, G_N_ELEMENTS(w_file));
512 /* If creating a new file name, convert and append the extension if any */
513 if (! doc->file_name && doc->file_type && doc->file_type->extension &&
514 n + 1 < (int)G_N_ELEMENTS(w_file))
516 w_file[n - 1] = L'.';
517 MultiByteToWideChar(CP_UTF8, 0, doc->file_type->extension, -1, &w_file[n],
518 G_N_ELEMENTS(w_file) - n - 1);
521 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
523 /* initialise file dialog info struct */
524 memset(&of, 0, sizeof of);
525 #ifdef OPENFILENAME_SIZE_VERSION_400
526 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
527 #else
528 of.lStructSize = sizeof of;
529 #endif
530 of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(parent)));
532 of.lpstrFilter = get_file_filter_all_files();
533 of.lpstrCustomFilter = NULL;
534 of.nFilterIndex = 0;
536 of.lpstrFile = w_file;
537 of.nMaxFile = 2048;
538 of.lpstrFileTitle = NULL;
539 of.lpstrTitle = w_title;
540 of.lpstrDefExt = L"";
541 of.Flags = OFN_OVERWRITEPROMPT | OFN_EXPLORER | OFN_ENABLEHOOK;
542 of.lpfnHook = win32_dialog_explorer_hook_proc;
543 retval = GetSaveFileNameW(&of);
545 if (! retval)
547 if (CommDlgExtendedError())
549 gchar *error = g_strdup_printf(
550 "File dialog box error (%x)", (gint) CommDlgExtendedError());
551 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
552 g_free(error);
554 return NULL;
557 WideCharToMultiByte(CP_UTF8, 0, w_file, -1, tmp, sizeof(tmp), NULL, NULL);
559 return g_strdup(tmp);
563 /* initial_dir can be NULL to use the current working directory.
564 * Returns: the selected filename */
565 gchar *win32_show_file_dialog(GtkWindow *parent, const gchar *title, const gchar *initial_file)
567 OPENFILENAMEW of;
568 gint retval;
569 gchar tmp[MAX_PATH];
570 wchar_t w_file[MAX_PATH];
571 wchar_t w_title[512];
573 w_file[0] = '\0';
575 if (initial_file != NULL)
576 MultiByteToWideChar(CP_UTF8, 0, initial_file, -1, w_file, G_N_ELEMENTS(w_file));
578 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
580 /* initialise file dialog info struct */
581 memset(&of, 0, sizeof of);
582 #ifdef OPENFILENAME_SIZE_VERSION_400
583 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
584 #else
585 of.lStructSize = sizeof of;
586 #endif
587 of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(parent)));
589 of.lpstrFile = w_file;
590 of.nMaxFile = 2048;
591 of.lpstrFileTitle = NULL;
592 of.lpstrTitle = w_title;
593 of.lpstrDefExt = L"";
594 of.Flags = OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_ENABLEHOOK;
595 of.lpfnHook = win32_dialog_explorer_hook_proc;
596 retval = GetOpenFileNameW(&of);
598 if (! retval)
600 if (CommDlgExtendedError())
602 gchar *error = g_strdup_printf(
603 "File dialog box error (%x)", (gint) CommDlgExtendedError());
604 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
605 g_free(error);
607 return NULL;
610 WideCharToMultiByte(CP_UTF8, 0, w_file, -1, tmp, sizeof(tmp), NULL, NULL);
612 return g_strdup(tmp);
616 void win32_show_font_dialog(void)
618 gint retval;
619 CHOOSEFONT cf;
620 LOGFONT lf; /* logical font structure */
622 memset(&lf, 0, sizeof lf);
623 /* TODO: init lf members */
625 memset(&cf, 0, sizeof cf);
626 cf.lStructSize = sizeof cf;
627 cf.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(main_widgets.window));
628 cf.lpLogFont = &lf;
629 /* support CF_APPLY? */
630 cf.Flags = CF_NOSCRIPTSEL | CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_ENABLEHOOK;
631 cf.lpfnHook = win32_dialog_hook_proc;
633 retval = ChooseFont(&cf);
635 if (retval)
637 gchar *editorfont = g_strdup_printf("%s %d", lf.lfFaceName, (cf.iPointSize / 10));
638 ui_set_editor_font(editorfont);
639 g_free(editorfont);
644 void win32_show_color_dialog(const gchar *colour)
646 CHOOSECOLOR cc;
647 static COLORREF acr_cust_clr[16];
648 static DWORD rgb_current;
649 gchar *hex = g_malloc0(12);
650 GeanyDocument *doc = document_get_current();
652 /* Initialize CHOOSECOLOR */
653 memset(&cc, 0, sizeof cc);
654 cc.lStructSize = sizeof(cc);
655 cc.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(main_widgets.window));
656 cc.lpCustColors = (LPDWORD) acr_cust_clr;
657 cc.rgbResult = (colour != NULL) ? utils_parse_color_to_bgr(colour) : 0;
658 cc.Flags = CC_FULLOPEN | CC_RGBINIT | CC_ENABLEHOOK;
659 cc.lpfnHook = win32_dialog_hook_proc;
661 if (ChooseColor(&cc))
663 rgb_current = cc.rgbResult;
664 g_snprintf(hex, 11, "#%02X%02X%02X",
665 (guint) (utils_scale_round(GetRValue(rgb_current), 255)),
666 (guint) (utils_scale_round(GetGValue(rgb_current), 255)),
667 (guint) (utils_scale_round(GetBValue(rgb_current), 255)));
669 editor_insert_color(doc->editor, hex);
671 g_free(hex);
675 void win32_show_pref_file_dialog(GtkEntry *item)
677 OPENFILENAMEW of;
678 gint retval, len;
679 wchar_t fname[MAX_PATH];
680 gchar tmp[MAX_PATH];
681 gchar **field, *filename;
683 fname[0] = '\0';
685 /* cut the options from the command line */
686 field = g_strsplit(gtk_entry_get_text(GTK_ENTRY(item)), " ", 2);
687 if (field[0])
689 filename = g_find_program_in_path(field[0]);
690 if (filename != NULL && g_file_test(filename, G_FILE_TEST_EXISTS))
692 MultiByteToWideChar(CP_UTF8, 0, filename, -1, fname, G_N_ELEMENTS(fname));
693 g_free(filename);
697 /* initialize file dialog info struct */
698 memset(&of, 0, sizeof of);
699 #ifdef OPENFILENAME_SIZE_VERSION_400
700 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
701 #else
702 of.lStructSize = sizeof of;
703 #endif
704 of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(ui_widgets.prefs_dialog));
706 of.lpstrFilter = get_filters(FALSE);
707 of.lpstrCustomFilter = NULL;
708 of.nFilterIndex = 1;
710 of.lpstrFile = fname;
711 of.nMaxFile = 2048;
712 of.lpstrFileTitle = NULL;
713 /*of.lpstrInitialDir = g_get_home_dir();*/
714 of.lpstrInitialDir = NULL;
715 of.lpstrTitle = NULL;
716 of.lpstrDefExt = L"exe";
717 of.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_ENABLEHOOK;
718 of.lpfnHook = win32_dialog_explorer_hook_proc;
719 retval = GetOpenFileNameW(&of);
721 if (!retval)
723 if (CommDlgExtendedError())
725 gchar error[100];
726 g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
727 win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
729 g_strfreev(field);
730 return;
733 len = WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
734 if ((of.nFileOffset - 1) != len)
736 if (g_strv_length(field) > 1)
737 /* add the command line args of the old command */
738 /** TODO this fails badly when the old command contained spaces, we need quoting here */
739 filename = g_strconcat(tmp, " ", field[1], NULL);
740 else
742 filename = g_strdup(tmp);
744 gtk_entry_set_text(GTK_ENTRY(item), filename);
745 g_free(filename);
747 g_strfreev(field);
751 /* Creates a native Windows message box of the given type and returns always TRUE
752 * or FALSE representing th pressed Yes or No button.
753 * If type is not GTK_MESSAGE_QUESTION, it returns always TRUE. */
754 gboolean win32_message_dialog(GtkWidget *parent, GtkMessageType type, const gchar *msg)
756 gboolean ret = TRUE;
757 gint rc;
758 guint t;
759 const gchar *title;
760 HWND parent_hwnd = NULL;
761 static wchar_t w_msg[512];
762 static wchar_t w_title[512];
764 switch (type)
766 case GTK_MESSAGE_ERROR:
768 t = MB_OK | MB_ICONERROR;
769 title = _("Error");
770 break;
772 case GTK_MESSAGE_QUESTION:
774 t = MB_YESNO | MB_ICONQUESTION;
775 title = _("Question");
776 break;
778 case GTK_MESSAGE_WARNING:
780 t = MB_OK | MB_ICONWARNING;
781 title = _("Warning");
782 break;
784 default:
786 t = MB_OK | MB_ICONINFORMATION;
787 title = _("Information");
788 break;
792 /* convert the Unicode chars to wide chars */
793 /** TODO test if LANG == C then possibly skip conversion => g_win32_getlocale() */
794 MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg));
795 MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
797 if (parent != NULL)
798 parent_hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(parent));
799 else if (main_widgets.window != NULL)
800 parent_hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(main_widgets.window));
802 /* display the message box */
803 rc = MessageBoxW(parent_hwnd, w_msg, w_title, t);
805 if (type == GTK_MESSAGE_QUESTION && rc != IDYES)
806 ret = FALSE;
808 return ret;
812 /* Little wrapper for _waccess(), returns errno or 0 if there was no error */
813 gint win32_check_write_permission(const gchar *dir)
815 static wchar_t w_dir[MAX_PATH];
816 MultiByteToWideChar(CP_UTF8, 0, dir, -1, w_dir, G_N_ELEMENTS(w_dir));
817 if (_waccess(w_dir, R_OK | W_OK) != 0)
818 return errno;
819 else
820 return 0;
824 /* Just a simple wrapper function to open a browser window */
825 void win32_open_browser(const gchar *uri)
827 if (strncmp(uri, "file://", 7) == 0)
829 uri += 7;
830 if (strchr(uri, ':') != NULL)
832 while (*uri == '/')
833 uri++;
836 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOWNORMAL);
840 /* Returns TRUE if the command, which child_pid refers to, returned with a successful exit code,
841 * otherwise FALSE. */
842 gboolean win32_get_exit_status(GPid child_pid)
844 DWORD exit_code;
845 GetExitCodeProcess(child_pid, &exit_code);
847 return (exit_code == 0);
851 static FILE *open_std_handle(DWORD handle, const char *mode)
853 HANDLE lStdHandle;
854 int hConHandle;
855 FILE *fp;
857 lStdHandle = GetStdHandle(handle);
858 if (lStdHandle == INVALID_HANDLE_VALUE)
860 gchar *err = g_win32_error_message(GetLastError());
861 g_warning("GetStdHandle(%ld) failed: %s", (long)handle, err);
862 g_free(err);
863 return NULL;
865 hConHandle = _open_osfhandle((long)lStdHandle, _O_TEXT);
866 if (hConHandle == -1)
868 gchar *err = g_win32_error_message(GetLastError());
869 g_warning("_open_osfhandle(%ld, _O_TEXT) failed: %s", (long)lStdHandle, err);
870 g_free(err);
871 return NULL;
873 fp = _fdopen(hConHandle, mode);
874 if (! fp)
876 gchar *err = g_win32_error_message(GetLastError());
877 g_warning("_fdopen(%d, \"%s\") failed: %s", hConHandle, mode, err);
878 g_free(err);
879 return NULL;
881 if (setvbuf(fp, NULL, _IONBF, 0) != 0)
883 gchar *err = g_win32_error_message(GetLastError());
884 g_warning("setvbuf(%p, NULL, _IONBF, 0) failed: %s", fp, err);
885 g_free(err);
886 fclose(fp);
887 return NULL;
890 return fp;
894 static void debug_setup_console(void)
896 static const WORD MAX_CONSOLE_LINES = 500;
897 CONSOLE_SCREEN_BUFFER_INFO coninfo;
898 FILE *fp;
900 /* allocate a console for this app */
901 AllocConsole();
903 /* set the screen buffer to be big enough to let us scroll text */
904 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
905 coninfo.dwSize.Y = MAX_CONSOLE_LINES;
906 SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
908 /* redirect unbuffered STDOUT to the console */
909 fp = open_std_handle(STD_OUTPUT_HANDLE, "w");
910 if (fp)
911 *stdout = *fp;
913 /* redirect unbuffered STDERR to the console */
914 fp = open_std_handle(STD_ERROR_HANDLE, "w");
915 if (fp)
916 *stderr = *fp;
918 /* redirect unbuffered STDIN to the console */
919 fp = open_std_handle(STD_INPUT_HANDLE, "r");
920 if (fp)
921 *stdin = *fp;
925 void win32_init_debug_code(void)
927 if (app->debug_mode)
929 /* create a console window to get log messages on Windows,
930 * especially useful when generating tags files */
931 debug_setup_console();
932 /* Enable GLib process spawn debug mode when Geany was started with the debug flag */
933 g_setenv("G_SPAWN_WIN32_DEBUG", "1", FALSE);
938 static gchar *create_temp_file(void)
940 gchar *name;
941 gint fd;
943 fd = g_file_open_tmp("tmp_XXXXXX", &name, NULL);
944 if (fd == -1)
945 name = NULL;
946 else
947 close(fd);
949 return name;
953 /* Sometimes this blocks for 30s before aborting when there are several
954 * pages of (error) output and sometimes hangs - see the FIXME.
955 * Also gw_spawn.dwExitCode seems to be not set properly. */
956 /* Process spawning implementation for Windows, by Pierre Joye.
957 * Don't call this function directly, use utils_spawn_[a]sync() instead. */
958 static
959 gboolean _broken_win32_spawn(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
960 gchar **std_out, gchar **std_err, gint *exit_status, GError **error)
962 TCHAR buffer[CMDSIZE]=TEXT("");
963 TCHAR cmdline[CMDSIZE] = TEXT("");
964 TCHAR* lpPart[CMDSIZE]={NULL};
965 DWORD retval = 0;
966 gint argc = 0, i;
967 gint cmdpos = 0;
969 SECURITY_ATTRIBUTES saAttr;
970 BOOL fSuccess;
971 geany_win32_spawn gw_spawn;
973 /* Temp file */
974 HANDLE hStdoutTempFile = NULL;
975 HANDLE hStderrTempFile = NULL;
977 gchar *stdout_content = NULL;
978 gchar *stderr_content = NULL;
980 while (argv[argc])
982 ++argc;
984 g_return_val_if_fail (std_out == NULL ||
985 !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
986 g_return_val_if_fail (std_err == NULL ||
987 !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
989 if (flags & G_SPAWN_SEARCH_PATH)
991 retval = SearchPath(NULL, argv[0], ".exe", sizeof(buffer), buffer, lpPart);
992 if (retval > 0)
993 g_snprintf(cmdline, sizeof(cmdline), "\"%s\"", buffer);
994 else
995 g_strlcpy(cmdline, argv[0], sizeof(cmdline));
996 cmdpos = 1;
999 for (i = cmdpos; i < argc; i++)
1001 g_snprintf(cmdline, sizeof(cmdline), "%s %s", cmdline, argv[i]);
1002 /*MessageBox(NULL, cmdline, cmdline, MB_OK);*/
1005 if (std_err != NULL)
1007 hStderrTempFile = GetTempFileHandle(error);
1008 if (hStderrTempFile == INVALID_HANDLE_VALUE)
1010 gchar *msg = g_win32_error_message(GetLastError());
1011 geany_debug("win32_spawn: Second CreateFile failed (%d)", (gint) GetLastError());
1012 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1013 g_free(msg);
1014 return FALSE;
1018 if (std_out != NULL)
1020 hStdoutTempFile = GetTempFileHandle(error);
1021 if (hStdoutTempFile == INVALID_HANDLE_VALUE)
1023 gchar *msg = g_win32_error_message(GetLastError());
1024 geany_debug("win32_spawn: Second CreateFile failed (%d)", (gint) GetLastError());
1025 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1026 g_free(msg);
1027 return FALSE;
1031 /* Set the bInheritHandle flag so pipe handles are inherited. */
1032 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
1033 saAttr.bInheritHandle = TRUE;
1034 saAttr.lpSecurityDescriptor = NULL;
1036 /* Get the handle to the current STDOUT and STDERR. */
1037 gw_spawn.hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
1038 gw_spawn.hStderr = GetStdHandle(STD_ERROR_HANDLE);
1039 gw_spawn.dwExitCode = 0;
1041 /* Create a pipe for the child process's STDOUT. */
1042 if (! CreatePipe(&(gw_spawn.hChildStdoutRd), &(gw_spawn.hChildStdoutWr), &saAttr, 0))
1044 gchar *msg = g_win32_error_message(GetLastError());
1045 geany_debug("win32_spawn: Stdout pipe creation failed (%d)", (gint) GetLastError());
1046 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
1047 g_free(msg);
1048 return FALSE;
1051 /* Ensure that the read handle to the child process's pipe for STDOUT is not inherited.*/
1052 SetHandleInformation(gw_spawn.hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
1054 /* Create a pipe for the child process's STDERR. */
1055 if (! CreatePipe(&(gw_spawn.hChildStderrRd), &(gw_spawn.hChildStderrWr), &saAttr, 0))
1057 gchar *msg = g_win32_error_message(GetLastError());
1058 geany_debug("win32_spawn: Stderr pipe creation failed");
1059 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
1060 g_free(msg);
1061 return FALSE;
1064 /* Ensure that the read handle to the child process's pipe for STDOUT is not inherited.*/
1065 SetHandleInformation(gw_spawn.hChildStderrRd, HANDLE_FLAG_INHERIT, 0);
1067 /* Create a pipe for the child process's STDIN. */
1068 if (! CreatePipe(&(gw_spawn.hChildStdinRd), &(gw_spawn.hChildStdinWr), &saAttr, 0))
1070 gchar *msg = g_win32_error_message(GetLastError());
1071 geany_debug("win32_spawn: Stdin pipe creation failed");
1072 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
1073 g_free(msg);
1074 return FALSE;
1077 /* Ensure that the write handle to the child process's pipe for STDIN is not inherited. */
1078 SetHandleInformation(gw_spawn.hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
1080 /* Now create the child process. */
1081 fSuccess = CreateChildProcess(&gw_spawn, cmdline, dir, error);
1082 if (exit_status)
1084 *exit_status = gw_spawn.dwExitCode;
1087 if (! fSuccess)
1089 geany_debug("win32_spawn: Create process failed");
1090 return FALSE;
1093 /* Read from pipe that is the standard output for child process. */
1094 if (std_out != NULL)
1096 ReadFromPipe(gw_spawn.hChildStdoutRd, gw_spawn.hChildStdoutWr, hStdoutTempFile, error);
1097 if (! GetContentFromHandle(hStdoutTempFile, &stdout_content, error))
1099 return FALSE;
1101 *std_out = stdout_content;
1104 if (std_err != NULL)
1106 ReadFromPipe(gw_spawn.hChildStderrRd, gw_spawn.hChildStderrWr, hStderrTempFile, error);
1107 if (! GetContentFromHandle(hStderrTempFile, &stderr_content, error))
1109 return FALSE;
1111 *std_err = stderr_content;
1113 return TRUE;
1117 /* Note: g_spawn is broken for receiving both stdio and stderr e.g. when
1118 * running make and there are compile errors. See glib/giowin32.c header
1119 * comment about Windows bugs, e.g. #338943 */
1120 /* Simple replacement for _broken_win32_spawn().
1121 * flags is ignored, G_SPAWN_SEARCH_PATH is implied.
1122 * Don't call this function directly, use utils_spawn_[a]sync() instead.
1123 * Adapted from tm_workspace_create_global_tags(). */
1124 gboolean win32_spawn(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1125 gchar **std_out, gchar **std_err, gint *exit_status, GError **error)
1127 gint ret;
1128 gboolean fail;
1129 gchar *tmp_file = create_temp_file();
1130 gchar *tmp_errfile = create_temp_file();
1131 gchar *command;
1132 gchar *locale_command;
1134 if (env != NULL)
1136 return _broken_win32_spawn(dir, argv, env, flags, std_out, std_err,
1137 exit_status, error);
1139 if (!tmp_file || !tmp_errfile)
1141 g_warning("%s: Could not create temporary files!", G_STRFUNC);
1142 return FALSE;
1144 command = g_strjoinv(" ", argv);
1145 SETPTR(command, g_strdup_printf("cmd.exe /S /C \"%s >%s 2>%s\"",
1146 command, tmp_file, tmp_errfile));
1147 locale_command = g_locale_from_utf8(command, -1, NULL, NULL, NULL);
1148 if (! locale_command)
1149 locale_command = g_strdup(command);
1150 geany_debug("WIN32: actually running command:\n%s", command);
1151 g_chdir(dir);
1152 errno = 0;
1153 ret = system(locale_command);
1154 /* the command can return -1 as an exit code, so check errno also */
1155 fail = ret == -1 && errno;
1156 if (!fail)
1158 if (std_out != NULL)
1159 g_file_get_contents(tmp_file, std_out, NULL, NULL);
1160 if (std_err != NULL)
1161 g_file_get_contents(tmp_errfile, std_err, NULL, NULL);
1163 else if (error)
1164 g_set_error_literal(error, G_SPAWN_ERROR, errno, g_strerror(errno));
1166 g_free(command);
1167 g_free(locale_command);
1168 g_unlink(tmp_file);
1169 g_free(tmp_file);
1170 g_unlink(tmp_errfile);
1171 g_free(tmp_errfile);
1172 if (exit_status)
1173 *exit_status = ret;
1175 return !fail;
1179 static gboolean GetContentFromHandle(HANDLE hFile, gchar **content, GError **error)
1181 DWORD filesize;
1182 gchar * buffer;
1183 DWORD dwRead;
1185 filesize = GetFileSize(hFile, NULL);
1186 if (filesize < 1)
1188 *content = NULL;
1189 return TRUE;
1192 buffer = g_malloc(filesize + 1);
1193 if (! buffer)
1195 gchar *msg = g_win32_error_message(GetLastError());
1196 geany_debug("GetContentFromHandle: Alloc failed");
1197 g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR, "%s", msg);
1198 g_free(msg);
1199 return FALSE;
1202 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
1203 if (! ReadFile(hFile, buffer, filesize, &dwRead, NULL) || dwRead == 0)
1205 gchar *msg = g_win32_error_message(GetLastError());
1206 geany_debug("GetContentFromHandle: Cannot read tempfile");
1207 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
1208 g_free(msg);
1209 return FALSE;
1212 if (! CloseHandle(hFile))
1214 gchar *msg = g_win32_error_message(GetLastError());
1215 geany_debug("GetContentFromHandle: CloseHandle failed (%d)", (gint) GetLastError());
1216 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
1217 g_free(msg);
1218 g_free(buffer);
1219 *content = NULL;
1220 return FALSE;
1222 buffer[filesize] = '\0';
1223 *content = buffer;
1224 return TRUE;
1228 gchar *win32_expand_environment_variables(const gchar *str)
1230 gchar expCmdline[32768]; /* 32768 is the limit for ExpandEnvironmentStrings() */
1232 if (ExpandEnvironmentStrings((LPCTSTR) str, (LPTSTR) expCmdline, sizeof(expCmdline)) != 0)
1233 return g_strdup(expCmdline);
1234 else
1235 return g_strdup(str);
1239 static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline,
1240 const TCHAR *dir, GError **error)
1242 PROCESS_INFORMATION piProcInfo;
1243 STARTUPINFOW siStartInfo;
1244 BOOL bFuncRetn = FALSE;
1245 gchar *expandedCmdline;
1246 wchar_t w_commandline[CMDSIZE];
1247 wchar_t w_dir[MAX_PATH];
1249 /* Set up members of the PROCESS_INFORMATION structure. */
1250 ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
1252 /* Set up members of the STARTUPINFO structure.*/
1253 ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
1255 siStartInfo.cb = sizeof(STARTUPINFO);
1256 siStartInfo.hStdError = gw_spawn->hChildStderrWr;
1257 siStartInfo.hStdOutput = gw_spawn->hChildStdoutWr;
1258 siStartInfo.hStdInput = gw_spawn->hChildStdinRd;
1259 siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
1261 /* Expand environment variables like %blah%. */
1262 expandedCmdline = win32_expand_environment_variables(szCmdline);
1264 MultiByteToWideChar(CP_UTF8, 0, expandedCmdline, -1, w_commandline, G_N_ELEMENTS(w_commandline));
1265 MultiByteToWideChar(CP_UTF8, 0, dir, -1, w_dir, G_N_ELEMENTS(w_dir));
1267 /* Create the child process. */
1268 bFuncRetn = CreateProcessW(NULL,
1269 w_commandline, /* command line */
1270 NULL, /* process security attributes */
1271 NULL, /* primary thread security attributes */
1272 TRUE, /* handles are inherited */
1273 CREATE_NO_WINDOW, /* creation flags */
1274 NULL, /* use parent's environment */
1275 w_dir, /* use parent's current directory */
1276 &siStartInfo, /* STARTUPINFO pointer */
1277 &piProcInfo); /* receives PROCESS_INFORMATION */
1279 g_free(expandedCmdline);
1281 if (bFuncRetn == 0)
1283 gchar *msg = g_win32_error_message(GetLastError());
1284 geany_debug("CreateChildProcess: CreateProcess failed (%s)", msg);
1285 g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "%s", msg);
1286 g_free(msg);
1287 return FALSE;
1289 else
1291 gint i;
1292 gsize ms = 30*1000;
1294 /* FIXME: this seems to timeout when there are many lines
1295 * to read - maybe because the child's pipe is full */
1296 for (i = 0; i < 2 &&
1297 WaitForSingleObject(piProcInfo.hProcess, ms) == WAIT_TIMEOUT; i++)
1299 ui_set_statusbar(FALSE, _("Process timed out after %.02f s!"), ms / 1000.0F);
1300 geany_debug("CreateChildProcess: timed out");
1301 TerminateProcess(piProcInfo.hProcess, WAIT_TIMEOUT); /* NOTE: This will not kill grandkids. */
1304 if (!GetExitCodeProcess(piProcInfo.hProcess, &gw_spawn->dwExitCode))
1306 gchar *msg = g_win32_error_message(GetLastError());
1307 geany_debug("GetExitCodeProcess failed: %s", msg);
1308 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg);
1309 g_free(msg);
1311 CloseHandle(piProcInfo.hProcess);
1312 CloseHandle(piProcInfo.hThread);
1313 return bFuncRetn;
1315 return FALSE;
1319 static VOID ReadFromPipe(HANDLE hRead, HANDLE hWrite, HANDLE hFile, GError **error)
1321 DWORD dwRead, dwWritten;
1322 CHAR chBuf[BUFSIZE];
1324 /* Close the write end of the pipe before reading from the
1325 read end of the pipe. */
1326 if (! CloseHandle(hWrite))
1328 gchar *msg = g_win32_error_message(GetLastError());
1329 geany_debug("ReadFromPipe: Closing handle failed");
1330 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg);
1331 g_free(msg);
1332 return;
1335 /* Read output from the child process, and write to parent's STDOUT. */
1336 for (;;)
1338 if (! ReadFile(hRead, chBuf, BUFSIZE, &dwRead, NULL) || dwRead == 0)
1339 break;
1341 if (! WriteFile(hFile, chBuf, dwRead, &dwWritten, NULL))
1342 break;
1347 static HANDLE GetTempFileHandle(GError **error)
1349 /* Temp file */
1350 DWORD dwBufSize = BUFSIZE;
1351 UINT uRetVal;
1352 TCHAR szTempName[BUFSIZE];
1353 TCHAR lpPathBuffer[BUFSIZE];
1354 DWORD dwRetVal;
1355 HANDLE hTempFile;
1357 /* Get the temp path. */
1358 dwRetVal = GetTempPath(dwBufSize, /* length of the buffer*/
1359 lpPathBuffer); /* buffer for path */
1361 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1363 gchar *msg = g_win32_error_message(GetLastError());
1364 geany_debug("GetTempFileHandle: GetTempPath failed (%d)", (gint) GetLastError());
1365 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1366 g_free(msg);
1367 return NULL;
1370 /* Create a temporary file for STDOUT. */
1371 uRetVal = GetTempFileName(lpPathBuffer, /* directory for tmp files */
1372 TEXT("GEANY_VCDIFF_"), /* temp file name prefix */
1373 0, /* create unique name */
1374 szTempName); /* buffer for name */
1375 if (uRetVal == 0)
1377 gchar *msg = g_win32_error_message(GetLastError());
1378 geany_debug("GetTempFileName failed (%d)", (gint) GetLastError());
1379 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1380 g_free(msg);
1381 return NULL;
1384 hTempFile = CreateFile((LPTSTR) szTempName, /* file name */
1385 GENERIC_READ | GENERIC_WRITE, /* open r-w */
1386 0, /* do not share */
1387 NULL, /* default security */
1388 CREATE_ALWAYS, /* overwrite existing */
1389 FILE_ATTRIBUTE_NORMAL,/* normal file */
1390 NULL); /* no template */
1392 if (hTempFile == INVALID_HANDLE_VALUE)
1394 gchar *msg = g_win32_error_message(GetLastError());
1395 geany_debug("GetTempFileHandle: Second CreateFile failed (%d)", (gint) GetLastError());
1396 g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg);
1397 g_free(msg);
1398 return NULL;
1400 return hTempFile;
1404 /* From GDK (they got it from MS Knowledge Base article Q130698) */
1405 static gboolean resolve_link(HWND hWnd, wchar_t *link, gchar **lpszPath)
1407 WIN32_FILE_ATTRIBUTE_DATA wfad;
1408 HRESULT hres;
1409 IShellLinkW *pslW = NULL;
1410 IPersistFile *ppf = NULL;
1411 LPVOID pslWV = NULL;
1412 LPVOID ppfV = NULL;
1414 /* Check if the file is empty first because IShellLink::Resolve for some reason succeeds
1415 * with an empty file and returns an empty "link target". (#524151) */
1416 if (!GetFileAttributesExW(link, GetFileExInfoStandard, &wfad) ||
1417 (wfad.nFileSizeHigh == 0 && wfad.nFileSizeLow == 0))
1419 return FALSE;
1422 /* Assume failure to start with: */
1423 *lpszPath = 0;
1425 CoInitialize(NULL);
1427 hres = CoCreateInstance(
1428 &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, &pslWV);
1430 if (SUCCEEDED(hres))
1432 /* The IShellLink interface supports the IPersistFile interface.
1433 * Get an interface pointer to it. */
1434 pslW = (IShellLinkW*) pslWV;
1435 hres = pslW->lpVtbl->QueryInterface(pslW, &IID_IPersistFile, &ppfV);
1438 if (SUCCEEDED(hres))
1440 /* Load the file. */
1441 ppf = (IPersistFile*) ppfV;
1442 hres = ppf->lpVtbl->Load(ppf, link, STGM_READ);
1445 if (SUCCEEDED(hres))
1447 /* Resolve the link by calling the Resolve() interface function. */
1448 hres = pslW->lpVtbl->Resolve(pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI);
1451 if (SUCCEEDED(hres))
1453 wchar_t wtarget[MAX_PATH];
1455 hres = pslW->lpVtbl->GetPath(pslW, wtarget, MAX_PATH, NULL, 0);
1456 if (SUCCEEDED(hres))
1457 *lpszPath = g_utf16_to_utf8(wtarget, -1, NULL, NULL, NULL);
1460 if (ppf)
1461 ppf->lpVtbl->Release(ppf);
1463 if (pslW)
1464 pslW->lpVtbl->Release(pslW);
1466 return SUCCEEDED(hres);
1470 /* Checks whether file_name is a Windows shortcut. file_name is expected in UTF-8 encoding.
1471 * If file_name is a Windows shortcut, it returns the target in UTF-8 encoding.
1472 * If it is not a shortcut, it returns a newly allocated copy of file_name. */
1473 gchar *win32_get_shortcut_target(const gchar *file_name)
1475 gchar *path = NULL;
1476 wchar_t *wfilename = g_utf8_to_utf16(file_name, -1, NULL, NULL, NULL);
1477 HWND hWnd = NULL;
1479 if (main_widgets.window != NULL)
1481 GdkWindow *window = gtk_widget_get_window(main_widgets.window);
1482 if (window != NULL)
1483 hWnd = GDK_WINDOW_HWND(window);
1486 resolve_link(hWnd, wfilename, &path);
1487 g_free(wfilename);
1489 if (path == NULL)
1490 return g_strdup(file_name);
1491 else
1492 return path;
1496 void win32_set_working_directory(const gchar *dir)
1498 SetCurrentDirectory(dir);
1502 gchar *win32_get_installation_dir(void)
1504 return g_win32_get_package_installation_directory_of_module(NULL);
1508 gchar *win32_get_user_config_dir(void)
1510 HRESULT hr;
1511 wchar_t path[MAX_PATH];
1513 hr = SHGetFolderPathAndSubDirW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, L"geany", path);
1514 if (SUCCEEDED(hr))
1516 // GLib always uses UTF-8 for filename encoding on Windows
1517 int u8_size = WideCharToMultiByte(CP_UTF8, 0, path, -1, NULL, 0, NULL, NULL);
1518 if (u8_size > 0)
1520 gchar *u8_path = g_malloc0(u8_size + 1);
1521 if (u8_path != NULL &&
1522 WideCharToMultiByte(CP_UTF8, 0, path, -1, u8_path, u8_size, NULL, NULL))
1524 return u8_path;
1529 // glib fallback
1530 g_warning("Failed to retrieve Windows config dir, falling back to default");
1531 return g_build_filename(g_get_user_config_dir(), "geany", NULL);
1534 #endif