commit 269482199d6472045720afeafa4211f7f510a1dd Author: Evgeniy Date: Wed Sep 17 17:08:21 2025 +0300 Initial commit diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d6cfbb6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tobii_eye_tracker_linux_installer"] + path = tobii_eye_tracker_linux_installer + url = https://git.robofob.ru/violator/tobii_eye_tracker_linux_installer.git diff --git a/0.install_lsl.sh b/0.install_lsl.sh new file mode 100644 index 0000000..a67ce6c --- /dev/null +++ b/0.install_lsl.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +#echo "!!! Downdloading LSL 1.16.2 package..." +#wget -P /tmp https://github.com/sccn/liblsl/releases/download/v1.16.2/liblsl-1.16.2-focal_amd64.deb + +echo "!!! Installing LSL 1.16.2 package..." +sudo dpkg -i ./lsl/liblsl-1.16.2-focal_amd64.deb + +echo "!!! Installing XML library package..." +sudo dpkg -i ./lsl/libpugixml1v5_1.10-1_amd64.deb diff --git a/0.install_qt.sh b/0.install_qt.sh new file mode 100644 index 0000000..1a4144c --- /dev/null +++ b/0.install_qt.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +echo Update package list... +sudo apt update + +echo Install essential packages... +sudo apt install build-essential qtcreator qt5-default qtbase5-dev + +echo Install optional components... +sudo apt install qt5-doc qtbase5-examples qtbase5-doc-html diff --git a/1.build.sh b/1.build.sh new file mode 100644 index 0000000..e20909b --- /dev/null +++ b/1.build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +mkdir build +cd build +cmake .. +make diff --git a/2.run.sh b/2.run.sh new file mode 100644 index 0000000..dbd0924 --- /dev/null +++ b/2.run.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +cd build +# gnome-terminal -- ./TobiiSendData +# konsole -e ./TobiiSendData +# terminal -e ./TobiiSendData +xterm -e ./TobiiSendData diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..21b9a5a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.13) +project(TobiiSendData) + +set(CMAKE_CXX_STANDARD 14) +add_executable(TobiiSendData main.cpp) +target_link_libraries(TobiiSendData /usr/lib/tobii/libtobii_stream_engine.so) +target_link_libraries(TobiiSendData pthread) +target_link_libraries(TobiiSendData lsl) diff --git a/TobiiSendData_editOnly_notForBuild.pro b/TobiiSendData_editOnly_notForBuild.pro new file mode 100644 index 0000000..1883fcc --- /dev/null +++ b/TobiiSendData_editOnly_notForBuild.pro @@ -0,0 +1,28 @@ +QT += core gui widgets + +CONFIG += c++17 +CONFIG += console + +DESTDIR = $$PWD/bin + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +############################################################# +## Tobii stuff +############################################################# +INCLUDEPATH += $$PWD/stream_engine_linux_3.0.4.6031/include +DEPENDPATH += $$PWD/stream_engine_linux_3.0.4.6031/include +LIBS += "/usr/lib/tobii/libtobii_stream_engine.so" +LIBS += -pthread +##----------------------------------------------------------- + +############################################################# +## LSL stuff +############################################################# +LIBS += -L/usr/lib -llsl +##----------------------------------------------------------- + +SOURCES += \ + main.cpp diff --git a/lsl/liblsl-1.16.2-focal_amd64.deb b/lsl/liblsl-1.16.2-focal_amd64.deb new file mode 100644 index 0000000..72bc588 Binary files /dev/null and b/lsl/liblsl-1.16.2-focal_amd64.deb differ diff --git a/lsl/libpugixml1v5_1.10-1_amd64.deb b/lsl/libpugixml1v5_1.10-1_amd64.deb new file mode 100644 index 0000000..fb848ba Binary files /dev/null and b/lsl/libpugixml1v5_1.10-1_amd64.deb differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..09fdb4f --- /dev/null +++ b/main.cpp @@ -0,0 +1,447 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // For uid_t +#include // For struct passwd and getpwuid() +#include +#include +#include +#include + +//tobii handles +static tobii_api_t *api = nullptr; +static tobii_device_t *device = nullptr; + +//LSL outlet streams +static lsl::stream_outlet *gazePointOutlet; +static lsl::stream_outlet *gazeOriginOutlet; +static lsl::stream_outlet *eyePositionNormalizedOutlet; +static lsl::stream_outlet *userPresenceOutlet; + +std::atomic exit_flag(false); + +void inputThread() +{ + struct termios oldt, newt; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + + char c; + read(STDIN_FILENO, &c, 1); // Blocks until a key is pressed + exit_flag = true; + + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); // Restore original settings +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// tobii callbacks +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +void gaze_point_callback(tobii_gaze_point_t const *gaze_point, void *user_data) +{ + (void)user_data; //ignore user_data argument + + std::vector result(2, 0.0); + + if(gaze_point->validity == TOBII_VALIDITY_VALID) + { + result[0] = gaze_point->position_xy[0]; + result[1] = gaze_point->position_xy[1]; + +// printf("[gaze point]: %f, %f\n", +// gaze_point->position_xy[0], +// gaze_point->position_xy[1]); + + if(gazePointOutlet) + { + gazePointOutlet->push_sample(result, lsl::local_clock()); + } + } + +} +void gaze_origin_callback(tobii_gaze_origin_t const *gaze_origin, void *user_data) +{ + (void)user_data; //ignore user_data argument + + std::vector result(6, 0.0); + + if(gaze_origin->left_validity == TOBII_VALIDITY_VALID) + { + result[0] = gaze_origin->left_xyz[0]; + result[1] = gaze_origin->left_xyz[1]; + result[2] = gaze_origin->left_xyz[2]; + } + else + { + result[0] = -1.0; + result[1] = -1.0; + result[2] = -1.0; + } + + if(gaze_origin->right_validity == TOBII_VALIDITY_VALID) + { + result[3] = gaze_origin->right_xyz[0]; + result[4] = gaze_origin->right_xyz[1]; + result[5] = gaze_origin->right_xyz[2]; + } + else + { + result[3] = -1.0; + result[4] = -1.0; + result[5] = -1.0; + } + +// printf("[gaze origin] left: %f, %f, %f\n[gaze origin] right: %f, %f, %f\n", +// result[0], +// result[1], +// result[2], +// result[3], +// result[4], +// result[5]); + if(gazeOriginOutlet) + { + gazeOriginOutlet->push_sample(result, lsl::local_clock()); + } +} +void eyePositionNormalizedCallback(tobii_eye_position_normalized_t const *eyePosition, void *user_data) +{ + (void)user_data; //ignore user_data argument + + std::vector result(6, 0.0); + if(eyePosition->left_validity == TOBII_VALIDITY_VALID) + { + result[0] = eyePosition->left_xyz[0]; + result[1] = eyePosition->left_xyz[1]; + result[2] = eyePosition->left_xyz[2]; + } + if(eyePosition->right_validity == TOBII_VALIDITY_VALID) + { + result[3] = eyePosition->right_xyz[0]; + result[4] = eyePosition->right_xyz[1]; + result[5] = eyePosition->right_xyz[2]; + } +// printf("[eye position] left: %f, %f, %f\n[eye position] right: %f, %f, %f\n", +// result[0], +// result[1], +// result[2], +// result[3], +// result[4], +// result[5]); + if(eyePositionNormalizedOutlet) + { + eyePositionNormalizedOutlet->push_sample(result, lsl::local_clock()); + } +} + +//NOT SUPPORTED :-| +/* +void headPoseCallback(tobii_head_pose_t const *headPose, void *user_data) +{ + (void)user_data; //ignore user_data argument + + static std::vector sample = {0, 0, 0, 0, 0, 0}; + + if(headPose->position_validity == TOBII_VALIDITY_VALID) + { + sample[0] = headPose->position_xyz[0]; + sample[1] = headPose->position_xyz[1]; + sample[2] = headPose->position_xyz[2]; + } + if(headPose->rotation_validity_xyz[0] == TOBII_VALIDITY_VALID) + { + sample[3] = headPose->rotation_xyz[0]; + } + if(headPose->rotation_validity_xyz[1] == TOBII_VALIDITY_VALID) + { + sample[4] = headPose->rotation_xyz[1]; + } + if(headPose->rotation_validity_xyz[2] == TOBII_VALIDITY_VALID) + { + sample[5] = headPose->rotation_xyz[2]; + } + +// printf("[head pose] left: %f, %f, %f\n[head rotation] right: %f, %f, %f\n", +// sample[0], +// sample[1], +// sample[2], +// sample[3], +// sample[4], +// sample[5]); +} +*/ +void userPresenceCallback(tobii_user_presence_status_t status, int64_t timestamp_us, void *user_data) +{ + (void)timestamp_us; //ignore timestamp_us argument + (void)user_data; //ignore user_data argument + + static std::string msgPrefix("[user presence]"); + if(status == TOBII_USER_PRESENCE_STATUS_PRESENT) + { +// printf("%s: present\n", msgPrefix.c_str()); + if(userPresenceOutlet) + { + std::string msg("present"); + userPresenceOutlet->push_sample(&msg, lsl::local_clock()); + } + } + else if(status == TOBII_USER_PRESENCE_STATUS_AWAY) + { +// printf("%s: away\n", msgPrefix.c_str()); + if(userPresenceOutlet) + { + std::string msg("away"); + userPresenceOutlet->push_sample(&msg, lsl::local_clock()); + } + } + else if(status == TOBII_USER_PRESENCE_STATUS_UNKNOWN) + { +// printf("%s: unknown\n", msgPrefix.c_str()); + if(userPresenceOutlet) + { + std::string msg("unknown"); + userPresenceOutlet->push_sample(&msg, lsl::local_clock()); + } + } +} +//-------------------------------------------------------------------- + +static void url_receiver(char const *url, void *user_data) +{ + char *buffer = (char *)user_data; + if(*buffer != '\0') // only keep first value + { + return; + } + + if(strlen(url) < 256) + { + strcpy(buffer, url); + } +} + +void initTobii() +{ + tobii_error_t error = tobii_api_create(&api, nullptr, nullptr); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_api_create() call error: %s\n", tobii_error_message(error)); + } + + char url[256] = {0x00}; + error = tobii_enumerate_local_device_urls(api, url_receiver, url); + if((error != TOBII_ERROR_NO_ERROR) && (*url != '\0')) + { + printf("tobii_enumerate_local_device_urls() call error: %s\n", tobii_error_message(error)); + } + + error = tobii_device_create(api, url, &device); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_device_create() call error: %s\n", tobii_error_message(error)); + } + + error = tobii_gaze_point_subscribe(device, gaze_point_callback, 0); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_gaze_point_subscribe() call error: %s\n", tobii_error_message(error)); + } + + error = tobii_gaze_origin_subscribe(device, gaze_origin_callback, 0); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_gaze_origin_subscribe() call error: %s\n", tobii_error_message(error)); + } + + error = tobii_eye_position_normalized_subscribe(device, eyePositionNormalizedCallback, 0); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_eye_position_normalized_subscribe() call error: %s\n", tobii_error_message(error)); + } + +//NOT SUPPORTED :-| +/* + error = tobii_head_pose_subscribe(device, headPoseCallback, 0); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_head_pose_subscribe() call error: %s\n", tobii_error_message(error)); + } +*/ + + error = tobii_user_presence_subscribe(device, userPresenceCallback, 0); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_user_presence_subscribe() call error: %s\n", tobii_error_message(error)); + } +} + +void deinitTobii() +{ + tobii_error_t error = tobii_user_presence_unsubscribe(device); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_user_presence_unsubscribe() call error: %s\n", tobii_error_message(error)); + } + + //NOT SUPPORTED :-| +// error = tobii_head_pose_unsubscribe(device); +// if(error != TOBII_ERROR_NO_ERROR) +// { +// printf("tobii_head_pose_unsubscribe() call error: %s\n", tobii_error_message(error)); +// } + + error = tobii_eye_position_normalized_unsubscribe(device); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_eye_position_normalized_unsubscribe() call error: %s\n", tobii_error_message(error)); + } + + error = tobii_gaze_origin_unsubscribe(device); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_gaze_origin_unsubscribe() call error: %s\n", tobii_error_message(error)); + } + + error = tobii_gaze_point_unsubscribe(device); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_gaze_point_unsubscribe() call error: %s\n", tobii_error_message(error)); + } + + error = tobii_device_destroy(device); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_device_destroy() call error: %s\n", tobii_error_message(error)); + } + + error = tobii_api_destroy(api); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_api_destroy() call error: %s\n", tobii_error_message(error)); + } +} + +void initLSL() +{ + //get username to use as source_id parameter for created LSL streams + uid_t uid = getuid(); // Get the real user ID of the calling process + struct passwd *pw = getpwuid(uid); // Get password structure for the given UID + if(pw != nullptr) + { + printf("Username: %s\n", pw->pw_name); + } + else + { + printf("Error: Could not retrieve username.\n"); + } + + //gaze point stream + lsl::stream_info gazePointOutletInfo(std::string("TobiiGazePoint"), + std::string("GAZE"), + 2, + 90, + lsl::cf_float32, + std::string(pw->pw_name)); + printf("!!! Create gaze point stream\n"); + gazePointOutlet = new lsl::stream_outlet(gazePointOutletInfo, 0); + + //gaze origin stream + lsl::stream_info gazeOriginOutletInfo(std::string("TobiiGazeOrigin"), + std::string("GAZE"), + 6, + 90, + lsl::cf_float32, + std::string(pw->pw_name)); + + printf("!!! Create gaze origin stream\n"); + gazeOriginOutlet = new lsl::stream_outlet(gazeOriginOutletInfo, 0); + + //eye position normalized stream + lsl::stream_info eyePositionNormalizedOutletInfo(std::string("TobiiEyePositionNormalized"), + std::string("GAZE"), + 6, + 90, + lsl::cf_float32, + std::string(pw->pw_name)); + + printf("!!! Create eye position normalized stream\n"); + eyePositionNormalizedOutlet = new lsl::stream_outlet(eyePositionNormalizedOutletInfo, 0); + + //eye position normalized stream + lsl::stream_info userPresenceOutletInfo(std::string("TobiiUserPresence"), + std::string("GAZE"), + 1, + lsl::IRREGULAR_RATE, + lsl::cf_string, + std::string(pw->pw_name)); + + printf("!!! Create user presence stream\n"); + userPresenceOutlet = new lsl::stream_outlet(userPresenceOutletInfo, 0); +} +void deinitLSL() +{ + if(gazePointOutlet) + { + delete gazePointOutlet; + gazePointOutlet = nullptr; + } + + if(gazeOriginOutlet) + { + delete gazeOriginOutlet; + gazeOriginOutlet = nullptr; + } + + if(eyePositionNormalizedOutlet) + { + delete eyePositionNormalizedOutlet; + eyePositionNormalizedOutlet = nullptr; + } + + if(userPresenceOutlet) + { + delete userPresenceOutlet; + userPresenceOutlet = nullptr; + } +} + +int main() +{ + tobii_error_t error = TOBII_ERROR_NO_ERROR; + + std::thread input_handler(inputThread); + + //init Tobii and LSL stuff + initLSL(); + initTobii(); + + printf("!!! run callbacks processing\n"); + while(!exit_flag) + { + error = tobii_wait_for_callbacks(1, &device); + if((error != TOBII_ERROR_NO_ERROR)&&(error != TOBII_ERROR_TIMED_OUT)) + { + printf("tobii_wait_for_callbacks() call error: %s\n", tobii_error_message(error)); + } + + error = tobii_device_process_callbacks(device); + if(error != TOBII_ERROR_NO_ERROR) + { + printf("tobii_device_process_callbacks() call error: %s\n", tobii_error_message(error)); + } + } + + input_handler.join(); // Wait for the input thread to finish + + //deinit Tobii and LSL stuff + deinitTobii(); + deinitLSL(); + + return 0; +} diff --git a/tobii_eye_tracker_linux_installer b/tobii_eye_tracker_linux_installer new file mode 160000 index 0000000..1ad41b5 --- /dev/null +++ b/tobii_eye_tracker_linux_installer @@ -0,0 +1 @@ +Subproject commit 1ad41b5b898b946893b25adb5fc702dde7af0cb2