2 * win32.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005 The Geany contributors
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * Special functions for the win32 platform, to provide native dialogs.
29 /* Need Windows XP for SHGetFolderPathAndSubDirW */
30 #define _WIN32_WINNT 0x0501
31 /* Needed for SHGFP_TYPE */
32 #define _WIN32_IE 0x0500
41 #include "filetypes.h"
59 #include <glib/gstdio.h>
60 #include <gdk/gdkwin32.h>
63 /* The timer handle used to refresh windows below modal native dialogs. If
64 * ever more than one dialog can be shown at a time, this needs to be changed
65 * to be for specific dialogs. */
66 static UINT_PTR dialog_timer
= 0;
69 G_INLINE_FUNC
void win32_dialog_reset_timer(HWND hwnd
)
71 if (G_UNLIKELY(dialog_timer
!= 0))
73 KillTimer(hwnd
, dialog_timer
);
80 win32_dialog_update_main_window(HWND hwnd
, UINT uMsg
, UINT_PTR idEvent
, DWORD dwTime
)
84 /* Pump the main window loop a bit, but not enough to lock-up.
85 * The typical `while(gtk_events_pending()) gtk_main_iteration();`
86 * loop causes the entire operating system to lock-up. */
87 for (i
= 0; i
< 4 && gtk_events_pending(); i
++)
90 /* Cancel any pending timers since we just did an update */
91 win32_dialog_reset_timer(hwnd
);
95 G_INLINE_FUNC UINT_PTR
win32_dialog_queue_main_window_redraw(HWND dlg
, UINT msg
,
96 WPARAM wParam
, LPARAM lParam
, gboolean postpone
)
100 /* Messages that likely mean the window below a dialog needs to be re-drawn. */
101 case WM_WINDOWPOSCHANGED
:
104 case WM_THEMECHANGED
:
107 win32_dialog_reset_timer(dlg
);
108 dialog_timer
= SetTimer(dlg
, 0, 33 /* around 30fps */, win32_dialog_update_main_window
);
111 win32_dialog_update_main_window(dlg
, msg
, wParam
, lParam
);
114 return 0; /* always let the default proc handle it */
118 /* This function is called for OPENFILENAME lpfnHook function and it establishes
119 * a timer that is reset each time which will update the main window loop eventually. */
120 static UINT_PTR CALLBACK
win32_dialog_explorer_hook_proc(HWND dlg
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
122 return win32_dialog_queue_main_window_redraw(dlg
, msg
, wParam
, lParam
, TRUE
);
126 /* This function is called for old-school win32 dialogs that accept a proper
127 * lpfnHook function for all messages, it doesn't use a timer. */
128 static UINT_PTR CALLBACK
win32_dialog_hook_proc(HWND dlg
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
130 return win32_dialog_queue_main_window_redraw(dlg
, msg
, wParam
, lParam
, FALSE
);
134 /* Converts the given UTF-8 filename or directory name into something usable for Windows and
135 * returns the directory part of the given filename. */
136 static wchar_t *get_dir_for_path(const gchar
*utf8_filename
)
138 static wchar_t w_dir
[MAX_PATH
];
141 if (g_file_test(utf8_filename
, G_FILE_TEST_IS_DIR
))
142 result
= (gchar
*) utf8_filename
;
144 result
= g_path_get_dirname(utf8_filename
);
146 MultiByteToWideChar(CP_UTF8
, 0, result
, -1, w_dir
, G_N_ELEMENTS(w_dir
));
148 if (result
!= utf8_filename
)
155 /* Callback function for setting the initial directory of the folder open dialog. This could also
156 * be done with BROWSEINFO.pidlRoot and SHParseDisplayName but SHParseDisplayName is not available
157 * on systems below Windows XP. So, we go the hard way by creating a callback which will set up the
158 * folder when the dialog is initialised. Yeah, I like Windows. */
159 static INT CALLBACK
BrowseCallbackProc(HWND hwnd
, UINT uMsg
, LPARAM lp
, LPARAM pData
)
161 win32_dialog_hook_proc(hwnd
, uMsg
, lp
, pData
);
164 case BFFM_INITIALIZED
:
166 SendMessageW(hwnd
, BFFM_SETSELECTIONW
, TRUE
, pData
);
169 case BFFM_SELCHANGED
:
171 /* set the status window to the currently selected path. */
172 static wchar_t szDir
[MAX_PATH
];
173 if (SHGetPathFromIDListW((LPITEMIDLIST
) lp
, szDir
))
175 SendMessageW(hwnd
, BFFM_SETSTATUSTEXTW
, 0, (LPARAM
) szDir
);
184 /* Shows a folder selection dialog.
185 * initial_dir is expected in UTF-8
186 * The selected folder name is returned. */
187 gchar
*win32_show_folder_dialog(GtkWidget
*parent
, const gchar
*title
, const gchar
*initial_dir
)
191 gchar
*result
= NULL
;
192 wchar_t fname
[MAX_PATH
];
193 wchar_t w_title
[512];
195 MultiByteToWideChar(CP_UTF8
, 0, title
, -1, w_title
, G_N_ELEMENTS(w_title
));
198 parent
= main_widgets
.window
;
200 memset(&bi
, 0, sizeof bi
);
201 bi
.hwndOwner
= GDK_WINDOW_HWND(gtk_widget_get_window(parent
));
203 bi
.lpszTitle
= w_title
;
204 bi
.lpfn
= BrowseCallbackProc
;
205 bi
.lParam
= (LPARAM
) get_dir_for_path(initial_dir
);
206 bi
.ulFlags
= BIF_DONTGOBELOWDOMAIN
| BIF_RETURNONLYFSDIRS
| BIF_STATUSTEXT
| BIF_USENEWUI
;
208 pidl
= SHBrowseForFolderW(&bi
);
210 /* convert the strange Windows folder list item something into an usual path string ;-) */
213 if (SHGetPathFromIDListW(pidl
, fname
))
215 result
= g_malloc0(MAX_PATH
* 2);
216 WideCharToMultiByte(CP_UTF8
, 0, fname
, -1, result
, MAX_PATH
* 2, NULL
, NULL
);
224 /* initial_dir can be NULL to use the current working directory.
225 * Returns: the selected filename */
226 gchar
*win32_show_file_dialog(GtkWindow
*parent
, const gchar
*title
, const gchar
*initial_file
)
231 wchar_t w_file
[MAX_PATH
];
232 wchar_t w_title
[512];
236 if (initial_file
!= NULL
)
237 MultiByteToWideChar(CP_UTF8
, 0, initial_file
, -1, w_file
, G_N_ELEMENTS(w_file
));
239 MultiByteToWideChar(CP_UTF8
, 0, title
, -1, w_title
, G_N_ELEMENTS(w_title
));
241 /* initialise file dialog info struct */
242 memset(&of
, 0, sizeof of
);
243 of
.lStructSize
= sizeof of
;
244 of
.hwndOwner
= GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(parent
)));
246 of
.lpstrFile
= w_file
;
248 of
.lpstrFileTitle
= NULL
;
249 of
.lpstrTitle
= w_title
;
250 of
.lpstrDefExt
= L
"";
251 of
.Flags
= OFN_FILEMUSTEXIST
| OFN_EXPLORER
| OFN_ENABLEHOOK
| OFN_ENABLESIZING
;
252 of
.lpfnHook
= win32_dialog_explorer_hook_proc
;
253 retval
= GetOpenFileNameW(&of
);
257 if (CommDlgExtendedError())
259 gchar
*error
= g_strdup_printf(
260 "File dialog box error (%x)", (gint
) CommDlgExtendedError());
261 win32_message_dialog(NULL
, GTK_MESSAGE_ERROR
, error
);
267 WideCharToMultiByte(CP_UTF8
, 0, w_file
, -1, tmp
, sizeof(tmp
), NULL
, NULL
);
269 return g_strdup(tmp
);
273 /* Creates a native Windows message box of the given type and returns always TRUE
274 * or FALSE representing th pressed Yes or No button.
275 * If type is not GTK_MESSAGE_QUESTION, it returns always TRUE. */
276 gboolean
win32_message_dialog(GtkWidget
*parent
, GtkMessageType type
, const gchar
*msg
)
282 HWND parent_hwnd
= NULL
;
283 static wchar_t w_msg
[512];
284 static wchar_t w_title
[512];
288 case GTK_MESSAGE_ERROR
:
290 t
= MB_OK
| MB_ICONERROR
;
294 case GTK_MESSAGE_QUESTION
:
296 t
= MB_YESNO
| MB_ICONQUESTION
;
297 title
= _("Question");
300 case GTK_MESSAGE_WARNING
:
302 t
= MB_OK
| MB_ICONWARNING
;
303 title
= _("Warning");
308 t
= MB_OK
| MB_ICONINFORMATION
;
309 title
= _("Information");
314 /* convert the Unicode chars to wide chars */
315 /** TODO test if LANG == C then possibly skip conversion => g_win32_getlocale() */
316 MultiByteToWideChar(CP_UTF8
, 0, msg
, -1, w_msg
, G_N_ELEMENTS(w_msg
));
317 MultiByteToWideChar(CP_UTF8
, 0, title
, -1, w_title
, G_N_ELEMENTS(w_title
));
320 parent_hwnd
= GDK_WINDOW_HWND(gtk_widget_get_window(parent
));
321 else if (main_widgets
.window
!= NULL
)
322 parent_hwnd
= GDK_WINDOW_HWND(gtk_widget_get_window(main_widgets
.window
));
324 /* display the message box */
325 rc
= MessageBoxW(parent_hwnd
, w_msg
, w_title
, t
);
327 if (type
== GTK_MESSAGE_QUESTION
&& rc
!= IDYES
)
334 /* Little wrapper for _waccess(), returns errno or 0 if there was no error */
335 gint
win32_check_write_permission(const gchar
*dir
)
337 static wchar_t w_dir
[MAX_PATH
];
338 MultiByteToWideChar(CP_UTF8
, 0, dir
, -1, w_dir
, G_N_ELEMENTS(w_dir
));
339 if (_waccess(w_dir
, R_OK
| W_OK
) != 0)
346 /* Just a simple wrapper function to open a browser window */
347 void win32_open_browser(const gchar
*uri
)
350 if (strncmp(uri
, "file://", 7) == 0)
353 if (strchr(uri
, ':') != NULL
)
359 ret
= (gint
) ShellExecute(NULL
, "open", uri
, NULL
, NULL
, SW_SHOWNORMAL
);
362 gchar
*err
= g_win32_error_message(GetLastError());
363 ui_set_statusbar(TRUE
, _("Failed to open URI \"%s\": %s"), uri
, err
);
364 g_warning("ShellExecute failed opening \"%s\" (code %d): %s", uri
, ret
, err
);
370 static FILE *open_std_handle(DWORD handle
, const char *mode
)
376 lStdHandle
= GetStdHandle(handle
);
377 if (lStdHandle
== INVALID_HANDLE_VALUE
)
379 gchar
*err
= g_win32_error_message(GetLastError());
380 g_warning("GetStdHandle(%ld) failed: %s", (long)handle
, err
);
384 hConHandle
= _open_osfhandle((intptr_t)lStdHandle
, _O_TEXT
);
385 if (hConHandle
== -1)
387 gchar
*err
= g_win32_error_message(GetLastError());
388 g_warning("_open_osfhandle(handle(%ld), _O_TEXT) failed: %s", (long)handle
, err
);
392 fp
= _fdopen(hConHandle
, mode
);
395 gchar
*err
= g_win32_error_message(GetLastError());
396 g_warning("_fdopen(%d, \"%s\") failed: %s", hConHandle
, mode
, err
);
400 if (setvbuf(fp
, NULL
, _IONBF
, 0) != 0)
402 gchar
*err
= g_win32_error_message(GetLastError());
403 g_warning("setvbuf(%p, NULL, _IONBF, 0) failed: %s", fp
, err
);
413 static void debug_setup_console(void)
415 static const WORD MAX_CONSOLE_LINES
= 500;
416 CONSOLE_SCREEN_BUFFER_INFO coninfo
;
419 /* allocate a console for this app */
422 /* set the screen buffer to be big enough to let us scroll text */
423 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &coninfo
);
424 coninfo
.dwSize
.Y
= MAX_CONSOLE_LINES
;
425 SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE
), coninfo
.dwSize
);
427 /* redirect unbuffered STDOUT to the console */
428 fp
= open_std_handle(STD_OUTPUT_HANDLE
, "w");
432 /* redirect unbuffered STDERR to the console */
433 fp
= open_std_handle(STD_ERROR_HANDLE
, "w");
437 /* redirect unbuffered STDIN to the console */
438 fp
= open_std_handle(STD_INPUT_HANDLE
, "r");
444 void win32_init_debug_code(void)
448 /* create a console window to get log messages on Windows,
449 * especially useful when generating tags files */
450 debug_setup_console();
455 /* expands environment placeholders in @str. input and output is in UTF-8 */
456 gchar
*win32_expand_environment_variables(const gchar
*str
)
458 wchar_t *cmdline
= g_utf8_to_utf16(str
, -1, NULL
, NULL
, NULL
);
459 wchar_t expCmdline
[32768]; /* 32768 is the limit for ExpandEnvironmentStrings() */
460 gchar
*expanded
= NULL
;
462 if (cmdline
&& ExpandEnvironmentStringsW(cmdline
, expCmdline
, sizeof(expCmdline
)) != 0)
463 expanded
= g_utf16_to_utf8(expCmdline
, -1, NULL
, NULL
, NULL
);
467 return expanded
? expanded
: g_strdup(str
);
471 /* From GDK (they got it from MS Knowledge Base article Q130698) */
472 static gboolean
resolve_link(HWND hWnd
, wchar_t *link
, gchar
**lpszPath
)
474 WIN32_FILE_ATTRIBUTE_DATA wfad
;
476 IShellLinkW
*pslW
= NULL
;
477 IPersistFile
*ppf
= NULL
;
481 /* Check if the file is empty first because IShellLink::Resolve for some reason succeeds
482 * with an empty file and returns an empty "link target". (#524151) */
483 if (!GetFileAttributesExW(link
, GetFileExInfoStandard
, &wfad
) ||
484 (wfad
.nFileSizeHigh
== 0 && wfad
.nFileSizeLow
== 0))
489 /* Assume failure to start with: */
494 hres
= CoCreateInstance(
495 &CLSID_ShellLink
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IShellLinkW
, &pslWV
);
499 /* The IShellLink interface supports the IPersistFile interface.
500 * Get an interface pointer to it. */
501 pslW
= (IShellLinkW
*) pslWV
;
502 hres
= pslW
->lpVtbl
->QueryInterface(pslW
, &IID_IPersistFile
, &ppfV
);
508 ppf
= (IPersistFile
*) ppfV
;
509 hres
= ppf
->lpVtbl
->Load(ppf
, link
, STGM_READ
);
514 /* Resolve the link by calling the Resolve() interface function. */
515 hres
= pslW
->lpVtbl
->Resolve(pslW
, hWnd
, SLR_ANY_MATCH
| SLR_NO_UI
);
520 wchar_t wtarget
[MAX_PATH
];
522 hres
= pslW
->lpVtbl
->GetPath(pslW
, wtarget
, MAX_PATH
, NULL
, 0);
524 *lpszPath
= g_utf16_to_utf8(wtarget
, -1, NULL
, NULL
, NULL
);
528 ppf
->lpVtbl
->Release(ppf
);
531 pslW
->lpVtbl
->Release(pslW
);
533 return SUCCEEDED(hres
);
537 /* Checks whether file_name is a Windows shortcut. file_name is expected in UTF-8 encoding.
538 * If file_name is a Windows shortcut, it returns the target in UTF-8 encoding.
539 * If it is not a shortcut, it returns a newly allocated copy of file_name. */
540 gchar
*win32_get_shortcut_target(const gchar
*file_name
)
543 wchar_t *wfilename
= g_utf8_to_utf16(file_name
, -1, NULL
, NULL
, NULL
);
546 if (main_widgets
.window
!= NULL
)
548 GdkWindow
*window
= gtk_widget_get_window(main_widgets
.window
);
550 hWnd
= GDK_WINDOW_HWND(window
);
553 resolve_link(hWnd
, wfilename
, &path
);
557 return g_strdup(file_name
);
563 void win32_set_working_directory(const gchar
*dir
)
565 SetCurrentDirectory(dir
);
569 gchar
*win32_get_installation_dir(void)
571 return g_win32_get_package_installation_directory_of_module(NULL
);
575 gchar
*win32_get_user_config_dir(void)
578 wchar_t path
[MAX_PATH
];
580 hr
= SHGetFolderPathAndSubDirW(NULL
, CSIDL_APPDATA
| CSIDL_FLAG_CREATE
, NULL
, SHGFP_TYPE_CURRENT
, L
"geany", path
);
583 // GLib always uses UTF-8 for filename encoding on Windows
584 int u8_size
= WideCharToMultiByte(CP_UTF8
, 0, path
, -1, NULL
, 0, NULL
, NULL
);
587 gchar
*u8_path
= g_malloc0(u8_size
+ 1);
588 if (u8_path
!= NULL
&&
589 WideCharToMultiByte(CP_UTF8
, 0, path
, -1, u8_path
, u8_size
, NULL
, NULL
))
597 g_warning("Failed to retrieve Windows config dir, falling back to default");
598 return g_build_filename(g_get_user_config_dir(), "geany", NULL
);