Merge pull request #3812 from Biswa96/meson-add-geany-icon
[geany-mirror.git] / src / win32.c
blob1f737ce58433f9b708a69873189326398861deb2
1 /*
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.
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
29 /* Need Windows XP for SHGetFolderPathAndSubDirW */
30 #define _WIN32_WINNT 0x0501
31 /* Needed for SHGFP_TYPE */
32 #define _WIN32_IE 0x0500
34 #include "win32.h"
36 #ifdef G_OS_WIN32
38 #include "dialogs.h"
39 #include "document.h"
40 #include "editor.h"
41 #include "filetypes.h"
42 #include "project.h"
43 #include "support.h"
44 #include "ui_utils.h"
45 #include "utils.h"
47 #include <ctype.h>
48 #include <fcntl.h>
49 #include <io.h>
50 #include <math.h>
51 #include <stdlib.h>
52 #include <string.h>
54 #include <windows.h>
55 #include <commdlg.h>
56 #include <shellapi.h>
57 #include <shlobj.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);
74 dialog_timer = 0;
79 static VOID CALLBACK
80 win32_dialog_update_main_window(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
82 gint i;
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++)
88 gtk_main_iteration();
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)
98 switch (msg)
100 /* Messages that likely mean the window below a dialog needs to be re-drawn. */
101 case WM_WINDOWPOSCHANGED:
102 case WM_MOVE:
103 case WM_SIZE:
104 case WM_THEMECHANGED:
105 if (postpone)
107 win32_dialog_reset_timer(dlg);
108 dialog_timer = SetTimer(dlg, 0, 33 /* around 30fps */, win32_dialog_update_main_window);
110 else
111 win32_dialog_update_main_window(dlg, msg, wParam, lParam);
112 break;
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];
139 gchar *result;
141 if (g_file_test(utf8_filename, G_FILE_TEST_IS_DIR))
142 result = (gchar*) utf8_filename;
143 else
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)
149 g_free(result);
151 return w_dir;
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);
162 switch (uMsg)
164 case BFFM_INITIALIZED:
166 SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, pData);
167 break;
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);
177 break;
180 return 0;
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)
189 BROWSEINFOW bi;
190 LPITEMIDLIST pidl;
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));
197 if (parent == NULL)
198 parent = main_widgets.window;
200 memset(&bi, 0, sizeof bi);
201 bi.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(parent));
202 bi.pidlRoot = NULL;
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 ;-) */
211 if (pidl != NULL)
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);
218 CoTaskMemFree(pidl);
220 return result;
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)
228 OPENFILENAMEW of;
229 gint retval;
230 gchar tmp[MAX_PATH];
231 wchar_t w_file[MAX_PATH];
232 wchar_t w_title[512];
234 w_file[0] = '\0';
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;
247 of.nMaxFile = 2048;
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);
255 if (! retval)
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);
262 g_free(error);
264 return NULL;
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)
278 gboolean ret = TRUE;
279 gint rc;
280 guint t;
281 const gchar *title;
282 HWND parent_hwnd = NULL;
283 static wchar_t w_msg[512];
284 static wchar_t w_title[512];
286 switch (type)
288 case GTK_MESSAGE_ERROR:
290 t = MB_OK | MB_ICONERROR;
291 title = _("Error");
292 break;
294 case GTK_MESSAGE_QUESTION:
296 t = MB_YESNO | MB_ICONQUESTION;
297 title = _("Question");
298 break;
300 case GTK_MESSAGE_WARNING:
302 t = MB_OK | MB_ICONWARNING;
303 title = _("Warning");
304 break;
306 default:
308 t = MB_OK | MB_ICONINFORMATION;
309 title = _("Information");
310 break;
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));
319 if (parent != NULL)
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)
328 ret = FALSE;
330 return ret;
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)
340 return errno;
341 else
342 return 0;
346 /* Just a simple wrapper function to open a browser window */
347 void win32_open_browser(const gchar *uri)
349 gint ret;
350 if (strncmp(uri, "file://", 7) == 0)
352 uri += 7;
353 if (strchr(uri, ':') != NULL)
355 while (*uri == '/')
356 uri++;
359 ret = (gint) ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOWNORMAL);
360 if (ret <= 32)
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);
365 g_free(err);
370 static FILE *open_std_handle(DWORD handle, const char *mode)
372 HANDLE lStdHandle;
373 int hConHandle;
374 FILE *fp;
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);
381 g_free(err);
382 return NULL;
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);
389 g_free(err);
390 return NULL;
392 fp = _fdopen(hConHandle, mode);
393 if (! fp)
395 gchar *err = g_win32_error_message(GetLastError());
396 g_warning("_fdopen(%d, \"%s\") failed: %s", hConHandle, mode, err);
397 g_free(err);
398 return NULL;
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);
404 g_free(err);
405 fclose(fp);
406 return NULL;
409 return fp;
413 static void debug_setup_console(void)
415 static const WORD MAX_CONSOLE_LINES = 500;
416 CONSOLE_SCREEN_BUFFER_INFO coninfo;
417 FILE *fp;
419 /* allocate a console for this app */
420 AllocConsole();
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");
429 if (fp)
430 *stdout = *fp;
432 /* redirect unbuffered STDERR to the console */
433 fp = open_std_handle(STD_ERROR_HANDLE, "w");
434 if (fp)
435 *stderr = *fp;
437 /* redirect unbuffered STDIN to the console */
438 fp = open_std_handle(STD_INPUT_HANDLE, "r");
439 if (fp)
440 *stdin = *fp;
444 void win32_init_debug_code(void)
446 if (app->debug_mode)
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);
465 g_free(cmdline);
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;
475 HRESULT hres;
476 IShellLinkW *pslW = NULL;
477 IPersistFile *ppf = NULL;
478 LPVOID pslWV = NULL;
479 LPVOID ppfV = 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))
486 return FALSE;
489 /* Assume failure to start with: */
490 *lpszPath = 0;
492 CoInitialize(NULL);
494 hres = CoCreateInstance(
495 &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, &pslWV);
497 if (SUCCEEDED(hres))
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);
505 if (SUCCEEDED(hres))
507 /* Load the file. */
508 ppf = (IPersistFile*) ppfV;
509 hres = ppf->lpVtbl->Load(ppf, link, STGM_READ);
512 if (SUCCEEDED(hres))
514 /* Resolve the link by calling the Resolve() interface function. */
515 hres = pslW->lpVtbl->Resolve(pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI);
518 if (SUCCEEDED(hres))
520 wchar_t wtarget[MAX_PATH];
522 hres = pslW->lpVtbl->GetPath(pslW, wtarget, MAX_PATH, NULL, 0);
523 if (SUCCEEDED(hres))
524 *lpszPath = g_utf16_to_utf8(wtarget, -1, NULL, NULL, NULL);
527 if (ppf)
528 ppf->lpVtbl->Release(ppf);
530 if (pslW)
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)
542 gchar *path = NULL;
543 wchar_t *wfilename = g_utf8_to_utf16(file_name, -1, NULL, NULL, NULL);
544 HWND hWnd = NULL;
546 if (main_widgets.window != NULL)
548 GdkWindow *window = gtk_widget_get_window(main_widgets.window);
549 if (window != NULL)
550 hWnd = GDK_WINDOW_HWND(window);
553 resolve_link(hWnd, wfilename, &path);
554 g_free(wfilename);
556 if (path == NULL)
557 return g_strdup(file_name);
558 else
559 return path;
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)
577 HRESULT hr;
578 wchar_t path[MAX_PATH];
580 hr = SHGetFolderPathAndSubDirW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, L"geany", path);
581 if (SUCCEEDED(hr))
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);
585 if (u8_size > 0)
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))
591 return u8_path;
596 // glib fallback
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);
601 #endif