summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2016-09-19 15:13:48 +0200
committerStanislaw Halik <sthalik@misaki.pl>2016-09-19 15:13:48 +0200
commitd993825ace9886d45d1519ed51d0fccae55da884 (patch)
tree9bdc7bdbf660019f726aae6f98abe2c63c9c8e3d
parenteee2cffd96fa2d7312708768fac7e67f129c5970 (diff)
parent9a85b814d908a28fb7444b7e1b5afae9fe629784 (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.cmake16
-rw-r--r--tracker-tobii-eyex/CMakeLists.txt32
-rw-r--r--tracker-tobii-eyex/images/tobii-eyex-logo.pngbin0 -> 1319 bytes
-rw-r--r--tracker-tobii-eyex/tobii-eyex-dialog.cpp26
-rw-r--r--tracker-tobii-eyex/tobii-eyex-dialog.ui630
-rw-r--r--tracker-tobii-eyex/tobii-eyex-res.qrc5
-rw-r--r--tracker-tobii-eyex/tobii-eyex.cpp413
-rw-r--r--tracker-tobii-eyex/tobii-eyex.hpp132
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
new file mode 100644
index 00000000..b952891b
--- /dev/null
+++ b/tracker-tobii-eyex/images/tobii-eyex-logo.png
Binary files differ
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, &params) == 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, &params) == 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"); }
+};
+