-
-
Save jbltx/db1f4df72654e2b2339956758ee0ce34 to your computer and use it in GitHub Desktop.
#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; | |
}; |
Hi @agjaeger, unfortunately even if the Unity window is well embedded into the Qt application, it seems to have a desynchronization about updating UI states between both windows (source). In my example, I instantiated a QGraphicsView
with a movable node to demonstrate how slow Qt becomes (the Unity window keeps a good refresh rate tho).
Even calling AttachThreadInput
to detach threads doesn't make any difference. It seems to be a very low-level issue (the way Qt handles Windows Messages in its main loop vs the Win32 way). I have made another application in XAML/C# and it works like a charm (I suppose, since .NET framework is from Microsoft, windows messages are well synchronized...).
I am wondering if you could post the unity script that you use?
There's no specific scripts on the Unity side. Maybe the drag event you re talking about is the one used in the QGraphicsView
to move the scene (but it's only for Qt). I didn't share communication protocols to communicate between Qt and Unity, since they are really specific for my project and TCP-based (not really the point of this gist).
I will keep this gist up-to-date if I find a solution.
thanks for your code, when i use this code, the unity app can not response to keyboard event? do you know why?
Hello I am in a very similar boat as yours
https://forum.unity.com/threads/unity-player-embedded-into-qt-application.537879/
I am wondering if you could post the unity script that you use? It seems (just from reading the code) that send a drag event from QT to Unity?