feat(framework/examples): add doom example

This commit is contained in:
☙◦ The Tablet ❀ GamerGirlandCo ◦❧ 2026-01-21 16:22:10 -05:00
parent fcb686ac0c
commit d515d522a5
Signed by: tablet
GPG Key ID: 924A5F6AF051E87C
14 changed files with 657 additions and 0 deletions

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "example/ultimate-artblock/3rdparty/libdoom"]
path = example/ultimate-artblock/3rdparty/libdoom
url = https://git.tablet.sh/tablet/libdoom
branch = master

View File

@ -36,4 +36,5 @@ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Gui Core5Compat Xml QUIET) find_package(Qt6 REQUIRED COMPONENTS Core Widgets Gui Core5Compat Xml QUIET)
add_subdirectory(simple) add_subdirectory(simple)
add_subdirectory(ultimate-artblock)
add_subdirectory(_all) add_subdirectory(_all)

View File

@ -21,4 +21,5 @@ set_target_properties(all-examples PROPERTIES
AUTORCC ON AUTORCC ON
) )
target_link_libraries(all-examples PUBLIC libtoonboom_static) target_link_libraries(all-examples PUBLIC libtoonboom_static)
target_link_libraries(all-examples PRIVATE doom-example)
target_link_libraries(all-examples PRIVATE simple-example) target_link_libraries(all-examples PRIVATE simple-example)

View File

@ -0,0 +1,32 @@
#pragma once
#include "./base.hpp"
#include <doom_view.hpp>
class DoomExample : public BaseExample {
public:
DoomExample() : BaseExample() {
}
~DoomExample() {}
std::function<QScriptValue(QScriptContext *, QScriptEngine *)>
run() override {
return
[this](QScriptContext *context, QScriptEngine *engine) -> QScriptValue {
auto lm = PLUG_Services::getLayoutManager();
if(doomView == nullptr) {
doomView = new DoomView();
lm->addArea("DoomView", doomView->displayName(), doomView, true, true, false, QSize(320, 200), true, false, true, true);
}
lm->raiseArea("DoomView", nullptr, true, QPoint(2020, 100));
auto widget =
dynamic_cast<ToonDoomWidget *>(doomView->getWidget());
widget->start();
return engine->undefinedValue();
};
}
QString jsName() override { return "runDoom"; }
private:
DoomView *doomView = nullptr;
};

View File

@ -7,12 +7,14 @@
#include <vector> #include <vector>
#include "./defs/base.hpp" #include "./defs/base.hpp"
#include "./defs/simple.hpp" #include "./defs/simple.hpp"
#include "./defs/doom.hpp"
class ToonBoomExamples { class ToonBoomExamples {
public: public:
ToonBoomExamples() { ToonBoomExamples() {
addExample(new SimpleExample()); addExample(new SimpleExample());
addExample(new ToolbarExample()); addExample(new ToolbarExample());
addExample(new DoomExample());
} }
void addExample(BaseExample *example) { examples.push_back(example); } void addExample(BaseExample *example) { examples.push_back(example); }
QScriptValue getExamples(QScriptEngine *engine) { QScriptValue getExamples(QScriptEngine *engine) {

@ -0,0 +1 @@
Subproject commit 6e975919c8c04f299aec7b06409f7f2222c5b2b7

View File

@ -0,0 +1,37 @@
# include(../common.cmake)
file(GLOB_RECURSE DOOM_EXAMPLE_HEADERS CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/src/include/*.hpp"
)
file(GLOB_RECURSE DOOM_EXAMPLE_SOURCES CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cxx"
)
list(FILTER DOOM_EXAMPLE_SOURCES EXCLUDE REGEX "/(out|build|cmake-build-|CMakeFiles|doom_proxy)/")
list(FILTER DOOM_EXAMPLE_HEADERS EXCLUDE REGEX "/(out|build|cmake-build-|CMakeFiles|doom_proxy)/")
message(STATUS "DOOM_EXAMPLE_SOURCES: ${DOOM_EXAMPLE_SOURCES}")
# add_library(libdoom OBJECT "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libdoom/doom.c" )
add_library(libdoom OBJECT "${CMAKE_CURRENT_SOURCE_DIR}/src/doom_proxy.c")
set_target_properties(libdoom PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_compile_definitions(libdoom PUBLIC "APP_WINDOWS" "__BYTEBOOL__=1" "ALLOW_MOUSE=0")
target_include_directories(libdoom PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libdoom/linuxdoom-1.10)
target_include_directories(libdoom PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libdoom/libs_win32)
target_compile_options(libdoom PUBLIC "/Zc:gotoScope-" "/Zc:strictStrings-" "/wd4244" "/wd4267" "/wd4838" "/wd4430" "/wd4996" "/wd4311" "/wd4113" "/showIncludes")
add_library(doom-example STATIC ${DOOM_EXAMPLE_SOURCES} ${DOOM_EXAMPLE_HEADERS} $<TARGET_OBJECTS:libdoom>)
target_compile_definitions(doom-example PRIVATE "APP_WINDOWS" "__BYTEBOOL__=1")
target_compile_options(doom-example PUBLIC "/std:c++20" "/Zc:gotoScope-" "/showIncludes")
target_include_directories(doom-example PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/include)
target_include_directories(doom-example PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libdoom/libs_win32)
target_include_directories(doom-example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libdoom/linuxdoom-1.10)
target_link_libraries(doom-example PRIVATE libtoonboom_static)
set_target_properties(doom-example PROPERTIES
AUTOMOC ON
AUTOUIC ON
AUTORCC ON
)

View File

@ -0,0 +1,189 @@
#ifndef __cplusplus
// typedef enum {false, true} boolean;
#define false 0
#define true 1
typedef unsigned char byte;
#endif
#define MY_APP_FATAL_ERROR(ctx, message) { printf( "FATAL ERROR: %s\n", message ); MessageBoxA( 0, message, "Fatal Error!", MB_OK | MB_ICONSTOP ); _flushall(); }
#define APP_LOG(ctx, level, message) printf( "%s\n", message )
// #include <app.h>
// #undef APP_IMPLEMENTATION
#define APP_IMPLEMENTATION
#include <app.h>
#undef APP_IMPLEMENTATION
#ifdef _WIN32
#define strcasecmp stricmp
#define strncasecmp strnicmp
#else
#include <strings.h>
#endif
#define CONCAT_IMPL( x, y ) x##y
#define CONCAT( x, y ) CONCAT_IMPL( x, y )
#define rcsid CONCAT( rcsid, __COUNTER__ )
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
int doom_access( char const* _FileName, int _AccessMode ) {
FILE* f = fopen( _FileName, "rb" );
if( f ) {
fclose(f);
return 0;
}
return 1;
}
#ifdef _WIN32
#include <unistd.h>
#else
#include <unistd.h>
#endif
#undef access
#define access doom_access
#define open doom_open
#define close doom_close
#undef LoadMenu
#include <am_map.c>
#include <doomdef.c>
#include <doomstat.c>
#include <dstrings.c>
#include <d_items.c>
#include <d_main.c>
#include <d_net.c>
#include <f_finale.c>
#include <f_wipe.c>
#include <g_game.c>
#include <hu_lib.c>
#include <hu_stuff.c>
#include <info.c>
#include <m_argv.c>
#include <m_bbox.c>
#include <m_cheat.c>
#include <m_fixed.c>
#include <m_random.c>
#include <m_swap.c>
#include <p_ceilng.c>
#include <p_doors.c>
#include <p_enemy.c>
#include <p_floor.c>
#include <p_inter.c>
#include <p_lights.c>
#include <p_map.c>
#include <p_maputl.c>
#include <p_mobj.c>
#include <p_plats.c>
#include <p_pspr.c>
#include <p_saveg.c>
#include <p_setup.c>
#include <p_sight.c>
#include <p_spec.c>
#include <p_switch.c>
#include <p_telept.c>
#include <p_tick.c>
#include <p_user.c>
#include <r_bsp.c>
#include <r_data.c>
#include <r_draw.c>
#include <r_main.c>
#include <r_plane.c>
#include <r_segs.c>
#include <r_sky.c>
#include <r_things.c>
#include <sounds.c>
#undef BG
#include <st_lib.c>
#include <st_stuff.c>
#define channels xchannels
#include <s_sound.c>
#undef channels
#include <tables.c>
#include <v_video.c>
#define anim_t wi_anim_t
#define anims wi_anims
#define time wi_time
#include <wi_stuff.c>
#undef anims
#undef anim_t
#undef time
#include <z_zone.c>
#undef open
#undef close
#include <m_menu.c>
#include <m_misc.c>
#define strupr xstrupr
#include <w_wad.c>
#undef strupr
#include <i_net.c>
#undef MAXCHAR
#undef MAXSHORT
#undef MAXINT
#undef MAXLONG
#undef MINCHAR
#undef MINSHORT
#undef MININT
#undef MINLONG
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#ifdef __wasm__
#define APP_WASM
#define WA_CORO_IMPLEMENT_NANOSLEEP
#else
#define APP_WINDOWS
#endif
#define boolean HACK_TO_MAKE_BOOLEAN_NOT_BE_DEFINED
#define FRAMETIMER_IMPLEMENTATION
#include <frametimer.h>
#define CRTEMU_IMPLEMENTATION
#include <crtemu.h>
#ifndef __wasm__
#define THREAD_IMPLEMENTATION
#if defined( __TINYC__ )
typedef struct _RTL_CONDITION_VARIABLE { PVOID Ptr; } RTL_CONDITION_VARIABLE, *PRTL_CONDITION_VARIABLE;
typedef RTL_CONDITION_VARIABLE CONDITION_VARIABLE, *PCONDITION_VARIABLE;
static VOID (*InitializeConditionVariable)( PCONDITION_VARIABLE );
static VOID (*WakeConditionVariable)( PCONDITION_VARIABLE );
static BOOL (*SleepConditionVariableCS)( PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD );
#endif
#include <thread.h>
#undef THREAD_IMPLEMENTATION
#endif
#undef boolean
#define MUS_IMPLEMENTATION
#include <mus.h>
#define TSF_IMPLEMENTATION
#define TSF_POW pow
#define TSF_POWF (float)pow
#define TSF_EXPF (float)exp
#define TSF_LOG log
#define TSF_TAN tan
#define TSF_LOG10 log10
#define TSF_SQRT (float)sqrt
#define TSF_SQRTF (float)sqrt
#include <math.h>
#include <tsf.h>
#include <soundfont.c>
#include <i_sound.c>
#include <i_video.c>
#include <i_system.c>

View File

@ -0,0 +1,32 @@
#include "./include/doom_view.hpp"
#include "common.h"
DoomView::DoomView() : TUWidgetLayoutViewBase<ToonDoomWidget>() {}
DoomView::~DoomView() {}
QString DoomView::displayName() const { return "Doom!?"; }
ToonDoomWidget *DoomView::createWidget() { return new ToonDoomWidget(); }
app_proc_t real_app_proc = app_proc;
namespace toon_doom {
int app_proc(app_t *app, void *user_data) {
APP_U32 canvas[320 * 200];
memset(canvas, 0xc0, sizeof(canvas));
auto asDoomThread = reinterpret_cast<DoomThread *>(user_data);
asDoomThread->setApp(app);
// return 0;
auto result = real_app_proc(app, user_data);
thread_atomic_int_store(&app_running, 0);
std::cout << "exiting app_proc" << std::endl;
return result;
}
int app_proc_thread(void *user_data) {
auto asDoomThread = reinterpret_cast<DoomThread *>(user_data);
asDoomThread->setId(GetCurrentThreadId());
auto result = app_run(app_proc, user_data, nullptr, nullptr, nullptr);
return result;
}
} // namespace toon_doom

View File

@ -0,0 +1,54 @@
#ifndef COMMON_H
#define COMMON_H
#pragma warning(disable : 4113)
#pragma warning(disable : 4311)
#pragma warning(disable : 4047)
#pragma warning(disable : 4024)
#pragma warning(disable : 4312)
#pragma warning(disable : 4020)
#pragma warning(disable : 4700)
#pragma warning(disable : 4133)
#pragma warning(disable : 4142)
#pragma warning(disable : 4005)
#pragma warning(disable : 4244)
#pragma warning(disable : 4267)
#pragma warning(disable : 4838)
#pragma warning(disable : 4430)
#pragma warning(disable : 4996)
#define APP_WINDOWS
#if defined(__BYTEBOOL__)
typedef unsigned char boolean;
typedef unsigned char byte;
#endif
#ifndef __cplusplus
#define true 1
#define false 0
#endif
#define MY_APP_FATAL_ERROR(ctx, message) { printf( "FATAL ERROR: %s\n", message ); MessageBoxA( 0, message, "Fatal Error!", MB_OK | MB_ICONSTOP ); _flushall(); }
#define APP_LOG(ctx, level, message) printf( "%s\n", message )
#ifdef __cplusplus
extern "C" {
void D_DoomMain(void);
#include <app_funcs.h>
#undef APP_FATAL_ERROR
#define APP_FATAL_ERROR MY_APP_FATAL_ERROR
#include <thread.h>
extern thread_mutex_t mus_mutex;
extern thread_signal_t vblank_signal;
extern thread_atomic_int_t app_running;
extern int maketic;
extern int gametic;
void M_StartControlPanel (void);
extern char** myargv;
extern int myargc;
int app_proc(app_t *app, void *user_data);
void M_QuitResponse(int ch);
}
#else
#include <app_funcs.h>
#undef APP_FATAL_ERROR
#define APP_FATAL_ERROR MY_APP_FATAL_ERROR
#endif
#endif

View File

@ -0,0 +1,10 @@
#pragma once
#include "./toon_doom.hpp"
class DoomView : public TUWidgetLayoutViewBase<ToonDoomWidget> {
public:
DoomView();
~DoomView() override;
QString displayName() const override;
protected:
ToonDoomWidget *createWidget() override;
};

View File

@ -0,0 +1,269 @@
#pragma once
#include <QtCore/QPointer>
#include <QtCore/qnamespace.h>
#include <QtCore/QEvent>
#include <QtCore/QThread>
#include <QtGui/QtGui>
#include <QtWidgets/QtWidgets>
#include <set>
#include <toon_boom/ext/layout.hpp>
#include <toon_boom/ext/util.hpp>
#include "./common.h"
#include "./util.hpp"
typedef int (*app_proc_t)(app_t *, void *);
namespace toon_doom {
int app_proc(app_t *app, void *user_data);
int app_proc_thread(void *user_data);
}; // namespace toon_doom
class ToonDoomWidget;
class DoomThread : public QThread {
Q_OBJECT
public:
DoomThread(ToonDoomWidget *widget, QObject *parent = nullptr)
: QThread(parent) {
m_widget = widget;
}
~DoomThread() {
M_QuitResponse('y');
thread_atomic_int_store(&app_running, 0);
maketic = 0;
gametic = 0;
if (isRunning()) {
terminate();
}
}
void startGame(app_t *app) { emit gameStarted(m_widget, app); }
void setApp(app_t *app) { this->app = app; }
DWORD getId() { return tid; }
void setId(DWORD tid) { this->tid = tid; }
private:
signals:
void gameStarted(ToonDoomWidget *widget, app_t *app);
void gameExited(const int &result);
protected:
QPointer<ToonDoomWidget> m_widget;
void run() override {
tid = GetCurrentThreadId();
myargc = 0;
myargv = nullptr;
thread_signal_init(&vblank_signal);
thread_mutex_init(&mus_mutex);
thread_atomic_int_store(&app_running, 1);
threadPtr = thread_create(&toon_doom::app_proc_thread, this,
THREAD_STACK_SIZE_DEFAULT);
int result =
thread_signal_wait(&vblank_signal, THREAD_SIGNAL_WAIT_INFINITE);
startGame(app);
D_DoomMain();
emit gameExited(result);
}
thread_ptr_t threadPtr = nullptr;
DWORD tid = 0;
app_t *app = nullptr;
};
class ToonDoomWidget : public QWidget {
Q_OBJECT
public:
ToonDoomWidget(QWidget *parent = nullptr) : QWidget(parent) {
setPalette(QPalette(QColor(0, 0, 0)));
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
setAutoFillBackground(true);
setMinimumSize(320, 200);
setWindowTitle("Doom!?");
m_layout = new QVBoxLayout(this);
m_layout->setAlignment(Qt::AlignCenter);
m_layout->setContentsMargins(0, 0, 0, 0);
m_layout->setSpacing(0);
setFocusPolicy(Qt::StrongFocus);
}
~ToonDoomWidget() {
QObject::disconnect(static_cast<QApplication *>(QApplication::instance()),
&QApplication::focusChanged, this,
&ToonDoomWidget::focusChanged);
auto inp =
AttachThreadInput(GetCurrentThreadId(), doomThread->getId(), false);
util::debug::out << "~ToonDoomWidget AttachThreadInput: " << inp
<< std::endl;
delete doomThread;
}
void setApp(app_t *app) {
this->app = app;
init();
}
app_t *getApp() const { return app; }
public slots:
void start() {
if (doomThread != nullptr && doomThread->isRunning()) {
return;
}
doomThread = new DoomThread(this, this);
connect(doomThread, &DoomThread::gameExited, this,
&ToonDoomWidget::gameExited);
connect(doomThread, &DoomThread::gameStarted, this,
&ToonDoomWidget::gameStarted);
doomThread->start();
}
void gameStarted(ToonDoomWidget *widget, app_t *app) { widget->setApp(app); }
void gameExited(const int &result) {}
private:
QWidget *doomWidget = nullptr;
app_t *app = nullptr;
QWindow *m_window = nullptr;
DoomThread *doomThread = nullptr;
QVBoxLayout *m_layout = nullptr;
protected:
void resizeEvent(QResizeEvent *event) override {
QWidget::resizeEvent(event);
updateGeometry();
}
void moveEvent(QMoveEvent *event) override {
QWidget::moveEvent(event);
updateGeometry();
}
void focusInEvent(QFocusEvent *event) override {
util::debug::out << "focusInEvent: " << event->type() << std::endl;
if (app) {
app->has_focus = true;
SetFocus(app->hwnd);
}
QWidget::focusInEvent(event);
}
bool eventFilter(QObject *obj, QEvent *event) override {
QMetaEnum metaEnum = QMetaEnum::fromType<QEvent::Type>();
util::debug::out << "eventFilter: " << metaEnum.valueToKey(event->type())
<< " obj: " << obj->metaObject()->className() << std::endl;
if (event->type() == QEvent::WindowActivate ||
(event->type() == QEvent::FocusIn && obj != this)) {
this->setFocus(Qt::OtherFocusReason);
}
if (obj == this && (event->type() == QEvent::FocusOut ||
event->type() == QEvent::FocusAboutToChange)) {
event->ignore();
setFocus();
return true;
}
return QWidget::eventFilter(obj, event);
}
bool event(QEvent *event) override {
if (event->type() == QEvent::FocusOut ||
event->type() == QEvent::FocusAboutToChange) {
util::debug::out << "focus out" << std::endl;
setEnabled(true);
if (event->type() == QEvent::FocusOut) {
auto asFocusEvent = static_cast<QFocusEvent *>(event);
util::debug::out << "focus out reason: " << asFocusEvent->reason()
<< std::endl;
if (asFocusEvent->reason() == Qt::OtherFocusReason) {
event->ignore();
setFocus();
QApplication::processEvents();
}
} else if (event->type() == QEvent::FocusAboutToChange) {
event->ignore();
}
return true;
}
return QWidget::event(event);
}
private:
std::pair<QKeyEvent *, QKeyEvent *> createEscapeKeyEvents() {
return std::pair<QKeyEvent *, QKeyEvent *>(
new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier),
new QKeyEvent(QEvent::KeyRelease, Qt::Key_Escape, Qt::NoModifier));
}
bool isMouseMoveEvent(QEvent *event) {
return event->type() == QEvent::MouseMove ||
event->type() == QEvent::Enter || event->type() == QEvent::Leave ||
event->type() == QEvent::TabletMove ||
event->type() == QEvent::NonClientAreaMouseMove ||
event->type() == QEvent::GrabMouse ||
event->type() == QEvent::HoverMove ||
event->type() == QEvent::HoverEnter ||
event->type() == QEvent::HoverLeave ||
event->type() == QEvent::GraphicsSceneHoverMove ||
event->type() == QEvent::GraphicsSceneHoverEnter ||
event->type() == QEvent::GraphicsSceneHoverLeave ||
event->type() == QEvent::GraphicsSceneMouseMove ||
event->type() == QEvent::GraphicsSceneLeave;
}
std::set<QWidget *> m_ancestors;
private slots:
void updateGeometry() {
if (m_window != nullptr) {
doomWidget->setGeometry(this->rect());
m_window->setGeometry(doomWidget->rect());
}
}
void windowClosed() {
util::debug::out << "window closed" << std::endl;
delete doomThread;
doomThread = nullptr;
}
void init() {
m_window = QWindow::fromWinId(WId(app->hwnd));
Qt::WindowFlags nflags = Qt::WindowType::Window | Qt::WindowType::Widget |
Qt::WindowType::FramelessWindowHint |
Qt::CustomizeWindowHint;
const auto dpr = m_window->devicePixelRatio();
// m_window->setFlags(m_window->flags() | nflags);
m_window->setGeometry(this->rect());
connect(m_window, &QWindow::close, this, &ToonDoomWidget::windowClosed);
doomWidget =
QWidget::createWindowContainer(m_window, this, Qt::WindowType::Widget);
doomWidget->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::MinimumExpanding);
util::debug::out << "doomwidget == window: "
<< (doomWidget->windowHandle() == m_window) << std::endl;
auto flags = doomWidget->windowFlags();
doomWidget->hide();
m_layout->addWidget(doomWidget, 1);
util::debug::out << "doom thread id: " << doomThread->getId() << std::endl;
auto inp =
AttachThreadInput(GetCurrentThreadId(), doomThread->getId(), true);
util::debug::out << "AttachThreadInput: " << inp << std::endl;
doomWidget->show();
auto parentWindow = window();
updateGeometry();
{
// add widget hierarchy to a set for focus tracking
QWidget *p = doomWidget;
while (p != nullptr) {
util::debug::out << "widget @ " << util::debug::addrToHex(p) << ": "
<< p->metaObject()->className() << " parent: "
<< util::debug::addrToHex(p->parentWidget())
<< std::endl;
m_ancestors.insert(p);
p = p->parentWidget();
}
}
installEventFilter(this);
doomWidget->setFocusPolicy(Qt::StrongFocus);
QObject::connect(static_cast<QApplication *>(QApplication::instance()),
&QApplication::focusChanged, this,
&ToonDoomWidget::focusChanged);
}
void focusChanged(QWidget *old, QWidget *now) {
if (m_ancestors.contains(old) && !m_ancestors.contains(now)) {
app->has_focus = false;
M_StartControlPanel();
}
}
};

View File

@ -0,0 +1,3 @@
#include <windows.h>
void sendEscapeKeyToWindow(HWND hwnd);

View File

@ -0,0 +1,22 @@
#include "./include/util.hpp"
#include <toon_boom/ext/util.hpp>
using namespace util;
void sendEscapeKeyToWindow(HWND hwnd) {
{
SetForegroundWindow(hwnd);
INPUT inputs[2] = {};
ZeroMemory(inputs, sizeof(inputs));
inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = VK_ESCAPE;
inputs[1].type = INPUT_KEYBOARD;
inputs[1].ki.wVk = VK_ESCAPE;
inputs[1].ki.dwFlags = KEYEVENTF_KEYUP;
UINT uSent = SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT));
debug::out << "SendInput: " << uSent << std::endl;
}
}