rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / pidgin / win32 / gtkwin32dep.c
blobcdd1d42bc52aeb3ac34afa52066d3aebbd9663f3
1 /*
2 * Pidgin is the legal property of its developers, whose names are too numerous
3 * to list here. Please refer to the COPYRIGHT file distributed with this
4 * source distribution.
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
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
21 #include "internal.h"
23 #include <io.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <winuser.h>
27 #include <shellapi.h>
29 #include <glib.h>
30 #include <glib/gstdio.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkwin32.h>
34 #include "debug.h"
35 #include "notify.h"
36 #include "network.h"
38 #include "resource.h"
39 #include "untar.h"
41 #include "gtkwin32dep.h"
42 #include "gtkblist.h"
43 #include "gtkconv.h"
44 #include "gtkconn.h"
45 #include "util.h"
48 * GLOBALS
50 HINSTANCE exe_hInstance = 0;
51 HINSTANCE dll_hInstance = 0;
52 HWND messagewin_hwnd;
53 static int gtkwin32_handle;
55 static gboolean pwm_handles_connections = TRUE;
59 * PUBLIC CODE
62 HINSTANCE winpidgin_exe_hinstance(void) {
63 return exe_hInstance;
66 void winpidgin_set_exe_hinstance(HINSTANCE hint)
68 exe_hInstance = hint;
71 HINSTANCE winpidgin_dll_hinstance(void) {
72 return dll_hInstance;
75 int winpidgin_gz_decompress(const char* in, const char* out) {
76 GFile *fin;
77 GFile *fout;
78 GInputStream *input;
79 GOutputStream *output;
80 GOutputStream *conv_out;
81 GZlibDecompressor *decompressor;
82 gssize size;
83 GError *error = NULL;
85 fin = g_file_new_for_path(in);
86 input = G_INPUT_STREAM(g_file_read(fin, NULL, &error));
87 g_object_unref(fin);
89 if (input == NULL) {
90 purple_debug_error("winpidgin_gz_decompress",
91 "Failed to open: %s: %s\n",
92 in, error->message);
93 g_clear_error(&error);
94 return 0;
97 fout = g_file_new_for_path(out);
98 output = G_OUTPUT_STREAM(g_file_replace(fout, NULL, FALSE,
99 G_FILE_CREATE_NONE, NULL, &error));
100 g_object_unref(fout);
102 if (output == NULL) {
103 purple_debug_error("winpidgin_gz_decompress",
104 "Error opening file: %s: %s\n",
105 out, error->message);
106 g_clear_error(&error);
107 g_object_unref(input);
108 return 0;
111 decompressor = g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP);
112 conv_out = g_converter_output_stream_new(output,
113 G_CONVERTER(decompressor));
114 g_object_unref(decompressor);
115 g_object_unref(output);
117 size = g_output_stream_splice(conv_out, input,
118 G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
119 G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error);
121 g_object_unref(input);
122 g_object_unref(conv_out);
124 if (size < 0) {
125 purple_debug_error("wpurple_gz_decompress",
126 "Error writing to file: %s\n",
127 error->message);
128 g_clear_error(&error);
129 return 0;
132 return 1;
135 int winpidgin_gz_untar(const char* filename, const char* destdir) {
136 char tmpfile[_MAX_PATH];
137 char template[]="wpidginXXXXXX";
139 sprintf(tmpfile, "%s%s%s", g_get_tmp_dir(), G_DIR_SEPARATOR_S, _mktemp(template));
140 if(winpidgin_gz_decompress(filename, tmpfile)) {
141 int ret;
142 if(untar(tmpfile, destdir, UNTAR_FORCE | UNTAR_QUIET))
143 ret = 1;
144 else {
145 purple_debug_error("winpidgin_gz_untar", "Failure untarring %s\n", tmpfile);
146 ret = 0;
148 g_unlink(tmpfile);
149 return ret;
151 else {
152 purple_debug_error("winpidgin_gz_untar", "Failed to gz decompress %s\n", filename);
153 return 0;
157 void winpidgin_shell_execute(const char *target, const char *verb, const char *clazz) {
159 SHELLEXECUTEINFOW wsinfo;
160 wchar_t *w_uri, *w_verb, *w_clazz = NULL;
162 g_return_if_fail(target != NULL);
163 g_return_if_fail(verb != NULL);
165 w_uri = g_utf8_to_utf16(target, -1, NULL, NULL, NULL);
166 w_verb = g_utf8_to_utf16(verb, -1, NULL, NULL, NULL);
168 memset(&wsinfo, 0, sizeof(wsinfo));
169 wsinfo.cbSize = sizeof(wsinfo);
170 wsinfo.lpVerb = w_verb;
171 wsinfo.lpFile = w_uri;
172 wsinfo.nShow = SW_SHOWNORMAL;
173 wsinfo.fMask |= SEE_MASK_FLAG_NO_UI;
174 if (clazz != NULL) {
175 w_clazz = g_utf8_to_utf16(clazz, -1, NULL, NULL, NULL);
176 wsinfo.fMask |= SEE_MASK_CLASSNAME;
177 wsinfo.lpClass = w_clazz;
180 if(!ShellExecuteExW(&wsinfo))
181 purple_debug_error("winpidgin", "Error opening URI: %s error: %d\n",
182 target, (int) wsinfo.hInstApp);
184 g_free(w_uri);
185 g_free(w_verb);
186 g_free(w_clazz);
190 void winpidgin_notify_uri(const char *uri) {
191 /* Allow a few commonly used and "safe" schemes to go to the specific
192 * class handlers and send everything else to the default http browser.
193 * This isn't optimal, but should cover the most common cases. I didn't
194 * see any better secure solutions when I did some research.
196 gchar *scheme = g_uri_parse_scheme(uri);
197 if (scheme && (g_ascii_strcasecmp(scheme, "https") == 0
198 || g_ascii_strcasecmp(scheme, "ftp") == 0
199 || g_ascii_strcasecmp(scheme, "mailto") == 0))
200 winpidgin_shell_execute(uri, "open", scheme);
201 else
202 winpidgin_shell_execute(uri, "open", "http");
203 g_free(scheme);
206 #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13)
207 #define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14)
209 static void*
210 winpidgin_netconfig_changed_cb(GNetworkMonitor *monitor, gboolean available, gpointer data)
212 pwm_handles_connections = FALSE;
214 return NULL;
217 static gboolean
218 winpidgin_pwm_reconnect()
220 g_signal_handlers_disconnect_by_func(g_network_monitor_get_default,
221 G_CALLBACK(winpidgin_netconfig_changed_cb),
222 NULL);
223 if (pwm_handles_connections == TRUE) {
224 PurpleConnectionUiOps *ui_ops = pidgin_connections_get_ui_ops();
226 purple_debug_info("winpidgin", "Resumed from standby, reconnecting accounts.\n");
228 if (ui_ops != NULL && ui_ops->network_connected != NULL)
229 ui_ops->network_connected();
230 } else {
231 purple_debug_info("winpidgin", "Resumed from standby, gtkconn will handle reconnecting.\n");
232 pwm_handles_connections = TRUE;
235 return FALSE;
238 static LRESULT CALLBACK message_window_handler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
240 if (msg == PIDGIN_WM_FOCUS_REQUEST) {
241 PidginBuddyList *blist;
242 purple_debug_info("winpidgin", "Got external Buddy List focus request.");
243 blist = pidgin_blist_get_default_gtk_blist();
244 if (blist != NULL && blist->window != NULL) {
245 gtk_window_present(GTK_WINDOW(blist->window));
247 return TRUE;
248 } else if (msg == PIDGIN_WM_PROTOCOL_HANDLE) {
249 char *proto_msg = (char *) lparam;
250 purple_debug_info("winpidgin", "Got protocol handler request: %s\n", proto_msg ? proto_msg : "");
251 purple_got_protocol_handler_uri(proto_msg);
252 return TRUE;
253 } else if (msg == WM_POWERBROADCAST) {
254 if (wparam == PBT_APMQUERYSUSPEND) {
255 purple_debug_info("winpidgin", "Windows requesting permission to suspend.\n");
256 return TRUE;
257 } else if (wparam == PBT_APMSUSPEND) {
258 PurpleConnectionUiOps *ui_ops = pidgin_connections_get_ui_ops();
260 purple_debug_info("winpidgin", "Entering system standby, disconnecting accounts.\n");
262 if (ui_ops != NULL && ui_ops->network_disconnected != NULL)
263 ui_ops->network_disconnected();
265 g_signal_connect(g_network_monitor_get_default(),
266 "network-changed",
267 G_CALLBACK(winpidgin_netconfig_changed_cb),
268 NULL);
270 return TRUE;
271 } else if (wparam == PBT_APMRESUMESUSPEND) {
272 purple_debug_info("winpidgin", "Resuming from system standby.\n");
273 /* TODO: It seems like it'd be wise to use the NLA message, if possible, instead of this. */
274 g_timeout_add_seconds(1, winpidgin_pwm_reconnect, NULL);
275 return TRUE;
279 return DefWindowProc(hwnd, msg, wparam, lparam);
282 static HWND winpidgin_message_window_init(void) {
283 HWND win_hwnd;
284 WNDCLASSEX wcx;
285 LPCTSTR wname;
287 wname = TEXT("WinpidginMsgWinCls");
289 wcx.cbSize = sizeof(wcx);
290 wcx.style = 0;
291 wcx.lpfnWndProc = message_window_handler;
292 wcx.cbClsExtra = 0;
293 wcx.cbWndExtra = 0;
294 wcx.hInstance = winpidgin_exe_hinstance();
295 wcx.hIcon = NULL;
296 wcx.hCursor = NULL;
297 wcx.hbrBackground = NULL;
298 wcx.lpszMenuName = NULL;
299 wcx.lpszClassName = wname;
300 wcx.hIconSm = NULL;
302 RegisterClassEx(&wcx);
304 /* Create the window */
305 if(!(win_hwnd = CreateWindow(wname, TEXT("WinpidginMsgWin"), 0, 0, 0, 0, 0,
306 NULL, NULL, winpidgin_exe_hinstance(), 0))) {
307 purple_debug_error("winpidgin",
308 "Unable to create message window.\n");
309 return NULL;
312 return win_hwnd;
315 static gboolean stop_flashing(GtkWidget *widget, GdkEventFocus *event, gpointer data) {
316 GtkWindow *window = data;
317 gpointer handler_id;
319 winpidgin_window_flash(window, FALSE);
321 if ((handler_id = g_object_get_data(G_OBJECT(window), "flash_stop_handler_id"))) {
322 g_signal_handler_disconnect(G_OBJECT(window), (gulong) GPOINTER_TO_UINT(handler_id));
323 g_object_steal_data(G_OBJECT(window), "flash_stop_handler_id");
326 return FALSE;
329 void
330 winpidgin_window_flash(GtkWindow *window, gboolean flash) {
331 GdkWindow * gdkwin;
332 FLASHWINFO info;
334 g_return_if_fail(window != NULL);
336 gdkwin = gtk_widget_get_window(GTK_WIDGET(window));
338 g_return_if_fail(GDK_IS_WINDOW(gdkwin));
339 g_return_if_fail(gdk_window_get_window_type(gdkwin) != GDK_WINDOW_CHILD);
341 if (gdk_window_is_destroyed(gdkwin))
342 return;
344 memset(&info, 0, sizeof(FLASHWINFO));
345 info.cbSize = sizeof(FLASHWINFO);
346 info.hwnd = GDK_WINDOW_HWND(gdkwin);
347 if (flash) {
348 DWORD flashCount;
349 info.uCount = 3;
350 if (SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &flashCount, 0))
351 info.uCount = flashCount;
352 info.dwFlags = FLASHW_ALL | FLASHW_TIMER;
353 } else
354 info.dwFlags = FLASHW_STOP;
355 FlashWindowEx(&info);
356 info.dwTimeout = 0;
360 void
361 winpidgin_conv_blink(PurpleConversation *conv) {
362 PidginConvWindow *win;
363 GtkWindow *window;
365 if(conv == NULL) {
366 purple_debug_info("winpidgin", "No conversation found to blink.\n");
367 return;
370 win = pidgin_conv_get_window(PIDGIN_CONVERSATION(conv));
371 if(win == NULL) {
372 purple_debug_info("winpidgin", "No conversation windows found to blink.\n");
373 return;
375 window = GTK_WINDOW(win->window);
377 /* Don't flash if the window is in the foreground */
378 if (GetForegroundWindow() ==
379 GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(window))))
381 return;
384 winpidgin_window_flash(window, TRUE);
385 /* Stop flashing when window receives focus */
386 if (g_object_get_data(G_OBJECT(window), "flash_stop_handler_id") == NULL) {
387 gulong handler_id = g_signal_connect(G_OBJECT(window), "focus-in-event",
388 G_CALLBACK(stop_flashing), window);
389 g_object_set_data(G_OBJECT(window), "flash_stop_handler_id", GUINT_TO_POINTER(handler_id));
393 static gboolean
394 winpidgin_conv_im_blink(PurpleConversation *conv, PurpleMessage *pmsg)
396 /* Don't flash for our own messages or system messages */
397 if (purple_message_get_flags(pmsg) & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_SYSTEM))
398 return FALSE;
399 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/win32/blink_im"))
400 winpidgin_conv_blink(conv);
401 return FALSE;
404 void winpidgin_init(void) {
405 typedef void (__cdecl* LPFNSETLOGFILE)(const LPCSTR);
406 LPFNSETLOGFILE MySetLogFile;
407 gchar *exchndl_dll_path;
409 if (purple_debug_is_verbose())
410 purple_debug_misc("winpidgin", "winpidgin_init start\n");
412 exchndl_dll_path = g_build_filename(wpurple_bin_dir(), "exchndl.dll", NULL);
413 MySetLogFile = (LPFNSETLOGFILE) wpurple_find_and_loadproc(exchndl_dll_path, "SetLogFile");
414 g_free(exchndl_dll_path);
415 exchndl_dll_path = NULL;
416 if (MySetLogFile) {
417 gchar *debug_dir, *locale_debug_dir;
419 debug_dir = g_build_filename(purple_cache_dir(), "pidgin.RPT", NULL);
420 locale_debug_dir = g_locale_from_utf8(debug_dir, -1, NULL, NULL, NULL);
422 purple_debug_info("winpidgin", "Setting exchndl.dll LogFile to %s\n", debug_dir);
424 MySetLogFile(locale_debug_dir);
426 g_free(debug_dir);
427 g_free(locale_debug_dir);
430 purple_debug_info("winpidgin", "GTK+: %u.%u.%u\n",
431 gtk_major_version, gtk_minor_version, gtk_micro_version);
433 messagewin_hwnd = winpidgin_message_window_init();
435 if (purple_debug_is_verbose())
436 purple_debug_misc("winpidgin", "winpidgin_init end\n");
439 void winpidgin_post_init(void) {
441 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/win32");
442 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/win32/blink_im", TRUE);
444 purple_signal_connect(pidgin_conversations_get_handle(),
445 "displaying-im-msg", &gtkwin32_handle, PURPLE_CALLBACK(winpidgin_conv_im_blink),
446 NULL);
450 /* Windows Cleanup */
452 void winpidgin_cleanup(void) {
453 purple_debug_info("winpidgin", "winpidgin_cleanup\n");
455 if(messagewin_hwnd)
456 DestroyWindow(messagewin_hwnd);
460 /* DLL initializer */
461 /* suppress gcc "no previous prototype" warning */
462 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
463 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
464 dll_hInstance = hinstDLL;
465 return TRUE;
468 static gboolean
469 get_WorkingAreaRectForWindow(HWND hwnd, RECT *workingAreaRc) {
471 HMONITOR monitor;
472 MONITORINFO info;
474 monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
476 info.cbSize = sizeof(info);
477 if(!GetMonitorInfo(monitor, &info))
478 return FALSE;
480 CopyRect(workingAreaRc, &(info.rcWork));
481 return TRUE;
484 typedef HRESULT (WINAPI* DwmIsCompositionEnabledFunction)(BOOL*);
485 typedef HRESULT (WINAPI* DwmGetWindowAttributeFunction)(HWND, DWORD, PVOID, DWORD);
486 static HMODULE dwmapi_module = NULL;
487 static DwmIsCompositionEnabledFunction DwmIsCompositionEnabled = NULL;
488 static DwmGetWindowAttributeFunction DwmGetWindowAttribute = NULL;
489 #ifndef DWMWA_EXTENDED_FRAME_BOUNDS
490 # define DWMWA_EXTENDED_FRAME_BOUNDS 9
491 #endif
493 static RECT
494 get_actualWindowRect(HWND hwnd)
496 RECT winR;
498 GetWindowRect(hwnd, &winR);
500 if (dwmapi_module == NULL) {
501 dwmapi_module = GetModuleHandleW(L"dwmapi.dll");
502 if (dwmapi_module != NULL) {
503 DwmIsCompositionEnabled = (DwmIsCompositionEnabledFunction) GetProcAddress(dwmapi_module, "DwmIsCompositionEnabled");
504 DwmGetWindowAttribute = (DwmGetWindowAttributeFunction) GetProcAddress(dwmapi_module, "DwmGetWindowAttribute");
508 if (DwmIsCompositionEnabled != NULL && DwmGetWindowAttribute != NULL) {
509 BOOL pfEnabled;
510 if (SUCCEEDED(DwmIsCompositionEnabled(&pfEnabled))) {
511 RECT tempR;
512 if (SUCCEEDED(DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &tempR, sizeof(tempR)))) {
513 winR = tempR;
518 return winR;
521 void winpidgin_ensure_onscreen(GtkWidget *win) {
522 RECT winR, wAR, intR;
523 HWND hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(win));
525 g_return_if_fail(hwnd != NULL);
526 winR = get_actualWindowRect(hwnd);
528 purple_debug_info("win32placement",
529 "Window RECT: L:%ld R:%ld T:%ld B:%ld\n",
530 winR.left, winR.right,
531 winR.top, winR.bottom);
533 if(!get_WorkingAreaRectForWindow(hwnd, &wAR)) {
534 purple_debug_info("win32placement",
535 "Couldn't get multimonitor working area\n");
536 if(!SystemParametersInfo(SPI_GETWORKAREA, 0, &wAR, FALSE)) {
537 /* I don't think this will ever happen */
538 wAR.left = 0;
539 wAR.top = 0;
540 wAR.bottom = GetSystemMetrics(SM_CYSCREEN);
541 wAR.right = GetSystemMetrics(SM_CXSCREEN);
545 purple_debug_info("win32placement",
546 "Working Area RECT: L:%ld R:%ld T:%ld B:%ld\n",
547 wAR.left, wAR.right,
548 wAR.top, wAR.bottom);
550 /** If the conversation window doesn't intersect perfectly, move it to do so */
551 if(!(IntersectRect(&intR, &winR, &wAR)
552 && EqualRect(&intR, &winR))) {
553 purple_debug_info("win32placement",
554 "conversation window out of working area, relocating\n");
556 /* Make sure the working area is big enough. */
557 if ((winR.right - winR.left) <= (wAR.right - wAR.left)
558 && (winR.bottom - winR.top) <= (wAR.bottom - wAR.top)) {
559 /* Is it off the bottom? */
560 if (winR.bottom > wAR.bottom) {
561 winR.top = wAR.bottom - (winR.bottom - winR.top);
562 winR.bottom = wAR.bottom;
564 /* Is it off the top? */
565 else if (winR.top < wAR.top) {
566 winR.bottom = wAR.top + (winR.bottom - winR.top);
567 winR.top = wAR.top;
570 /* Is it off the left? */
571 if (winR.left < wAR.left) {
572 winR.right = wAR.left + (winR.right - winR.left);
573 winR.left = wAR.left;
575 /* Is it off the right? */
576 else if (winR.right > wAR.right) {
577 winR.left = wAR.right - (winR.right - winR.left);
578 winR.right = wAR.right;
581 } else {
582 /* We couldn't salvage it; move it to the top left corner of the working area */
583 winR.right = wAR.left + (winR.right - winR.left);
584 winR.bottom = wAR.top + (winR.bottom - winR.top);
585 winR.left = wAR.left;
586 winR.top = wAR.top;
589 purple_debug_info("win32placement",
590 "Relocation RECT: L:%ld R:%ld T:%ld B:%ld\n",
591 winR.left, winR.right,
592 winR.top, winR.bottom);
594 MoveWindow(hwnd, winR.left, winR.top,
595 (winR.right - winR.left),
596 (winR.bottom - winR.top), TRUE);
601 DWORD winpidgin_get_lastactive() {
602 DWORD result = 0;
604 LASTINPUTINFO lii;
605 memset(&lii, 0, sizeof(lii));
606 lii.cbSize = sizeof(lii);
607 if (GetLastInputInfo(&lii))
608 result = lii.dwTime;
610 return result;