diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2016-09-19 15:13:48 +0200 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2016-09-19 15:13:48 +0200 |
commit | d993825ace9886d45d1519ed51d0fccae55da884 (patch) | |
tree | 9bdc7bdbf660019f726aae6f98abe2c63c9c8e3d | |
parent | eee2cffd96fa2d7312708768fac7e67f129c5970 (diff) | |
parent | 9a85b814d908a28fb7444b7e1b5afae9fe629784 (diff) |
Merge branch 'feature/tobii-eyex-support' into unstable
THIS IS UNFINISHED.
The code is built to be fixed in real time against internal API changes.
-rw-r--r-- | cmake/opentrack-boilerplate.cmake | 16 | ||||
-rw-r--r-- | tracker-tobii-eyex/CMakeLists.txt | 32 | ||||
-rw-r--r-- | tracker-tobii-eyex/images/tobii-eyex-logo.png | bin | 0 -> 1319 bytes | |||
-rw-r--r-- | tracker-tobii-eyex/tobii-eyex-dialog.cpp | 26 | ||||
-rw-r--r-- | tracker-tobii-eyex/tobii-eyex-dialog.ui | 630 | ||||
-rw-r--r-- | tracker-tobii-eyex/tobii-eyex-res.qrc | 5 | ||||
-rw-r--r-- | tracker-tobii-eyex/tobii-eyex.cpp | 413 | ||||
-rw-r--r-- | tracker-tobii-eyex/tobii-eyex.hpp | 132 |
8 files changed, 1248 insertions, 6 deletions
diff --git a/cmake/opentrack-boilerplate.cmake b/cmake/opentrack-boilerplate.cmake index 2e593138..4546de28 100644 --- a/cmake/opentrack-boilerplate.cmake +++ b/cmake/opentrack-boilerplate.cmake @@ -116,7 +116,7 @@ endfunction() function(opentrack_boilerplate n) message(STATUS "module ${n}") cmake_parse_arguments(arg - "STATIC;NO-COMPAT;BIN;EXECUTABLE;NO-QT;WIN32-CONSOLE" + "STATIC;NO-COMPAT;BIN;EXECUTABLE;NO-QT;WIN32-CONSOLE;NO-INSTALL" "LINK;COMPILE" "SOURCES" ${ARGN} @@ -169,7 +169,9 @@ function(opentrack_boilerplate n) target_link_libraries(${n} opentrack-api opentrack-options opentrack-compat) endif() - opentrack_install_sources(${n}) + if(NOT arg_NO-INSTALL) + opentrack_install_sources(${n}) + endif() opentrack_compat(${n}) if(CMAKE_COMPILER_IS_GNUCXX) @@ -192,10 +194,12 @@ function(opentrack_boilerplate n) string(REPLACE "-" "_" n_ ${n_}) target_compile_definitions(${n} PRIVATE "BUILD_${n_}") - if(arg_BIN AND WIN32) - install(TARGETS ${n} RUNTIME DESTINATION . LIBRARY DESTINATION .) - else() - install(TARGETS ${n} ${opentrack-hier-str}) + if(NOT arg_NO-INSTALL) + if(arg_BIN AND WIN32) + install(TARGETS ${n} RUNTIME DESTINATION . LIBRARY DESTINATION .) + else() + install(TARGETS ${n} ${opentrack-hier-str}) + endif() endif() endif() endfunction() diff --git a/tracker-tobii-eyex/CMakeLists.txt b/tracker-tobii-eyex/CMakeLists.txt new file mode 100644 index 00000000..9446072c --- /dev/null +++ b/tracker-tobii-eyex/CMakeLists.txt @@ -0,0 +1,32 @@ +if(WIN32) + set(SDK_TOBII_EYEX "" CACHE PATH "") + if(SDK_TOBII_EYEX) + opentrack_boilerplate(opentrack-tracker-tobii-eyex NO-INSTALL) + target_link_libraries(opentrack-tracker-tobii-eyex opentrack-spline-widget) + set(tobii-libdir ${SDK_TOBII_EYEX}/lib/x86/) + set(tobii-dll ${tobii-libdir}/Tobii.EyeX.Client.dll) + # we only care about the .lib for MSVC++ build anyway + set(tobii-link ${tobii-libdir}/Tobii.EyeX.Client.lib) + target_include_directories(opentrack-tracker-tobii-eyex PRIVATE ${CMAKE_SOURCE_DIR}/spline-widget) + target_link_libraries(opentrack-tracker-tobii-eyex ${tobii-link} opentrack-spline-widget) + # we only ever use the C headers due to Microsoft CRT ABI incompatibility with GNU + set(tobii-incdir ${SDK_TOBII_EYEX}/include/eyex) + target_include_directories(opentrack-tracker-tobii-eyex SYSTEM PUBLIC ${tobii-incdir}) + + if(FALSE) + install(FILES ${tobii-dll} DESTINATION ${opentrack-hier-pfx} ${opentrack-perms}) + endif() + if((CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") AND (CMAKE_SYSTEM_NAME STREQUAL "Windows")) + file(TO_CMAKE_PATH "$ENV{SystemRoot}" sysroot) + if (IS_DIRECTORY "${sysroot}/SysWOW64") + set(src "${sysroot}/SysWOW64") + else() + set(src "${sysroot}/System32") + endif() + if(FALSE) + install(FILES "${src}/msvcp110.dll" DESTINATION ${opentrack-hier-pfx} ${opentrack-perms}) + install(FILES "${src}/msvcr110.dll" DESTINATION ${opentrack-hier-pfx} ${opentrack-perms}) + endif() + endif() + endif() +endif() diff --git a/tracker-tobii-eyex/images/tobii-eyex-logo.png b/tracker-tobii-eyex/images/tobii-eyex-logo.png Binary files differnew file mode 100644 index 00000000..b952891b --- /dev/null +++ b/tracker-tobii-eyex/images/tobii-eyex-logo.png diff --git a/tracker-tobii-eyex/tobii-eyex-dialog.cpp b/tracker-tobii-eyex/tobii-eyex-dialog.cpp new file mode 100644 index 00000000..bb9ffcea --- /dev/null +++ b/tracker-tobii-eyex/tobii-eyex-dialog.cpp @@ -0,0 +1,26 @@ +#include "tobii-eyex.hpp" + +tobii_eyex_dialog::tobii_eyex_dialog() +{ + ui.setupUi(this); + + connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &tobii_eyex_dialog::do_ok); + connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &tobii_eyex_dialog::do_cancel); + + ui.tracking_mode->addItem("Relative", tobii_relative); + ui.tracking_mode->addItem("Absolute", tobii_absolute); + + tie_setting(s.mode, ui.tracking_mode); +} + +void tobii_eyex_dialog::do_ok() +{ + s.b->save(); + rs.b->save(); + close(); +} + +void tobii_eyex_dialog::do_cancel() +{ + close(); +} diff --git a/tracker-tobii-eyex/tobii-eyex-dialog.ui b/tracker-tobii-eyex/tobii-eyex-dialog.ui new file mode 100644 index 00000000..1a85f417 --- /dev/null +++ b/tracker-tobii-eyex/tobii-eyex-dialog.ui @@ -0,0 +1,630 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>tobii_eyex_dialog_widgets</class> + <widget class="QWidget" name="tobii_eyex_dialog_widgets"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>710</width> + <height>440</height> + </rect> + </property> + <property name="windowTitle"> + <string>Tracker options</string> + </property> + <property name="windowIcon"> + <iconset resource="tobii-eyex-res.qrc"> + <normaloff>:/images/tobii-eyex-logo.png</normaloff>:/images/tobii-eyex-logo.png</iconset> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Tracking settings</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Relative mode shifts the view toward a target that may be offscreen then fixes upon it.The absolute mode is not gradual.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QFrame" name="frame"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <property name="horizontalSpacing"> + <number>9</number> + </property> + <property name="verticalSpacing"> + <number>4</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Tracking mode</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="tracking_mode"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="0" column="2"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>510</width> + <height>17</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Relative tracking mode gain</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Adjust the gain mapping and speed to suit your preference, game type, display size, and distance from the screen.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QFrame" name="frame_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </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="QFrame" name="frame_3"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <property name="verticalSpacing"> + <number>3</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="label_11"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Speed</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="speed_label"> + <property name="minimumSize"> + <size> + <width>24</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Deadzone</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="deadzone_label"> + <property name="minimumSize"> + <size> + <width>24</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QSlider" name="deadzone"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>25</number> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_10"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Exponent</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="exponent_label"> + <property name="minimumSize"> + <size> + <width>24</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QSlider" name="exponent"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>25</number> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_5"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Exponential length</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="exponent_len_label"> + <property name="minimumSize"> + <size> + <width>24</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QSlider" name="exponent_len"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>25</number> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_7"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Linear coefficient</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="linear_c_label"> + <property name="minimumSize"> + <size> + <width>24</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QSlider" name="linear_c"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>25</number> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Linear length</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="linear_len_label"> + <property name="minimumSize"> + <size> + <width>24</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QSlider" name="linear_len"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>25</number> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_8"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Logarithm base</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QLabel" name="log_base_label"> + <property name="minimumSize"> + <size> + <width>24</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="6" column="2"> + <widget class="QSlider" name="log_base"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>25</number> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_9"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Logarithm coefficient</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QLabel" name="log_c_label"> + <property name="minimumSize"> + <size> + <width>24</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="7" column="2"> + <widget class="QSlider" name="log_c"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>25</number> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QSlider" name="speed"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>25</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="spline_widget" name="relative_mode_gain" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>150</height> + </size> + </property> + <property name="colorBezier" stdset="0"> + <color> + <red>192</red> + <green>32</green> + <blue>8</blue> + </color> + </property> + <property name="colorBackground" stdset="0"> + <color> + <red>240</red> + <green>240</green> + <blue>240</blue> + </color> + </property> + <property name="is_preview_only" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + <zorder>relative_mode_gain</zorder> + <zorder>frame_3</zorder> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>spline_widget</class> + <extends>QWidget</extends> + <header>spline-widget/spline-widget.hpp</header> + </customwidget> + </customwidgets> + <resources> + <include location="tobii-eyex-res.qrc"/> + </resources> + <connections/> +</ui> diff --git a/tracker-tobii-eyex/tobii-eyex-res.qrc b/tracker-tobii-eyex/tobii-eyex-res.qrc new file mode 100644 index 00000000..e3395df9 --- /dev/null +++ b/tracker-tobii-eyex/tobii-eyex-res.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>images/tobii-eyex-logo.png</file> + </qresource> +</RCC> diff --git a/tracker-tobii-eyex/tobii-eyex.cpp b/tracker-tobii-eyex/tobii-eyex.cpp new file mode 100644 index 00000000..74c31240 --- /dev/null +++ b/tracker-tobii-eyex/tobii-eyex.cpp @@ -0,0 +1,413 @@ +#include "tobii-eyex.hpp" +#include <cstdlib> +#include <cstdio> +#include <cmath> +#include <functional> +#include <vector> +#include <algorithm> +#include <iterator> +#include <QDebug> +#include <QMutexLocker> +#include <QMessageBox> + +// XXX TODO whole opentrack needs different debug levels -sh 20160801 + +//#define TOBII_EYEX_DEBUG_PRINTF +#define TOBII_EYEX_VERBOSE_PRINTF + +#ifdef TOBII_EYEX_VERBOSE_PRINTF +# define dbg_verbose(msg) (qDebug() << "tobii-eyex:" << (msg)) +#else +# define dbg_verbose(msg) (QMessageLogger().noDebug() << (msg)) +#endif + +#ifdef TOBII_EYEX_DEBUG_PRINTF +# define dbg_debug(msg) (qDebug() << "tobii-eyex:" << (msg)) +#else +# define dbg_debug(msg) (QMessageLogger().noDebug() << (msg)) +#endif + +#define dbg_notice(msg) (qDebug() << "tobii-eyex:" << (msg)) + +std::atomic_flag tobii_eyex_tracker::atexit_done = ATOMIC_FLAG_INIT; + +static inline tobii_eyex_tracker& to_self(TX_USERPARAM param) +{ + return *reinterpret_cast<tobii_eyex_tracker*>(param); +} + +template<typename t> +static constexpr t clamp(t datum, t min, t max) +{ + return ((datum > max) ? max : ((datum < min) ? min : datum)); +} + +void rel_settings::draw_spline() +{ + spline& spline = acc_mode_spline; + + spline.removeAllPoints(); + + static constexpr float std_norm_expt = 1.f/3; + const float norm_expt = std_norm_expt * float(expt_norm->cur()); + static constexpr float std_norm_lin = 2.f/3; + const float norm_lin = clamp((1-norm_expt) * lin_norm->cur() * std_norm_lin, 0., 1.); + +} + +rel_settings::rel_settings() : + opts("tobii-eyex-relative-mode"), + speed(b, "speed", s(5, .1, 10)), + dz_end_pt(b, "deadzone-length", s(4, 0, 15)), + expt_slope(b, "exponent-slope", s(1.5, 1.25, 3)), + expt_norm(b, "exponent-norm", s(1, .25, 4)), + lin_norm(b, "linear-norm", s(1, .25, 4)), + acc_mode_spline(100, 100, "") +{ + QObject::connect(&dz_end_pt, + static_cast<void(base_value::*)(const slider_value&) const>(&base_value::valueChanged), + this, + &rel_settings::draw_spline); + QObject::connect(&expt_slope, + static_cast<void(base_value::*)(const slider_value&) const>(&base_value::valueChanged), + this, + &rel_settings::draw_spline); + QObject::connect(&expt_norm, + static_cast<void(base_value::*)(const slider_value&) const>(&base_value::valueChanged), + this, + &rel_settings::draw_spline); + QObject::connect(&lin_norm, + static_cast<void(base_value::*)(const slider_value&) const>(&base_value::valueChanged), + this, + &rel_settings::draw_spline); + draw_spline(); +} + +tobii_eyex_tracker::tobii_eyex_tracker() : + dev_ctx(TX_EMPTY_HANDLE), + conn_state_changed_ticket(TX_INVALID_TICKET), + event_handler_ticket(TX_INVALID_TICKET), + state_snapshot(TX_EMPTY_HANDLE), + display_state(TX_EMPTY_HANDLE), + yaw(0), + pitch(0), + do_center(false) +{ +} + +void tobii_eyex_tracker::call_tx_deinit() +{ + dbg_notice("uninitialize in atexit at _fini time"); + (void) txUninitializeEyeX(); +} + +tobii_eyex_tracker::~tobii_eyex_tracker() +{ + dbg_verbose("dtor"); + + (void) txDisableConnection(dev_ctx); + (void) txReleaseObject(&state_snapshot); + + bool status = true; + status &= txShutdownContext(dev_ctx, TX_CLEANUPTIMEOUT_FORCEIMMEDIATE, TX_FALSE) == TX_RESULT_OK; + status &= txReleaseContext(&dev_ctx) == TX_RESULT_OK; + + // the API cleanup function needs to be called exactly once over image lifetime. + // client software communicates with a service and a desktop program. + // API is ambiguous as to what happens if the image doesn't call it or crashes. + if (!atexit_done.test_and_set()) + std::atexit(call_tx_deinit); + + if (!status) + dbg_notice("tobii-eyex: can't shutdown properly"); +} + +bool tobii_eyex_tracker::register_state_snapshot(TX_CONTEXTHANDLE dev_ctx, TX_HANDLE* state_snapshot_ptr) +{ + TX_HANDLE handle = TX_EMPTY_HANDLE; + TX_GAZEPOINTDATAPARAMS params = { TX_GAZEPOINTDATAMODE_LIGHTLYFILTERED }; + + bool status = true; + + status &= txCreateGlobalInteractorSnapshot(dev_ctx, client_id, state_snapshot_ptr, &handle) == TX_RESULT_OK; + status &= txCreateGazePointDataBehavior(handle, ¶ms) == TX_RESULT_OK; + + (void) txReleaseObject(&handle); + + return status; +} + +void tobii_eyex_tracker::process_display_state(TX_HANDLE display_state_handle) +{ + TX_SIZE2 screen_res; + + if (txGetStateValueAsSize2(display_state_handle, TX_STATEPATH_EYETRACKINGSCREENBOUNDS, &screen_res) == TX_RESULT_OK) + { + dbg_verbose("got display resolution") << screen_res.Width << screen_res.Height; + + QMutexLocker l(&global_state_mtx); + + dev_state.display_res_x = screen_res.Width; + dev_state.display_res_y = screen_res.Height; + } + else + dbg_notice("can't get display resolution"); +} + +void tobii_eyex_tracker::display_state_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM param) +{ + tobii_eyex_tracker& self = to_self(param); + + TX_RESULT result = TX_RESULT_UNKNOWN; + TX_HANDLE state = TX_EMPTY_HANDLE; + + if (txGetAsyncDataResultCode(async_data_handle, &result) == TX_RESULT_OK && + txGetAsyncDataContent(async_data_handle, &state) == TX_RESULT_OK) + { + self.process_display_state(state); + txReleaseObject(&state); + } + else + dbg_notice("error in display state handler"); +} + +void tobii_eyex_tracker::snapshot_committed_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM) +{ + TX_RESULT result = TX_RESULT_UNKNOWN; + txGetAsyncDataResultCode(async_data_handle, &result); + + if (!(result == TX_RESULT_OK || result == TX_RESULT_CANCELLED)) + dbg_notice("snapshot bad result code") << result; +} + +void tobii_eyex_tracker::connection_state_change_handler(TX_CONNECTIONSTATE state, TX_USERPARAM param) +{ + tobii_eyex_tracker& self = to_self(param); + + switch (state) + { + case TX_CONNECTIONSTATE_CONNECTED: + { + bool status = txCommitSnapshotAsync(self.state_snapshot, snapshot_committed_handler, param) == TX_RESULT_OK; + if (!status) + dbg_notice("connected but failed to initialize data stream"); + else + { + txGetStateAsync(self.dev_ctx, TX_STATEPATH_EYETRACKINGSCREENBOUNDS, display_state_handler, param); + dbg_notice("connected, data stream ok"); + } + } + break; + case TX_CONNECTIONSTATE_DISCONNECTED: + dbg_notice("connection state is now disconnected"); + break; + case TX_CONNECTIONSTATE_TRYINGTOCONNECT: + dbg_verbose("trying to establish connection"); + break; + case TX_CONNECTIONSTATE_SERVERVERSIONTOOLOW: + dbg_notice("installed driver version too low"); + break; + case TX_CONNECTIONSTATE_SERVERVERSIONTOOHIGH: + dbg_notice("new driver came up, we need to update sdk"); + break; + } +} + +void tobii_eyex_tracker::gaze_data_handler(TX_HANDLE gaze_data_handle) +{ + TX_GAZEPOINTDATAEVENTPARAMS params; + + if (txGetGazePointDataEventParams(gaze_data_handle, ¶ms) == TX_RESULT_OK) + { + { + QMutexLocker l(&global_state_mtx); + + if (params.Timestamp > dev_state.last_timestamp && + dev_state.display_res_x > 0 && + // the API allows for events outside screen bounds to e.g. detect looking at keyboard. + // closer to the screen bounds, the values get less accurate. + // ignore events outside the screen bounds. + params.X >= 0 && params.X < dev_state.display_res_x && + params.Y >= 0 && params.Y < dev_state.display_res_y) + { + dev_state.last_timestamp = params.Timestamp; + dev_state.px = params.X; + dev_state.py = params.Y; + +#ifdef TOBII_EYEX_DEBUG_PRINTF + char buf[256] = {0}; + (void) std::sprintf(buf, "gaze data: (%.1f, %.1f)", params.X, params.Y); + dbg_debug(buf); +#endif + + dev_state.fresh = true; + } + } + } + else + { + dbg_notice("failed to interpret gaze data event packet"); + } +} + +void tobii_eyex_tracker::event_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM param) +{ + tobii_eyex_tracker& self = to_self(param); + + TX_HANDLE event_handle = TX_EMPTY_HANDLE; + TX_HANDLE behavior_handle = TX_EMPTY_HANDLE; + + txGetAsyncDataContent(async_data_handle, &event_handle); + + if (txGetEventBehavior(event_handle, &behavior_handle, TX_BEHAVIORTYPE_GAZEPOINTDATA) == TX_RESULT_OK) + { + self.gaze_data_handler(behavior_handle); + txReleaseObject(&behavior_handle); + } + + txReleaseObject(&event_handle); +} + +void tobii_eyex_tracker::start_tracker(QFrame*) +{ + dbg_verbose("start tracker"); + + bool status = true; + + status &= txInitializeEyeX(TX_EYEXCOMPONENTOVERRIDEFLAG_NONE, nullptr, nullptr, nullptr, nullptr) == TX_RESULT_OK; + status &= txCreateContext(&dev_ctx, TX_FALSE) == TX_RESULT_OK; + status &= register_state_snapshot(dev_ctx, &state_snapshot); + status &= txRegisterConnectionStateChangedHandler(dev_ctx, &conn_state_changed_ticket, connection_state_change_handler, reinterpret_cast<TX_USERPARAM>(this)) == TX_RESULT_OK; + status &= txRegisterEventHandler(dev_ctx, &event_handler_ticket, event_handler, reinterpret_cast<TX_USERPARAM>(this)) == TX_RESULT_OK; + status &= txEnableConnection(dev_ctx) == TX_RESULT_OK; + + if (!status) + dbg_verbose("connection can't be established. device missing?"); + else + dbg_verbose("api initialized"); +} + +// the gain function was prototyped in python with jupyter qtconsole. +// you can use qtconsole's inline matplotlib support to see the gain function. +// the `piecewise' function assumes monotonic growth or constant value for all functions. +/* + +from math import * +from itertools import izip +import matplotlib +import matplotlib.pyplot as plt + +try: + import IPython + IPython.get_ipython().magic(u'matplotlib inline') +except: + pass + +def frange(from_, to_, step_=1e-4): + i = from_ + while True: + yield i + i += step_ + if i >= to_: + break + +def plot_fn(fn, from_=0., to_=1., step=None): + if step is None: + step = max(1e-4, (to_-from_)*1e-4) + xs = [i for i in frange(from_, to_, step)] + plt.plot(xs, map(fn, xs)) + +def piecewise(x, funs, bounds): + y = 0. + last_bound = 0. + norm = 0. + for fun in funs: + norm += fun(1.) + for fun, bound in izip(funs, bounds): + if x > bound: + y += fun(1.) + else: + b = bound - last_bound + x_ = (x - last_bound) / b + y += fun(x_) + break + last_bound = bound + return y / norm + +def f(x): return x**1.75 +def g(x): return 1.75*1.75*x +def h(x): return log(1+x)/log(2.5) +def zero(x): return 0. + +plot_fn(lambda x: piecewise(x, [zero, f, g, h], [.05, .25, .7, 1.])) + +*/ + +tobii_eyex_tracker::num tobii_eyex_tracker::gain(num x_) +{ + return 1; +} + +void tobii_eyex_tracker::data(double* data) +{ + TX_REAL px, py, dw, dh, x_, y_; + bool fresh; + + { + QMutexLocker l(&global_state_mtx); + + if (!dev_state.is_valid()) + return; + + px = dev_state.px; + py = dev_state.py; + dw = dev_state.display_res_x; + dh = dev_state.display_res_y; + + fresh = dev_state.fresh; + dev_state.fresh = false; + } + + x_ = (px-dw/2.) / (dw/2.); + y_ = (py-dh/2.) / (dh/2.); + + data[TX] = x_ * 50; + data[TY] = y_ * -50; + + if (fresh) + { + const double dt = t.elapsed_seconds(); + t.start(); + // XXX TODO make slider + static constexpr double v = 300; + + const double x = gain(x_); + const double y = gain(y_); + + const double yaw_delta = (x * v) * dt; + const double pitch_delta = (y * -v) * dt; + + yaw += yaw_delta; + pitch += pitch_delta; + + yaw = clamp(yaw, -180., 180.); + pitch = clamp(pitch, -60., 60.); + } + + if (do_center) + { + do_center = false; + yaw = 0; + pitch = 0; + } + + data[Yaw] = yaw; + data[Pitch] = pitch; + data[Roll] = 0; + data[TZ] = 0; // XXX TODO + + // tan(x) in 0->.7 is almost linear. we don't need to adjust. + // .7 is 40 degrees which is already quite a lot from the monitor. +} + +OPENTRACK_DECLARE_TRACKER(tobii_eyex_tracker, tobii_eyex_dialog, tobii_eyex_metadata) diff --git a/tracker-tobii-eyex/tobii-eyex.hpp b/tracker-tobii-eyex/tobii-eyex.hpp new file mode 100644 index 00000000..aeac4d89 --- /dev/null +++ b/tracker-tobii-eyex/tobii-eyex.hpp @@ -0,0 +1,132 @@ +#pragma once + +/* Copyright (c) 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 <EyeX.h> + +#include "ui_tobii-eyex-dialog.h" +#include "api/plugin-api.hpp" +#include "options/options.hpp" +using namespace options; +#include "compat/timer.hpp" +#include "spline-widget/spline.hpp" +#include "spline-widget/spline-widget.hpp" + +#include <atomic> +#include <QObject> +#include <QMutex> + +enum tobii_mode +{ + tobii_relative, + tobii_absolute, +}; + +class rel_settings final : public QObject, public opts +{ + Q_OBJECT +public: + using s = slider_value; + value<slider_value> speed, dz_end_pt, expt_slope, expt_norm, lin_norm; + spline acc_mode_spline; + rel_settings(); +private slots: + void draw_spline(); +}; + +struct settings final : public opts +{ + value<tobii_mode> mode; + settings() : + opts("tobii-eyex"), + mode(b, "mode", tobii_relative) + {} +}; + +class tobii_eyex_tracker : public ITracker +{ +public: + tobii_eyex_tracker(); + ~tobii_eyex_tracker() override; + void start_tracker(QFrame *) override; + void data(double *data) override; + bool center() override + { + do_center = true; + return true; + } +private: + static constexpr const char* client_id = "opentrack-tobii-eyex"; + + static void call_tx_deinit(); + + static bool register_state_snapshot(TX_CONTEXTHANDLE ctx, TX_HANDLE* state_snapshot_ptr); + static std::atomic_flag atexit_done; + static void TX_CALLCONVENTION connection_state_change_handler(TX_CONNECTIONSTATE state, TX_USERPARAM param); + static void TX_CALLCONVENTION event_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM param); + void gaze_data_handler(TX_HANDLE gaze_data_handle); + static void TX_CALLCONVENTION snapshot_committed_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM param); + static void TX_CALLCONVENTION display_state_handler(TX_CONSTHANDLE async_data_handle, TX_USERPARAM param); + void process_display_state(TX_HANDLE display_state_handle); + + using num = double; + + num gain(num x); + + settings s; + rel_settings rel_s; + + TX_CONTEXTHANDLE dev_ctx; + TX_TICKET conn_state_changed_ticket; + TX_TICKET event_handler_ticket; + TX_HANDLE state_snapshot; + TX_HANDLE display_state; + + QMutex global_state_mtx; + + Timer t; + + struct state + { + TX_REAL display_res_x, display_res_y; + TX_REAL px, py; + TX_REAL last_timestamp; + bool fresh; + + state() : display_res_x(-1), display_res_y(-1), px(-1), py(-1), last_timestamp(0), fresh(false) {} + bool is_valid() const { return !(display_res_x < 0 || px < 0); } + } dev_state; + + double yaw, pitch; + volatile bool do_center; +}; + +class tobii_eyex_dialog final : public ITrackerDialog +{ + Q_OBJECT +public: + tobii_eyex_dialog(); + void register_tracker(ITracker *) override {} + void unregister_tracker() override {} +private: + Ui::tobii_eyex_dialog_widgets ui; + settings s; + rel_settings rs; +private slots: + void do_ok(); + void do_cancel(); +}; + +class tobii_eyex_metadata : public Metadata +{ +public: + QString name() { return QString("Tobii EyeX"); } + QIcon icon() { return QIcon(":/images/tobii-eyex-logo.png"); } +}; + |