Home › Forums › Software Development › Accessing gaze data stream with AutoHotkey
Tagged: autohotkey
- This topic has 3 replies, 2 voices, and was last updated 8 years, 6 months ago by Grant [Tobii].
- AuthorPosts
- 01/05/2016 at 01:49 #5124CliffParticipant
I’d like to access a gaze data stream in my AutoHotkey script. As a first step, I’ve been trying to replicate the MinimalGazeDataStream sample that’s included with the C SDK, but in AHK. I’ve managed to get my AHK version of HandleEvent() called, but the problem is it only gets called a limited number of times, then never gets called again. And the number of times it gets called differs for each run.
#NoEnv #SingleInstance force #EscapeChar ~ ; avoid default of backtick to prevent clash with tobii.com developer forums ; ---------------------------------------------------------------------------------------------------------------------- ; AHK version of MinimalGazeDataStream ; ---------------------------------------------------------------------------------------------------------------------- ; values of enum TX_RESULT from EyeXFrameworkTypes.h TX_RESULT_UNKNOWN := 1 TX_RESULT_OK := 2 TX_RESULT_CANCELLED := 28 ; values of enum TX_BEHAVIORTYPE from EyeXFrameworkTypes.h TX_BEHAVIORTYPE_GAZEPOINTDATA := 1 TX_BEHAVIORTYPE_EYEPOSITIONDATA := 2 TX_BEHAVIORTYPE_FIXATIONDATA := 6 ; values of enum TX_GAZEPOINTDATAMODE from EyeXFrameworkTypes.h TX_GAZEPOINTDATAMODE_UNFILTERED := 1 TX_GAZEPOINTDATAMODE_LIGHTLYFILTERED := 2 ; values of enum TX_EYEXCOMPONENTOVERRIDEFLAGS from EyeXClientTypes.h TX_EYEXCOMPONENTOVERRIDEFLAG_NONE := 0 ; values of enum TX_CONNECTIONSTATE from EyeXClientTypes.h TX_CONNECTIONSTATE_CONNECTED := 1 TX_CONNECTIONSTATE_DISCONNECTED := 2 TX_CONNECTIONSTATE_TRYINGTOCONNECT := 3 TX_CONNECTIONSTATE_SERVERVERSIONTOOLOW := 4 TX_CONNECTIONSTATE_SERVERVERSIONTOOHIGH := 5 ; values from EyeXClientTypes.h TX_EMPTY_HANDLE := 0 TX_INVALID_TICKET := 0 TX_TRUE := 1 TX_FALSE := 0 TX_CLEANUPTIMEOUT_DEFAULT := 500 dllFile := A_ScriptDir . "\Tobii.EyeX.Client.dll" InteractorId := "Twilight Sparkle" g_hGlobalInteractorSnapshot := TX_EMPTY_HANDLE ; AutoExecute main() Return InitializeGlobalInteractorSnapshot(hContext) { Global dllFile, InteractorId, g_hGlobalInteractorSnapshot, TX_EMPTY_HANDLE, TX_RESULT_OK, TX_GAZEPOINTDATAMODE_LIGHTLYFILTERED hInteractor := TX_EMPTY_HANDLE VarSetCapacity(gazeParamStruct, 4, 0) NumPut(TX_GAZEPOINTDATAMODE_LIGHTLYFILTERED, gazeParamStruct, 0, "UInt") success := DllCall(dllFile . "\txCreateGlobalInteractorSnapshot" , "Int", hContext , "Str", InteractorId , "Int*", g_hGlobalInteractorSnapshot , "Int*", hInteractor , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txCreateGazePointDataBehavior" , "Int", hInteractor , "Ptr", &gazeParamStruct , "Cdecl Int") == TX_RESULT_OK DllCall(dllFile . "\txReleaseObject" , "Int*", hInteractor , "Cdecl") return success } OnSnapshotCommitted(hAsyncData, param) { Global dllFile, TX_RESULT_OK, TX_RESULT_UNKNOWN result := TX_RESULT_UNKNOWN success := DllCall(dllFile . "\txGetAsyncDataResultCode" , "Int", hAsyncData , "Int*", result , "Cdecl Int") == TX_RESULT_OK DebugMessage("OnSnapshotCommitted: success=" . success . ", result=" . result) } OnEngineConnectionStateChanged(connectionState, userParam) { Global dllFile, g_hGlobalInteractorSnapshot, TX_RESULT_OK, TX_CONNECTIONSTATE_CONNECTED, TX_CONNECTIONSTATE_DISCONNECTED, TX_CONNECTIONSTATE_TRYINGTOCONNECT, TX_CONNECTIONSTATE_SERVERVERSIONTOOLOW, TX_CONNECTIONSTATE_SERVERVERSIONTOOHIGH if (connectionState == TX_CONNECTIONSTATE_CONNECTED) { success := DllCall(dllFile . "\txCommitSnapshotAsync" , "Int", g_hGlobalInteractorSnapshot , "Ptr", RegisterCallback("OnSnapshotCommitted", "Cdecl") , "Int", 0 , "Cdecl Int") == TX_RESULT_OK if (!success) { DebugMessage("Failed to initialize the data stream.") } else { DebugMessage("Waiting for gaze data to start streaming...") } } else if (connectionState == TX_CONNECTIONSTATE_DISCONNECTED) { DebugMessage("The connection state is now DISCONNECTED (We are disconnected from the EyeX Engine)") } else if (connectionState == TX_CONNECTIONSTATE_TRYINGTOCONNECT) { DebugMessage("The connection state is now TRYINGTOCONNECT (We are trying to connect to the EyeX Engine)") } else if (connectionState == TX_CONNECTIONSTATE_SERVERVERSIONTOOLOW) { DebugMessage("The connection state is now SERVER_VERSION_TOO_LOW: this application requires a more recent version of the EyeX Engine to run.") } else if (connectionState == TX_CONNECTIONSTATE_SERVERVERSIONTOOHIGH) { DebugMessage("The connection state is now SERVER_VERSION_TOO_HIGH: this application requires an older version of the EyeX Engine to run.") } } HandleEvent(hAsyncData, userParam) { Global dllFile, TX_EMPTY_HANDLE, TX_RESULT_OK, TX_BEHAVIORTYPE_GAZEPOINTDATA hEvent := TX_EMPTY_HANDLE hBehavior := TX_EMPTY_HANDLE Static count := 0 DebugMessage(count++ . ": hAsyncData=" . hAsyncData . ", userParam=" . userParam) } main() { Global dllFile, TX_EMPTY_HANDLE, TX_INVALID_TICKET, TX_EYEXCOMPONENTOVERRIDEFLAG_NONE, TX_RESULT_OK, TX_FALSE, TX_CLEANUPTIMEOUT_DEFAULT hContext := TX_EMPTY_HANDLE hConnectionStateChangedTicket := TX_INVALID_TICKET hEventHandlerTicket := TX_INVALID_TICKET hModule := DllCall("LoadLibrary", "Str", dllFile) success := DllCall(dllFile . "\txInitializeEyeX" , "Int", TX_EYEXCOMPONENTOVERRIDEFLAG_NONE , "Int", 0 , "Int", 0 , "Int", 0 , "Int", 0 , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txCreateContext" , "Int*", hContext , "Int", TX_FALSE , "Cdecl Int") == TX_RESULT_OK success &= InitializeGlobalInteractorSnapshot(hContext) success &= DllCall(dllFile . "\txRegisterConnectionStateChangedHandler" , "Int", hContext , "Int*", hConnectionStateChangedTicket , "Ptr", RegisterCallback("OnEngineConnectionStateChanged", "Cdecl", 2) , "Int", 0 , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txRegisterEventHandler" , "Int", hContext , "Int*", hEventHandlerTicket , "Ptr", RegisterCallback("HandleEvent", "Cdecl", 2) , "Int", 0 , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txEnableConnection" , "Int", hContext , "Cdecl Int") == TX_RESULT_OK DebugMessage("success=" . success . ", hConnectionStateChangedTicket=" . hConnectionStateChangedTicket . ", hEventHandlerTicket=" . hEventHandlerTicket) if (success) { DebugMessage("Initialization was successful.") } else { DebugMessage("Initialization failed.") } DebugMessage("Press any key to exit...") Input, SingleKey, L1, {LControl}{RControl}{LAlt}{RAlt}{LShift}{RShift}{LWin}{RWin}{AppsKey}{F1}{F2}{F3}{F4}{F5}{F6}{F7}{F8}{F9}{F10}{F11}{F12}{Left}{Right}{Up}{Down}{Home}{End}{PgUp}{PgDn}{Del}{Ins}{BS}{Capslock}{Numlock}{PrintScreen}{Pause} DebugMessage("Exiting.") DllCall(dllFile . "\txDisableConnection" , "Int", hContext , "Cdecl Int") DllCall(dllFile . "\txReleaseObject" , "Int*", g_hGlobalInteractorSnapshot , "Cdecl Int") success &= DllCall(dllFile . "\txShutdownContext" , "Int", hContext , "Int", TX_CLEANUPTIMEOUT_DEFAULT , "Int", TX_FALSE , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txReleaseContext" , "Int*", hContext , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txUninitializeEyeX" , "Cdecl Int") == TX_RESULT_OK DllCall("FreeLibrary", "Ptr", hModule) } ; From https://autohotkey.com/board/topic/59612-simple-debug-console-output/ DebugMessage(str) { global h_stdout DebugConsoleInitialize() ; start console window if not yet started str .= "~n" ; add line feed ;DllCall("WriteFile", "uint", h_Stdout, "uint", &str, "uint", StrLen(str), "uint*", BytesWritten, "uint", NULL) ; write into the console FileAppend %str%, CONOUT$ WinSet, Bottom,, ahk_id %h_stout% ; keep console on bottom } DebugConsoleInitialize() { global h_Stdout ; Handle for console static is_open = 0 ; toogle whether opened before if (is_open = 1) ; yes, so don't open again return is_open := 1 ; two calls to open, no error check (it's debug, so you know what you are doing) DllCall("AttachConsole", int, -1, int) DllCall("AllocConsole", int) dllcall("SetConsoleTitle", "str","Paddy Debug Console") ; Set the name. Example. Probably could use a_scriptname here h_Stdout := DllCall("GetStdHandle", "int", -11) ; get the handle WinSet, Bottom,, ahk_id %h_stout% ; make sure it's on the bottom ;WinActivate,Lightroom ; Application specific; I need to make sure this application is running in the foreground. YMMV return }
I’m running on Windows 8.1 Pro, 64-bit; AHK 1.1.23.05 32-bit Unicode; using EyeX SDK for C/C++ 1.7.480. FWIW, MinimalGazeDataStream.c from the same SDK on the same system compiles and runs fine.
Anyone have any suggestions? Thanks.
02/05/2016 at 00:32 #5126CliffParticipantIf anyone else is interested, I added “Critical” as the first line of HandleEvent() which seems to have helped.
On an unrelated note, I also added “ExitApp” to the end of main() to make sure the script exits.
02/05/2016 at 22:55 #5140CliffParticipantHere’s the complete MinimalGazeDataStream.ahk that works for me, with either 32-bit or 64-bit Unicode AHK. Just make sure you use the corresponding 32-bit or 64-bit Tobii.EyeX.Client.dll.
#NoEnv #SingleInstance force #EscapeChar ~ ; avoid default of backtick to prevent clash with tobii.com developer forums ; ---------------------------------------------------------------------------------------------------------------------- ; AHK version of MinimalGazeDataStream from EyeX SDK sample ; ---------------------------------------------------------------------------------------------------------------------- ; values of enum TX_RESULT from EyeXFrameworkTypes.h TX_RESULT_UNKNOWN := 1 TX_RESULT_OK := 2 TX_RESULT_CANCELLED := 28 ; values of enum TX_BEHAVIORTYPE from EyeXFrameworkTypes.h TX_BEHAVIORTYPE_GAZEPOINTDATA := 1 TX_BEHAVIORTYPE_EYEPOSITIONDATA := 2 TX_BEHAVIORTYPE_FIXATIONDATA := 6 ; values of enum TX_GAZEPOINTDATAMODE from EyeXFrameworkTypes.h TX_GAZEPOINTDATAMODE_UNFILTERED := 1 TX_GAZEPOINTDATAMODE_LIGHTLYFILTERED := 2 ; values of enum TX_EYEXCOMPONENTOVERRIDEFLAGS from EyeXClientTypes.h TX_EYEXCOMPONENTOVERRIDEFLAG_NONE := 0 ; values of enum TX_CONNECTIONSTATE from EyeXClientTypes.h TX_CONNECTIONSTATE_CONNECTED := 1 TX_CONNECTIONSTATE_DISCONNECTED := 2 TX_CONNECTIONSTATE_TRYINGTOCONNECT := 3 TX_CONNECTIONSTATE_SERVERVERSIONTOOLOW := 4 TX_CONNECTIONSTATE_SERVERVERSIONTOOHIGH := 5 ; values from EyeXClientTypes.h TX_EMPTY_HANDLE := 0 TX_INVALID_TICKET := 0 TX_TRUE := 1 TX_FALSE := 0 TX_CLEANUPTIMEOUT_DEFAULT := 500 dllFile := A_ScriptDir . "\Tobii.EyeX.Client.dll" InteractorId := "Twilight Sparkle" g_hGlobalInteractorSnapshot := TX_EMPTY_HANDLE ; AutoExecute main() Return InitializeGlobalInteractorSnapshot(hContext) { Global dllFile, InteractorId, g_hGlobalInteractorSnapshot, TX_EMPTY_HANDLE, TX_RESULT_OK, TX_GAZEPOINTDATAMODE_LIGHTLYFILTERED hInteractor := TX_EMPTY_HANDLE VarSetCapacity(gazeParamStruct, 4, 0) NumPut(TX_GAZEPOINTDATAMODE_LIGHTLYFILTERED, gazeParamStruct, 0, "UInt") success := DllCall(dllFile . "\txCreateGlobalInteractorSnapshot" , "Int", hContext , "Str", InteractorId , "Int*", g_hGlobalInteractorSnapshot , "Int*", hInteractor , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txCreateGazePointDataBehavior" , "Int", hInteractor , "Ptr", &gazeParamStruct , "Cdecl Int") == TX_RESULT_OK DllCall(dllFile . "\txReleaseObject" , "Int*", hInteractor , "Cdecl") return success } OnSnapshotCommitted(hAsyncData, param) { Global dllFile, TX_RESULT_OK, TX_RESULT_UNKNOWN result := TX_RESULT_UNKNOWN success := DllCall(dllFile . "\txGetAsyncDataResultCode" , "Int", hAsyncData , "Int*", result , "Cdecl Int") == TX_RESULT_OK DebugMessage("OnSnapshotCommitted: success=" . success . ", result=" . result) } OnEngineConnectionStateChanged(connectionState, userParam) { Global dllFile, g_hGlobalInteractorSnapshot, TX_RESULT_OK, TX_CONNECTIONSTATE_CONNECTED, TX_CONNECTIONSTATE_DISCONNECTED, TX_CONNECTIONSTATE_TRYINGTOCONNECT, TX_CONNECTIONSTATE_SERVERVERSIONTOOLOW, TX_CONNECTIONSTATE_SERVERVERSIONTOOHIGH if (connectionState == TX_CONNECTIONSTATE_CONNECTED) { success := DllCall(dllFile . "\txCommitSnapshotAsync" , "Int", g_hGlobalInteractorSnapshot , "Ptr", RegisterCallback("OnSnapshotCommitted", "Cdecl") , "Int", 0 , "Cdecl Int") == TX_RESULT_OK if (!success) { DebugMessage("Failed to initialize the data stream.") } else { DebugMessage("Waiting for gaze data to start streaming...") } } else if (connectionState == TX_CONNECTIONSTATE_DISCONNECTED) { DebugMessage("The connection state is now DISCONNECTED (We are disconnected from the EyeX Engine)") } else if (connectionState == TX_CONNECTIONSTATE_TRYINGTOCONNECT) { DebugMessage("The connection state is now TRYINGTOCONNECT (We are trying to connect to the EyeX Engine)") } else if (connectionState == TX_CONNECTIONSTATE_SERVERVERSIONTOOLOW) { DebugMessage("The connection state is now SERVER_VERSION_TOO_LOW: this application requires a more recent version of the EyeX Engine to run.") } else if (connectionState == TX_CONNECTIONSTATE_SERVERVERSIONTOOHIGH) { DebugMessage("The connection state is now SERVER_VERSION_TOO_HIGH: this application requires an older version of the EyeX Engine to run.") } } OnGazeDataEvent(hGazeDataBehavior) { Global dllFile, TX_RESULT_OK VarSetCapacity(eventParamsStruct, 32, 0) ; 4 (Int) + 8 (Double) + 8 (Double) + 8 (Double) = 28, but compiler has padded Int to 8 as member of struct success := DllCall(dllFile . "\txGetGazePointDataEventParams" , "Int", hGazeDataBehavior , "Ptr", &eventParamsStruct , "Cdecl Int") == TX_RESULT_OK if (success) { ;gazePointDataMode := NumGet(eventParamsStruct, 0, "Int") timestamp := NumGet(eventParamsStruct, 8, "Double") x := NumGet(eventParamsStruct, 16, "Double") y := NumGet(eventParamsStruct, 24, "Double") DebugMessage("Gaze Data: (" . x . ", " . y . ") " . timestamp . " ms") } else { DebugMessage("Failed to interpret gaze data event packet.") } } HandleEvent(hAsyncData, userParam) { Critical Global dllFile, TX_EMPTY_HANDLE, TX_RESULT_OK, TX_BEHAVIORTYPE_GAZEPOINTDATA hEvent := TX_EMPTY_HANDLE hBehavior := TX_EMPTY_HANDLE ;Static count := 0 ;DebugMessage(count++ . ": hAsyncData=" . hAsyncData . ", userParam=" . userParam) success := DllCall(dllFile . "\txGetAsyncDataContent" , "Int", hAsyncData , "Int*", hEvent , "Cdecl Int") == TX_RESULT_OK isGazePointData := DllCall(dllFile . "\txGetEventBehavior" , "Int", hEvent , "Int*", hBehavior , "Int", TX_BEHAVIORTYPE_GAZEPOINTDATA , "Cdecl Int") == TX_RESULT_OK if (isGazePointData) { OnGazeDataEvent(hBehavior) DllCall(dllFile . "\txReleaseObject" , "Int*", hBehavior , "Cdecl Int") } DllCall(dllFile . "\txReleaseObject" , "Int*", hEvent , "Cdecl Int") } main() { Global dllFile, TX_EMPTY_HANDLE, TX_INVALID_TICKET, TX_EYEXCOMPONENTOVERRIDEFLAG_NONE, TX_RESULT_OK, TX_FALSE, TX_CLEANUPTIMEOUT_DEFAULT hContext := TX_EMPTY_HANDLE hConnectionStateChangedTicket := TX_INVALID_TICKET hEventHandlerTicket := TX_INVALID_TICKET hModule := DllCall("LoadLibrary", "Str", dllFile) success := DllCall(dllFile . "\txInitializeEyeX" , "Int", TX_EYEXCOMPONENTOVERRIDEFLAG_NONE , "Int", 0 , "Int", 0 , "Int", 0 , "Int", 0 , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txCreateContext" , "Int*", hContext , "Int", TX_FALSE , "Cdecl Int") == TX_RESULT_OK success &= InitializeGlobalInteractorSnapshot(hContext) success &= DllCall(dllFile . "\txRegisterConnectionStateChangedHandler" , "Int", hContext , "Int*", hConnectionStateChangedTicket , "Ptr", RegisterCallback("OnEngineConnectionStateChanged", "Cdecl", 2) , "Int", 0 , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txRegisterEventHandler" , "Int", hContext , "Int*", hEventHandlerTicket , "Ptr", RegisterCallback("HandleEvent", "Cdecl", 2) , "Int", 0 , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txEnableConnection" , "Int", hContext , "Cdecl Int") == TX_RESULT_OK DebugMessage("success=" . success . ", hConnectionStateChangedTicket=" . hConnectionStateChangedTicket . ", hEventHandlerTicket=" . hEventHandlerTicket) if (success) { DebugMessage("Initialization was successful.") } else { DebugMessage("Initialization failed.") } DebugMessage("Press any key to exit...") Input, SingleKey, L1, {LControl}{RControl}{LAlt}{RAlt}{LShift}{RShift}{LWin}{RWin}{AppsKey}{F1}{F2}{F3}{F4}{F5}{F6}{F7}{F8}{F9}{F10}{F11}{F12}{Left}{Right}{Up}{Down}{Home}{End}{PgUp}{PgDn}{Del}{Ins}{BS}{Capslock}{Numlock}{PrintScreen}{Pause} DebugMessage("Exiting.") DllCall(dllFile . "\txDisableConnection" , "Int", hContext , "Cdecl Int") DllCall(dllFile . "\txReleaseObject" , "Int*", g_hGlobalInteractorSnapshot , "Cdecl Int") success &= DllCall(dllFile . "\txShutdownContext" , "Int", hContext , "Int", TX_CLEANUPTIMEOUT_DEFAULT , "Int", TX_FALSE , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txReleaseContext" , "Int*", hContext , "Cdecl Int") == TX_RESULT_OK success &= DllCall(dllFile . "\txUninitializeEyeX" , "Cdecl Int") == TX_RESULT_OK DllCall("FreeLibrary", "Ptr", hModule) ExitApp } ; From https://autohotkey.com/board/topic/59612-simple-debug-console-output/ DebugMessage(str) { global h_stdout DebugConsoleInitialize() ; start console window if not yet started str .= "~n" ; add line feed ;DllCall("WriteFile", "uint", h_Stdout, "uint", &str, "uint", StrLen(str), "uint*", BytesWritten, "uint", NULL) ; write into the console FileAppend %str%, CONOUT$ WinSet, Bottom,, ahk_id %h_stout% ; keep console on bottom } DebugConsoleInitialize() { global h_Stdout ; Handle for console static is_open = 0 ; toogle whether opened before if (is_open = 1) ; yes, so don't open again return is_open := 1 ; two calls to open, no error check (it's debug, so you know what you are doing) DllCall("AttachConsole", int, -1, int) DllCall("AllocConsole", int) dllcall("SetConsoleTitle", "str","Paddy Debug Console") ; Set the name. Example. Probably could use a_scriptname here h_Stdout := DllCall("GetStdHandle", "int", -11) ; get the handle WinSet, Bottom,, ahk_id %h_stout% ; make sure it's on the bottom ;WinActivate,Lightroom ; Application specific; I need to make sure this application is running in the foreground. YMMV return }
10/05/2016 at 13:39 #5162Grant [Tobii]KeymasterHi @clifforama,
Thank you for sharing your code and using the EyeX in this interesting way!
- AuthorPosts
- You must be logged in to reply to this topic.