1 // W32Compat.cc --- W32 compatibility
2 //
3 // Copyright (C) 2004, 2007, 2010, 2012 Rob Caelers, Raymond Penners, Ray Satiro
4 // All rights reserved.
5 //
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 3 of the License, or
9 // (at your option) any later version.
10 //
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.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 //
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "debug.hh"
25
26 #include <windows.h>
27 #include <tchar.h>
28 #include <wtsapi32.h>
29
30 #ifdef PLATFORM_OS_WIN32_NATIVE
31 #undef max
32 #endif
33 #ifdef PLATFORM_OS_WIN32
34 #undef interface
35 #endif
36
37 #include <gtkmm.h>
38 #include <gdk/gdkwin32.h>
39
40 #include "IBreak.hh"
41 #include "BreakWindow.hh"
42 #include "CoreFactory.hh"
43 #include "IConfigurator.hh"
44 #include "GtkUtil.hh"
45 #include "W32ForceFocus.hh"
46 #include "W32CriticalSection.hh"
47 #include "W32Compat.hh"
48
49
50 using namespace workrave;
51
52 bool W32Compat::ime_magic = false;
53 bool W32Compat::reset_window_always = false;
54 bool W32Compat::reset_window_never = false;
55
56 W32Compat::SWITCHTOTHISWINDOWPROC W32Compat::switch_to_this_window_proc = NULL;
57
58
59
60 typedef BOOLEAN (WINAPI * PWINSTATIONQUERYINFORMATIONW)(
61 HANDLE, ULONG, INT, PVOID, ULONG, PULONG );
62 namespace { PWINSTATIONQUERYINFORMATIONW dyn_WinStationQueryInformationW; }
63
64 /* W32Compat::WinStationQueryInformationW()
65
66 Query information about our winstation.
67 http://msdn.microsoft.com/en-us/library/windows/desktop/aa383827.aspx
68
69 This API is deprecated so its use is limited. Some queries are problematic on x64:
70 http://www.remkoweijnen.nl/blog/2011/01/29/querying-a-user-token-under-64-bit-version-of-2003xp/
71
72 Help for WinStationInformationClass info:
73 http://msdn.microsoft.com/en-us/library/cc248604.aspx
74 http://msdn.microsoft.com/en-us/library/cc248834.aspx
75
76 returns whatever the API returns.
77 if the API is unavailable this function returns FALSE and last error is set ERROR_INVALID_FUNCTION.
78 */
WinStationQueryInformationW(HANDLE hServer,ULONG LogonId,INT WinStationInformationClass,PVOID pWinStationInformation,ULONG WinStationInformationLength,PULONG pReturnLength)79 BOOLEAN W32Compat::WinStationQueryInformationW(
80 HANDLE hServer, // use WTS_CURRENT_SERVER_HANDLE
81 ULONG LogonId, // use WTS_CURRENT_SESSION
82 INT WinStationInformationClass, // review msdn links in comment block
83 PVOID pWinStationInformation,
84 ULONG WinStationInformationLength,
85 PULONG pReturnLength
86 )
87 {
88 init();
89
90 if( !dyn_WinStationQueryInformationW )
91 {
92 SetLastError( ERROR_INVALID_FUNCTION );
93 return FALSE;
94 }
95
96 return dyn_WinStationQueryInformationW(
97 hServer,
98 LogonId,
99 WinStationInformationClass,
100 pWinStationInformation,
101 WinStationInformationLength,
102 pReturnLength
103 );
104 }
105
106
107
108 VOID
SwitchToThisWindow(HWND hwnd,BOOL emulate_alt_tab)109 W32Compat::SwitchToThisWindow( HWND hwnd, BOOL emulate_alt_tab )
110 {
111 init();
112
113 if ( switch_to_this_window_proc != NULL )
114 {
115 ( *switch_to_this_window_proc )( hwnd, emulate_alt_tab );
116 }
117 return;
118 }
119
120
121
122 static W32CriticalSection cs__init;
123 volatile LONG W32Compat::_initialized = 0;
124
125 /* W32Compat::_init()
126
127 Initialize the W32Compat class. The functions should call init(), which is inline, instead of this.
128 A call to init() should be the first line in each of the other functions in this class.
129 */
_init()130 void W32Compat::_init()
131 {
132 W32CriticalSection::Guard guard( cs__init );
133
134 if( _initialized )
135 return;
136
137 HMODULE user_lib = GetModuleHandleA( "user32.dll" );
138 if( user_lib )
139 {
140 switch_to_this_window_proc = (SWITCHTOTHISWINDOWPROC) GetProcAddress(user_lib, "SwitchToThisWindow");
141 }
142
143 HMODULE winsta_lib = LoadLibraryA( "winsta" );
144 if( winsta_lib )
145 {
146 dyn_WinStationQueryInformationW =
147 (PWINSTATIONQUERYINFORMATIONW)GetProcAddress(
148 winsta_lib,
149 "WinStationQueryInformationW"
150 );
151 }
152
153 // Should SetWindowOnTop() call IMEWindowMagic() ?
154 if( !CoreFactory::get_configurator()->get_value( "advanced/ime_magic", ime_magic ) )
155 {
156 ime_magic = false;
157 }
158
159 // As of writing SetWindowOnTop() always calls ResetWindow()
160 // ResetWindow() determines whether to "reset" when both
161 // reset_window_always and reset_window_never are false.
162 //
163 // If reset_window_always is true, and if ResetWindow() is continually
164 // passed the same hwnd, hwnd will flicker as a result of the continual
165 // z-order position changes / resetting.
166 if( !CoreFactory::get_configurator()->get_value( "advanced/reset_window_always", reset_window_always ) )
167 {
168 reset_window_always = false;
169 }
170 // ResetWindow() will always abort when reset_window_never is true.
171 if( !CoreFactory::get_configurator()->get_value( "advanced/reset_window_never", reset_window_never ) )
172 {
173 reset_window_never = false;
174 }
175
176 InterlockedExchange( &_initialized, 1 );
177 }
178
179
180 void
SetWindowOnTop(HWND hwnd,BOOL topmost)181 W32Compat::SetWindowOnTop( HWND hwnd, BOOL topmost )
182 {
183 init();
184
185 SetWindowPos( hwnd, topmost ? HWND_TOPMOST : HWND_NOTOPMOST,
186 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
187
188 ResetWindow( hwnd, (bool)topmost );
189
190 if( ime_magic && topmost )
191 {
192 IMEWindowMagic( hwnd );
193 }
194
195 if( W32ForceFocus::GetForceFocusValue() && topmost )
196 {
197 W32ForceFocus::ForceWindowFocus( hwnd );
198 }
199
200 return;
201 }
202
203
204 // Bug 587 - Vista: Workrave not modal / coming to front
205 // http://issues.workrave.org/show_bug.cgi?id=587
206 // There is an issue with IME and window z-ordering.
207 //
208 // hwnd == window to "reset" in z-order
209 // topmost == true if window should be topmost, false otherwise
210 void
ResetWindow(HWND hwnd,bool topmost)211 W32Compat::ResetWindow( HWND hwnd, bool topmost )
212 {
213 init();
214
215 if( !IsWindow( hwnd ) || reset_window_never )
216 return;
217
218 const bool DEBUG = false;
219 bool reset = false;
220 DWORD gwl_exstyle = 0;
221 DWORD valid_exstyle_diff = 0;
222
223 WINDOWINFO gwi;
224 ZeroMemory( &gwi, sizeof( gwi ) );
225 gwi.cbSize = sizeof( WINDOWINFO );
226
227
228 SetLastError( 0 );
229 gwl_exstyle = (DWORD)GetWindowLong( hwnd, GWL_EXSTYLE );
230 if( !GetLastError() )
231 {
232 // if desired and actual topmost style differ, plan to reset
233 if( topmost != ( gwl_exstyle & WS_EX_TOPMOST ? true : false ) )
234 reset = true;
235 }
236
237 SetLastError( 0 );
238 GetWindowInfo( hwnd, &gwi );
239 if( !GetLastError() )
240 {
241 // if desired and actual topmost style differ, plan to reset
242 if( topmost != ( gwi.dwExStyle & WS_EX_TOPMOST ? true : false ) )
243 reset = true;
244 }
245
246 #ifdef BREAKAGE
247 // GetWindowInfo() and GetWindowLong() extended style info can differ.
248 // Compare the two results but filter valid values only.
249 valid_exstyle_diff = ( gwl_exstyle ^ gwi.dwExStyle ) & ~0xF1A08802;
250 if( valid_exstyle_diff || DEBUG )
251 {
252 // if the extended style info differs, plan to reset.
253 // e.g. gwl returned ws_ex_toolwindow but gwi didn't
254 reset = true;
255
256 // attempt to sync differences:
257 DWORD swl_exstyle = ( valid_exstyle_diff | gwl_exstyle ) & ~0xF1A08802;
258
259 if( ( swl_exstyle & WS_EX_APPWINDOW ) && ( swl_exstyle & WS_EX_TOOLWINDOW ) )
260 // this hasn't happened and shouldn't happen, but i suppose it could.
261 // if both styles are set change to appwindow only.
262 // why not toolwindow only? well, why are they both set in the first place?
263 // in this case it's better to make hwnd visible on the taskbar.
264 {
265 swl_exstyle &= ~WS_EX_TOOLWINDOW;
266 }
267
268 ShowWindow( hwnd, SW_HIDE );
269 SetWindowLong( hwnd, GWL_EXSTYLE, (LONG)swl_exstyle );
270 ShowWindow( hwnd, SW_SHOWNA );
271 }
272 #endif
273
274 // "reset" window position in z-order.
275 // if the window is supposed to be topmost but is really not:
276 // set HWND_NOTOPMOST followed by HWND_TOPMOST
277 // the above sequence is key: review test results in 587#c17
278 //
279 // if the window is not supposed to be topmost but is, reverse:
280 // set HWND_TOPMOST followed by HWND_NOTOPMOST
281 // the reverse is currently unproven.
282 // i don't know of any problems removing the topmost style.
283 if( IsWindow( hwnd ) && ( reset || reset_window_always ) )
284 {
285 SetWindowPos( hwnd, !topmost ? HWND_TOPMOST : HWND_NOTOPMOST,
286 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
287 SetWindowPos( hwnd, topmost ? HWND_TOPMOST : HWND_NOTOPMOST,
288 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
289 }
290
291 return;
292 }
293
294
295 // Bug 587 - Vista: Workrave not modal / coming to front
296 // http://issues.workrave.org/show_bug.cgi?id=587
297 // There is an issue with IME and window z-ordering.
298 //
299 // ResetWindow() tests sufficient. This code is for troubleshooting.
300 // if all else fails request user enable advanced/ime_magic = "1"
301 void
IMEWindowMagic(HWND hwnd)302 W32Compat::IMEWindowMagic( HWND hwnd )
303 {
304 init();
305
306 if( !IsWindow( hwnd ) )
307 return;
308
309 // This message works to make hwnd topmost without activation or focus.
310 // I found it by watching window messages. I don't know its intended use.
311 SendMessage( hwnd, 0x287, 0x17/*0x18*/, (LPARAM)hwnd );
312
313 SetWindowPos( hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOOWNERZORDER |
314 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
315 }
316
317
318
319 /* W32Compat::IsOurWinStationConnected()
320
321 Check if our winstation is connected by querying terminal services.
322
323 Note: Terminal services API can cause a harmless exception that it handles and should be ignored:
324 First-chance exception at 0x7656fc56 in workrave.exe: 0x000006BA: The RPC server is unavailable.
325
326 returns true if our winstation is connected
327 */
IsOurWinStationConnected()328 bool W32Compat::IsOurWinStationConnected()
329 {
330 init();
331
332 bool func_retval = false;
333 DWORD bytes_returned = 0;
334
335 #ifdef PLATFORM_OS_WIN32_NATIVE
336 enum WTS_INFO_CLASS *state = NULL;
337 #else
338 // TODO: check compiler warnings and native/mingw difference.
339 WTS_INFO_CLASS *state = NULL;
340 #endif
341
342 if( WTSQuerySessionInformation(
343 WTS_CURRENT_SERVER_HANDLE,
344 WTS_CURRENT_SESSION,
345 WTSConnectState,
346 reinterpret_cast<LPTSTR*>(&state),
347 &bytes_returned
348 )
349 && state
350 )
351 {
352 if( ( bytes_returned == sizeof( *state ) )
353 && ( ( *state == WTSActive )
354 || ( *state == WTSConnected ) )
355 )
356 {
357 func_retval = true;
358 }
359
360 WTSFreeMemory( state );
361 }
362
363 return func_retval;
364 }
365
366
367
368 /* W32Compat::IsOurWinStationLocked()
369
370 Check if our winstation is locked by querying terminal services.
371
372 Note: Terminal services API can cause a harmless exception that it handles and should be ignored:
373 First-chance exception at 0x7656fc56 in workrave.exe: 0x000006BA: The RPC server is unavailable.
374
375 returns true if our winstation is locked
376 */
IsOurWinStationLocked()377 bool W32Compat::IsOurWinStationLocked()
378 {
379 init();
380
381 BOOL locked = FALSE;
382 DWORD bytes_returned = 0;
383
384 if( !WinStationQueryInformationW(
385 WTS_CURRENT_SERVER_HANDLE, // SERVERNAME_CURRENT
386 WTS_CURRENT_SESSION, // LOGONID_CURRENT
387 28, // WinStationLockedState
388 &locked,
389 sizeof( locked ),
390 &bytes_returned
391 )
392 || ( bytes_returned != sizeof( locked ) )
393 )
394 {
395 return false;
396 }
397
398 return !!locked;
399 }
400
401
402
403 /* W32Compat::IsOurDesktopVisible()
404
405 Check if our desktop can be viewed by the user: Check that our process' window station is connected
406 and its input desktop is the same as our calling thread's desktop.
407
408 If our desktop is visible that implies that this process' workstation is unlocked.
409
410 returns true if our desktop is visible
411 */
IsOurDesktopVisible()412 bool W32Compat::IsOurDesktopVisible()
413 {
414 init();
415
416 bool func_retval = false;
417
418 HDESK input_desktop_handle = NULL;
419 HDESK our_desktop_handle = NULL;
420
421 wchar_t input_desktop_name[ MAX_PATH ] = { L'\0', };
422 wchar_t our_desktop_name[ MAX_PATH ] = { L'\0', };
423
424 BOOL ret = 0;
425 DWORD bytes_needed = 0;
426
427
428 /*
429 Get the input desktop name
430 */
431 input_desktop_handle = OpenInputDesktop( 0, false, GENERIC_READ );
432 if( !input_desktop_handle )
433 goto cleanup;
434
435 bytes_needed = 0;
436 ret = GetUserObjectInformationW(
437 input_desktop_handle,
438 UOI_NAME,
439 input_desktop_name,
440 sizeof( input_desktop_name ),
441 &bytes_needed
442 );
443
444 if( !ret || ( bytes_needed > sizeof( input_desktop_name ) ) )
445 goto cleanup;
446
447
448 /*
449 Get our calling thread's desktop name
450 */
451 our_desktop_handle = GetThreadDesktop( GetCurrentThreadId() );
452 if( !our_desktop_handle )
453 goto cleanup;
454
455 bytes_needed = 0;
456 ret = GetUserObjectInformationW(
457 our_desktop_handle,
458 UOI_NAME,
459 our_desktop_name,
460 sizeof( our_desktop_name ),
461 &bytes_needed
462 );
463 if( !ret || ( bytes_needed > sizeof( our_desktop_name ) ) )
464 goto cleanup;
465
466
467 // If the desktop names are different then our thread is not associated with the input desktop
468 if( _wcsnicmp( input_desktop_name, our_desktop_name, MAX_PATH ) )
469 goto cleanup;
470
471
472 // If our winstation is not connected then our desktop is not visible
473 if( !IsOurWinStationConnected() )
474 goto cleanup;
475
476
477 func_retval = true;
478 cleanup:
479 if( input_desktop_handle )
480 CloseHandle( input_desktop_handle );
481
482 if( our_desktop_handle )
483 CloseHandle( our_desktop_handle );
484
485 return func_retval;
486 }
487
488
489
490 /* W32Compat::RefreshBreakWindow()
491
492 Refresh a BreakWindow:
493 - Make keyboard shortcuts available without pressing ALT after five seconds of inactivity
494 - Set our window topmost unless a tooltip is visible (tooltips are topmost when visible)
495 - Make sure the window manager has not disabled our topmost status; reset if necessary
496 - Force focus to the main break window if the preference advanced/force_focus is true
497 */
RefreshBreakWindow(BreakWindow & window)498 void W32Compat::RefreshBreakWindow( BreakWindow &window )
499 {
500 ICore *core = CoreFactory::get_core();
501 bool user_active = core->is_user_active();
502
503 // GTK keyboard shortcuts can be accessed by using the ALT key. This appear
504 // to be non-standard behaviour on windows, so make shortcuts available
505 // without ALT after the user is idle for 5s
506 if ( !user_active && !window.accel_added )
507 {
508 IBreak *b = core->get_break( BreakId( window.break_id ) );
509 assert( b != NULL );
510
511 //TRACE_MSG(b->get_elapsed_idle_time());
512 if( b->get_elapsed_idle_time() > 5 )
513 {
514 if( window.postpone_button != NULL )
515 {
516 GtkUtil::update_mnemonic( window.postpone_button, window.accel_group );
517 }
518 if( window.skip_button != NULL )
519 {
520 GtkUtil::update_mnemonic( window.skip_button, window.accel_group );
521 }
522 // FIXME:
523 // if( window.shutdown_button != NULL )
524 // {
525 // GtkUtil::update_mnemonic( window.shutdown_button, window.accel_group );
526 // }
527 // if( window.lock_button != NULL )
528 // {
529 // GtkUtil::update_mnemonic( window.lock_button, window.accel_group );
530 // }
531 window.accel_added = true;
532 }
533 }
534
535 /* We can't call WindowHints::set_always_on_top() or W32Compat::SetWindowOnTop() for every
536 refresh. While the logic here is similar it is adjusted for the specific case of refreshing.
537 */
538
539 HWND hwnd = (HWND)GDK_WINDOW_HWND( window.Gtk::Widget::gobj()->window );
540 if( !hwnd )
541 return;
542
543 // don't enforce topmost while a tooltip is visible, otherwise we could cover the tooltip
544 if( !GtkUtil::get_visible_tooltip_window() )
545 SetWindowPos( hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
546
547 // this checks if the window manager has disabled our topmost ability and resets it if necessary
548 W32Compat::ResetWindow( hwnd, true );
549
550 /* If there are multiple break windows only force focus on the first, otherwise focus would be
551 continuously switched to each break window on every refresh, making interaction very difficult.
552 */
553 if( W32ForceFocus::GetForceFocusValue() && ( window.head.count == 0 ) )
554 {
555 W32ForceFocus::ForceWindowFocus( hwnd, 0 ); // try without blocking
556 }
557 }
558