Skip to content

Instantly share code, notes, and snippets.

@jbltx
Last active January 11, 2024 06:52
Show Gist options
  • Save jbltx/db1f4df72654e2b2339956758ee0ce34 to your computer and use it in GitHub Desktop.
Save jbltx/db1f4df72654e2b2339956758ee0ce34 to your computer and use it in GitHub Desktop.
Unity Window in Qt
#include <QApplication>
#include <QMainWindow>
#include <QSplitter>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsTextItem>
#include "UnityWindow.h"
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QMainWindow mainWindow;
QSplitter splitter(&mainWindow);
splitter.setOrientation(Qt::Horizontal);
UnityWindow unityWindow("unity-game.exe", &mainWindow);
splitter.addWidget(&unityWindow);
QGraphicsView view(&mainWindow);
QGraphicsScene scene(&mainWindow);
QGraphicsTextItem item("Test Item");
item.setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
scene.addItem(&item);
view.setRenderHint(QPainter::Antialiasing, false);
view.setDragMode(QGraphicsView::ScrollHandDrag);
view.setInteractive(true);
view.setOptimizationFlags(QGraphicsView::DontSavePainterState);
view.setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
view.setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
view.setScene(&scene);
splitter.addWidget(&view);
mainWindow.setCentralWidget(&splitter);
mainWindow.show();
return app.exec();
}
#include "UnityWindow.h"
#include <QApplication>
#include <QResizeEvent>
#include <QShowEvent>
#include <QHideEvent>
#include <QFocusEvent>
#include <QMouseEvent>
#include <QFileInfo>
#include <QProcess>
#include <QDir>
#include <QMessageBox>
#include <QDebug>
UnityWindow::UnityWindow(const QString& unityExePath, QWidget* parent)
: QWidget(parent)
, m_pUnityProcess(nullptr)
#ifdef Q_OS_WIN
, m_unityHandle(0)
#endif
, m_unityExecutablePath(unityExePath)
{
connect(qApp, &QApplication::applicationStateChanged, this, &UnityWindow::applicationStateChanged);
}
UnityWindow::~UnityWindow()
{
disconnect(qApp, &QApplication::applicationStateChanged, this, &UnityWindow::applicationStateChanged);
if (m_pUnityProcess && m_pUnityProcess->state() == QProcess::ProcessState::Running)
{
m_pUnityProcess->kill();
}
}
bool UnityWindow::isValid() const
{
return m_bIsValid;
}
#ifdef Q_OS_WIN
bool UnityWindow::enumChildWindows(HWND handle)
{
m_unityHandle = handle;
auto qtMainThread = GetWindowThreadProcessId(reinterpret_cast<HWND>(winId()), nullptr);
auto unityMainThread = GetWindowThreadProcessId(m_unityHandle, nullptr);
AttachThreadInput(qtMainThread, unityMainThread, FALSE);
AttachThreadInput(unityMainThread, qtMainThread, FALSE);
SendMessageA(m_unityHandle, WM_ACTIVATE, WA_ACTIVE, 0);
MoveWindow(m_unityHandle, 0, 0, width(), height(), true);
return false; // first iteration should be Unity window
}
#endif
void UnityWindow::applicationStateChanged(Qt::ApplicationState state)
{
#ifdef Q_OS_WIN
if (state == Qt::ApplicationActive)
{
SendMessageA(m_unityHandle, WM_ACTIVATE, WA_ACTIVE, 0);
}
else if (state == Qt::ApplicationSuspended)
{
SendMessageA(m_unityHandle, WM_ACTIVATE, WA_INACTIVE, 0);
}
else if (state == Qt::ApplicationInactive)
{
// Apparently, Qt calls ApplicationInactive just after ApplicationActive...
// so I have disabled handling this state
//SendMessage(m_unityHandle, WM_ACTIVATE, WA_INACTIVE, 0);
}
else if (state == Qt::ApplicationHidden)
{
SendMessageA(m_unityHandle, WM_ACTIVATE, WA_INACTIVE, 0);
}
#endif
}
void UnityWindow::resizeEvent(QResizeEvent *ev)
{
QWidget::resizeEvent(ev);
#ifdef Q_OS_WIN
if (m_unityHandle != 0)
{
MoveWindow(m_unityHandle, 0, 0, width(), height(), true);
}
#endif
}
#ifdef Q_OS_WIN
BOOL UnityWindow::sEnumChildWindows(HWND handle, LPARAM params)
{
UnityWindow *w = reinterpret_cast<UnityWindow*>(params);
return w->enumChildWindows(handle);
}
#endif
void UnityWindow::showEvent(QShowEvent *ev)
{
QWidget::showEvent(ev);
#ifdef Q_OS_WIN
if (m_unityHandle == 0)
{
QFileInfo unityFileInfo(QDir(QCoreApplication::applicationDirPath()), m_unityExecutablePath);
m_pUnityProcess = new QProcess(this);
QStringList args2;
args2 << "-parentHWND";
args2 << QString::number(winId());
m_pUnityProcess->startDetached(unityFileInfo.canonicalFilePath(), args2);
HWND qtHandle = reinterpret_cast<HWND>(winId());
while (m_unityHandle == nullptr)
EnumChildWindows(qtHandle, sEnumChildWindows, reinterpret_cast<LPARAM>(this));
m_bIsValid = true;
}
#endif
}
void UnityWindow::hideEvent(QHideEvent *ev)
{
QWidget::hideEvent(ev);
#ifdef Q_OS_WIN
SendMessage(m_unityHandle, WM_ACTIVATE, WA_INACTIVE, 0);
#endif
}
bool UnityWindow::nativeEvent(const QByteArray& eventType, void* message, long* result)
{/*
#ifdef Q_OS_WIN
static QByteArray win32MSG = QByteArrayLiteral("windows_generic_MSG");
if (eventType == win32MSG)
{
MSG* msg = reinterpret_cast<MSG*>(message);
if (msg->message == WM_PARENTNOTIFY)
{
if (m_unityHandle != 0)
SetFocus(m_unityHandle);
}
}
#endif*/
return QWidget::nativeEvent(eventType, message, result);
}
#pragma once
#include <QWidget>
#ifdef Q_OS_WIN
#include <Windows.h>
#endif
#include <QString>
class QProcess;
class UnityWindow : public QWidget
{
Q_OBJECT
public:
UnityWindow(const QString& unityExePath, QWidget* parent = nullptr);
~UnityWindow();
bool isValid() const;
protected:
virtual void applicationStateChanged(Qt::ApplicationState state);
virtual void resizeEvent(QResizeEvent * ev) override;
virtual void showEvent(QShowEvent * ev) override;
virtual void hideEvent(QHideEvent * ev) override;
virtual bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
private:
#ifdef Q_OS_WIN
static BOOL CALLBACK sEnumChildWindows(HWND handle, LPARAM params);
bool enumChildWindows(HWND handle);
HWND m_unityHandle;
#endif
bool m_bIsValid = false;
QProcess* m_pUnityProcess = nullptr;
QString m_unityExecutablePath;
};
@weijibin
Copy link

thanks for your code, when i use this code, the unity app can not response to keyboard event? do you know why?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment