Home Forums Software Development Accessing gaze data stream with AutoHotkey Reply To: Accessing gaze data stream with AutoHotkey

#5140
Cliff
Participant

Here’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
}