|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the test suite of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include "windowmanager.h" |
|
43 #include <QtCore/QTime> |
|
44 #include <QtCore/QThread> |
|
45 #include <QtCore/QDebug> |
|
46 #include <QtCore/QTextStream> |
|
47 |
|
48 #ifdef Q_WS_X11 |
|
49 # include <string.h> // memset |
|
50 # include <X11/Xlib.h> |
|
51 # include <X11/Xatom.h> // XA_WM_STATE |
|
52 # include <X11/Xutil.h> |
|
53 # include <X11/Xmd.h> // CARD32 |
|
54 #endif |
|
55 |
|
56 #if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) |
|
57 # include <windows.h> |
|
58 #endif |
|
59 |
|
60 // Export the sleep function |
|
61 class FriendlySleepyThread : public QThread { |
|
62 public: |
|
63 static void sleepMS(int milliSeconds) { msleep(milliSeconds); } |
|
64 }; |
|
65 |
|
66 #ifdef Q_WS_X11 |
|
67 // X11 Window manager |
|
68 |
|
69 // Register our own error handler to prevent the defult crashing |
|
70 // behaviour. It simply counts errors in global variables that |
|
71 // can be checked after calls. |
|
72 |
|
73 static unsigned x11ErrorCount = 0; |
|
74 static const char *currentX11Function = 0; |
|
75 |
|
76 int xErrorHandler(Display *, XErrorEvent *e) |
|
77 { |
|
78 x11ErrorCount++; |
|
79 |
|
80 QString msg; |
|
81 QTextStream str(&msg); |
|
82 str << "An X11 error (#" << x11ErrorCount<< ") occurred: "; |
|
83 if (currentX11Function) |
|
84 str << ' ' << currentX11Function << "()"; |
|
85 str << " code: " << e->error_code; |
|
86 str.setIntegerBase(16); |
|
87 str << " resource: 0x" << e->resourceid; |
|
88 qWarning("%s", qPrintable(msg)); |
|
89 |
|
90 return 0; |
|
91 } |
|
92 |
|
93 static bool isMapped(Display *display, Atom xa_wm_state, Window window, bool *isMapped) |
|
94 { |
|
95 Atom actual_type; |
|
96 int actual_format; |
|
97 unsigned long nitems; |
|
98 unsigned long bytes_after; |
|
99 unsigned char *prop; |
|
100 |
|
101 *isMapped = false; |
|
102 currentX11Function = "XGetWindowProperty"; |
|
103 const int retv = XGetWindowProperty(display, window, xa_wm_state, 0L, 1L, False, xa_wm_state, |
|
104 &actual_type, &actual_format, &nitems, &bytes_after, &prop); |
|
105 |
|
106 if (retv != Success || actual_type == None || actual_type != xa_wm_state |
|
107 || nitems != 1 || actual_format != 32) |
|
108 return false; |
|
109 |
|
110 const CARD32 state = * reinterpret_cast<CARD32 *>(prop); |
|
111 |
|
112 switch ((int) state) { |
|
113 case WithdrawnState: |
|
114 *isMapped = false; |
|
115 break; |
|
116 case NormalState: |
|
117 case IconicState: |
|
118 *isMapped = true; |
|
119 break; |
|
120 default: |
|
121 *isMapped = true; |
|
122 break; |
|
123 } |
|
124 return true; |
|
125 } |
|
126 |
|
127 // Wait until a X11 top level has been mapped, courtesy of xtoolwait. |
|
128 static Window waitForTopLevelMapped(Display *display, unsigned count, int timeOutMS, QString * errorMessage) |
|
129 { |
|
130 unsigned mappingsCount = count; |
|
131 Atom xa_wm_state; |
|
132 XEvent event; |
|
133 |
|
134 // Discard all pending events |
|
135 currentX11Function = "XSync"; |
|
136 XSync(display, True); |
|
137 |
|
138 // Listen for top level creation |
|
139 currentX11Function = "XSelectInput"; |
|
140 XSelectInput(display, DefaultRootWindow(display), SubstructureNotifyMask); |
|
141 |
|
142 /* We assume that the window manager provides the WM_STATE property on top-level |
|
143 * windows, as required by ICCCM 2.0. |
|
144 * If the window manager has not yet completed its initialisation, the WM_STATE atom |
|
145 * might not exist, in which case we create it. */ |
|
146 |
|
147 #ifdef XA_WM_STATE /* probably in X11R7 */ |
|
148 xa_wm_state = XA_WM_STATE; |
|
149 #else |
|
150 xa_wm_state = XInternAtom(display, "WM_STATE", False); |
|
151 #endif |
|
152 |
|
153 QTime elapsedTime; |
|
154 elapsedTime.start(); |
|
155 while (mappingsCount) { |
|
156 if (elapsedTime.elapsed() > timeOutMS) { |
|
157 *errorMessage = QString::fromLatin1("X11: Timed out waiting for toplevel %1ms").arg(timeOutMS); |
|
158 return 0; |
|
159 } |
|
160 currentX11Function = "XNextEvent"; |
|
161 unsigned errorCount = x11ErrorCount; |
|
162 XNextEvent(display, &event); |
|
163 if (x11ErrorCount > errorCount) { |
|
164 *errorMessage = QString::fromLatin1("X11: Error in XNextEvent"); |
|
165 return 0; |
|
166 } |
|
167 switch (event.type) { |
|
168 case CreateNotify: |
|
169 // Window created, listen for its mapping now |
|
170 if (!event.xcreatewindow.send_event && !event.xcreatewindow.override_redirect) |
|
171 XSelectInput(display, event.xcreatewindow.window, PropertyChangeMask); |
|
172 break; |
|
173 case PropertyNotify: |
|
174 // Watch for map |
|
175 if (!event.xproperty.send_event && event.xproperty.atom == xa_wm_state) { |
|
176 bool mapped; |
|
177 if (isMapped(display, xa_wm_state, event.xproperty.window, &mapped)) { |
|
178 if (mapped && --mappingsCount == 0) |
|
179 return event.xproperty.window; |
|
180 // Past splash screen, listen for next window to be created |
|
181 XSelectInput(display, DefaultRootWindow(display), SubstructureNotifyMask); |
|
182 } else { |
|
183 // Some temporary window disappeared. Listen for next creation |
|
184 XSelectInput(display, DefaultRootWindow(display), SubstructureNotifyMask); |
|
185 } |
|
186 // Main app window opened? |
|
187 } |
|
188 break; |
|
189 default: |
|
190 break; |
|
191 } |
|
192 } |
|
193 *errorMessage = QString::fromLatin1("X11: Timed out waiting for toplevel %1ms").arg(timeOutMS); |
|
194 return 0; |
|
195 } |
|
196 |
|
197 |
|
198 class X11_WindowManager : public WindowManager |
|
199 { |
|
200 public: |
|
201 X11_WindowManager(); |
|
202 ~X11_WindowManager(); |
|
203 |
|
204 protected: |
|
205 virtual bool isDisplayOpenImpl() const; |
|
206 virtual bool openDisplayImpl(QString *errorMessage); |
|
207 virtual QString waitForTopLevelWindowImpl(unsigned count, Q_PID, int timeOutMS, QString *errorMessage); |
|
208 virtual bool sendCloseEventImpl(const QString &winId, Q_PID pid, QString *errorMessage); |
|
209 |
|
210 private: |
|
211 Display *m_display; |
|
212 const QByteArray m_displayVariable; |
|
213 XErrorHandler m_oldErrorHandler; |
|
214 }; |
|
215 |
|
216 X11_WindowManager::X11_WindowManager() : |
|
217 m_display(0), |
|
218 m_displayVariable(qgetenv("DISPLAY")), |
|
219 m_oldErrorHandler(0) |
|
220 { |
|
221 } |
|
222 |
|
223 X11_WindowManager::~X11_WindowManager() |
|
224 { |
|
225 if (m_display) { |
|
226 XSetErrorHandler(m_oldErrorHandler); |
|
227 XCloseDisplay(m_display); |
|
228 } |
|
229 } |
|
230 |
|
231 bool X11_WindowManager::isDisplayOpenImpl() const |
|
232 { |
|
233 return m_display != 0; |
|
234 } |
|
235 |
|
236 bool X11_WindowManager::openDisplayImpl(QString *errorMessage) |
|
237 { |
|
238 if (m_displayVariable.isEmpty()) { |
|
239 *errorMessage = QLatin1String("X11: Display not set"); |
|
240 return false; |
|
241 } |
|
242 m_display = XOpenDisplay(NULL); |
|
243 if (!m_display) { |
|
244 *errorMessage = QString::fromLatin1("X11: Cannot open display %1.").arg(QString::fromLocal8Bit(m_displayVariable)); |
|
245 return false; |
|
246 } |
|
247 |
|
248 m_oldErrorHandler = XSetErrorHandler(xErrorHandler); |
|
249 return true; |
|
250 } |
|
251 |
|
252 QString X11_WindowManager::waitForTopLevelWindowImpl(unsigned count, Q_PID, int timeOutMS, QString *errorMessage) |
|
253 { |
|
254 const Window w = waitForTopLevelMapped(m_display, count, timeOutMS, errorMessage); |
|
255 if (w == 0) |
|
256 return QString(); |
|
257 return QLatin1String("0x") + QString::number(w, 16); |
|
258 } |
|
259 |
|
260 bool X11_WindowManager::sendCloseEventImpl(const QString &winId, Q_PID, QString *errorMessage) |
|
261 { |
|
262 // Get win id |
|
263 bool ok; |
|
264 const Window window = winId.toULong(&ok, 16); |
|
265 if (!ok) { |
|
266 *errorMessage = QString::fromLatin1("Invalid win id %1.").arg(winId); |
|
267 return false; |
|
268 } |
|
269 // Send a window manager close event |
|
270 XEvent ev; |
|
271 memset(&ev, 0, sizeof (ev)); |
|
272 ev.xclient.type = ClientMessage; |
|
273 ev.xclient.window = window; |
|
274 ev.xclient.message_type = XInternAtom(m_display, "WM_PROTOCOLS", true); |
|
275 ev.xclient.format = 32; |
|
276 ev.xclient.data.l[0] = XInternAtom(m_display, "WM_DELETE_WINDOW", false); |
|
277 ev.xclient.data.l[1] = CurrentTime; |
|
278 // Window disappeared or some error triggered? |
|
279 unsigned errorCount = x11ErrorCount; |
|
280 currentX11Function = "XSendEvent"; |
|
281 XSendEvent(m_display, window, False, NoEventMask, &ev); |
|
282 if (x11ErrorCount > errorCount) { |
|
283 *errorMessage = QString::fromLatin1("Error sending event to win id %1.").arg(winId); |
|
284 return false; |
|
285 } |
|
286 currentX11Function = "XSync"; |
|
287 errorCount = x11ErrorCount; |
|
288 XSync(m_display, False); |
|
289 if (x11ErrorCount > errorCount) { |
|
290 *errorMessage = QString::fromLatin1("Error sending event to win id %1 (XSync).").arg(winId); |
|
291 return false; |
|
292 } |
|
293 return true; |
|
294 } |
|
295 |
|
296 #endif |
|
297 |
|
298 #if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) |
|
299 // Windows |
|
300 |
|
301 QString winErrorMessage(unsigned long error) |
|
302 { |
|
303 QString rc = QString::fromLatin1("#%1: ").arg(error); |
|
304 ushort *lpMsgBuf; |
|
305 |
|
306 const int len = FormatMessage( |
|
307 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
|
308 NULL, error, 0, (LPTSTR)&lpMsgBuf, 0, NULL); |
|
309 if (len) { |
|
310 rc = QString::fromUtf16(lpMsgBuf, len); |
|
311 LocalFree(lpMsgBuf); |
|
312 } else { |
|
313 rc += QString::fromLatin1("<unknown error>"); |
|
314 } |
|
315 return rc; |
|
316 } |
|
317 |
|
318 class Win_WindowManager : public WindowManager |
|
319 { |
|
320 public: |
|
321 Win_WindowManager() {} |
|
322 |
|
323 protected: |
|
324 virtual bool isDisplayOpenImpl() const; |
|
325 virtual bool openDisplayImpl(QString *errorMessage); |
|
326 virtual QString waitForTopLevelWindowImpl(unsigned count, Q_PID, int timeOutMS, QString *errorMessage); |
|
327 virtual bool sendCloseEventImpl(const QString &winId, Q_PID pid, QString *errorMessage); |
|
328 |
|
329 private: |
|
330 }; |
|
331 |
|
332 bool Win_WindowManager::isDisplayOpenImpl() const |
|
333 { |
|
334 return true; |
|
335 } |
|
336 |
|
337 bool Win_WindowManager::openDisplayImpl(QString *) |
|
338 { |
|
339 return true; |
|
340 } |
|
341 |
|
342 // Enumerate window looking for toplevel of process id |
|
343 struct FindProcessWindowEnumContext { |
|
344 FindProcessWindowEnumContext(DWORD pid) : window(0),processId(pid) {} |
|
345 |
|
346 HWND window; |
|
347 DWORD processId; |
|
348 }; |
|
349 |
|
350 /* Check for the active main window of the Application |
|
351 * of class QWidget. */ |
|
352 static inline bool isQtMainWindow(HWND hwnd) |
|
353 { |
|
354 static char buffer[MAX_PATH]; |
|
355 if (!GetClassNameA(hwnd, buffer, MAX_PATH) || qstrcmp(buffer, "QWidget")) |
|
356 return false; |
|
357 WINDOWINFO windowInfo; |
|
358 if (!GetWindowInfo(hwnd, &windowInfo)) |
|
359 return false; |
|
360 if (!(windowInfo.dwWindowStatus & WS_ACTIVECAPTION)) |
|
361 return false; |
|
362 // Check the style for a real mainwindow |
|
363 const DWORD excluded = WS_DISABLED | WS_POPUP; |
|
364 const DWORD required = WS_CAPTION | WS_SYSMENU | WS_VISIBLE; |
|
365 return (windowInfo.dwStyle & excluded) == 0 |
|
366 && (windowInfo.dwStyle & required) == required; |
|
367 } |
|
368 |
|
369 static BOOL CALLBACK findProcessWindowEnumWindowProc(HWND hwnd, LPARAM lParam) |
|
370 { |
|
371 DWORD processId = 0; |
|
372 FindProcessWindowEnumContext *context= reinterpret_cast<FindProcessWindowEnumContext *>(lParam); |
|
373 GetWindowThreadProcessId(hwnd, &processId); |
|
374 if (context->processId == processId && isQtMainWindow(hwnd)) { |
|
375 context->window = hwnd; |
|
376 return FALSE; |
|
377 } |
|
378 return TRUE; |
|
379 } |
|
380 |
|
381 QString Win_WindowManager::waitForTopLevelWindowImpl(unsigned /* count */, Q_PID pid, int timeOutMS, QString *errorMessage) |
|
382 { |
|
383 QTime elapsed; |
|
384 elapsed.start(); |
|
385 // First, wait until the application is up |
|
386 if (WaitForInputIdle(pid->hProcess, timeOutMS) != 0) { |
|
387 *errorMessage = QString::fromLatin1("WaitForInputIdle time out after %1ms").arg(timeOutMS); |
|
388 return QString(); |
|
389 } |
|
390 // Try to locate top level app window. App still might be in splash screen or initialization |
|
391 // phase. |
|
392 const int remainingMilliSeconds = qMax(timeOutMS - elapsed.elapsed(), 500); |
|
393 const int attempts = 10; |
|
394 const int intervalMilliSeconds = remainingMilliSeconds / attempts; |
|
395 for (int a = 0; a < attempts; a++) { |
|
396 FindProcessWindowEnumContext context(pid->dwProcessId); |
|
397 EnumWindows(findProcessWindowEnumWindowProc, reinterpret_cast<LPARAM>(&context)); |
|
398 if (context.window) |
|
399 return QLatin1String("0x") + QString::number(reinterpret_cast<quintptr>(context.window), 16); |
|
400 sleepMS(intervalMilliSeconds); |
|
401 } |
|
402 *errorMessage = QString::fromLatin1("Unable to find toplevel of process %1 after %2ms.").arg(pid->dwProcessId).arg(timeOutMS); |
|
403 return QString(); |
|
404 } |
|
405 |
|
406 bool Win_WindowManager::sendCloseEventImpl(const QString &winId, Q_PID, QString *errorMessage) |
|
407 { |
|
408 // Convert window back. |
|
409 quintptr winIdIntPtr; |
|
410 QTextStream str(const_cast<QString*>(&winId), QIODevice::ReadOnly); |
|
411 str.setIntegerBase(16); |
|
412 str >> winIdIntPtr; |
|
413 if (str.status() != QTextStream::Ok) { |
|
414 *errorMessage = QString::fromLatin1("Invalid win id %1.").arg(winId); |
|
415 return false; |
|
416 } |
|
417 if (!PostMessage(reinterpret_cast<HWND>(winIdIntPtr), WM_CLOSE, 0, 0)) { |
|
418 *errorMessage = QString::fromLatin1("Cannot send event to 0x%1: %2").arg(winIdIntPtr, 0, 16).arg(winErrorMessage(GetLastError())); |
|
419 return false; |
|
420 } |
|
421 return true; |
|
422 } |
|
423 #endif |
|
424 |
|
425 // ------- Default implementation |
|
426 |
|
427 WindowManager::WindowManager() |
|
428 { |
|
429 } |
|
430 |
|
431 WindowManager::~WindowManager() |
|
432 { |
|
433 } |
|
434 |
|
435 QSharedPointer<WindowManager> WindowManager::create() |
|
436 { |
|
437 #ifdef Q_WS_X11 |
|
438 return QSharedPointer<WindowManager>(new X11_WindowManager); |
|
439 #endif |
|
440 #if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) |
|
441 return QSharedPointer<WindowManager>(new Win_WindowManager); |
|
442 #else |
|
443 return QSharedPointer<WindowManager>(new WindowManager); |
|
444 #endif |
|
445 } |
|
446 |
|
447 static inline QString msgNoDisplayOpen() { return QLatin1String("No display opened."); } |
|
448 |
|
449 bool WindowManager::openDisplay(QString *errorMessage) |
|
450 { |
|
451 if (isDisplayOpen()) |
|
452 return true; |
|
453 return openDisplayImpl(errorMessage); |
|
454 } |
|
455 |
|
456 bool WindowManager::isDisplayOpen() const |
|
457 { |
|
458 return isDisplayOpenImpl(); |
|
459 } |
|
460 |
|
461 |
|
462 |
|
463 QString WindowManager::waitForTopLevelWindow(unsigned count, Q_PID pid, int timeOutMS, QString *errorMessage) |
|
464 { |
|
465 if (!isDisplayOpen()) { |
|
466 *errorMessage = msgNoDisplayOpen(); |
|
467 return QString(); |
|
468 } |
|
469 return waitForTopLevelWindowImpl(count, pid, timeOutMS, errorMessage); |
|
470 } |
|
471 |
|
472 bool WindowManager::sendCloseEvent(const QString &winId, Q_PID pid, QString *errorMessage) |
|
473 { |
|
474 if (!isDisplayOpen()) { |
|
475 *errorMessage = msgNoDisplayOpen(); |
|
476 return false; |
|
477 } |
|
478 return sendCloseEventImpl(winId, pid, errorMessage); |
|
479 } |
|
480 |
|
481 // Default Implementation |
|
482 bool WindowManager::openDisplayImpl(QString *errorMessage) |
|
483 { |
|
484 *errorMessage = QLatin1String("Not implemented."); |
|
485 return false; |
|
486 } |
|
487 |
|
488 bool WindowManager::isDisplayOpenImpl() const |
|
489 { |
|
490 return false; |
|
491 } |
|
492 |
|
493 QString WindowManager::waitForTopLevelWindowImpl(unsigned, Q_PID, int, QString *errorMessage) |
|
494 { |
|
495 *errorMessage = QLatin1String("Not implemented."); |
|
496 return QString(); |
|
497 } |
|
498 |
|
499 bool WindowManager::sendCloseEventImpl(const QString &, Q_PID, QString *errorMessage) |
|
500 { |
|
501 *errorMessage = QLatin1String("Not implemented."); |
|
502 return false; |
|
503 } |
|
504 |
|
505 void WindowManager::sleepMS(int milliSeconds) |
|
506 { |
|
507 FriendlySleepyThread::sleepMS(milliSeconds); |
|
508 } |