summaryrefslogtreecommitdiffhomepage
path: root/opentrack
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2019-03-28 13:40:33 +0100
committerStanislaw Halik <sthalik@misaki.pl>2019-03-28 13:44:59 +0100
commit47d4b25bd0583b1fd65ae885950efd34c9df256b (patch)
treef3a155717092c436e9211e1dc244edc9c9d62fa9 /opentrack
parent79d56147198dc873e30ae0ca8d554d37106db56f (diff)
cmake: move around variant directories
Diffstat (limited to 'opentrack')
-rw-r--r--opentrack/CMakeLists.txt9
-rw-r--r--opentrack/lang/nl_NL.ts189
-rw-r--r--opentrack/lang/ru_RU.ts193
-rw-r--r--opentrack/lang/stub.ts189
-rw-r--r--opentrack/lang/zh_CN.ts193
-rw-r--r--opentrack/main-window.cpp920
-rw-r--r--opentrack/main-window.hpp145
-rw-r--r--opentrack/main-window.ui1459
-rw-r--r--opentrack/main.cpp23
-rw-r--r--opentrack/new_config.ui45
-rw-r--r--opentrack/new_file_dialog.cpp37
-rw-r--r--opentrack/new_file_dialog.h25
-rw-r--r--opentrack/opentrack.icobin0 -> 67134 bytes
-rw-r--r--opentrack/resources.rc2
14 files changed, 3429 insertions, 0 deletions
diff --git a/opentrack/CMakeLists.txt b/opentrack/CMakeLists.txt
new file mode 100644
index 00000000..a8829aa5
--- /dev/null
+++ b/opentrack/CMakeLists.txt
@@ -0,0 +1,9 @@
+otr_module(executable EXECUTABLE BIN)
+
+set_target_properties(opentrack-executable PROPERTIES
+ SUFFIX "${opentrack-binary-suffix}"
+ OUTPUT_NAME "opentrack"
+ PREFIX ""
+)
+
+target_link_libraries(${self} opentrack-user-interface opentrack-version)
diff --git a/opentrack/lang/nl_NL.ts b/opentrack/lang/nl_NL.ts
new file mode 100644
index 00000000..7a465820
--- /dev/null
+++ b/opentrack/lang/nl_NL.ts
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>UI_new_config</name>
+ <message>
+ <source>Config filename</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>New file name:</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>main_window</name>
+ <message>
+ <source>Raw tracker data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Game data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Options</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracking</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Input</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>🔨</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Output</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Running as root is bad</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Do not run as root. Set correct device node permissions.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Running as root is bad, seriously</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Do not run as root. I&apos;ll keep whining at every startup.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Create new empty config</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Create new copied config</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Open configuration directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>opentrack</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> (debug)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Show the Octopus</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hide the Octopus</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Protocol settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mappings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Exit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The Octopus is sad</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Check permissions for your .ini directory:
+
+&quot;%1&quot;%2
+
+Exiting now.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> :: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Be annoyed, comprehensively.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Don&apos;t run as root to remove these annoying messages.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>new_file_dialog</name>
+ <message>
+ <source>File exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>This file already exists. Pick another name.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/opentrack/lang/ru_RU.ts b/opentrack/lang/ru_RU.ts
new file mode 100644
index 00000000..684a8212
--- /dev/null
+++ b/opentrack/lang/ru_RU.ts
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>UI_new_config</name>
+ <message>
+ <source>Config filename</source>
+ <translation>Создание профиля</translation>
+ </message>
+ <message>
+ <source>New file name:</source>
+ <translation>Новое имя профиля:</translation>
+ </message>
+</context>
+<context>
+ <name>main_window</name>
+ <message>
+ <source>Create new empty config</source>
+ <translation>Создать новый пустой профиль</translation>
+ </message>
+ <message>
+ <source>Create new copied config</source>
+ <translation>Создать новый профиль на основе текущего</translation>
+ </message>
+ <message>
+ <source>Open configuration directory</source>
+ <translation>Открыть каталог с профилями</translation>
+ </message>
+ <message>
+ <source>opentrack</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source> (debug)</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Show the Octopus</source>
+ <translation>Показать осьминожка</translation>
+ </message>
+ <message>
+ <source>Hide the Octopus</source>
+ <translation>Спрятать осьминожка</translation>
+ </message>
+ <message>
+ <source>Tracker settings</source>
+ <translation>Настройка источника данных</translation>
+ </message>
+ <message>
+ <source>Filter settings</source>
+ <translation>Настройка фильтрации/сглаживания</translation>
+ </message>
+ <message>
+ <source>Protocol settings</source>
+ <translation>Настройка выходного интерфейса</translation>
+ </message>
+ <message>
+ <source>Mappings</source>
+ <translation>Настройка кривых</translation>
+ </message>
+ <message>
+ <source>Options</source>
+ <translation>Настройки</translation>
+ </message>
+ <message>
+ <source>Exit</source>
+ <translation>Закрыть</translation>
+ </message>
+ <message>
+ <source>The Octopus is sad</source>
+ <translation>Осьминожек опечален</translation>
+ </message>
+ <message>
+ <source>Check permissions for your .ini directory:
+
+&quot;%1&quot;%2
+
+Exiting now.</source>
+ <translation>Проверьте права доступа на Вашу .ini папку:
+
+&quot;%1&quot;%2
+
+Закрытие программы.</translation>
+ </message>
+ <message>
+ <source> :: </source>
+ <translation> :: </translation>
+ </message>
+ <message>
+ <source>Raw tracker data</source>
+ <translation>Исходные данные</translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation>Z</translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation>Pitch</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation>Roll</translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation>Yaw</translation>
+ </message>
+ <message>
+ <source>Game data</source>
+ <translation>Игровые данные</translation>
+ </message>
+ <message>
+ <source>Profile</source>
+ <translation>Профиль</translation>
+ </message>
+ <message>
+ <source>Mapping</source>
+ <translation> Кривые</translation>
+ </message>
+ <message>
+ <source>Tracking</source>
+ <translation>Статус работы трекера</translation>
+ </message>
+ <message>
+ <source>Start</source>
+ <translation>Запустить</translation>
+ </message>
+ <message>
+ <source>Stop</source>
+ <translation>Остановить</translation>
+ </message>
+ <message>
+ <source>Input</source>
+ <translation>Источник данных</translation>
+ </message>
+ <message>
+ <source>🔨</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Output</source>
+ <translation>Выходной интерфейс</translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation>Фильтрация/сглаживание</translation>
+ </message>
+ <message>
+ <source>Running as root is bad</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Do not run as root. Set correct device node permissions.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Running as root is bad, seriously</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Do not run as root. I&apos;ll keep whining at every startup.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Be annoyed, comprehensively.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Don&apos;t run as root to remove these annoying messages.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>new_file_dialog</name>
+ <message>
+ <source>File exists</source>
+ <translation>Файл создан</translation>
+ </message>
+ <message>
+ <source>This file already exists. Pick another name.</source>
+ <translation>Данный файл уже создан. Пожалуйста выберите другое имя.</translation>
+ </message>
+</context>
+</TS>
diff --git a/opentrack/lang/stub.ts b/opentrack/lang/stub.ts
new file mode 100644
index 00000000..04555dd0
--- /dev/null
+++ b/opentrack/lang/stub.ts
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>UI_new_config</name>
+ <message>
+ <source>Config filename</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>New file name:</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>main_window</name>
+ <message>
+ <source>Raw tracker data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Game data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Options</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracking</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Input</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>🔨</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Output</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Running as root is bad</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Do not run as root. Set correct device node permissions.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Running as root is bad, seriously</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Do not run as root. I&apos;ll keep whining at every startup.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Create new empty config</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Create new copied config</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Open configuration directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>opentrack</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> (debug)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Show the Octopus</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hide the Octopus</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Filter settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Protocol settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Mappings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Exit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The Octopus is sad</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Check permissions for your .ini directory:
+
+&quot;%1&quot;%2
+
+Exiting now.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> :: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Be annoyed, comprehensively.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Don&apos;t run as root to remove these annoying messages.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>new_file_dialog</name>
+ <message>
+ <source>File exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>This file already exists. Pick another name.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/opentrack/lang/zh_CN.ts b/opentrack/lang/zh_CN.ts
new file mode 100644
index 00000000..15609d70
--- /dev/null
+++ b/opentrack/lang/zh_CN.ts
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>UI_new_config</name>
+ <message>
+ <source>Config filename</source>
+ <translation>配置文件名字:</translation>
+ </message>
+ <message>
+ <source>New file name:</source>
+ <translation>新文件名字:</translation>
+ </message>
+</context>
+<context>
+ <name>main_window</name>
+ <message>
+ <source>Raw tracker data</source>
+ <translation>跟踪器原始数据</translation>
+ </message>
+ <message>
+ <source>Z</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Pitch</source>
+ <translation>仰俯</translation>
+ </message>
+ <message>
+ <source>Y</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>X</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Roll</source>
+ <translation>横滚</translation>
+ </message>
+ <message>
+ <source>Yaw</source>
+ <translation>偏航</translation>
+ </message>
+ <message>
+ <source>Game data</source>
+ <translation>游戏得到的数据</translation>
+ </message>
+ <message>
+ <source>Profile</source>
+ <translation>配置文件</translation>
+ </message>
+ <message>
+ <source>Options</source>
+ <translation>选项</translation>
+ </message>
+ <message>
+ <source>Mapping</source>
+ <translation>映射</translation>
+ </message>
+ <message>
+ <source>Tracking</source>
+ <translation>跟踪</translation>
+ </message>
+ <message>
+ <source>Start</source>
+ <translation>开始</translation>
+ </message>
+ <message>
+ <source>Stop</source>
+ <translation>停止</translation>
+ </message>
+ <message>
+ <source>Input</source>
+ <translation>输入</translation>
+ </message>
+ <message>
+ <source>🔨</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Output</source>
+ <translation>输出</translation>
+ </message>
+ <message>
+ <source>Filter</source>
+ <translation>过滤器</translation>
+ </message>
+ <message>
+ <source>Running as root is bad</source>
+ <translation>以管理员运行不是什么好主意</translation>
+ </message>
+ <message>
+ <source>Do not run as root. Set correct device node permissions.</source>
+ <translation>请不要以管理员运行。可以设置何时的设备访问权限来解决。</translation>
+ </message>
+ <message>
+ <source>Running as root is bad, seriously</source>
+ <translation>再说一遍,以管理员运行不是什么好主意</translation>
+ </message>
+ <message>
+ <source>Do not run as root. I&apos;ll keep whining at every startup.</source>
+ <translation>请不要以管理员运行。这话每次启动我都会强调一遍。</translation>
+ </message>
+ <message>
+ <source>Don&apos;t run as root to remove these annoying messages.</source>
+ <translation>以管理员运行真的非常不好</translation>
+ </message>
+ <message>
+ <source>Be annoyed, comprehensively.</source>
+ <translation>请不要以管理员运行。烦了吧?赶紧动作起来</translation>
+ </message>
+ <message>
+ <source>Create new empty config</source>
+ <translation>新建一个空的配置</translation>
+ </message>
+ <message>
+ <source>Create new copied config</source>
+ <translation>新建一个复制配置</translation>
+ </message>
+ <message>
+ <source>Open configuration directory</source>
+ <translation>打开配置目录</translation>
+ </message>
+ <message>
+ <source>opentrack</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source> (debug)</source>
+ <translation> (调试)</translation>
+ </message>
+ <message>
+ <source>Show the Octopus</source>
+ <translation>显示八爪鱼</translation>
+ </message>
+ <message>
+ <source>Hide the Octopus</source>
+ <translation>隐藏八爪鱼</translation>
+ </message>
+ <message>
+ <source>Tracker settings</source>
+ <translation>跟踪器设置</translation>
+ </message>
+ <message>
+ <source>Filter settings</source>
+ <translation>过滤器设置</translation>
+ </message>
+ <message>
+ <source>Protocol settings</source>
+ <translation>协议设置</translation>
+ </message>
+ <message>
+ <source>Mappings</source>
+ <translation>影射</translation>
+ </message>
+ <message>
+ <source>Exit</source>
+ <translation>退出</translation>
+ </message>
+ <message>
+ <source>The Octopus is sad</source>
+ <translation>八爪鱼不开心了</translation>
+ </message>
+ <message>
+ <source>Check permissions for your .ini directory:
+
+&quot;%1&quot;%2
+
+Exiting now.</source>
+ <translation>检查一下你的 .ini 目录权限:
+
+&quot;%1&quot;%2
+
+退出先.</translation>
+ </message>
+ <message>
+ <source> :: </source>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>new_file_dialog</name>
+ <message>
+ <source>File exists</source>
+ <translation>文件已经存在</translation>
+ </message>
+ <message>
+ <source>This file already exists. Pick another name.</source>
+ <translation>文件重名了,换个其他名字</translation>
+ </message>
+</context>
+</TS>
diff --git a/opentrack/main-window.cpp b/opentrack/main-window.cpp
new file mode 100644
index 00000000..b90aa3bd
--- /dev/null
+++ b/opentrack/main-window.cpp
@@ -0,0 +1,920 @@
+/* Copyright (c) 2013-2016, Stanislaw Halik <sthalik@misaki.pl>
+
+ * Permission to use, copy, modify, and/or distribute this
+ * software for any purpose with or without fee is hereby granted,
+ * provided that the above copyright notice and this permission
+ * notice appear in all copies.
+ */
+
+#include "main-window.hpp"
+#include "logic/pipeline.hpp"
+#include "options/options.hpp"
+#include "new_file_dialog.h"
+#include "migration/migration.hpp"
+#include "compat/check-visible.hpp"
+#include "compat/sleep.hpp"
+#include "compat/macros.hpp"
+#include "compat/library-path.hpp"
+#include "compat/math.hpp"
+#include "compat/sysexits.hpp"
+
+#include <algorithm>
+#include <utility>
+
+#include <QMessageBox>
+#include <QDesktopServices>
+#include <QDir>
+
+extern "C" const char* const opentrack_version;
+
+using namespace options::globals;
+using namespace options;
+
+main_window::main_window() : State(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH)
+{
+ ui.setupUi(this);
+
+#if !defined _WIN32 && !defined __APPLE__
+ annoy_if_root();
+#endif
+
+ init_profiles();
+ init_buttons();
+ init_tray_menu();
+ init_dylibs();
+ init_shortcuts();
+
+ setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | windowFlags());
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ adjustSize();
+
+ if (!start_in_tray())
+ {
+ setVisible(true);
+ show();
+ }
+ else
+ setVisible(false);
+
+ connect(&pose_update_timer, &QTimer::timeout,
+ this, &main_window::show_pose, Qt::DirectConnection);
+ connect(&det_timer, &QTimer::timeout,
+ this, &main_window::maybe_start_profile_from_executable);
+ det_timer.start(1000);
+}
+
+void main_window::init_shortcuts()
+{
+ register_shortcuts();
+
+ // ctrl+q exits
+ connect(&kbd_quit, &QShortcut::activated, this, [this]() { main_window::exit(EXIT_SUCCESS); }, Qt::DirectConnection);
+ kbd_quit.setEnabled(true);
+}
+
+void main_window::init_dylibs()
+{
+ using dylib_ptr = Modules::dylib_ptr;
+ using dylib_list = Modules::dylib_list;
+
+ modules.filters().insert(modules.filters().begin(),
+ std::make_shared<dylib>("", dylib_type::Filter));
+
+ for (dylib_ptr& x : modules.trackers())
+ ui.iconcomboTrackerSource->addItem(x->icon, x->name, x->module_name);
+
+ for (dylib_ptr& x : modules.protocols())
+ ui.iconcomboProtocol->addItem(x->icon, x->name, x->module_name);
+
+ for (dylib_ptr& x : modules.filters())
+ ui.iconcomboFilter->addItem(x->icon, x->name, x->module_name);
+
+ connect(ui.iconcomboTrackerSource, &QComboBox::currentTextChanged,
+ this, [&](const QString&) { pTrackerDialog = nullptr; });
+
+ connect(ui.iconcomboProtocol, &QComboBox::currentTextChanged,
+ this, [&](const QString&) { pProtocolDialog = nullptr; });
+
+ connect(ui.iconcomboFilter, &QComboBox::currentTextChanged,
+ this, [&](const QString&) { pFilterDialog = nullptr; });
+
+ connect(&m.tracker_dll, value_::value_changed<QString>(),
+ this, &main_window::save_modules,
+ Qt::DirectConnection);
+
+ connect(&m.protocol_dll, value_::value_changed<QString>(),
+ this, &main_window::save_modules,
+ Qt::DirectConnection);
+
+ connect(&m.filter_dll, value_::value_changed<QString>(),
+ this, &main_window::save_modules,
+ Qt::DirectConnection);
+
+ {
+ struct list {
+ dylib_list& libs;
+ QComboBox* input;
+ value<QString>& place;
+ };
+
+ list types[] {
+ { modules.trackers(), ui.iconcomboTrackerSource, m.tracker_dll },
+ { modules.protocols(), ui.iconcomboProtocol, m.protocol_dll },
+ { modules.filters(), ui.iconcomboFilter, m.filter_dll },
+ };
+
+ for (list& type : types)
+ {
+ list t = type;
+ tie_setting(t.place, t.input,
+ [t](const QString& name) {
+ auto [ptr, idx] = module_by_name(name, t.libs);
+ return idx;
+ },
+ [t](int, const QVariant& userdata) {
+ auto [ptr, idx] = module_by_name(userdata.toString(), t.libs);
+ if (ptr)
+ return ptr->module_name;
+ else
+ return QString();
+ });
+ }
+ }
+}
+
+void main_window::init_profiles()
+{
+ refresh_profile_list();
+ // implicitly created by `ini_directory()'
+ if (ini_directory().isEmpty() || !QDir(ini_directory()).isReadable())
+ die_on_profile_not_writable();
+
+ set_profile(ini_filename());
+
+ // profile menu
+ profile_menu.addAction(tr("Create new empty config"), this, &main_window::create_empty_profile);
+ profile_menu.addAction(tr("Create new copied config"), this, &main_window::create_copied_profile);
+ profile_menu.addAction(tr("Open configuration directory"), this, &main_window::open_profile_directory);
+ ui.profile_button->setMenu(&profile_menu);
+
+ connect(&profile_list_timer, &QTimer::timeout, this, &main_window::refresh_profile_list);
+ profile_list_timer.start(1000 * 5);
+
+ connect(ui.iconcomboProfile, &QComboBox::currentTextChanged,
+ this, [this](const QString& x) { main_window::set_profile(x); });
+}
+
+void main_window::init_tray_menu()
+{
+ tray_menu.clear();
+
+ QString display_name(opentrack_version);
+ if (display_name.startsWith("opentrack-"))
+ display_name = tr("opentrack") + " " + display_name.mid(sizeof("opentrack-") - 1);
+ if (display_name.endsWith("-DEBUG"))
+ display_name.replace(display_name.size() - int(sizeof("DEBUG")), display_name.size(), tr(" (debug)"));
+
+ menu_action_header.setEnabled(false);
+ menu_action_header.setText(display_name);
+ menu_action_header.setIcon(QIcon(":/images/opentrack.png"));
+ tray_menu.addAction(&menu_action_header);
+
+ menu_action_show.setIconVisibleInMenu(true);
+ menu_action_show.setText(isHidden() ? tr("Show the Octopus") : tr("Hide the Octopus"));
+ menu_action_show.setIcon(QIcon(":/images/opentrack.png"));
+ QObject::connect(&menu_action_show, &QAction::triggered, this, [&] { toggle_restore_from_tray(QSystemTrayIcon::Trigger); });
+ tray_menu.addAction(&menu_action_show);
+
+ tray_menu.addSeparator();
+
+ menu_action_tracker.setText(tr("Tracker settings"));
+ menu_action_tracker.setIcon(QIcon(":/images/tools.png"));
+ QObject::connect(&menu_action_tracker, &QAction::triggered, this, &main_window::show_tracker_settings);
+ tray_menu.addAction(&menu_action_tracker);
+
+ menu_action_filter.setText(tr("Filter settings"));
+ menu_action_filter.setIcon(QIcon(":/images/filter-16.png"));
+ QObject::connect(&menu_action_filter, &QAction::triggered, this, &main_window::show_filter_settings);
+ tray_menu.addAction(&menu_action_filter);
+
+ menu_action_proto.setText(tr("Protocol settings"));
+ menu_action_proto.setIcon(QIcon(":/images/settings16.png"));
+ QObject::connect(&menu_action_proto, &QAction::triggered, this, &main_window::show_proto_settings);
+ tray_menu.addAction(&menu_action_proto);
+
+ tray_menu.addSeparator();
+
+ menu_action_mappings.setIcon(QIcon(":/images/curves.png"));
+ menu_action_mappings.setText(tr("Mappings"));
+ QObject::connect(&menu_action_mappings, &QAction::triggered, this, &main_window::show_mapping_window);
+ tray_menu.addAction(&menu_action_mappings);
+
+ menu_action_options.setIcon(QIcon(":/images/tools.png"));
+ menu_action_options.setText(tr("Options"));
+ QObject::connect(&menu_action_options, &QAction::triggered, this, &main_window::show_options_dialog);
+ tray_menu.addAction(&menu_action_options);
+
+ tray_menu.addSeparator();
+
+ menu_action_exit.setText(tr("Exit"));
+ QObject::connect(&menu_action_exit, &QAction::triggered, this, &main_window::exit);
+ tray_menu.addAction(&menu_action_exit);
+
+ connect(&s.tray_enabled, value_::value_changed<bool>(),
+ this, &main_window::ensure_tray);
+
+ ensure_tray();
+}
+
+void main_window::init_buttons()
+{
+ update_button_state(false, false);
+ connect(ui.btnEditCurves, &QPushButton::clicked, this, &main_window::show_mapping_window);
+ connect(ui.btnShortcuts, &QPushButton::clicked, this, &main_window::show_options_dialog);
+ connect(ui.btnShowEngineControls, &QPushButton::clicked, this, &main_window::show_tracker_settings);
+ connect(ui.btnShowServerControls, &QPushButton::clicked, this, &main_window::show_proto_settings);
+ connect(ui.btnShowFilterControls, &QPushButton::clicked, this, &main_window::show_filter_settings);
+ connect(ui.btnStartTracker, &QPushButton::clicked, this, &main_window::start_tracker_);
+ connect(ui.btnStopTracker, &QPushButton::clicked, this, &main_window::stop_tracker_);
+}
+
+void main_window::register_shortcuts()
+{
+ global_shortcuts.reload({
+ { s.key_start_tracking1, [this](bool) { start_tracker(); }, true },
+ { s.key_start_tracking2, [this](bool) { start_tracker(); }, true },
+
+ { s.key_stop_tracking1, [this](bool) { stop_tracker(); }, true },
+ { s.key_stop_tracking2, [this](bool) { stop_tracker(); }, true },
+
+ { s.key_toggle_tracking1, [this](bool) { toggle_tracker(); }, true },
+ { s.key_toggle_tracking2, [this](bool) { toggle_tracker(); }, true },
+
+ { s.key_restart_tracking1, [this](bool) { restart_tracker(); }, true },
+ { s.key_restart_tracking2, [this](bool) { restart_tracker(); }, true },
+ });
+
+ if (work)
+ work->reload_shortcuts();
+}
+
+void main_window::die_on_profile_not_writable()
+{
+ stop_tracker_();
+
+ static const QString pad(16, QChar(' '));
+
+ QMessageBox::critical(this,
+ tr("The Octopus is sad"),
+ tr("Check permissions for your .ini directory:\n\n\"%1\"%2\n\nExiting now.").arg(ini_directory(), pad),
+ QMessageBox::Close, QMessageBox::NoButton);
+
+ exit(EX_OSFILE);
+}
+
+bool main_window::profile_name_from_dialog(QString& ret)
+{
+ new_file_dialog dlg;
+ dlg.exec();
+ return dlg.is_ok(ret);
+}
+
+main_window::~main_window()
+{
+ // stupid ps3 eye has LED issues
+ if (work && ui.video_frame->layout())
+ {
+ hide();
+ stop_tracker_();
+ close();
+
+ constexpr int inc = 25, max = 1000;
+
+ for (int k = 0; k < max; k += inc)
+ {
+ QEventLoop ev;
+ ev.processEvents();
+ portable::sleep(inc);
+ }
+ }
+
+ exit();
+}
+
+void main_window::save_modules()
+{
+ m.b->save();
+}
+
+void main_window::create_empty_profile()
+{
+ QString name;
+ if (profile_name_from_dialog(name))
+ {
+ QFile(ini_combine(name)).open(QFile::ReadWrite);
+ refresh_profile_list();
+
+ if (profile_list.contains(name))
+ {
+ QSignalBlocker q(ui.iconcomboProfile);
+
+ set_profile(name, false);
+ mark_profile_as_not_needing_migration();
+ }
+ }
+}
+
+void main_window::create_copied_profile()
+{
+ const QString cur = ini_pathname();
+ QString name;
+ if (!cur.isEmpty() && profile_name_from_dialog(name))
+ {
+ const QString new_name = ini_combine(name);
+ (void) QFile::remove(new_name);
+ QFile::copy(cur, new_name);
+
+ refresh_profile_list();
+
+ if (profile_list.contains(name))
+ {
+ QSignalBlocker q(ui.iconcomboProfile);
+
+ set_profile(name, false);
+ mark_profile_as_not_needing_migration();
+ }
+ }
+
+}
+
+void main_window::open_profile_directory()
+{
+ QDesktopServices::openUrl("file:///" + QDir::toNativeSeparators(ini_directory()));
+}
+
+void main_window::refresh_profile_list()
+{
+ if (work)
+ return;
+
+ QStringList list = ini_list();
+ QString current = ini_filename();
+
+ if (list == profile_list)
+ return;
+
+ if (!list.contains(current))
+ current = OPENTRACK_DEFAULT_PROFILE;
+
+ profile_list = list;
+
+ static const QIcon icon(":/images/settings16.png");
+
+ QSignalBlocker l(ui.iconcomboProfile);
+
+ ui.iconcomboProfile->clear();
+ ui.iconcomboProfile->addItems(list);
+
+ for (int i = 0; i < list.size(); i++)
+ ui.iconcomboProfile->setItemIcon(i, icon);
+
+ ui.iconcomboProfile->setCurrentText(current);
+}
+
+
+
+void main_window::update_button_state(bool running, bool inertialp)
+{
+ bool not_running = !running;
+ ui.iconcomboProfile->setEnabled(not_running);
+ ui.btnStartTracker->setEnabled(not_running);
+ ui.btnStopTracker->setEnabled(running);
+ ui.iconcomboProtocol->setEnabled(not_running);
+ ui.iconcomboFilter->setEnabled(not_running);
+ ui.iconcomboTrackerSource->setEnabled(not_running);
+ ui.profile_button->setEnabled(not_running);
+ ui.video_frame_label->setVisible(not_running || inertialp);
+ if(not_running)
+ {
+ ui.video_frame_label->setPixmap(QPixmap(":/images/tracking-not-started.png"));
+ }
+ else {
+ ui.video_frame_label->setPixmap(QPixmap(":/images/no-feed.png"));
+ }
+}
+
+void main_window::start_tracker_()
+{
+ if (work)
+ return;
+
+ work = std::make_shared<Work>(pose, ev, ui.video_frame, current_tracker(), current_protocol(), current_filter());
+
+ if (!work->is_ok())
+ {
+ work = nullptr;
+ return;
+ }
+
+ {
+ double p[6] = {0,0,0, 0,0,0};
+ show_pose_(p, p);
+ }
+
+ if (pTrackerDialog)
+ pTrackerDialog->register_tracker(work->libs.pTracker.get());
+
+ if (pFilterDialog)
+ pFilterDialog->register_filter(work->libs.pFilter.get());
+
+ if (pProtocolDialog)
+ pProtocolDialog->register_protocol(work->libs.pProtocol.get());
+
+ pose_update_timer.start(50);
+
+ // NB check valid since SelectedLibraries ctor called
+ // trackers take care of layout state updates
+ const bool is_inertial = ui.video_frame->layout() == nullptr;
+ update_button_state(true, is_inertial);
+
+ ui.btnStopTracker->setFocus();
+}
+
+void main_window::stop_tracker_()
+{
+ if (!work)
+ return;
+
+ force_is_visible(true);
+ with_tracker_teardown sentinel;
+
+ pose_update_timer.stop();
+ ui.pose_display->present(0,0,0, 0,0,0);
+
+ if (pTrackerDialog)
+ pTrackerDialog->unregister_tracker();
+
+ if (pProtocolDialog)
+ pProtocolDialog->unregister_protocol();
+
+ if (pFilterDialog)
+ pFilterDialog->unregister_filter();
+
+ work = nullptr;
+
+ {
+ double p[6] {};
+ show_pose_(p, p);
+ }
+
+ update_button_state(false, false);
+ set_title();
+ ui.btnStartTracker->setFocus();
+}
+
+void main_window::show_pose_(const double* mapped, const double* raw)
+{
+ ui.pose_display->present(mapped[Yaw], mapped[Pitch], -mapped[Roll],
+ mapped[TX], mapped[TY], mapped[TZ]);
+
+ QLCDNumber* raw_[] = {
+ ui.raw_x, ui.raw_y, ui.raw_z,
+ ui.raw_yaw, ui.raw_pitch, ui.raw_roll,
+ };
+
+ QLCDNumber* mapped_[] = {
+ ui.pose_x, ui.pose_y, ui.pose_z,
+ ui.pose_yaw, ui.pose_pitch, ui.pose_roll,
+ };
+
+ for (int k = 0; k < 6; k++)
+ {
+ raw_[k]->display(iround(raw[k]));
+ mapped_[k]->display(iround(mapped[k]));
+ }
+
+ QString game_title;
+ if (work && work->libs.pProtocol)
+ game_title = work->libs.pProtocol->game_name();
+ set_title(game_title);
+}
+
+void main_window::set_title(const QString& game_title)
+{
+ static const QString version{opentrack_version};
+ static const QString sep { tr(" :: ") };
+ static const QString pat1{ version + sep + "%1" + sep + "%2" };
+ static const QString pat2{ version + sep + "%1" };
+
+ const QString current = ini_filename();
+
+ if (game_title.isEmpty())
+ setWindowTitle(pat2.arg(current));
+ else
+ setWindowTitle(pat1.arg(current, game_title));
+}
+
+void main_window::show_pose()
+{
+ set_is_visible(*this);
+
+ if (mapping_widget)
+ mapping_widget->refresh_tab();
+
+ if (!check_is_visible())
+ return;
+
+ double mapped[6], raw[6];
+
+ work->pipeline_.raw_and_mapped_pose(mapped, raw);
+
+ show_pose_(mapped, raw);
+}
+
+static void show_window(QWidget& d, bool fresh)
+{
+ if (fresh)
+ {
+ d.setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | d.windowFlags());
+ d.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+ d.show();
+ d.adjustSize();
+ d.raise();
+ }
+ else
+ {
+ d.show();
+ d.raise();
+ }
+}
+
+template<typename t, typename F>
+static bool mk_window_common(std::unique_ptr<t>& d, F&& fun)
+{
+ bool fresh = false;
+
+ if (!d)
+ d = fun(), fresh = !!d;
+
+ if (d)
+ show_window(*d, fresh);
+
+ return fresh;
+}
+
+template<typename t, typename... Args>
+static bool mk_window(std::unique_ptr<t>& place, Args&&... params)
+{
+ return mk_window_common(place, [&] {
+ return std::make_unique<t>(params...);
+ });
+}
+
+template<typename t>
+static bool mk_dialog(std::unique_ptr<t>& place, const std::shared_ptr<dylib>& lib)
+{
+ using u = std::unique_ptr<t>;
+
+ return mk_window_common(place, [&] {
+ if (lib && lib->Dialog)
+ return u{ (t*)lib->Dialog() };
+ else
+ return u{};
+ });
+}
+
+void main_window::show_tracker_settings()
+{
+ if (mk_dialog(pTrackerDialog, current_tracker()) && work && work->libs.pTracker)
+ pTrackerDialog->register_tracker(work->libs.pTracker.get());
+ if (pTrackerDialog)
+ QObject::connect(pTrackerDialog.get(), &ITrackerDialog::closing,
+ this, [this] { pTrackerDialog = nullptr; });
+}
+
+void main_window::show_proto_settings()
+{
+ if (mk_dialog(pProtocolDialog, current_protocol()) && work && work->libs.pProtocol)
+ pProtocolDialog->register_protocol(work->libs.pProtocol.get());
+ if (pProtocolDialog)
+ QObject::connect(pProtocolDialog.get(), &IProtocolDialog::closing,
+ this, [this] { pProtocolDialog = nullptr; });
+}
+
+void main_window::show_filter_settings()
+{
+ if (mk_dialog(pFilterDialog, current_filter()) && work && work->libs.pFilter)
+ pFilterDialog->register_filter(work->libs.pFilter.get());
+ if (pFilterDialog)
+ QObject::connect(pFilterDialog.get(), &IFilterDialog::closing,
+ this, [this] { pFilterDialog = nullptr; });
+}
+
+void main_window::show_options_dialog()
+{
+ if (mk_window(options_widget, [&](bool flag) { set_keys_enabled(!flag); }))
+ {
+ // XXX this should logically connect to a bundle
+ // also doesn't work when switching profiles with options dialog open
+ // move shortcuts to a separate bundle and add a migration -sh 20180218
+ connect(options_widget.get(), &options_dialog::closing,
+ this, &main_window::register_shortcuts);
+ }
+}
+
+void main_window::show_mapping_window()
+{
+ mk_window(mapping_widget, pose);
+}
+
+void main_window::exit(int status)
+{
+ // don't use std::call_once here, leads to freeze in Microsoft's CRT
+ // this function never needs reentrancy anyway
+
+ // this is probably harmless, but better safe than sorry
+ if (exiting_already)
+ return;
+ exiting_already = true;
+
+ qDebug() << "opentrack: exiting";
+
+ if (tray)
+ tray->hide();
+ tray = nullptr;
+
+ //close();
+ QApplication::setQuitOnLastWindowClosed(true);
+ QApplication::exit(status);
+}
+
+void main_window::set_profile(const QString& new_name_, bool migrate)
+{
+ QSignalBlocker b(ui.iconcomboProfile);
+
+ QString new_name = new_name_;
+
+ if (!profile_list.contains(new_name))
+ {
+ new_name = OPENTRACK_DEFAULT_PROFILE;
+ if (!profile_list.contains(new_name))
+ migrate = false;
+ }
+
+ const bool status = new_name != ini_filename();
+
+ if (status)
+ set_profile_in_registry(new_name);
+
+ using bundler = options::detail::bundler;
+
+ bundler::reload_no_notify();
+
+ if (migrate)
+ // migrations are for config layout changes and other user-visible
+ // incompatibilities in future versions
+ run_migrations();
+ else
+ mark_profile_as_not_needing_migration();
+
+ bundler::notify();
+
+ set_title();
+
+ if (status)
+ ui.iconcomboProfile->setCurrentText(new_name);
+}
+
+void main_window::ensure_tray()
+{
+ if (!QSystemTrayIcon::isSystemTrayAvailable())
+ {
+ QApplication::setQuitOnLastWindowClosed(true);
+ return;
+ }
+
+ if (s.tray_enabled)
+ {
+ if (!tray)
+ {
+ tray = std::make_unique<QSystemTrayIcon>(this);
+ tray->setIcon(QIcon(":/images/opentrack.png"));
+ tray->setContextMenu(&tray_menu);
+ tray->show();
+
+ connect(tray.get(),
+ &QSystemTrayIcon::activated,
+ this,
+ &main_window::toggle_restore_from_tray);
+ }
+
+ QApplication::setQuitOnLastWindowClosed(false);
+ }
+ else
+ {
+ if (!isVisible())
+ {
+ show();
+ setVisible(true);
+
+ raise(); // for OSX
+ activateWindow(); // for Windows
+ }
+
+ if (tray)
+ tray->hide();
+ tray = nullptr;
+
+ QApplication::setQuitOnLastWindowClosed(true);
+ }
+}
+
+void main_window::toggle_restore_from_tray(QSystemTrayIcon::ActivationReason e)
+{
+ switch (e)
+ {
+ // if we enable double click also then it causes
+ // toggle back to the original state
+ //case QSystemTrayIcon::DoubleClick:
+ case QSystemTrayIcon::Trigger: // single click
+ break;
+ default:
+ return;
+ }
+
+ ensure_tray();
+
+ const bool is_minimized = isHidden() || !tray_enabled();
+
+ menu_action_show.setText(!isHidden() ? tr("Show the Octopus") : tr("Hide the Octopus"));
+
+ setVisible(is_minimized);
+ setHidden(!is_minimized);
+
+ setWindowState(is_minimized ? windowState() & ~Qt::WindowMinimized : Qt::WindowNoState);
+
+ if (is_minimized)
+ {
+ raise(); // for OSX
+ activateWindow(); // for Windows
+ }
+ else
+ {
+ lower();
+ clearFocus();
+ }
+}
+
+bool main_window::maybe_hide_to_tray(QEvent* e)
+{
+ if (e->type() == QEvent::WindowStateChange &&
+ (windowState() & Qt::WindowMinimized) &&
+ tray_enabled())
+ {
+ e->accept();
+ ensure_tray();
+ hide();
+
+ return true;
+ }
+
+ return false;
+}
+
+void main_window::closeEvent(QCloseEvent*)
+{
+ exit();
+}
+
+void main_window::maybe_start_profile_from_executable()
+{
+ if (!work)
+ {
+ QString profile;
+ if (det.profile_to_start(profile) && profile_list.contains(profile))
+ {
+ set_profile(profile);
+ start_tracker_();
+ }
+ }
+ else
+ {
+ if (det.should_stop())
+ stop_tracker_();
+ }
+}
+
+void main_window::set_keys_enabled(bool flag)
+{
+ if (!flag)
+ {
+ if (work)
+ work->sc.reload({});
+ global_shortcuts.reload({});
+ }
+ else
+ register_shortcuts();
+}
+
+void main_window::changeEvent(QEvent* e)
+{
+ if (!maybe_hide_to_tray(e))
+ e->ignore();
+}
+
+bool main_window::event(QEvent* event)
+{
+ using t = QEvent::Type;
+
+ if (work)
+ {
+ switch (event->type())
+ {
+ case t::Hide:
+ case t::WindowActivate:
+ case t::WindowDeactivate:
+ case t::WindowStateChange:
+ case t::FocusIn:
+ set_is_visible(*this, true);
+ break;
+ default:
+ break;
+ }
+ }
+ return QMainWindow::event(event);
+}
+
+bool main_window::tray_enabled()
+{
+ return s.tray_enabled && QSystemTrayIcon::isSystemTrayAvailable();
+}
+
+bool main_window::start_in_tray()
+{
+ return tray_enabled() && s.tray_start;
+}
+
+void main_window::set_profile_in_registry(const QString &profile)
+{
+ with_global_settings_object([&](QSettings& s) {
+ s.setValue(OPENTRACK_PROFILE_FILENAME_KEY, profile);
+ });
+}
+
+void main_window::restart_tracker_()
+{
+ qDebug() << "restart tracker";
+
+ stop_tracker_();
+ start_tracker_();
+}
+
+void main_window::toggle_tracker_()
+{
+ qDebug() << "toggle tracker";
+
+ if (work)
+ stop_tracker_();
+ else
+ start_tracker_();
+}
+
+#if !defined _WIN32
+# include <unistd.h>
+void main_window::annoy_if_root()
+{
+ if (geteuid() == 0)
+ {
+ struct lst {
+ QString caption;
+ QString msg;
+ int sleep_ms;
+ };
+
+ const lst list[] = {
+ {
+ tr("Running as root is bad"),
+ tr("Do not run as root. Set correct device node permissions."),
+ 1000,
+ },
+ {
+ tr("Running as root is bad, seriously"),
+ tr("Do not run as root. I'll keep whining at every startup."),
+ 3000,
+ },
+ {
+ tr("Be annoyed, comprehensively."),
+ tr("Don't run as root to remove these annoying messages."),
+ 0
+ }
+ };
+
+ for (const auto& x : list)
+ {
+ QMessageBox::critical(this, x.caption, x.msg, QMessageBox::Ok);
+ portable::sleep(x.sleep_ms);
+ }
+ }
+}
+#endif
diff --git a/opentrack/main-window.hpp b/opentrack/main-window.hpp
new file mode 100644
index 00000000..9ffb7019
--- /dev/null
+++ b/opentrack/main-window.hpp
@@ -0,0 +1,145 @@
+/* Copyright (c) 2013-2016, Stanislaw Halik <sthalik@misaki.pl>
+
+ * Permission to use, copy, modify, and/or distribute this
+ * software for any purpose with or without fee is hereby granted,
+ * provided that the above copyright notice and this permission
+ * notice appear in all copies.
+ */
+
+#pragma once
+
+#include "api/plugin-support.hpp"
+#include "gui/mapping-dialog.hpp"
+#include "gui/settings.hpp"
+#include "gui/process_detector.h"
+#include "logic/main-settings.hpp"
+#include "logic/pipeline.hpp"
+#include "logic/shortcuts.h"
+#include "logic/work.hpp"
+#include "logic/state.hpp"
+#include "options/options.hpp"
+#include "compat/qt-signal.hpp"
+
+#include <QApplication>
+#include <QMainWindow>
+#include <QKeySequence>
+#include <QShortcut>
+#include <QPixmap>
+#include <QTimer>
+#include <QSystemTrayIcon>
+#include <QString>
+#include <QMenu>
+#include <QAction>
+#include <QEvent>
+#include <QCloseEvent>
+#include <QList>
+
+#include <tuple>
+#include <memory>
+
+#include "ui_main-window.h"
+
+class main_window final : public QMainWindow, private State
+{
+ Q_DECLARE_TR_FUNCTIONS(main_window)
+
+ Ui::main_window ui;
+
+ std::unique_ptr<QSystemTrayIcon> tray;
+ QMenu tray_menu { this };
+
+ QTimer pose_update_timer { this };
+ QTimer det_timer;
+ QTimer profile_list_timer;
+
+ Shortcuts global_shortcuts;
+ QShortcut kbd_quit { QKeySequence("Ctrl+Q"), this };
+
+ std::unique_ptr<options_dialog> options_widget;
+ std::unique_ptr<mapping_dialog> mapping_widget;
+
+ std::unique_ptr<IFilterDialog> pFilterDialog;
+ std::unique_ptr<IProtocolDialog> pProtocolDialog;
+ std::unique_ptr<ITrackerDialog> pTrackerDialog;
+
+ process_detector_worker det;
+ QMenu profile_menu;
+
+ QList<QString> profile_list;
+
+ QAction menu_action_header { &tray_menu },
+ menu_action_show { &tray_menu },
+ menu_action_exit { &tray_menu },
+ menu_action_tracker { &tray_menu },
+ menu_action_filter { &tray_menu },
+ menu_action_proto { &tray_menu },
+ menu_action_options { &tray_menu },
+ menu_action_mappings { &tray_menu };
+
+ bool exiting_already { false };
+
+ qt_sig::nullary start_tracker { this, &main_window::start_tracker_, Qt::QueuedConnection };
+ qt_sig::nullary stop_tracker { this, &main_window::stop_tracker_, Qt::QueuedConnection };
+ qt_sig::nullary toggle_tracker { this, &main_window::toggle_tracker_, Qt::QueuedConnection };
+ qt_sig::nullary restart_tracker { this, &main_window::restart_tracker_, Qt::QueuedConnection };
+
+ void init_dylibs();
+ void init_tray_menu();
+ void init_profiles();
+ void init_buttons();
+
+ void init_shortcuts();
+ void register_shortcuts();
+ void set_keys_enabled(bool flag);
+
+ void update_button_state(bool running, bool inertialp);
+
+#if !defined _WIN32
+ void annoy_if_root();
+#endif
+
+ void changeEvent(QEvent* e) override;
+ bool maybe_hide_to_tray(QEvent* e);
+ void closeEvent(QCloseEvent *event) override;
+ bool event(QEvent *event) override;
+
+ void show_tracker_settings();
+ void show_proto_settings();
+ void show_filter_settings();
+
+ void show_options_dialog();
+ void show_mapping_window();
+
+ void show_pose();
+ void show_pose_(const double* mapped, const double* raw);
+ void set_title(const QString& game_title = QString());
+
+ void start_tracker_();
+ void stop_tracker_();
+ void restart_tracker_();
+ void toggle_tracker_();
+
+ void set_profile(const QString& new_name, bool migrate = true);
+ void set_profile_in_registry(const QString& profile);
+ void refresh_profile_list();
+ void die_on_profile_not_writable();
+ void maybe_start_profile_from_executable();
+ [[nodiscard]] static bool profile_name_from_dialog(QString& ret);
+
+ void create_empty_profile();
+ void create_copied_profile();
+ void open_profile_directory();
+
+ void ensure_tray();
+ void toggle_restore_from_tray(QSystemTrayIcon::ActivationReason e);
+ bool tray_enabled();
+ bool start_in_tray();
+
+ void save_modules();
+
+ void exit(int status = EXIT_SUCCESS);
+
+public:
+ main_window();
+ ~main_window() override;
+};
diff --git a/opentrack/main-window.ui b/opentrack/main-window.ui
new file mode 100644
index 00000000..7aab90e8
--- /dev/null
+++ b/opentrack/main-window.ui
@@ -0,0 +1,1459 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <author>Lovecraftian Octopus</author>
+ <class>main_window</class>
+ <widget class="QMainWindow" name="main_window">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>646</width>
+ <height>517</height>
+ </rect>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../../gui/opentrack-res.qrc">
+ <normaloff>:/images/opentrack.png</normaloff>:/images/opentrack.png</iconset>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">#video_feed { border: 0; }
+</string>
+ </property>
+ <widget class="QWidget" name="content">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QFrame" name="frame">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignLeft|Qt::AlignTop">
+ <widget class="QWidget" name="top" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item alignment="Qt::AlignLeft|Qt::AlignTop">
+ <widget class="QFrame" name="video_feed">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <widget class="QFrame" name="video_frame">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>320</width>
+ <height>240</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <widget class="QLabel" name="video_frame_label">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>320</width>
+ <height>240</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <family>Candara</family>
+ <pointsize>37</pointsize>
+ <weight>50</weight>
+ <bold>false</bold>
+ <kerning>true</kerning>
+ </font>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../gui/opentrack-res.qrc">:/images/tracking-not-started.png</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="pose_widget" name="pose_display" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="top_display">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="box_raw_headpose">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Raw tracker data</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_12">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="3">
+ <widget class="QLCDNumber" name="raw_yaw">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Outline</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="lblZ_4">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="lblRotY_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLCDNumber" name="raw_pitch">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Outline</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblY_4">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblX_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLCDNumber" name="raw_x">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Outline</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="lblRotZ_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="lblRotX_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLCDNumber" name="raw_y">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Outline</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QLCDNumber" name="raw_roll">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Outline</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLCDNumber" name="raw_z">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Outline</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="box_mapped_headpose">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Game data</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_14">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="1" column="3">
+ <widget class="QLCDNumber" name="pose_pitch">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLCDNumber" name="pose_yaw">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLCDNumber" name="pose_z">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblx">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblY_2">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLCDNumber" name="pose_x">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="lblRotY_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Pitch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="lblRotZ_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Roll</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="lblRotX_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Yaw</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="lblZ_2">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Z</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLCDNumber" name="pose_y">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QLCDNumber" name="pose_roll">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <stylestrategy>NoAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="smallDecimalPoint">
+ <bool>true</bool>
+ </property>
+ <property name="digitCount">
+ <number>4</number>
+ </property>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="bottom_controls">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item row="0" column="1">
+ <widget class="QFrame" name="groupWindows">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QFrame" name="groupProfile">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolButton" name="profile_button">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string>Profile</string>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ <property name="arrowType">
+ <enum>Qt::DownArrow</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="iconcomboProfile">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="maxVisibleItems">
+ <number>20</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnShortcuts">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Options</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../gui/opentrack-res.qrc">
+ <normaloff>:/images/tools.png</normaloff>:/images/tools.png</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>80</width>
+ <height>24</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnEditCurves">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Mapping</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../gui/opentrack-res.qrc">
+ <normaloff>:/images/curves.png</normaloff>:/images/curves.png</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>80</width>
+ <height>24</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupStartStop">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>3</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Tracking</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>8</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QToolButton" name="btnStartTracker">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Start</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="btnStopTracker">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Stop</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QFrame" name="frame_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>8</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupTrackerSource">
+ <property name="title">
+ <string>Input</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>3</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="iconcomboTrackerSource">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::TabFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QToolButton" name="btnShowEngineControls">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>DejaVu Sans</family>
+ <stylestrategy>PreferAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <property name="text">
+ <string>🔨</string>
+ </property>
+ <property name="flat" stdset="0">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupGameProtocol">
+ <property name="title">
+ <string>Output</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>3</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="iconcomboProtocol">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::TabFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QToolButton" name="btnShowServerControls">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>DejaVu Sans</family>
+ <stylestrategy>PreferAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <property name="text">
+ <string>🔨</string>
+ </property>
+ <property name="flat" stdset="0">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupFilter">
+ <property name="title">
+ <string>Filter</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>3</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="iconcomboFilter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::TabFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QToolButton" name="btnShowFilterControls">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>DejaVu Sans</family>
+ <stylestrategy>PreferAntialias</stylestrategy>
+ <kerning>false</kerning>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <property name="text">
+ <string>🔨</string>
+ </property>
+ <property name="flat" stdset="0">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>pose_widget</class>
+ <extends>QWidget</extends>
+ <header>pose-widget/pose-widget.hpp</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>btnStartTracker</tabstop>
+ <tabstop>btnStopTracker</tabstop>
+ <tabstop>profile_button</tabstop>
+ <tabstop>iconcomboProfile</tabstop>
+ <tabstop>btnShortcuts</tabstop>
+ <tabstop>btnEditCurves</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../../gui/opentrack-res.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/opentrack/main.cpp b/opentrack/main.cpp
new file mode 100644
index 00000000..2c1dc607
--- /dev/null
+++ b/opentrack/main.cpp
@@ -0,0 +1,23 @@
+#include "gui/init.hpp"
+#include "main-window.hpp"
+
+#if defined _WIN32
+# include <windows.h>
+#endif
+
+#ifdef __clang__
+# pragma GCC diagnostic ignored "-Wmain"
+#endif
+
+int main(int argc, char** argv)
+{
+ return run_application(argc, argv, [] { return std::make_unique<main_window>(); });
+}
+
+#if defined _MSC_VER
+
+int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int /* nCmdShow */)
+{
+ return main(__argc, __argv);
+}
+#endif
diff --git a/opentrack/new_config.ui b/opentrack/new_config.ui
new file mode 100644
index 00000000..a262e725
--- /dev/null
+++ b/opentrack/new_config.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UI_new_config</class>
+ <widget class="QDialog" name="UI_new_config">
+ <property name="windowModality">
+ <enum>Qt::ApplicationModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>269</width>
+ <height>67</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Config filename</string>
+ </property>
+ <property name="windowIcon">
+ <iconset>
+ <normaloff>images/opentrack.png</normaloff>images/opentrack.png</iconset>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>New file name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/opentrack/new_file_dialog.cpp b/opentrack/new_file_dialog.cpp
new file mode 100644
index 00000000..70816c5d
--- /dev/null
+++ b/opentrack/new_file_dialog.cpp
@@ -0,0 +1,37 @@
+#include "new_file_dialog.h"
+
+new_file_dialog::new_file_dialog(QWidget* parent) : QDialog(parent)
+{
+ ui.setupUi(this);
+ connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(ok_clicked()));
+ connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(cancel_clicked()));
+ setFixedSize(size());
+}
+
+bool new_file_dialog::is_ok(QString& name_)
+{
+ name_ = name;
+ return ok;
+}
+
+void new_file_dialog::cancel_clicked() { close(); }
+
+void new_file_dialog::ok_clicked()
+{
+ QString text = ui.lineEdit->text();
+ text = text.replace('/', "");
+ text = text.replace('\\', "");
+ if (text != "" && !text.endsWith(".ini"))
+ text += ".ini";
+ if (text == "" || text == ".ini" || QFile(options::globals::ini_directory() + "/" + text).exists())
+ {
+ QMessageBox::warning(this,
+ tr("File exists"),
+ tr("This file already exists. Pick another name."),
+ QMessageBox::Ok, QMessageBox::NoButton);
+ return;
+ }
+ ok = true;
+ close();
+ name = text;
+}
diff --git a/opentrack/new_file_dialog.h b/opentrack/new_file_dialog.h
new file mode 100644
index 00000000..7244e524
--- /dev/null
+++ b/opentrack/new_file_dialog.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "ui_new_config.h"
+#include "options/options.hpp"
+#include <QFile>
+#include <QRegExp>
+#include <QString>
+#include <QMessageBox>
+
+class new_file_dialog : public QDialog
+{
+ Q_OBJECT
+public:
+ new_file_dialog(QWidget* parent = nullptr);
+ bool is_ok(QString& name_);
+
+private:
+ Ui::UI_new_config ui;
+ bool ok = false;
+ QString name;
+
+private slots:
+ void cancel_clicked();
+ void ok_clicked();
+};
diff --git a/opentrack/opentrack.ico b/opentrack/opentrack.ico
new file mode 100644
index 00000000..5cac8da1
--- /dev/null
+++ b/opentrack/opentrack.ico
Binary files differ
diff --git a/opentrack/resources.rc b/opentrack/resources.rc
new file mode 100644
index 00000000..6fd0253b
--- /dev/null
+++ b/opentrack/resources.rc
@@ -0,0 +1,2 @@
+#include <windows.h>
+IDI_ICON1 ICON "opentrack.ico"