Last active
August 8, 2024 08:22
-
-
Save jbltx/db1f4df72654e2b2339956758ee0ce34 to your computer and use it in GitHub Desktop.
Unity Window in Qt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
}; |
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
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...).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.