1
/****************************************************************************
2
**
3
** Copyright (C) 2011 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
** GNU Lesser General Public License Usage
11
** This file may be used under the terms of the GNU Lesser General Public
12
** License version 2.1 as published by the Free Software Foundation and
13
** appearing in the file LICENSE.LGPL included in the packaging of this
14
** file. Please review the following information to ensure the GNU Lesser
15
** General Public License version 2.1 requirements will be met:
16
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17
**
18
** In addition, as a special exception, Nokia gives you certain additional
19
** rights. These rights are described in the Nokia Qt LGPL Exception
20
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21
**
22
** GNU General Public License Usage
23
** Alternatively, this file may be used under the terms of the GNU General
24
** Public License version 3.0 as published by the Free Software Foundation
25
** and appearing in the file LICENSE.GPL included in the packaging of this
26
** file. Please review the following information to ensure the GNU General
27
** Public License version 3.0 requirements will be met:
28
** http://www.gnu.org/copyleft/gpl.html.
29
**
30
** Other Usage
31
** Alternatively, this file may be used in accordance with the terms and
32
** conditions contained in a signed written agreement between you and Nokia.
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
}