Skip to content

Instantly share code, notes, and snippets.

@jbltx
Last active January 11, 2024 06:52
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • 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;
};
@agjaeger
Copy link

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?

@jbltx
Copy link
Author

jbltx commented May 1, 2020

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.

@matthiashack
Copy link

I also wonder how to drag some model resources from Qt window to unity player window, the drag icon show forbidden indicator.
截图

@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