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
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
30 #include <glib/gstdio.h>
32 #include <gdk/gdkwin32.h>
41 #include "gtkwin32dep.h"
50 HINSTANCE exe_hInstance
= 0;
51 HINSTANCE dll_hInstance
= 0;
53 static int gtkwin32_handle
;
55 static gboolean pwm_handles_connections
= TRUE
;
62 HINSTANCE
winpidgin_exe_hinstance(void) {
66 void winpidgin_set_exe_hinstance(HINSTANCE hint
)
71 HINSTANCE
winpidgin_dll_hinstance(void) {
75 int winpidgin_gz_decompress(const char* in
, const char* out
) {
79 GOutputStream
*output
;
80 GOutputStream
*conv_out
;
81 GZlibDecompressor
*decompressor
;
85 fin
= g_file_new_for_path(in
);
86 input
= G_INPUT_STREAM(g_file_read(fin
, NULL
, &error
));
90 purple_debug_error("winpidgin_gz_decompress",
91 "Failed to open: %s: %s\n",
93 g_clear_error(&error
);
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
);
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
);
125 purple_debug_error("wpurple_gz_decompress",
126 "Error writing to file: %s\n",
128 g_clear_error(&error
);
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
)) {
142 if(untar(tmpfile
, destdir
, UNTAR_FORCE
| UNTAR_QUIET
))
145 purple_debug_error("winpidgin_gz_untar", "Failure untarring %s\n", tmpfile
);
152 purple_debug_error("winpidgin_gz_untar", "Failed to gz decompress %s\n", filename
);
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
;
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
);
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
);
202 winpidgin_shell_execute(uri
, "open", "http");
206 #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13)
207 #define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14)
210 winpidgin_netconfig_changed_cb(GNetworkMonitor
*monitor
, gboolean available
, gpointer data
)
212 pwm_handles_connections
= FALSE
;
218 winpidgin_pwm_reconnect()
220 g_signal_handlers_disconnect_by_func(g_network_monitor_get_default
,
221 G_CALLBACK(winpidgin_netconfig_changed_cb
),
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();
231 purple_debug_info("winpidgin", "Resumed from standby, gtkconn will handle reconnecting.\n");
232 pwm_handles_connections
= TRUE
;
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
));
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
);
253 } else if (msg
== WM_POWERBROADCAST
) {
254 if (wparam
== PBT_APMQUERYSUSPEND
) {
255 purple_debug_info("winpidgin", "Windows requesting permission to suspend.\n");
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(),
267 G_CALLBACK(winpidgin_netconfig_changed_cb
),
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
);
279 return DefWindowProc(hwnd
, msg
, wparam
, lparam
);
282 static HWND
winpidgin_message_window_init(void) {
287 wname
= TEXT("WinpidginMsgWinCls");
289 wcx
.cbSize
= sizeof(wcx
);
291 wcx
.lpfnWndProc
= message_window_handler
;
294 wcx
.hInstance
= winpidgin_exe_hinstance();
297 wcx
.hbrBackground
= NULL
;
298 wcx
.lpszMenuName
= NULL
;
299 wcx
.lpszClassName
= wname
;
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");
315 static gboolean
stop_flashing(GtkWidget
*widget
, GdkEventFocus
*event
, gpointer data
) {
316 GtkWindow
*window
= data
;
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");
330 winpidgin_window_flash(GtkWindow
*window
, gboolean flash
) {
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
))
344 memset(&info
, 0, sizeof(FLASHWINFO
));
345 info
.cbSize
= sizeof(FLASHWINFO
);
346 info
.hwnd
= GDK_WINDOW_HWND(gdkwin
);
350 if (SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT
, 0, &flashCount
, 0))
351 info
.uCount
= flashCount
;
352 info
.dwFlags
= FLASHW_ALL
| FLASHW_TIMER
;
354 info
.dwFlags
= FLASHW_STOP
;
355 FlashWindowEx(&info
);
361 winpidgin_conv_blink(PurpleConversation
*conv
) {
362 PidginConvWindow
*win
;
366 purple_debug_info("winpidgin", "No conversation found to blink.\n");
370 win
= pidgin_conv_get_window(PIDGIN_CONVERSATION(conv
));
372 purple_debug_info("winpidgin", "No conversation windows found to blink.\n");
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
))))
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
));
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
))
399 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/win32/blink_im"))
400 winpidgin_conv_blink(conv
);
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
;
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
);
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", >kwin32_handle
, PURPLE_CALLBACK(winpidgin_conv_im_blink
),
450 /* Windows Cleanup */
452 void winpidgin_cleanup(void) {
453 purple_debug_info("winpidgin", "winpidgin_cleanup\n");
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
;
469 get_WorkingAreaRectForWindow(HWND hwnd
, RECT
*workingAreaRc
) {
474 monitor
= MonitorFromWindow(hwnd
, MONITOR_DEFAULTTOPRIMARY
);
476 info
.cbSize
= sizeof(info
);
477 if(!GetMonitorInfo(monitor
, &info
))
480 CopyRect(workingAreaRc
, &(info
.rcWork
));
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
494 get_actualWindowRect(HWND hwnd
)
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
) {
510 if (SUCCEEDED(DwmIsCompositionEnabled(&pfEnabled
))) {
512 if (SUCCEEDED(DwmGetWindowAttribute(hwnd
, DWMWA_EXTENDED_FRAME_BOUNDS
, &tempR
, sizeof(tempR
)))) {
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 */
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",
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
);
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
;
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
;
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() {
605 memset(&lii
, 0, sizeof(lii
));
606 lii
.cbSize
= sizeof(lii
);
607 if (GetLastInputInfo(&lii
))