diff --git a/UnitTestProject1/CoreUnitTests.csproj b/UnitTestProject1/CoreUnitTests.csproj new file mode 100644 index 0000000..99e4b6a --- /dev/null +++ b/UnitTestProject1/CoreUnitTests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + diff --git a/UnitTestProject1/SmarteyeTests.cs b/UnitTestProject1/SmarteyeTests.cs new file mode 100644 index 0000000..5cccb18 --- /dev/null +++ b/UnitTestProject1/SmarteyeTests.cs @@ -0,0 +1,70 @@ +/******************************************************************************************************************************************************** +* @file SmarteyeTests.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using iTrace_Core; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace UnitTestProject1 +{ + [TestClass] + public class SmarteyeTests + { + [TestMethod] + public void ValidNetstringPass() + { + string netstring = "7:jeffrey,"; + string result = NetstringUtils.TrimSENetstring(netstring); + + Assert.AreEqual(result, "jeffrey"); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException), "")] + public void BadLengthNetstringNoPass() + { + string netstring = "9:jeffrey,"; + string result = NetstringUtils.TrimSENetstring(netstring); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException), "")] + public void ZeroLengthNetstringNoPass() + { + string netstring = "2:,"; + string result = NetstringUtils.TrimSENetstring(netstring); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException),"")] + public void NoLengthNetstringNoPass() + { + string netstring = ":jeffrey,"; + string result = NetstringUtils.TrimSENetstring(netstring); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException), "")] + public void NoSeperatorNetstringNoPass() + { + string netstring = "7jeffrey,"; + string result = NetstringUtils.TrimSENetstring(netstring); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException), "")] + public void NoCommaNetstringNoPass() + { + string netstring = "7:jeffrey"; + string result = NetstringUtils.TrimSENetstring(netstring); + } + } +} diff --git a/XUnitTestProject1/UnitTest1.cs b/XUnitTestProject1/UnitTest1.cs new file mode 100644 index 0000000..48329ae --- /dev/null +++ b/XUnitTestProject1/UnitTest1.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace XUnitTestProject1 +{ + public class UnitTest1 + { + [Fact] + public void Test1() + { + Console.WriteLine("Test"); + } + } +} diff --git a/XUnitTestProject1/XUnitTestProject1.csproj b/XUnitTestProject1/XUnitTestProject1.csproj new file mode 100644 index 0000000..4ec64e7 --- /dev/null +++ b/XUnitTestProject1/XUnitTestProject1.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + diff --git a/itrace_core.sln b/itrace_core.sln index 6bcd9b0..c191136 100644 --- a/itrace_core.sln +++ b/itrace_core.sln @@ -1,31 +1,41 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2047 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "itrace_core", "itrace_core\itrace_core.csproj", "{5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Debug|x64.ActiveCfg = Debug|x64 - {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Debug|x64.Build.0 = Debug|x64 - {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Release|Any CPU.Build.0 = Release|Any CPU - {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Release|x64.ActiveCfg = Release|x64 - {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {FD8E01EA-52AD-4714-995D-89FF2A3CAE5F} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31624.102 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "itrace_core", "itrace_core\itrace_core.csproj", "{5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreUnitTests", "UnitTestProject1\CoreUnitTests.csproj", "{F7B7FC84-7401-4185-BAE0-B78524B26BAE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Debug|x64.ActiveCfg = Debug|x64 + {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Debug|x64.Build.0 = Debug|x64 + {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Release|Any CPU.Build.0 = Release|Any CPU + {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Release|x64.ActiveCfg = Release|x64 + {5783FB9E-5502-48CF-89BF-12B3A4DFFCB2}.Release|x64.Build.0 = Release|x64 + {F7B7FC84-7401-4185-BAE0-B78524B26BAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7B7FC84-7401-4185-BAE0-B78524B26BAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7B7FC84-7401-4185-BAE0-B78524B26BAE}.Debug|x64.ActiveCfg = Debug|Any CPU + {F7B7FC84-7401-4185-BAE0-B78524B26BAE}.Debug|x64.Build.0 = Debug|Any CPU + {F7B7FC84-7401-4185-BAE0-B78524B26BAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7B7FC84-7401-4185-BAE0-B78524B26BAE}.Release|Any CPU.Build.0 = Release|Any CPU + {F7B7FC84-7401-4185-BAE0-B78524B26BAE}.Release|x64.ActiveCfg = Release|Any CPU + {F7B7FC84-7401-4185-BAE0-B78524B26BAE}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FD8E01EA-52AD-4714-995D-89FF2A3CAE5F} + EndGlobalSection +EndGlobal diff --git a/itrace_core/App.config b/itrace_core/App.config index 80b50d4..41449f7 100644 --- a/itrace_core/App.config +++ b/itrace_core/App.config @@ -18,6 +18,15 @@ 7007 + + 192.169.100.42 + + + 5800 + + + 0 + \ No newline at end of file diff --git a/itrace_core/App.xaml b/itrace_core/App.xaml index f601ff7..3fbac2f 100644 --- a/itrace_core/App.xaml +++ b/itrace_core/App.xaml @@ -1,4 +1,16 @@ -. +********************************************************************************************************************************************************/ +--> +. +********************************************************************************************************************************************************/ + +using System; using System.Collections.Generic; using System.Configuration; using System.Data; diff --git a/itrace_core/CalibrationResult.cs b/itrace_core/CalibrationResult.cs index 737d8e7..c874ef2 100644 --- a/itrace_core/CalibrationResult.cs +++ b/itrace_core/CalibrationResult.cs @@ -1,5 +1,17 @@ -using System.Collections.Generic; +/******************************************************************************************************************************************************** +* @file CalibrationResult.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System.Collections.Generic; using System.Windows; +using System.Windows.Forms; using System.Xml; namespace iTrace_Core @@ -7,11 +19,16 @@ namespace iTrace_Core abstract class CalibrationResult { public abstract void WriteToXMLWriter(XmlTextWriter xmlTextWriter); + public abstract bool IsValid(); } class EmptyCalibrationResult : CalibrationResult { public override void WriteToXMLWriter(XmlTextWriter xmlTextWriter) { } + public override bool IsValid() //TODO + { + throw new System.NotImplementedException(); + } } class TobiiCalibrationResult : CalibrationResult @@ -29,6 +46,11 @@ public TobiiCalibrationResult(Tobii.Research.CalibrationResult calibrationResult SessionManager.GetInstance().GenerateCalibrationTimeStamp(); } + public override bool IsValid() //TODO + { + throw new System.NotImplementedException(); + } + public List GetLeftEyePoints() { List leftEyePoints = new List(); @@ -121,6 +143,11 @@ public GazePointCalibrationResult(string xmlCalibrationData, int numberOfPoints) SessionManager.GetInstance().GenerateCalibrationTimeStamp(); } + public override bool IsValid() //TODO + { + throw new System.NotImplementedException(); + } + public override void WriteToXMLWriter(XmlTextWriter xmlTextWriter) { XmlNode recNode = XmlDoc.FirstChild; @@ -154,4 +181,84 @@ public override void WriteToXMLWriter(XmlTextWriter xmlTextWriter) xmlTextWriter.WriteEndElement(); } } + + class SmartEyeCalibrationResult : CalibrationResult + { + //These are the calibration vectors produced by the SE gaze calibration dialogue + private List calibrationTargets; + private SEWorldModel worldModel; + public ScreenMapping screenMapping { get; private set; } + + public SmartEyeCalibrationResult(SEWorldModel worldModel, List calibrationTargets) + { + //TODO: world model sanity checks + + this.worldModel = worldModel; + this.calibrationTargets = calibrationTargets; + this.screenMapping = new ScreenMapping(worldModel.GetScreens(), Screen.AllScreens); + + SessionManager.GetInstance().GenerateCalibrationTimeStamp(); + } + + public override bool IsValid() //TODO + { + return screenMapping.IsValid() && calibrationTargets.Count > 0; + } + + public override void WriteToXMLWriter(XmlTextWriter xmlTextWriter) + { + worldModel.WriteToXMLWriter(xmlTextWriter); + + xmlTextWriter.WriteStartElement("calibration"); + xmlTextWriter.WriteAttributeString("timestamp", SessionManager.GetInstance().CurrentCalibrationTimeStamp); + + foreach (SETarget target in calibrationTargets) + { + xmlTextWriter.WriteStartElement("calibration_point"); + xmlTextWriter.WriteAttributeString("targetId", target.targetId.ToString()); + + //Write X and Y of calibration point as percent of screen size. This may require extracting from the world model string! + //xmlTextWriter.WriteAttributeString("x", "0.5"); + //xmlTextWriter.WriteAttributeString("y", "0.5"); + + int max = target.errorsxl.Length >= target.errorsxr.Length ? target.errorsxl.Length : target.errorsxr.Length; + + for (int i = 0; i < max; i++) + { + xmlTextWriter.WriteStartElement("sample"); + + if (i < target.errorsxl.Length) + { + xmlTextWriter.WriteAttributeString("left_x", target.errorsxl[i].ToString()); + xmlTextWriter.WriteAttributeString("left_y", target.errorsyl[i].ToString()); + xmlTextWriter.WriteAttributeString("left_validity", "1"); + } else + { + xmlTextWriter.WriteAttributeString("left_x", "0.0"); + xmlTextWriter.WriteAttributeString("left_y", "0.0"); + xmlTextWriter.WriteAttributeString("left_validity", "0"); + } + + if (i < target.errorsxr.Length) + { + xmlTextWriter.WriteAttributeString("right_x", target.errorsxr[i].ToString()); + xmlTextWriter.WriteAttributeString("right_y", target.errorsyr[i].ToString()); + xmlTextWriter.WriteAttributeString("right_validity", "1"); + } + else + { + xmlTextWriter.WriteAttributeString("right_x", "0.0"); + xmlTextWriter.WriteAttributeString("right_y", "0.0"); + xmlTextWriter.WriteAttributeString("right_validity", "0"); + } + + xmlTextWriter.WriteEndElement(); + } + + xmlTextWriter.WriteEndElement(); + } + + xmlTextWriter.WriteEndElement(); + } + } } diff --git a/itrace_core/CalibrationWindow.xaml b/itrace_core/CalibrationWindow.xaml index d510588..139981b 100644 --- a/itrace_core/CalibrationWindow.xaml +++ b/itrace_core/CalibrationWindow.xaml @@ -1,4 +1,16 @@ -. +********************************************************************************************************************************************************/ +--> +. +********************************************************************************************************************************************************/ + +using System; using System.Threading; using System.Windows; using System.Windows.Controls; @@ -6,6 +17,7 @@ using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Windows.Threading; +using iTrace_Core.Properties; namespace iTrace_Core { @@ -49,9 +61,11 @@ private enum AnimationStates public CalibrationWindow() { InitializeComponent(); - - Left = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Left; - Top = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Top; + + System.Windows.Forms.Screen calibrationMonitor = System.Windows.Forms.Screen.AllScreens[Settings.Default.calibration_monitor]; + + Left = calibrationMonitor.Bounds.Left; + Top = calibrationMonitor.Bounds.Top; } private void WindowLoaded(object sender, RoutedEventArgs e) diff --git a/itrace_core/DejaVu/ComputerEventReader.cs b/itrace_core/DejaVu/ComputerEventReader.cs new file mode 100644 index 0000000..318d989 --- /dev/null +++ b/itrace_core/DejaVu/ComputerEventReader.cs @@ -0,0 +1,94 @@ +/******************************************************************************************************************************************************** +* @file ComputerEventReader.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; + + +namespace iTrace_Core +{ + public class ComputerEventReader + { + string filename; + StreamReader streamReader; + EventFactoryMap eventFactory; + List events; + int current; + + bool skipPause = false; + + public ComputerEventReader(string filename, EventFactoryMap eventFactoryMap) + { + this.filename = filename; + streamReader = new StreamReader(filename); + eventFactory = eventFactoryMap; + events = new List(); + string line; + while((line = streamReader.ReadLine()) != null) + { + events.Add(line); + } + current = 0; + streamReader.Close(); + } + + public bool Finished() + { + //return streamReader.EndOfStream; + return current == events.Count; + } + + public ComputerEvent ReadEvent() + { + //string line = streamReader.ReadLine(); + string line = events[current]; + + ComputerEvent result = eventFactory.GenerateFromCSV(line); + if(skipPause) + { + skipPause = false; + //result.PauseStrategy = new FixedLengthPause(0); + result.PauseStrategy = new EmptyPause(); + } + + + // If the event is a left mouse click, we need to check for a double click + if(result.Serialize().Contains("LeftMouseDown,") && current < events.Count - 3) + { + // Check the next 2 (3?) events to see if a double click is possible + string check1 = events[current + 1], + check2 = events[current + 2]; + + if (check1.Contains("LeftMouseUp,") && check2.Contains("LeftMouseDown,")) + { + // Next, make sure the last mouse event happened within the click interval + long changeInNS = (long.Parse(check2.Split(',')[1]) - long.Parse(events[current].Split(',')[1])) * 100; + long doubleClickTimeInNS = System.Windows.Forms.SystemInformation.DoubleClickTime * 1000000; + if(changeInNS <= doubleClickTimeInNS) + { + skipPause = true; + //result.PauseStrategy = new FixedLengthPause(0); + result.PauseStrategy = new EmptyPause(); + } + } + } + + ++current; + return result; + } + + public void Close() + { + streamReader.Close(); + } + } +} diff --git a/itrace_core/DejaVu/ComputerEventWriter.cs b/itrace_core/DejaVu/ComputerEventWriter.cs new file mode 100644 index 0000000..4117c0f --- /dev/null +++ b/itrace_core/DejaVu/ComputerEventWriter.cs @@ -0,0 +1,41 @@ +/******************************************************************************************************************************************************** +* @file ComputerEventWriter.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System.IO; + +namespace iTrace_Core +{ + public class ComputerEventWriter + { + string filename; + StreamWriter streamWriter; + + public ComputerEventWriter(string filename) + { + this.filename = filename; + streamWriter = new StreamWriter(filename); + } + private object locker = new object(); + public void Write(ComputerEvent computerEvent) + { + lock(locker) + { + streamWriter.Write(computerEvent.Serialize()); + } + } + + public void Close() + { + streamWriter.Flush(); + streamWriter.Close(); + } + } +} diff --git a/itrace_core/DejaVu/EventRecorder.cs b/itrace_core/DejaVu/EventRecorder.cs new file mode 100644 index 0000000..a097955 --- /dev/null +++ b/itrace_core/DejaVu/EventRecorder.cs @@ -0,0 +1,70 @@ +/******************************************************************************************************************************************************** +* @file EventRecorder.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace iTrace_Core +{ + public class EventRecorder + { + public bool IsRecordInProgress { get; private set; } + + KeyboardHook keyPressListener = new KeyboardHook(); + MouseHook mouseListener = new MouseHook(); + CoreClient gazeListener = new CoreClient(); + ComputerEventWriter eventWriter; + + public EventRecorder(ComputerEventWriter writer) + { + eventWriter = writer; + + mouseListener.OnMouseEvent += ComputerEventListener; + mouseListener.HookMouse(); + + keyPressListener.OnKeyboardEvent += ComputerEventListener; + keyPressListener.HookKeyboard(); + + gazeListener.OnCoreMessage += ComputerEventListener; + } + + private void ComputerEventListener(object sender, ComputerEvent e) + { + if (IsRecordInProgress) + eventWriter.Write(e); + } + + public void StartRecording() + { + IsRecordInProgress = true; + } + + public void StopRecording() + { + IsRecordInProgress = false; + } + + public void ConnectToCore() + { + gazeListener.Connect(); + } + + public void Dispose() + { + keyPressListener.Dispose(); + mouseListener.Dispose(); + eventWriter.Close(); + } + } +} diff --git a/itrace_core/DejaVu/EventReplayer.cs b/itrace_core/DejaVu/EventReplayer.cs new file mode 100644 index 0000000..583e89c --- /dev/null +++ b/itrace_core/DejaVu/EventReplayer.cs @@ -0,0 +1,131 @@ +/******************************************************************************************************************************************************** +* @file EventReplayer.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; +using System.Threading; +using System.Windows.Forms; + +namespace iTrace_Core +{ + public abstract class EventReplayer + { + protected ComputerEventReader eventReader; + protected Thread replayerThread; + + public bool IsReplayInProgress { get; protected set; } + + public event EventHandler OnReplayFinished; + + protected virtual void Replay() + { + while (!eventReader.Finished()) + { + ComputerEvent computerEvent = eventReader.ReadEvent(); + computerEvent.Replay(); + computerEvent.Pause(); + //SocketServer.Instance().SendToClients(computerEvent.Serialize()); + //WebSocketServer.Instance().SendToClients(computerEvent.Serialize()); + } + ReplayDone(); + } + + protected void ReplayDone() + { + IsReplayInProgress = false; + OnReplayFinished?.Invoke(this, new EventArgs()); + } + + public void StartReplay() + { + if (!IsReplayInProgress) + { + IsReplayInProgress = true; + + replayerThread = new Thread(Replay); + replayerThread.Start(); + } + } + + public void StopReplay() + { + eventReader.Close(); + + if (replayerThread != null) + { + SocketServer.Instance().CancelWait(); + replayerThread.Abort(); + } + } + } + + public class FixedPauseEventReplayer : EventReplayer + { + public FixedPauseEventReplayer(string filename, int pause) + { + eventReader = new ComputerEventReader(filename, new FixedPauseEventFactoryMap(pause)); + } + } + + public class BidirectionalCommunicationEventReplayer : EventReplayer + { + public BidirectionalCommunicationEventReplayer(string filename, int pause) + { + eventReader = new ComputerEventReader(filename, new BidirectionalCommunicationFactoryMap(pause)); + } + } + + public class ProportionalEventReplayer : EventReplayer + { + private ComputerEvent nextEvent; + private ComputerEvent currentEvent; + + public ProportionalEventReplayer(string filename, int scale) + { + eventReader = new ComputerEventReader(filename, new ProportionalFactoryMap(scale)); + } + + // Warning: A minimum of 3 events is required to be present in the file + // being read from. Otherwise, this function will crash. + protected override void Replay() + { + currentEvent = eventReader.ReadEvent(); + nextEvent = eventReader.ReadEvent(); + + do + { + if (currentEvent.PauseStrategy is EmptyPause) + { + currentEvent.Replay(); + currentEvent.Pause(); + } + else + { + ProportionalLengthPause strategy = currentEvent.PauseStrategy as ProportionalLengthPause; + strategy.NextEventTime = nextEvent.EventTime; + + currentEvent.Replay(); + currentEvent.Pause(); + } + + currentEvent = nextEvent; + nextEvent = eventReader.ReadEvent(); + + } while (!eventReader.Finished()); + + currentEvent.Replay(); + currentEvent.Pause(); + nextEvent.Replay(); + + ReplayDone(); + } + } + +} diff --git a/itrace_core/DejaVuLib/ComputerEvent.cs b/itrace_core/DejaVuLib/ComputerEvent.cs new file mode 100644 index 0000000..e933047 --- /dev/null +++ b/itrace_core/DejaVuLib/ComputerEvent.cs @@ -0,0 +1,458 @@ +/******************************************************************************************************************************************************** +* @file ComputerEvent.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; +using System.Threading; +using System.Windows.Forms; +using System.Drawing; + + + +namespace iTrace_Core +{ + public abstract class ComputerEvent : EventArgs + { + public IPauseStrategy PauseStrategy { get; set; } + + protected ComputerEvent() + { + EventTime = PreciseSystemTime.GetTime(); + } + + public long EventTime { get; protected set; } + + public abstract void Replay(); + public abstract string Serialize(); + public abstract void DeserializeFrom(string msg); + + public void Pause() + { + if (PauseStrategy != null) + { + PauseStrategy.Pause(this); + } + } + } + + public class KeyDown : ComputerEvent + { + private byte virtualKeyCode; + + public KeyDown() { } + + public KeyDown(byte vk) : base() + { + virtualKeyCode = vk; + } + + public override void Replay() + { + Win32.keybd_event(virtualKeyCode, 0, 0, 0); + } + + public override string Serialize() + { + return "KeyDown," + EventTime.ToString() + "," + virtualKeyCode + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + virtualKeyCode = Convert.ToByte(split[2]); + } + } + + public class KeyUp : ComputerEvent + { + byte virtualKeyCode; + + public KeyUp() { } + + public KeyUp(byte vk) : base() + { + virtualKeyCode = vk; + } + + public override void Replay() + { + Win32.keybd_event(virtualKeyCode, 0, Win32.KEYEVENTF_KEYUP, 0); + } + + public override string Serialize() + { + return "KeyUp," + EventTime.ToString() + "," + virtualKeyCode + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + virtualKeyCode = Convert.ToByte(split[2]); + } + } + + public class LeftMouseDown : ComputerEvent + { + public LeftMouseDown() : base() { } + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, (UIntPtr)0); + } + + public override string Serialize() + { + return "LeftMouseDown," + EventTime.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + } + } + + public class LeftMouseUp : ComputerEvent + { + public LeftMouseUp() : base() { } + + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_LEFTUP, 0, 0, 0, (UIntPtr)0); + } + + public override string Serialize() + { + return "LeftMouseUp," + EventTime.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + } + } + + public class RightMouseDown : ComputerEvent + { + public RightMouseDown() : base() { } + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, (UIntPtr)0); + } + + public override string Serialize() + { + return "RightMouseDown," + EventTime.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + } + } + + public class RightMouseUp : ComputerEvent + { + public RightMouseUp() : base() { } + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_RIGHTUP, 0, 0, 0, (UIntPtr)0); + } + + public override string Serialize() + { + return "RightMouseUp," + EventTime.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + } + } + + public class MiddleMouseDown : ComputerEvent + { + public MiddleMouseDown() : base() { } + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_MIDDLEDOWN, 0, 0, 0, (UIntPtr)0); + } + + public override string Serialize() + { + return "MiddleMouseDown," + EventTime.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + } + } + + public class MiddleMouseUp : ComputerEvent + { + public MiddleMouseUp() : base() { } + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_MIDDLEUP, 0, 0, 0, (UIntPtr)0); + } + + public override string Serialize() + { + return "MiddleMouseUp," + EventTime.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + } + } + + public class ForwardMouseDown : ComputerEvent + { + public ForwardMouseDown() : base() { } + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_XDOWN, 0, 0, Win32.XBUTTON2, (UIntPtr)0); + } + + public override string Serialize() + { + return "ForwardMouseDown," + EventTime.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + } + } + + public class ForwardMouseUp : ComputerEvent + { + public ForwardMouseUp() : base() { } + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_XUP, 0, 0, Win32.XBUTTON2, (UIntPtr)0); + } + + public override string Serialize() + { + return "ForwardMouseUp," + EventTime.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + } + } + + + public class BackMouseDown : ComputerEvent + { + public BackMouseDown() : base() { } + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_XDOWN, 0, 0, Win32.XBUTTON1, (UIntPtr)0); + } + + public override string Serialize() + { + return "BackMouseDown," + EventTime.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + } + } + + public class BackMouseUp : ComputerEvent + { + public BackMouseUp() : base() { } + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_XUP, 0, 0, Win32.XBUTTON1, (UIntPtr)0); + } + + public override string Serialize() + { + return "BackMouseUp," + EventTime.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + } + } + + public class MouseMove : ComputerEvent + { + int x; + int y; + + public MouseMove() { } + + public MouseMove(int newX, int newY) : base() + { + x = (newX < 0) ? -newX : newX; + y = (newY < 0) ? -newY : newY; + } + public override void Replay() + { + float dpiX, dpiY; + Graphics graphics = Graphics.FromHwnd(IntPtr.Zero); + dpiX = graphics.DpiX; + dpiY = graphics.DpiY; + + uint defaultDPI = 96; // Considered default screen DPI? Check this + //mouse_event wants position to be specified in a weird way - also need to account for screen's DPI + uint computedX = Convert.ToUInt32((x / System.Windows.SystemParameters.PrimaryScreenWidth) * 65536.0 * (defaultDPI / dpiX)); + uint computedY = Convert.ToUInt32((y / System.Windows.SystemParameters.PrimaryScreenHeight) * 65536.0 * (defaultDPI / dpiY)); + + Win32.mouse_event(Win32.MOUSEEVENTF_ABSOLUTE | Win32.MOUSEEVENTF_MOVE, computedX, computedY, 0, (UIntPtr)0); + } + + public override string Serialize() + { + return "MouseMove," + EventTime.ToString() + "," + x.ToString() + "," + y.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + x = Convert.ToInt32(split[2]); + y = Convert.ToInt32(split[3].TrimEnd('\n')); + } + } + + public class MouseWheel : ComputerEvent + { + int scrollAmount; + + public MouseWheel() { } + + public MouseWheel(uint rawScrollAmount) : base() + { + scrollAmount = Convert.ToInt32((rawScrollAmount & 0xffff0000) >> 16); + + if (scrollAmount > SystemInformation.MouseWheelScrollDelta) + scrollAmount = scrollAmount - (ushort.MaxValue + 1); + } + + public override void Replay() + { + Win32.mouse_event(Win32.MOUSEEVENTF_WHEEL, 0, 0, (uint)scrollAmount, (UIntPtr)0); + } + + public override string Serialize() + { + return "MouseWheel," + EventTime.ToString() + "," + scrollAmount.ToString() + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + + EventTime = Convert.ToInt64(split[1]); + scrollAmount = Convert.ToInt32(split[2]); + } + } + + public class CoreMessage : ComputerEvent + { + string message; + + public CoreMessage() { } + + public CoreMessage(string msg) : base() + { + message = msg; + } + + public override void Replay() + { + SocketServer.Instance().SendToClients(message + "\n"); + WebSocketServer.Instance().SendToClients(message + "\n"); + } + + public override string Serialize() + { + return message + "\n"; + } + + public override void DeserializeFrom(string msg) + { + string[] split = msg.Split(','); + if (msg != "session_end") + { + EventTime = Convert.ToInt64(split[1]); + } + else + { + EventTime = 0; + } + + message = msg; + } + + public bool IsSessionStart() + { + return message.Split(',')[0] == "session_start"; + } + } + + public class EmptyComputerEvent : ComputerEvent + { + public EmptyComputerEvent() : base() { } + + public override void Replay() { } + + public override string Serialize() + { + return ""; + } + + public override void DeserializeFrom(string msg) { } + } +} diff --git a/itrace_core/DejaVuLib/CoreClient.cs b/itrace_core/DejaVuLib/CoreClient.cs new file mode 100644 index 0000000..e95943a --- /dev/null +++ b/itrace_core/DejaVuLib/CoreClient.cs @@ -0,0 +1,72 @@ +/******************************************************************************************************************************************************** +* @file CoreClient.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; +using System.IO; +using System.Net.Sockets; +using System.Threading; + +namespace iTrace_Core +{ + public class CoreClient + { + public event EventHandler OnCoreMessage; + + const string localhostAddress = "127.0.0.1"; + const int port = 8008; + + TcpClient tcpClient; + NetworkStream networkStream; + StreamReader streamReader; + + Thread listener; + + public CoreClient() + { + tcpClient = new TcpClient(); + } + + public void Connect() + { + tcpClient.Connect(localhostAddress, port); + networkStream = tcpClient.GetStream(); + streamReader = new StreamReader(networkStream); + listener = new Thread(Listen); + listener.IsBackground = true; + listener.Start(); + } + + public void Disconnect() + { + listener.Abort(); + } + + public void Listen() + { + try + { + while (true) + { + string text = streamReader.ReadLine(); + OnCoreMessage(this, new CoreMessage(text)); + } + } + catch (ThreadAbortException e) + { + + } + catch (IOException e) + { + + } + } + } +} diff --git a/itrace_core/DejaVuLib/EventFactory.cs b/itrace_core/DejaVuLib/EventFactory.cs new file mode 100644 index 0000000..3924857 --- /dev/null +++ b/itrace_core/DejaVuLib/EventFactory.cs @@ -0,0 +1,39 @@ +/******************************************************************************************************************************************************** +* @file EventFactory.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +namespace iTrace_Core +{ + public interface IEventFactory + { + ComputerEvent Create(string serialized); + } + + public class EventFactory : IEventFactory + where T: ComputerEvent, new() + { + protected IPauseStrategy strategy; + + public EventFactory(IPauseStrategy strategy) + { + this.strategy = strategy; + } + + public ComputerEvent Create(string serialized) + { + ComputerEvent result = new T(); + + result.DeserializeFrom(serialized); + result.PauseStrategy = strategy; + + return result; + } + } +} diff --git a/itrace_core/DejaVuLib/EventFactoryMap.cs b/itrace_core/DejaVuLib/EventFactoryMap.cs new file mode 100644 index 0000000..b496d0f --- /dev/null +++ b/itrace_core/DejaVuLib/EventFactoryMap.cs @@ -0,0 +1,117 @@ +/******************************************************************************************************************************************************** +* @file EventFactoryMap.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System.Collections.Generic; + +namespace iTrace_Core +{ + public class EventFactoryMap + { + protected Dictionary factories; + + public ComputerEvent GenerateFromCSV(string line) + { + string type = line.Split(',')[0]; + + return factories.TryGetValue(type, out var factory) ? factory.Create(line) : new EmptyComputerEvent(); + } + } + + public class FixedPauseEventFactoryMap : EventFactoryMap + { + public FixedPauseEventFactoryMap(int pause) + { + FixedLengthPause strategy = new FixedLengthPause(pause); + + factories = new Dictionary + { + ["KeyDown"] = new EventFactory(strategy), + ["KeyUp"] = new EventFactory(strategy), + ["LeftMouseDown"] = new EventFactory(strategy), + ["LeftMouseUp"] = new EventFactory(strategy), + ["RightMouseDown"] = new EventFactory(strategy), + ["RightMouseUp"] = new EventFactory(strategy), + ["MouseMove"] = new EventFactory(strategy), + ["MouseWheel"] = new EventFactory(strategy), + ["gaze"] = new EventFactory(strategy), + ["session_start"] = new EventFactory(strategy), + ["session_end"] = new EventFactory(strategy), + ["MiddleMouseDown"] = new EventFactory(strategy), + ["MiddleMouseUp"] = new EventFactory(strategy), + ["ForwardMouseDown"] = new EventFactory(strategy), + ["ForwardMouseUp"] = new EventFactory(strategy), + ["BackMouseDown"] = new EventFactory(strategy), + ["BackMouseUp"] = new EventFactory(strategy) + }; + } + } + + public class BidirectionalCommunicationFactoryMap : EventFactoryMap + { + public BidirectionalCommunicationFactoryMap(int pause) + { + FixedLengthPause fixedLengthPause = new FixedLengthPause(pause); + WaitForClientPause waitForClientsPause = new WaitForClientPause(); + + factories = new Dictionary + { + ["KeyDown"] = new EventFactory(fixedLengthPause), + ["KeyUp"] = new EventFactory(fixedLengthPause), + ["LeftMouseDown"] = new EventFactory(fixedLengthPause), + ["LeftMouseUp"] = new EventFactory(fixedLengthPause), + ["RightMouseDown"] = new EventFactory(fixedLengthPause), + ["RightMouseUp"] = new EventFactory(fixedLengthPause), + ["MouseMove"] = new EventFactory(fixedLengthPause), + ["MouseWheel"] = new EventFactory(fixedLengthPause), + ["gaze"] = new EventFactory(waitForClientsPause), + ["session_start"] = new EventFactory(waitForClientsPause), + ["session_end"] = new EventFactory(waitForClientsPause), + ["MiddleMouseDown"] = new EventFactory(fixedLengthPause), + ["MiddleMouseUp"] = new EventFactory(fixedLengthPause), + ["ForwardMouseDown"] = new EventFactory(fixedLengthPause), + ["ForwardMouseUp"] = new EventFactory(fixedLengthPause), + ["BackMouseDown"] = new EventFactory(fixedLengthPause), + ["BackMouseUp"] = new EventFactory(fixedLengthPause) + }; + + } + } + + + public class ProportionalFactoryMap : EventFactoryMap + { + public ProportionalFactoryMap(int scale) + { + ProportionalLengthPause strategy = new ProportionalLengthPause(scale); + + factories = new Dictionary + { + ["KeyDown"] = new EventFactory(strategy), + ["KeyUp"] = new EventFactory(strategy), + ["LeftMouseDown"] = new EventFactory(strategy), + ["LeftMouseUp"] = new EventFactory(strategy), + ["RightMouseDown"] = new EventFactory(strategy), + ["RightMouseUp"] = new EventFactory(strategy), + ["MouseMove"] = new EventFactory(strategy), + ["MouseWheel"] = new EventFactory(strategy), + ["gaze"] = new EventFactory(strategy), + ["session_start"] = new EventFactory(strategy), + ["session_end"] = new EventFactory(strategy), + ["MiddleMouseDown"] = new EventFactory(strategy), + ["MiddleMouseUp"] = new EventFactory(strategy), + ["ForwardMouseDown"] = new EventFactory(strategy), + ["ForwardMouseUp"] = new EventFactory(strategy), + ["BackMouseDown"] = new EventFactory(strategy), + ["BackMouseUp"] = new EventFactory(strategy) + }; + } + } +} diff --git a/itrace_core/DejaVuLib/IPauseStrategy.cs b/itrace_core/DejaVuLib/IPauseStrategy.cs new file mode 100644 index 0000000..ee9df28 --- /dev/null +++ b/itrace_core/DejaVuLib/IPauseStrategy.cs @@ -0,0 +1,82 @@ +/******************************************************************************************************************************************************** +* @file IPauseStrategy.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace iTrace_Core +{ + public interface IPauseStrategy + { + void Pause(ComputerEvent toPause); + } + + public class EmptyPause : IPauseStrategy + { + public void Pause(ComputerEvent toPause) { } + } + + public class WaitForClientPause : IPauseStrategy + { + const int timeoutLength = 5000; + + public void Pause (ComputerEvent toPause) + { + SocketServer.Instance().WaitUntilClientsAreReady(timeoutLength); + } + } + + public class FixedLengthPause : IPauseStrategy + { + private int lengthInMilliseconds; + + public FixedLengthPause(int periodInMilliseconds) + { + lengthInMilliseconds = periodInMilliseconds; + } + + public void Pause (ComputerEvent toPause) + { + Thread.Sleep(lengthInMilliseconds); + } + } + + public class ProportionalLengthPause : IPauseStrategy + { + public long NextEventTime { get; set; } + + private int scaleProportion; + + private static long HundredNanosecondsToMilliseconds(long hundredNanoseconds) + { + return hundredNanoseconds / 10000; + } + + public ProportionalLengthPause(int scaleProportion) + { + this.scaleProportion = scaleProportion; + } + + public void Pause(ComputerEvent toPause) + { + long difference = NextEventTime - toPause.EventTime; + + if (difference < 0) + return; + + Thread.Sleep(Convert.ToInt32(HundredNanosecondsToMilliseconds(difference)) * scaleProportion); + } + } +} diff --git a/itrace_core/DejaVuLib/KeyboardHook.cs b/itrace_core/DejaVuLib/KeyboardHook.cs new file mode 100644 index 0000000..6f0f601 --- /dev/null +++ b/itrace_core/DejaVuLib/KeyboardHook.cs @@ -0,0 +1,65 @@ +/******************************************************************************************************************************************************** +* @file KeyboardHook.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace iTrace_Core +{ + public class KeyboardHook : IDisposable + { + private Win32.CallBackHandler callBackHandler; + private IntPtr hookID = IntPtr.Zero; + + public event EventHandler OnKeyboardEvent; + + public KeyboardHook() + { + callBackHandler = HookCallback; + } + + public void HookKeyboard() + { + using (Process curProcess = Process.GetCurrentProcess()) + using (ProcessModule curModule = curProcess.MainModule) + { + hookID = Win32.SetWindowsHookEx(Win32.WH_KEYBOARD_LL, callBackHandler, Win32.GetModuleHandle(curModule.ModuleName), 0); + } + } + + public void Dispose() + { + Win32.UnhookWindowsHookEx(hookID); + } + + private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) + { + if (nCode >= 0) + { + if ((wParam == (IntPtr)Win32.WM_KEYDOWN || wParam == (IntPtr)Win32.WM_SYSKEYDOWN)) + { + byte virtualKeyCode = Marshal.ReadByte(lParam); + + OnKeyboardEvent(this, new KeyDown(virtualKeyCode)); + } + else + { + byte virtualKeyCode = Marshal.ReadByte(lParam); + + OnKeyboardEvent(this, new KeyUp(virtualKeyCode)); + } + } + + return Win32.CallNextHookEx(hookID, nCode, wParam, lParam); + } + } +} diff --git a/itrace_core/DejaVuLib/MouseHook.cs b/itrace_core/DejaVuLib/MouseHook.cs new file mode 100644 index 0000000..598ec79 --- /dev/null +++ b/itrace_core/DejaVuLib/MouseHook.cs @@ -0,0 +1,115 @@ +/******************************************************************************************************************************************************** +* @file MouseHook.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace iTrace_Core +{ + public class MouseHook : IDisposable + { + private Win32.CallBackHandler callBackHandler; + private IntPtr hookID = IntPtr.Zero; + + public event EventHandler OnMouseEvent; + + public MouseHook() + { + callBackHandler = HookCallback; + } + + public void HookMouse() + { + using (Process currentProcess = Process.GetCurrentProcess()) + using (ProcessModule currentModule = currentProcess.MainModule) + { + hookID = Win32.SetWindowsHookEx(Win32.WH_MOUSE_LL, callBackHandler, Win32.GetModuleHandle(currentModule.ModuleName), 0); + } + } + + public void Dispose() + { + Win32.UnhookWindowsHookEx(hookID); + } + + private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) + { + if (nCode >= 0 && OnMouseEvent != null) + { + if (wParam == (IntPtr)Win32.WM_LBUTTONDOWN) + { + OnMouseEvent(this, new LeftMouseDown()); + } + else if (wParam == (IntPtr)Win32.WM_LBUTTONUP) + { + OnMouseEvent(this, new LeftMouseUp()); + } + else if (wParam == (IntPtr)Win32.WM_RBUTTONDOWN) + { + OnMouseEvent(this, new RightMouseDown()); + } + else if (wParam == (IntPtr)Win32.WM_RBUTTONUP) + { + OnMouseEvent(this, new RightMouseUp()); + } + else if (wParam == (IntPtr)Win32.WM_MOUSEMOVE) + { + Win32.MSLHOOKSTRUCT hookStruct = (Win32.MSLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.MSLHOOKSTRUCT)); + OnMouseEvent(this, new MouseMove(hookStruct.pt.x, hookStruct.pt.y)); + } + else if (wParam == (IntPtr)Win32.WM_MOUSEWHEEL) + { + Win32.MSLHOOKSTRUCT hookStruct = (Win32.MSLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.MSLHOOKSTRUCT)); + OnMouseEvent(this, new MouseWheel(hookStruct.mouseData)); + } + else if (wParam == (IntPtr)Win32.WM_MBUTTONDOWN) + { + OnMouseEvent(this, new MiddleMouseDown()); + } + else if (wParam == (IntPtr)Win32.WM_MBUTTONUP) + { + OnMouseEvent(this, new MiddleMouseUp()); + } + else if (wParam == (IntPtr)Win32.WM_XBUTTONDOWN) + { + Win32.MSLHOOKSTRUCT hookStruct = (Win32.MSLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.MSLHOOKSTRUCT)); + + UInt16 mouseDataHighOrderWord = (UInt16)(hookStruct.mouseData >> 16); + + if (mouseDataHighOrderWord == Win32.XBUTTON2) + { + OnMouseEvent(this, new ForwardMouseDown()); + } + else if (mouseDataHighOrderWord == Win32.XBUTTON1) + { + OnMouseEvent(this, new BackMouseDown()); + } + } + else if (wParam == (IntPtr)Win32.WM_XBUTTONUP) + { + Win32.MSLHOOKSTRUCT hookStruct = (Win32.MSLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.MSLHOOKSTRUCT)); + UInt16 mouseDataHighOrderWord = (UInt16)(hookStruct.mouseData >> 16); + + if (mouseDataHighOrderWord == Win32.XBUTTON2) + { + OnMouseEvent(this, new ForwardMouseUp()); + } + else if (mouseDataHighOrderWord == Win32.XBUTTON1) + { + OnMouseEvent(this, new BackMouseUp()); + } + } + } + return Win32.CallNextHookEx(hookID, nCode, wParam, lParam); + } + } +} diff --git a/itrace_core/DejaVuLib/Win32.cs b/itrace_core/DejaVuLib/Win32.cs new file mode 100644 index 0000000..d64ec26 --- /dev/null +++ b/itrace_core/DejaVuLib/Win32.cs @@ -0,0 +1,147 @@ +/******************************************************************************************************************************************************** +* @file Win32.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; +using System.Runtime.InteropServices; + +namespace iTrace_Core +{ + public class Win32 + { + //Keyboard hook constants + public const int WH_KEYBOARD_LL = 13; + public const int WH_CALLWNDPROCRET = 12; + public const int WH_KEYBOARD = 2; + public const int WM_KEYDOWN = 0x0100; + public const int WM_SYSKEYDOWN = 0x0104; + public const int WM_KEYUP = 0x0101; + public const int WM_SYSKEYUP = 0x0105; + + + //Constants for simulating key strokes + public const int KEYEVENTF_EXTENDEDKEY = 0x0001; + public const int KEYEVENTF_KEYUP = 0x0002; + + //Mouse Side buttons + public const int XBUTTON1 = 0x0001; + public const int XBUTTON2 = 0x0002; + + //Mouse hook constants + public const int WH_MOUSE_LL = 14; + public const int WM_LBUTTONDOWN = 0x0201; + public const int WM_LBUTTONUP = 0x0202; + public const int WM_MOUSEMOVE = 0x0200; + public const int WM_MOUSEWHEEL = 0x020A; + public const int WM_RBUTTONDOWN = 0x0204; + public const int WM_RBUTTONUP = 0x0205; + public const int WM_MBUTTONDOWN = 0x0207; + public const int WM_MBUTTONUP = 0x0208; + public const int WM_XBUTTONDOWN = 523; + public const int WM_XBUTTONUP = 524; + + //Constants for simulating mouse events + public const int MOUSEEVENTF_ABSOLUTE = 0x8000; + public const int MOUSEEVENTF_LEFTDOWN = 0x0002; + public const int MOUSEEVENTF_LEFTUP = 0x0004; + public const int MOUSEEVENTF_MIDDLEDOWN = 0x0020; + public const int MOUSEEVENTF_MIDDLEUP = 0x0040; + public const int MOUSEEVENTF_MOVE = 0x0001; + public const int MOUSEEVENTF_RIGHTDOWN = 0x0008; + public const int MOUSEEVENTF_RIGHTUP = 0x0010; + public const int MOUSEEVENTF_WHEEL = 0x0800; + public const int MOUSEEVENTF_XDOWN = 0x0080; + public const int MOUSEEVENTF_XUP = 0x0100; + + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int x; + public int y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MSLHOOKSTRUCT + { + public POINT pt; + public uint mouseData; + public uint flags; + public uint time; + public IntPtr dwExtraInfo; + } + + //For creating hooks + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr GetModuleHandle(string lpModuleName); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr SetWindowsHookEx(int idHook, CallBackHandler lpfn, IntPtr hMod, uint dwThreadId); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool UnhookWindowsHookEx(IntPtr hhk); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); + + public delegate IntPtr CallBackHandler(int nCode, IntPtr wParam, IntPtr lParam); + + //For simulating mouse and keyboard events + [DllImport("user32.dll", SetLastError = true)] + public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo); + + [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] + public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo); + + // WindowPositionManager related definitions + [StructLayout(LayoutKind.Sequential)] + public struct Rect + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + + [StructLayout(LayoutKind.Sequential)] + public struct WindowInfo + { + public uint cbSize; + public Rect rcWindow; + public Rect rcClient; + public uint dwStyle; + public uint dwExStyle; + public uint dwWindowStatus; + public uint cxWindowBorders; + public uint cyWindowBorders; + public long atom; // Todo: should be of type Atom (struct containing a pointer) + public uint wCreatorVersion; + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetWindowRect(IntPtr hWnd, out Rect rect); + + [DllImport("user32.dll")] + public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); + + public delegate bool CallBackPtr(IntPtr hWnd, int lParam); + + [DllImport("user32.dll")] + public static extern int EnumWindows(CallBackPtr callPtr, int lPar); + + [DllImport("user32.dll")] + public static extern bool GetWindowInfo(IntPtr hWnd, out WindowInfo pWi); + + public const uint WS_MINIMIZEBOX = 0x00020000; + public const uint WS_MAXIMIZEBOX = 0x00010000; + } +} diff --git a/itrace_core/DejaVuLib/WindowPositionManager.cs b/itrace_core/DejaVuLib/WindowPositionManager.cs new file mode 100644 index 0000000..871b863 --- /dev/null +++ b/itrace_core/DejaVuLib/WindowPositionManager.cs @@ -0,0 +1,111 @@ +/******************************************************************************************************************************************************** +* @file WindowPositionManager.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace iTrace_Core +{ + public class WindowPositionManager + { + private Thread forcerThread; + + private HashSet handles; + private HashSet refreshedHandles; + + public WindowPositionManager() + { + handles = new HashSet(); + refreshedHandles = new HashSet(); + } + + public void Start() + { + Win32.EnumWindows(InitialSeedCallback, 0); + StartForcingFixedStartLocation(); + } + + public void Stop() + { + if(forcerThread != null) + { + forcerThread.Abort(); + } + } + + private bool MoveIfNotInSetCallback(IntPtr hWnd, int lParam) + { + refreshedHandles.Add(hWnd); + + if (!handles.Contains(hWnd)) + { + handles.Add(hWnd); + + new Thread(new ThreadStart(() => + { + Thread.Sleep(200); + + var info = new Win32.WindowInfo(); + + if (Win32.GetWindowInfo(hWnd, out info)) + { + bool maximizable = (info.dwStyle & Win32.WS_MAXIMIZEBOX) != 0; + bool minimizable = (info.dwStyle & Win32.WS_MINIMIZEBOX) != 0; + + if (!maximizable && minimizable) + { + int height = Convert.ToInt32(info.rcWindow.Bottom - info.rcWindow.Top); + int width = Convert.ToInt32(info.rcWindow.Right - info.rcWindow.Left); + + Win32.MoveWindow(hWnd, 10, 10, width, height, false); + } + else if (minimizable) + { + Win32.MoveWindow(hWnd, 10, 10, 500, 500, false); + } + } + })).Start(); + } + + return true; + } + + private void StartForcingFixedStartLocation() + { + forcerThread = new Thread(new ThreadStart(() => + { + try + { + while (true) + { + Win32.EnumWindows(MoveIfNotInSetCallback, 0); + handles = refreshedHandles; + refreshedHandles = new HashSet(); + } + } + catch (ThreadAbortException e) { } + })); + + forcerThread.Start(); + } + + private bool InitialSeedCallback(IntPtr hWnd, int lParam) + { + handles.Add(hWnd); + + return true; + } + } +} diff --git a/itrace_core/EyeStatusWindow.xaml b/itrace_core/EyeStatusWindow.xaml index fe34e8e..6c964e8 100644 --- a/itrace_core/EyeStatusWindow.xaml +++ b/itrace_core/EyeStatusWindow.xaml @@ -1,4 +1,16 @@ -. +********************************************************************************************************************************************************/ +--> +. +********************************************************************************************************************************************************/ + +using System; using System.Numerics; using System.Windows; using System.Windows.Media; diff --git a/itrace_core/GazeData.cs b/itrace_core/GazeData.cs index def478d..004f3e2 100644 --- a/itrace_core/GazeData.cs +++ b/itrace_core/GazeData.cs @@ -1,4 +1,16 @@ -using System; +/******************************************************************************************************************************************************** +* @file GazeData.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using iTrace_Core.Properties; +using System; using System.Windows.Forms; using System.Xml; @@ -96,25 +108,35 @@ public TobiiGazeData(Tobii.Research.GazeDataEventArgs tobiiRawGaze) : base() { bool isLeftEyeValid = tobiiRawGaze.LeftEye.GazePoint.Validity == Tobii.Research.Validity.Valid; bool isRightEyeValid = tobiiRawGaze.RightEye.GazePoint.Validity == Tobii.Research.Validity.Valid; + Screen screen = Screen.AllScreens[Settings.Default.calibration_monitor]; + + RightX = tobiiRawGaze.RightEye.GazePoint.PositionOnDisplayArea.X * screen.Bounds.Width + screen.Bounds.Left; + RightY = tobiiRawGaze.RightEye.GazePoint.PositionOnDisplayArea.Y * screen.Bounds.Height + screen.Bounds.Top; + RightPupil = tobiiRawGaze.RightEye.Pupil.PupilDiameter; + RightValidation = Convert.ToInt32(isRightEyeValid); + + LeftX = tobiiRawGaze.LeftEye.GazePoint.PositionOnDisplayArea.X * screen.Bounds.Width + screen.Bounds.Left; + LeftY = tobiiRawGaze.LeftEye.GazePoint.PositionOnDisplayArea.Y * screen.Bounds.Height + screen.Bounds.Top; + LeftPupil = tobiiRawGaze.LeftEye.Pupil.PupilDiameter; + LeftValidation = Convert.ToInt32(isLeftEyeValid); if (isLeftEyeValid && isRightEyeValid) { - double avgX = (tobiiRawGaze.LeftEye.GazePoint.PositionOnDisplayArea.X + tobiiRawGaze.RightEye.GazePoint.PositionOnDisplayArea.X) / 2.0D; - double avgY = (tobiiRawGaze.LeftEye.GazePoint.PositionOnDisplayArea.Y + tobiiRawGaze.RightEye.GazePoint.PositionOnDisplayArea.Y) / 2.0D; - - X = Convert.ToInt32(avgX * Screen.PrimaryScreen.Bounds.Width); - Y = Convert.ToInt32(avgY * Screen.PrimaryScreen.Bounds.Height); + double avgX = (RightX + LeftX) / 2.0D; + double avgY = (RightY + LeftY) / 2.0D; + X = Convert.ToInt32(avgX); + Y = Convert.ToInt32(avgY); } else if (isLeftEyeValid) { - X = Convert.ToInt32(tobiiRawGaze.LeftEye.GazePoint.PositionOnDisplayArea.X * Screen.PrimaryScreen.Bounds.Width); - Y = Convert.ToInt32(tobiiRawGaze.LeftEye.GazePoint.PositionOnDisplayArea.Y * Screen.PrimaryScreen.Bounds.Height); + X = Convert.ToInt32(LeftX); + Y = Convert.ToInt32(LeftY); } else if (isRightEyeValid) { - X = Convert.ToInt32(tobiiRawGaze.RightEye.GazePoint.PositionOnDisplayArea.X * Screen.PrimaryScreen.Bounds.Width); - Y = Convert.ToInt32(tobiiRawGaze.RightEye.GazePoint.PositionOnDisplayArea.Y * Screen.PrimaryScreen.Bounds.Height); + X = Convert.ToInt32(RightX); + Y = Convert.ToInt32(RightY); } else { @@ -122,17 +144,6 @@ public TobiiGazeData(Tobii.Research.GazeDataEventArgs tobiiRawGaze) : base() X = null; Y = null; } - - RightX = tobiiRawGaze.RightEye.GazePoint.PositionOnDisplayArea.X * Screen.PrimaryScreen.Bounds.Width; - RightY = tobiiRawGaze.RightEye.GazePoint.PositionOnDisplayArea.Y * Screen.PrimaryScreen.Bounds.Height; - RightPupil = tobiiRawGaze.RightEye.Pupil.PupilDiameter; - RightValidation = Convert.ToInt32(isRightEyeValid); - - LeftX = tobiiRawGaze.LeftEye.GazePoint.PositionOnDisplayArea.X * Screen.PrimaryScreen.Bounds.Width; - LeftY = tobiiRawGaze.LeftEye.GazePoint.PositionOnDisplayArea.Y * Screen.PrimaryScreen.Bounds.Height; - LeftPupil = tobiiRawGaze.LeftEye.Pupil.PupilDiameter; - LeftValidation = Convert.ToInt32(isLeftEyeValid); - UserLeftX = tobiiRawGaze.LeftEye.GazeOrigin.PositionInUserCoordinates.X; UserLeftY = tobiiRawGaze.LeftEye.GazeOrigin.PositionInUserCoordinates.Y; UserLeftZ = tobiiRawGaze.LeftEye.GazeOrigin.PositionInUserCoordinates.Z; @@ -140,7 +151,7 @@ public TobiiGazeData(Tobii.Research.GazeDataEventArgs tobiiRawGaze) : base() UserRightX = tobiiRawGaze.RightEye.GazeOrigin.PositionInUserCoordinates.X; UserRightY = tobiiRawGaze.RightEye.GazeOrigin.PositionInUserCoordinates.Y; UserRightZ = tobiiRawGaze.RightEye.GazeOrigin.PositionInUserCoordinates.Z; - + TrackerTime = tobiiRawGaze.DeviceTimeStamp; } } @@ -164,6 +175,410 @@ public MouseTrackerGazeData(int mousePosX, int mousePosY) : base() } } + public class SmartEyeGazeData : GazeData + { + //Debug messages + private const bool PrintPacketInfo = false; + private const bool PrintIntersectionInfo = true; + private const bool PrintPupilInfo = false; + + //Lot of constants + //All subpacket IDs are uint16s + //These are in the same order as in the ProgrammersGuide, not in order of ID + private const UInt16 SEFrameNumber = 0x0001; + private const UInt16 SEEstimatedDelay = 0x0002; + private const UInt16 SETimeStamp = 0x0003; + private const UInt16 SEUserTimeStamp = 0x0004; + private const UInt16 SEFrameRate = 0x0005; + private const UInt16 SECameraPositions = 0x0006; + private const UInt16 SECameraRotations = 0x0007; + private const UInt16 SEUserDefinedData = 0x0008; + private const UInt16 SERealTimeClock = 0x0009; + private const UInt16 SEHeadPosition = 0x0010; //Yes, 0x10 + private const UInt16 SEHeadPositionQ = 0x0011; + private const UInt16 SEHeadRotationRodrigues = 0x0012; + private const UInt16 SEHeadRotationQuaternion = 0x001D; + private const UInt16 SEHeadLeftEarDirection = 0x0015; + private const UInt16 SEHeadUpDirection = 0x0014; + private const UInt16 SEHeadNoseDirection = 0x0013; + private const UInt16 SEHeadHeading = 0x0016; + private const UInt16 SEHeadPitch = 0x0017; + private const UInt16 SEHeadRoll = 0x0018; + private const UInt16 SEHeadRotationQ = 0x0019; + private const UInt16 SEGazeOrigin = 0x001A; + private const UInt16 SELeftGazeOrigin = 0x001B; + private const UInt16 SERightGazeOrigin = 0x001C; + private const UInt16 SEEyePosition = 0x0020; + private const UInt16 SEGazeDirection = 0x0021; + private const UInt16 SEGazeDirectionQ = 0x0022; + private const UInt16 SELeftEyePosition = 0x0023; + private const UInt16 SELeftGazeDirection = 0x0024; + private const UInt16 SELeftGazeDirectionQ = 0x0025; + private const UInt16 SERightEyePosition = 0x0026; + private const UInt16 SERightGazeDirection = 0x0027; + private const UInt16 SERightGazeDirectionQ = 0x0028; + private const UInt16 SEGazeHeading = 0x0029; + private const UInt16 SEGazePitch = 0x002A; + private const UInt16 SELeftGazeHeading = 0x002B; + private const UInt16 SELeftGazePitch = 0x002C; + private const UInt16 SERightGazeHeading = 0x002D; + private const UInt16 SERightGazePitch = 0x002E; + private const UInt16 SEFilteredGazeDirection = 0x0030; + private const UInt16 SEFilteredLeftGazeDirection = 0x0032; + private const UInt16 SEFilteredRightGazeDirection = 0x0034; + private const UInt16 SEFilteredGazeHeading = 0x0036; + private const UInt16 SEFilteredGazePitch = 0x0037; + private const UInt16 SEFilteredLeftGazeHeading = 0x0038; + private const UInt16 SEFilteredLeftGazePitch = 0x0039; + private const UInt16 SEFilteredRightGazeHeading = 0x003A; + private const UInt16 SEFilteredRightGazePitch = 0x003B; + private const UInt16 SESaccade = 0x003D; + private const UInt16 SEFixation = 0x003E; + private const UInt16 SEBlink = 0x003F; + private const UInt16 SEClosestWorldIntersection = 0x0040; + private const UInt16 SEFilteredClosestWorldIntersectionId = 0x0041; + private const UInt16 SEAllWorldIntersections = 0x0042; + private const UInt16 SEFilteredAllWorldIntersections = 0x0043; + private const UInt16 SEZoneId = 0x0044; + private const UInt16 SEEstimatedClosestWorldIntersection = 0x0045; + private const UInt16 SEEstimatedAllWorldIntersections = 0x0046; + private const UInt16 SEHeadClosestWorldIntersection = 0x0049; + private const UInt16 SEHeadAllWorldIntersections = 0x004A; + private const UInt16 SEEyelidOpening = 0x0050; + private const UInt16 SEEyelidOpeningQ = 0x0051; + private const UInt16 SELeftEyelidOpening = 0x0052; + private const UInt16 SELeftEyelidOpeningQ = 0x0053; + private const UInt16 SERightEyelidOpening = 0x0054; + private const UInt16 SERightEyelidOpeningQ = 0x0055; + private const UInt16 SEKeyboardState = 0x0056; + private const UInt16 SELeftLowerEyelidExtremePoint = 0x0058; + private const UInt16 SELeftUpperEyelidExtremePoint = 0x0059; + private const UInt16 SERightLowerEyelidExtremePoint = 0x005A; + private const UInt16 SERightUpperEyelidExtremePoint = 0x005B; + private const UInt16 SEPupilDiameter = 0x0060; + private const UInt16 SEPupilDiameterQ = 0x0061; + private const UInt16 SELeftPupilDiameter = 0x0062; + private const UInt16 SELeftPupilDiamterQ = 0x0063; + private const UInt16 SERightPupilDiameter = 0x0064; + private const UInt16 SERightPupilDiameterQ = 0x0065; + private const UInt16 SEFilteredPupilDiameter = 0x0066; + private const UInt16 SEFilteredPupilDiameterQ = 0x0067; + private const UInt16 SEFilteredLeftPupilDiameter = 0x0068; + private const UInt16 SEFilteredLeftPupilDiamterQ = 0x0069; + private const UInt16 SEFilteredRightPupilDiameter = 0x006A; + private const UInt16 SEFilteredERightPupilDiameterQ = 0x006B; + private const UInt16 SEGPSPosition = 0x0070; + private const UInt16 SEGPSGroundSpeed = 0x0071; + private const UInt16 SEGPSCourse = 0x0072; + private const UInt16 SEGPSTime = 0x0073; + private const UInt16 SEEstimatedGazeOrigin = 0x007A; + private const UInt16 SEEstimatedLeftGazeOrigin = 0x007B; + private const UInt16 SEEstimatedRightGazeOrigin = 0x007C; + private const UInt16 SEEstimatedEyePosition = 0x0080; + private const UInt16 SEEstimatedGazeDirection = 0x0081; + private const UInt16 SEEstimatedGazeDirectionQ = 0x0082; + private const UInt16 SEEstimatedGazeHeading = 0x0083; + private const UInt16 SEEstimatedGazePitch = 0x0084; + private const UInt16 SEEstimatedLeftEyePosition = 0x0085; + private const UInt16 SEEstimatedLeftGazeDirection = 0x0086; + private const UInt16 SEEstimatedLeftGazeDirectionQ = 0x0087; + private const UInt16 SEEstimatedLeftGazeHeading = 0x0088; + private const UInt16 SEEstimatedLeftGazePitch = 0x0089; + private const UInt16 SEEstimatedRightEyePosition = 0x008A; + private const UInt16 SEEstimatedRightGazeDirection = 0x008B; + private const UInt16 SEEstimatedRightGazeDirectionQ = 0x008C; + private const UInt16 SEEstimatedRightGazeHeading = 0x008D; + private const UInt16 SEEstimatedRightGazePitch = 0x008E; + private const UInt16 SEFilteredEstimatedGazeDirection = 0x0091; + private const UInt16 SEFilteredEstimatedGazeHeading = 0x0093; + private const UInt16 SEFilteredEstimatedGazePitch = 0x0094; + private const UInt16 SEFilteredEstimatedLeftGazeDirection = 0x0096; + private const UInt16 SEFilteredEstimatedLeftGazeHeading = 0x0098; + private const UInt16 SEFilteredEstimatedLeftGazePitch = 0x0099; + private const UInt16 SEFilteredEstimatedRightGazeDirection = 0x009B; + private const UInt16 SEFilteredEstimatedRightGazeHeading = 0x009D; + private const UInt16 SEFilteredEstimatedRightGazePitch = 0x009E; + private const UInt16 SEASCIIKeyboardState = 0x00A4; + private const UInt16 SECalibrationGazeIntersection = 0x00B0; + private const UInt16 SETaggedGazeIntersection = 0x00B1; + private const UInt16 SELeftClosestWorldIntersection = 0x00B2; + private const UInt16 SELeftAllWorldIntersections = 0x00B3; + private const UInt16 SERightClosestWorldIntersection = 0x00B4; + private const UInt16 SERightAllWorldIntersections = 0x00B5; + private const UInt16 SEFilteredLeftClosestWorldIntersection = 0x00B6; + private const UInt16 SEFilteredLeftAllWorldIntersections = 0x00B7; + private const UInt16 SEFilteredRightClosestWorldIntersection = 0x00B8; + private const UInt16 SEFilteredRightAllWorldIntersections = 0x00B9; + private const UInt16 SEEstimatedLeftClosestWorldIntersection = 0x00BA; + private const UInt16 SEEstimatedLeftAllWorldIntersections = 0x00BB; + private const UInt16 SEEstimatedRightClosestWorldIntersection = 0x00BC; + private const UInt16 SEEstimatedRightAllWorldIntersections = 0x00BD; + private const UInt16 SEOptimalReflexReductionMode = 0x00C3; + private const UInt16 SELeftBlinkClosingMidTime = 0x00E0; + private const UInt16 SELeftBlinkOpeningMidTime = 0x00E1; + private const UInt16 SELeftBlinkClosingAmplitude = 0x00E2; + private const UInt16 SELeftBlinkOpeningAmplitude = 0x00E3; + private const UInt16 SELeftBlinkClosingSpeed = 0x00E4; + private const UInt16 SELeftBlinkOpeningSpeed = 0x00E5; + private const UInt16 SERightBlinkClosingMidTime = 0x00E6; + private const UInt16 SERightBlinkOpeningMidTime = 0x00E7; + private const UInt16 SERightBlinkClosingAmplitude = 0x00E8; + private const UInt16 SERightBlinkOpeningAmplitude = 0x00E9; + private const UInt16 SERightBlinkClosingSpeed = 0x00EA; + private const UInt16 SERightBlinkOpeningSpeed = 0x00EB; + private const UInt16 SELeftEyeOuterCorner3D = 0x0300; + private const UInt16 SELeftEyeInnerCorner3D = 0x0301; + private const UInt16 SERightEyeInnerCorner3D = 0x0302; + private const UInt16 SERightEyeOuterCorner3D = 0x0303; + private const UInt16 SELeftNostril3D = 0x0304; + private const UInt16 SERightNostril3D = 0x0305; + private const UInt16 SELeftMouthCorner3D = 0x0306; + private const UInt16 SERightMouthCorner3D = 0x0307; + private const UInt16 SELeftEar3D = 0x0308; + private const UInt16 SERightEar3D = 0x0309; + private const UInt16 SENoseTip3D = 0x0360; + private const UInt16 SELeftEyeOuterCorner2D = 0x0310; + private const UInt16 SELeftEyeInnerCorner2D = 0x0311; + private const UInt16 SERightEyeInnerCorner2D = 0x0312; + private const UInt16 SERightEyeOuterCorner2D = 0x0313; + private const UInt16 SELeftNostril2D = 0x0314; + private const UInt16 SERightNostril2D = 0x0315; + private const UInt16 SELeftMouthCorner2D = 0x0316; + private const UInt16 SERightMouthCorner2D = 0x0317; + private const UInt16 SELeftEar2D = 0x0318; + private const UInt16 SERightEar2D = 0x0319; + private const UInt16 SEIrisMatch = 0x0350; + private const UInt16 SEPupilMatch = 0x0351; + private const UInt16 SERobustIrisMatch = 0x0352; + private const UInt16 SENoseTip2D = 0x0370; + private const UInt16 SELowerEyelidPoints = 0x0380; + private const UInt16 SEUpperEyelidPoints = 0x0381; + private const UInt16 SELeftEyelidState = 0x0390; + private const UInt16 SERightEyelidState = 0x0391; + private const UInt16 SEUserMarker = 0x03A0; + private const UInt16 SECameraClocks = 0x03A1; + + private const int SEType_u16_Size = 2; + private const int SEType_u32_Size = 4; + private const int SEType_u64_Size = 8; + private const int SEType_f64_Size = 8; + + // TODO: these should be per-session + private static UInt32 lastFixation = 0; + private static UInt32 lastBlink = 0; + + // Intersection name used primarily for multiple screens + public string intersectionName = ""; + + public SmartEyeGazeData(byte[] packet) + { + //Print packet header + UInt16 PacketType = ParseSEType_u16(packet, 4); + UInt16 PacketLength = ParseSEType_u16(packet, 6); + + if (PrintPacketInfo) + Console.WriteLine("Packet Type: {0} Length: {1}", PacketType, PacketLength); + + //Print subpackets and their IDs + //Start of first subpacket at 8 bytes + Int32 Index = 8; + + while (Index < PacketLength) + { + //Pg 9 in the SmartEye Programmers Guide gives the following offsets to the Subpacket Id and Length + UInt16 SubpacketId = ParseSEType_u16(packet, Index); + UInt16 SubpacketLength = ParseSEType_u16(packet, Index + SEType_u16_Size); + + //Advance beyond the 4 byte packet header + Index += 2 * SEType_u16_Size; + + if (PrintPacketInfo) + Console.WriteLine("\tSubpacketType: 0x{0:X} Length: {1}", SubpacketId, SubpacketLength); + + Int32 SubpacketOffset = Index; + + //Look for a left right or combined world intersection subpacket + if (SubpacketId == SEFilteredClosestWorldIntersectionId || + SubpacketId == SEFilteredLeftClosestWorldIntersection || + SubpacketId == SEFilteredRightClosestWorldIntersection) + { + + //Check if an intersection exists, the first U16 will be 1 if this is the case. + if (ParseSEType_u16(packet, SubpacketOffset) == 1) + { + int x; + int y; + + GetScreenCoordsFromWorldIntersection(packet, SubpacketOffset, out x, out y); + + //Skip over fields, need a better way of conveying where things are + SubpacketOffset += SEType_u16_Size + 6 * SEType_f64_Size; + + //Read name of intersected object + UInt16 intersectNameLength = ParseSEType_u16(packet, SubpacketOffset); + + SubpacketOffset += SEType_u16_Size; + + String intersectName = System.Text.Encoding.ASCII.GetString(packet, SubpacketOffset, intersectNameLength); + + switch (SubpacketId) + { + case SEFilteredClosestWorldIntersectionId: + this.X = x; + this.Y = y; + this.intersectionName = intersectName; + if (PrintIntersectionInfo) + Console.WriteLine("Combined Intersection \"{0}\" at coords {1}, {2}", intersectName, x, y); + break; + + case SEFilteredLeftClosestWorldIntersection: + this.LeftX = x; + this.LeftY = y; + this.LeftValidation = 1; + if (PrintIntersectionInfo) + Console.WriteLine("Left Intersection \"{0}\" at coords {1}, {2}", intersectName, x, y); + break; + + case SEFilteredRightClosestWorldIntersection: + this.RightX = x; + this.RightY = y; + this.RightValidation = 1; + if (PrintIntersectionInfo) + Console.WriteLine("Right Intersection \"{0}\" at coords {1}, {2}", intersectName, x, y); + break; + } + } + } + else if (SubpacketId == SETimeStamp) + { + this.TrackerTime = (long)ParseSEType_u64(packet, SubpacketOffset); + } + else if (SubpacketId == SELeftGazeOrigin) + { + this.UserLeftX = ParseSEType_f64(packet, SubpacketOffset); + this.UserLeftY = ParseSEType_f64(packet, SubpacketOffset + SEType_f64_Size); + this.UserLeftZ = ParseSEType_f64(packet, SubpacketOffset + 2 * SEType_f64_Size); + } + else if (SubpacketId == SERightGazeOrigin) + { + this.UserRightX = ParseSEType_f64(packet, SubpacketOffset); + this.UserRightY = ParseSEType_f64(packet, SubpacketOffset + SEType_f64_Size); + this.UserRightZ = ParseSEType_f64(packet, SubpacketOffset + 2 * SEType_f64_Size); + } + else if (SubpacketId == SEFixation) + { + UInt32 fixationNumber = ParseSEType_u32(packet, SubpacketOffset); + + if (fixationNumber > lastFixation) + { + if (true) //TODO: Add toggle + Console.WriteLine("Fixation: {0}", fixationNumber); + + lastFixation = fixationNumber; + } + } + else if (SubpacketId == SEBlink) + { + UInt32 blinkNumber = ParseSEType_u32(packet, SubpacketOffset); + + if (blinkNumber > lastBlink) + { + if (true) //TODO: Add toggle + Console.WriteLine("Blink: {0}", blinkNumber); + + lastBlink = blinkNumber; + } + } + + if (SubpacketId == SEFilteredLeftPupilDiameter || SubpacketId == SEFilteredRightPupilDiameter) + { + double diam = ParseSEType_f64(packet, SubpacketOffset); + + switch (SubpacketId) + { + case SEFilteredLeftPupilDiameter: + this.LeftPupil = diam; + if (PrintPupilInfo) + Console.WriteLine("Left Pupil: {0}", diam); + break; + + case SEFilteredRightPupilDiameter: + this.RightPupil = diam; + if (PrintPupilInfo) + Console.WriteLine("Right Pupil: {0}", diam); + break; + } + } + + //Advance to the next Subpacket + Index += SubpacketLength; + } + } + + //Offset by screen position, for multi-screen use + public void Offset(int x, int y) + { + this.X += x; + this.Y += y; + } + + //Parse a UInt16 from an SmartEye packet, accounting for endianness + private UInt16 ParseSEType_u16(byte[] packet, Int32 offset) + { + byte[] bytes = new byte[SEType_u16_Size]; + Array.ConstrainedCopy(packet, offset, bytes, 0, SEType_u16_Size); + + //Reverse bytes if system endianness is not Big + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes); + + return BitConverter.ToUInt16(bytes, 0); + } + + private UInt32 ParseSEType_u32(byte[] packet, Int32 offset) + { + byte[] bytes = new byte[SEType_u32_Size]; + Array.ConstrainedCopy(packet, offset, bytes, 0, SEType_u32_Size); + + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes); + + return BitConverter.ToUInt32(bytes, 0); + } + + private UInt64 ParseSEType_u64(byte[] packet, Int32 offset) + { + byte[] bytes = new byte[SEType_u64_Size]; + Array.ConstrainedCopy(packet, offset, bytes, 0, SEType_u64_Size); + + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes); + + return BitConverter.ToUInt64(bytes, 0); + } + + private double ParseSEType_f64(byte[] packet, Int32 offset) + { + byte[] bytes = new byte[SEType_f64_Size]; + Array.ConstrainedCopy(packet, offset, bytes, 0, SEType_f64_Size); + + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes); + + return BitConverter.ToDouble(bytes, 0); + } + + //Get X and Y coordinates from an SEType_WorldIntersection + private void GetScreenCoordsFromWorldIntersection(byte[] packet, Int32 offset, out int x, out int y) + { + //Skip over fields SEType_u16 intersections, and SEType_Point3D worldPoint (3 floats of 8 bytes each) + offset += SEType_u16_Size + 3 * SEType_f64_Size; + + x = (int)ParseSEType_f64(packet, offset); + y = (int)ParseSEType_f64(packet, offset + SEType_f64_Size); + } + } + public class GazepointGazeData : GazeData { public GazepointGazeData(String gazePointRawGaze) : base() @@ -179,20 +594,42 @@ public GazepointGazeData(String gazePointRawGaze) : base() Y = null; return; } + Screen screen = Screen.AllScreens[Settings.Default.calibration_monitor]; - X = Convert.ToInt32(Double.Parse(recNode.Attributes["BPOGX"].Value) * Screen.PrimaryScreen.Bounds.Width); - Y = Convert.ToInt32(Double.Parse(recNode.Attributes["BPOGY"].Value) * Screen.PrimaryScreen.Bounds.Height); + X = Convert.ToInt32(Double.Parse(recNode.Attributes["BPOGX"].Value) * screen.Bounds.Width + screen.Bounds.Left); + Y = Convert.ToInt32(Double.Parse(recNode.Attributes["BPOGY"].Value) * screen.Bounds.Height + screen.Bounds.Top); RightX = Double.Parse(recNode.Attributes["RPOGX"].Value) * Screen.PrimaryScreen.Bounds.Width; RightY = Double.Parse(recNode.Attributes["RPOGY"].Value) * Screen.PrimaryScreen.Bounds.Height; - RightPupil = Double.Parse(recNode.Attributes["RPD"].Value); RightValidation = Int32.Parse(recNode.Attributes["RPOGV"].Value); LeftX = Double.Parse(recNode.Attributes["LPOGX"].Value) * Screen.PrimaryScreen.Bounds.Width; LeftY = Double.Parse(recNode.Attributes["LPOGY"].Value) * Screen.PrimaryScreen.Bounds.Height; - LeftPupil = Double.Parse(recNode.Attributes["LPD"].Value); + LeftValidation = Int32.Parse(recNode.Attributes["LPOGV"].Value); + + bool rightPupilValid = Int32.Parse(recNode.Attributes["RPUPILV"].Value) == 1; + if (rightPupilValid) + { + RightPupil = Double.Parse(recNode.Attributes["RPUPILD"].Value) * 1000.0; + } + else + { + RightPupil = -1; + } + + bool leftPupilValid = Int32.Parse(recNode.Attributes["LPUPILV"].Value) == 1; + if (leftPupilValid) + { + LeftPupil = Double.Parse(recNode.Attributes["LPUPILD"].Value) * 1000.0; + } + else + { + LeftPupil = -1; + } + + UserLeftX = Double.Parse(recNode.Attributes["LEYEX"].Value); UserLeftY = Double.Parse(recNode.Attributes["LEYEY"].Value); UserLeftZ = Double.Parse(recNode.Attributes["LEYEZ"].Value); diff --git a/itrace_core/GazeHandler.cs b/itrace_core/GazeHandler.cs index 9af6b1a..3efd0e5 100644 --- a/itrace_core/GazeHandler.cs +++ b/itrace_core/GazeHandler.cs @@ -1,4 +1,15 @@ -using System; +/******************************************************************************************************************************************************** +* @file GazeHandler.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -40,7 +51,7 @@ private void DequeueGaze() { OnGazeDataReceived(this, new GazeDataReceivedEventArgs(gd)); } - Console.WriteLine(gd.Output()); + //Console.WriteLine(gd.Output()); gd = GazeQueue.Take(); } Console.WriteLine("Queue Thread Done!"); diff --git a/itrace_core/GazePointTracker.cs b/itrace_core/GazePointTracker.cs index 285b614..5e71285 100644 --- a/itrace_core/GazePointTracker.cs +++ b/itrace_core/GazePointTracker.cs @@ -1,4 +1,15 @@ -using System; +/******************************************************************************************************************************************************** +* @file GazePointTracker.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -21,6 +32,13 @@ public GazePointTracker() try { Client = new System.Net.Sockets.TcpClient(); + IAsyncResult conn = Client.BeginConnect(GAZEPOINT_ADDRESS, GAZEPOINT_PORT, null, null); + + bool asyncConnectSuccess = conn.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); + + if (!asyncConnectSuccess) + throw new Exception("Gazepoint timed out while connecting (Most likely there is no Gazepoint device available)"); + Client.Connect(GAZEPOINT_ADDRESS, GAZEPOINT_PORT); Reader = new System.IO.StreamReader(Client.GetStream()); Writer = new System.IO.StreamWriter(Client.GetStream()); @@ -64,7 +82,7 @@ public String GetTrackerSerialNumber() return TrackerSerialNumber; } - public void StartTracker() + public bool StartTracker() { new System.Threading.Thread(() => { @@ -74,6 +92,8 @@ public void StartTracker() Console.WriteLine("START GP TRACKING"); Writer.Write("\r\n"); Writer.Flush(); + + return true; } public void StopTracker() diff --git a/itrace_core/ITracker.cs b/itrace_core/ITracker.cs index b367dbc..5d6e75b 100644 --- a/itrace_core/ITracker.cs +++ b/itrace_core/ITracker.cs @@ -1,4 +1,15 @@ -using System; +/******************************************************************************************************************************************************** +* @file ITracker.cs +* +* @Copyright (C) 2022 i-trace.org +* +* This file is part of iTrace Infrastructure http://www.i-trace.org/. +* iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see . +********************************************************************************************************************************************************/ + +using System; namespace iTrace_Core { @@ -6,7 +17,7 @@ interface ITracker { void EnterCalibration(); void LeaveCalibration(); - void StartTracker(); + bool StartTracker(); void StopTracker(); String GetTrackerName(); String GetTrackerSerialNumber(); diff --git a/itrace_core/MainWindow.xaml b/itrace_core/MainWindow.xaml index 54ec467..f3b1688 100644 --- a/itrace_core/MainWindow.xaml +++ b/itrace_core/MainWindow.xaml @@ -1,60 +1,111 @@ - - - - - - - - - - - - - - - - - - - - - - -