| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the test suite of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtWidgets/QAction> |
| #include <QtWidgets/QApplication> |
| #include <QtWidgets/QDesktopWidget> |
| #include <QtWidgets/QMainWindow> |
| #include <QtWidgets/QMenu> |
| #include <QtWidgets/QMenuBar> |
| #include <QtWidgets/QToolBar> |
| |
| #include <QtGui/QScreen> |
| #include <QtGui/QWindow> |
| |
| #include <QtCore/QCommandLineOption> |
| #include <QtCore/QCommandLineParser> |
| #include <QtCore/QDebug> |
| #include <QtCore/QSharedPointer> |
| #include <QtCore/QStringList> |
| #include <QtCore/QTextStream> |
| #include <QtCore/QTimer> |
| |
| #ifdef Q_OS_WIN |
| # include <QtCore/qt_windows.h> |
| #endif |
| |
| #include <eventfilter.h> // diaglib |
| #include <nativewindowdump.h> |
| #include <qwidgetdump.h> |
| #include <qwindowdump.h> |
| |
| #include <iostream> |
| #include <algorithm> |
| |
| QT_USE_NAMESPACE |
| |
| typedef QSharedPointer<QWidget> WidgetPtr; |
| typedef QList<WidgetPtr> WidgetPtrList; |
| typedef QList<WId> WIdList; |
| |
| // Create some pre-defined Windows controls by class name |
| static WId createInternalWindow(const QString &name) |
| { |
| WId result = 0; |
| #ifdef Q_OS_WIN |
| if (name == QLatin1String("BUTTON") || name == QLatin1String("COMBOBOX") |
| || name == QLatin1String("EDIT") || name.startsWith(QLatin1String("RICHEDIT"))) { |
| const HWND hwnd = |
| CreateWindowEx(0, reinterpret_cast<const wchar_t *>(name.utf16()), |
| L"NativeCtrl", WS_OVERLAPPEDWINDOW, |
| CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, |
| 0, 0, GetModuleHandle(NULL), NULL); |
| if (hwnd) { |
| SetWindowText(hwnd, L"Demo"); |
| result = WId(hwnd); |
| } else { |
| qErrnoWarning("Cannot create window \"%s\"", qPrintable(name)); |
| } |
| } |
| #else // Q_OS_WIN |
| Q_UNUSED(name) |
| #endif |
| return result; |
| } |
| |
| // Embed a foreign window using createWindowContainer() providing |
| // menu actions to dump information. |
| class EmbeddingWindow : public QMainWindow |
| { |
| Q_OBJECT |
| public: |
| explicit EmbeddingWindow(QWindow *window); |
| |
| public slots: |
| void releaseForeignWindow(); |
| |
| private: |
| QWindow *m_window; |
| QAction *m_releaseAction; |
| }; |
| |
| EmbeddingWindow::EmbeddingWindow(QWindow *window) : m_window(window) |
| { |
| const QString title = QLatin1String("Qt ") + QLatin1String(QT_VERSION_STR) |
| + QLatin1String(" 0x") + QString::number(window->winId(), 16); |
| setWindowTitle(title); |
| setObjectName("MainWindow"); |
| QWidget *container = QWidget::createWindowContainer(window, nullptr, Qt::Widget); |
| container->setObjectName("Container"); |
| setCentralWidget(container); |
| |
| QMenu *fileMenu = menuBar()->addMenu("File"); |
| fileMenu->setObjectName("FileMenu"); |
| QToolBar *toolbar = new QToolBar; |
| addToolBar(Qt::TopToolBarArea, toolbar); |
| |
| // Manipulation |
| QAction *action = fileMenu->addAction("Visible"); |
| action->setCheckable(true); |
| action->setChecked(true); |
| connect(action, &QAction::toggled, m_window, &QWindow::setVisible); |
| toolbar->addAction(action); |
| |
| m_releaseAction = fileMenu->addAction("Release", this, &EmbeddingWindow::releaseForeignWindow); |
| toolbar->addAction(m_releaseAction); |
| |
| fileMenu->addSeparator(); // Diaglib actions |
| action = fileMenu->addAction("Dump Widgets", |
| this, [] () { QtDiag::dumpAllWidgets(); }); |
| toolbar->addAction(action); |
| action = fileMenu->addAction("Dump Windows", |
| this, [] () { QtDiag::dumpAllWindows(); }); |
| toolbar->addAction(action); |
| action = fileMenu->addAction("Dump Native Windows", |
| this, [this] () { QtDiag::dumpNativeWindows(winId()); }); |
| toolbar->addAction(action); |
| |
| fileMenu->addSeparator(); |
| action = fileMenu->addAction("Quit", qApp, &QCoreApplication::quit); |
| toolbar->addAction(action); |
| action->setShortcut(Qt::CTRL + Qt::Key_Q); |
| } |
| |
| void EmbeddingWindow::releaseForeignWindow() |
| { |
| if (m_window) { |
| m_window->setParent(nullptr); |
| m_window = nullptr; |
| m_releaseAction->setEnabled(false); |
| } |
| } |
| |
| // Dump information about foreign windows. |
| class WindowDumper : public QObject { |
| Q_OBJECT |
| public: |
| explicit WindowDumper(const QWindowList &watchedWindows) |
| : m_watchedWindows(watchedWindows) {} |
| |
| public slots: |
| void dump() const; |
| |
| private: |
| const QWindowList m_watchedWindows; |
| }; |
| |
| void WindowDumper::dump() const |
| { |
| static int n = 0; |
| QString s; |
| QDebug debug(&s); |
| debug.nospace(); |
| debug.setVerbosity(3); |
| debug << '#' << n++; |
| if (m_watchedWindows.size() > 1) |
| debug << '\n'; |
| foreach (const QWindow *w, m_watchedWindows) { |
| const QPoint globalPos = w->mapToGlobal(QPoint()); |
| debug << " " << w << " pos=" << globalPos.x() << ',' << globalPos.y() << '\n'; |
| } |
| |
| std::cout << qPrintable(s); |
| } |
| |
| static QString description(const QString &appName) |
| { |
| QString result; |
| QTextStream(&result) |
| << "\nDumps information about foreign windows passed on the command line or\n" |
| "tests embedding foreign windows into Qt.\n\nUse cases:\n\n" |
| << appName << " -a Dump a list of all native window ids.\n" |
| << appName << " <winid> Dump information on the window.\n" |
| << appName << " -m <winid> Move window to top left corner\n" |
| << QByteArray(appName.size(), ' ') |
| << " (recover lost windows after changing monitor setups).\n" |
| << appName << " -c <winid> Dump information on the window continuously.\n" |
| << appName << " -e <winid> Embed window into a Qt widget.\n" |
| << "\nOn Windows, class names of well known controls (EDIT, BUTTON...) can be\n" |
| "passed as <winid> along with -e, which will create the control.\n"; |
| return result; |
| } |
| |
| struct EventFilterOption |
| { |
| const char *name; |
| const char *description; |
| QtDiag::EventFilter::EventCategories categories; |
| }; |
| |
| EventFilterOption eventFilterOptions[] = { |
| {"mouse-events", "Dump mouse events.", QtDiag::EventFilter::MouseEvents}, |
| {"keyboard-events", "Dump keyboard events.", QtDiag::EventFilter::KeyEvents}, |
| {"state-events", "Dump state/focus change events.", QtDiag::EventFilter::StateChangeEvents | QtDiag::EventFilter::FocusEvents} |
| }; |
| |
| static inline bool isOptionSet(int argc, char *argv[], const char *option) |
| { |
| return (argv + argc) != |
| std::find_if(argv + 1, argv + argc, |
| [option] (const char *arg) { return !qstrcmp(arg, option); }); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| // Check for no scaling before QApplication is instantiated. |
| if (isOptionSet(argc, argv, "-s")) |
| QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); |
| QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); |
| QGuiApplication::setApplicationDisplayName("Foreign window tester"); |
| |
| QApplication app(argc, argv); |
| |
| QCommandLineParser parser; |
| parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); |
| parser.setApplicationDescription(description(QCoreApplication::applicationName())); |
| parser.addHelpOption(); |
| parser.addVersionOption(); |
| QCommandLineOption noScalingDummy(QStringLiteral("s"), |
| QStringLiteral("Disable High DPI scaling.")); |
| parser.addOption(noScalingDummy); |
| QCommandLineOption outputAllOption(QStringList() << QStringLiteral("a") << QStringLiteral("all"), |
| QStringLiteral("Output all native window ids (requires diaglib).")); |
| parser.addOption(outputAllOption); |
| QCommandLineOption continuousOption(QStringList() << QStringLiteral("c") << QStringLiteral("continuous"), |
| QStringLiteral("Output continuously.")); |
| parser.addOption(outputAllOption); |
| QCommandLineOption moveOption(QStringList() << QStringLiteral("m") << QStringLiteral("move"), |
| QStringLiteral("Move window to top left corner.")); |
| parser.addOption(moveOption); |
| parser.addOption(continuousOption); |
| QCommandLineOption embedOption(QStringList() << QStringLiteral("e") << QStringLiteral("embed"), |
| QStringLiteral("Embed a foreign window into a Qt widget.")); |
| parser.addOption(embedOption); |
| const int eventFilterOptionCount = int(sizeof(eventFilterOptions) / sizeof(eventFilterOptions[0])); |
| for (int i = 0; i < eventFilterOptionCount; ++i) { |
| parser.addOption(QCommandLineOption(QLatin1String(eventFilterOptions[i].name), |
| QLatin1String(eventFilterOptions[i].description))); |
| } |
| parser.addPositionalArgument(QStringLiteral("[windows]"), QStringLiteral("Window IDs.")); |
| |
| parser.process(QCoreApplication::arguments()); |
| |
| if (parser.isSet(outputAllOption)) { |
| QtDiag::dumpNativeWindows(); |
| return 0; |
| } |
| |
| QWindowList windows; |
| foreach (const QString &argument, parser.positionalArguments()) { |
| bool ok = true; |
| WId wid = createInternalWindow(argument); |
| if (!wid) |
| wid = argument.toULongLong(&ok, 0); |
| if (!wid || !ok) { |
| std::cerr << "Invalid window id: \"" << qPrintable(argument) << "\"\n"; |
| return -1; |
| } |
| QWindow *foreignWindow = QWindow::fromWinId(wid); |
| if (!foreignWindow) |
| return -1; |
| foreignWindow->setObjectName("ForeignWindow" + QString::number(wid, 16)); |
| windows.append(foreignWindow); |
| if (parser.isSet(moveOption)) |
| foreignWindow->setFramePosition(QGuiApplication::primaryScreen()->availableGeometry().topLeft()); |
| } |
| |
| if (windows.isEmpty()) |
| parser.showHelp(0); |
| |
| int exitCode = 0; |
| |
| if (parser.isSet(embedOption)) { |
| QtDiag::EventFilter::EventCategories eventCategories = 0; |
| for (int i = 0; i < eventFilterOptionCount; ++i) { |
| if (parser.isSet(QLatin1String(eventFilterOptions[i].name))) |
| eventCategories |= eventFilterOptions[i].categories; |
| } |
| if (eventCategories) |
| app.installEventFilter(new QtDiag::EventFilter(eventCategories, &app)); |
| |
| const QRect availableGeometry = QApplication::desktop()->availableGeometry(0); |
| QPoint pos = availableGeometry.topLeft() + QPoint(availableGeometry.width(), availableGeometry.height()) / 3; |
| |
| WidgetPtrList mainWindows; |
| foreach (QWindow *window, windows) { |
| WidgetPtr mainWindow(new EmbeddingWindow(window)); |
| mainWindow->move(pos); |
| mainWindow->resize(availableGeometry.size() / 4); |
| mainWindow->show(); |
| pos += QPoint(40, 40); |
| mainWindows.append(mainWindow); |
| } |
| exitCode = app.exec(); |
| |
| } else if (parser.isSet(continuousOption)) { |
| WindowDumper dumper(windows); |
| dumper.dump(); |
| QTimer *timer = new QTimer(&dumper); |
| QObject::connect(timer, &QTimer::timeout, &dumper, &WindowDumper::dump); |
| timer->start(1000); |
| exitCode = app.exec(); |
| |
| } else { |
| WindowDumper(windows).dump(); |
| } |
| |
| return exitCode; |
| } |
| |
| #include "main.moc" |