From ad87c966ea6551d1f039d7201e9a00a9a7931f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Nar=C4=99bski?= Date: Wed, 10 Sep 2025 02:07:27 +0200 Subject: [PATCH 1/5] EyeTracker: Use distinct "remark" for distinct error --- src/main/java/trackers/EyeTracker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/trackers/EyeTracker.java b/src/main/java/trackers/EyeTracker.java index 207026e..13919c0 100644 --- a/src/main/java/trackers/EyeTracker.java +++ b/src/main/java/trackers/EyeTracker.java @@ -224,7 +224,7 @@ public void processRawData(String message) { editorX = editor.getContentComponent().getLocationOnScreen().x; editorY = editor.getContentComponent().getLocationOnScreen().y; } catch (IllegalComponentStateException e) { - gaze.setAttribute("remark", "Fail | No Editor"); + gaze.setAttribute("remark", "Fail | IllegalComponentStateException in Editor"); return; } int relativeX = eyeX - editorX; From 6c8871193b6d3b42fc7df590c32a170aee9684ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Nar=C4=99bski?= Date: Wed, 10 Sep 2025 02:15:50 +0200 Subject: [PATCH 2/5] EyeTracker: Introduce isGazeNormalized field This field is to be used in the cases where eye-tracker hardware (or eye-tracker emulation) returns position in pixels, rather than normalized position in the 0.0..1.0 that needs to be rescaled. --- src/main/java/trackers/EyeTracker.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/trackers/EyeTracker.java b/src/main/java/trackers/EyeTracker.java index 13919c0..46bafd6 100644 --- a/src/main/java/trackers/EyeTracker.java +++ b/src/main/java/trackers/EyeTracker.java @@ -61,6 +61,12 @@ public class EyeTracker implements Disposable { String pythonScriptTobii; String pythonScriptMouse; int deviceIndex = 0; + /** + * This variable indicates whether the gaze coordinates are normalized to the screen size, + * that is returned as values between (0, 0) for the upper left corner and (1, 1) for the lower right corner. + * See, for example, 'Coordinate system' page in Tobii Pro SDK documentation + */ + boolean isGazeNormalized = true; /** * This variable indicates whether the real-time data is transmitting. @@ -216,8 +222,14 @@ public void processRawData(String message) { return; } - int eyeX = (int) ((Double.parseDouble(leftGazePointX) + Double.parseDouble(rightGazePointX)) / 2 * screenWidth); - int eyeY = (int) ((Double.parseDouble(leftGazePointY) + Double.parseDouble(rightGazePointY)) / 2 * screenHeight); + int eyeX, eyeY; + if (isGazeNormalized) { + eyeX = (int) ((Double.parseDouble(leftGazePointX) + Double.parseDouble(rightGazePointX)) / 2 * screenWidth); + eyeY = (int) ((Double.parseDouble(leftGazePointY) + Double.parseDouble(rightGazePointY)) / 2 * screenHeight); + } else { + eyeX = (int) ((Double.parseDouble(leftGazePointX) + Double.parseDouble(rightGazePointX)) / 2); + eyeY = (int) ((Double.parseDouble(leftGazePointY) + Double.parseDouble(rightGazePointY)) / 2); + } int editorX, editorY; try { From c6f39f3ec34a09d68233e0de658cfe59a3ca9270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Nar=C4=99bski?= Date: Wed, 10 Sep 2025 10:20:53 +0200 Subject: [PATCH 3/5] AvailabilityChecker: Remove hard requirement on tobii_research Now tobii_research [1][2] module does not need to be installed to be able to use mouse emulation. [1]: https://developer.tobiipro.com/python.html [2]: https://pypi.org/project/tobii-research/ This change removes 'import tobii_research as tr' from checkPythonEnvironment() method, and adjusts checkEyeTracker() and getEyeTrackerName() to work correctly even if tobii_research is not installed. Not tested yet. The problem with requiring tobii_research is that it requires specific Python version to be able to be installed: 3.8 or 3.10 [3][4][5] [3]: https://www.tobii.com/products/software/applications-and-developer-kits/tobii-pro-sdk [4]: https://developer.tobiipro.com/tobiiprosdk/platform-and-language.html [5]: https://codegrits.github.io/CodeGRITS/usage-guide/#python-environment --- src/main/java/utils/AvailabilityChecker.java | 29 ++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main/java/utils/AvailabilityChecker.java b/src/main/java/utils/AvailabilityChecker.java index a47f51f..72af8a7 100644 --- a/src/main/java/utils/AvailabilityChecker.java +++ b/src/main/java/utils/AvailabilityChecker.java @@ -19,7 +19,6 @@ public class AvailabilityChecker { */ public static boolean checkPythonEnvironment(String pythonInterpreter) throws IOException, InterruptedException { String pythonScript = """ - import tobii_research as tr from screeninfo import get_monitors import pyautogui import time @@ -41,13 +40,17 @@ public static boolean checkPythonEnvironment(String pythonInterpreter) throws IO */ public static boolean checkEyeTracker(String pythonInterpreter) throws IOException, InterruptedException { String pythonScript = """ - import tobii_research as tr + try: + import tobii_research as tr - found_eyetrackers = tr.find_all_eyetrackers() - if found_eyetrackers == (): + found_eyetrackers = tr.find_all_eyetrackers() + if found_eyetrackers == (): + print('Not Found') + else: + print('Found') + + except ImportError: print('Not Found') - else: - print('Found') """; String line = runPythonScript(pythonInterpreter, pythonScript); @@ -62,13 +65,17 @@ public static boolean checkEyeTracker(String pythonInterpreter) throws IOExcepti */ public static String getEyeTrackerName(String pythonInterpreter) throws IOException, InterruptedException { String pythonScript = """ - import tobii_research as tr + try: + import tobii_research as tr - found_eyetrackers = tr.find_all_eyetrackers() - if found_eyetrackers == (): + found_eyetrackers = tr.find_all_eyetrackers() + if found_eyetrackers == (): + print('Not Found') + else: + print(found_eyetrackers[0].device_name) + + except ImportError: print('Not Found') - else: - print(found_eyetrackers[0].device_name) """; return runPythonScript(pythonInterpreter, pythonScript); From 41d3db261798fc7e78190a5df92043a52b8fb6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Nar=C4=99bski?= Date: Wed, 10 Sep 2025 10:32:28 +0200 Subject: [PATCH 4/5] build.gradle.kts: Bump version, adding "-dev1" suffix --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 8b3ed5d..e304ac1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "io.github.codegrits" -version = "0.3.2" +version = "0.3.2-dev1" repositories { mavenCentral() From 45b0033ad9ca65c5e35e4a85812122d8fc9f1b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Nar=C4=99bski?= Date: Tue, 16 Sep 2025 17:17:20 +0200 Subject: [PATCH 5/5] EyeTracker: wrap call to getASTStructureElement() in runReadAction() This should fix the sporadic RuntimeExceptionWithAttachments exception in CodeGRITS plugin that happens on some projects when running eye tracker. com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments: Read access is allowed from inside read-action only (see Application.runReadAction()); If you access or modify model on EDT consider wrapping your code in WriteIntentReadAction ; see https://jb.gg/ij-platform-threading for details [...] at trackers.EyeTracker.getASTStructureElement(EyeTracker.java:450) at trackers.EyeTracker.lambda$processRawData$1(EyeTracker.java:306) This fix is based on the https://stackoverflow.com/q/76809649 and the discussion in issue codegrits/CodeGRITS#17 . --- src/main/java/trackers/EyeTracker.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/trackers/EyeTracker.java b/src/main/java/trackers/EyeTracker.java index 46bafd6..beffbfb 100644 --- a/src/main/java/trackers/EyeTracker.java +++ b/src/main/java/trackers/EyeTracker.java @@ -9,6 +9,7 @@ import com.intellij.openapi.fileEditor.FileEditorManagerEvent; import com.intellij.openapi.fileEditor.FileEditorManagerListener; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; @@ -262,7 +263,9 @@ public void processRawData(String message) { location.setAttribute("column", String.valueOf(logicalPosition.column)); location.setAttribute("path", RelativePathGetter.getRelativePath(filePath, projectPath)); gaze.appendChild(location); - Element aSTStructure = getASTStructureElement(psiElement); + Element aSTStructure = ApplicationManager.getApplication().runReadAction( + (Computable) () -> getASTStructureElement(psiElement) + ); gaze.appendChild(aSTStructure); lastElement = psiElement; // System.out.println(gaze.getAttribute("timestamp") + " " + System.currentTimeMillis());