├── .gitignore
├── .gitmodules
├── Documentation
├── Images
│ └── CodeStructure.png
└── ReleaseNotes.md
├── LICENSE
├── README.md
└── Src
├── AndroidPlugin
├── EmotivCortexLib
│ └── build.gradle
├── app
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ └── ic_launcher_foreground.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
├── gradle.properties
└── unityplugin
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emotiv
│ └── unityplugin
│ ├── CortexConnection.java
│ ├── CortexConnectionInterface.java
│ ├── CortexLibActivity.java
│ ├── CortexLibManager.java
│ └── JavaLogInterface.java
├── Authorizer.cs
├── BCIGameItf.cs
├── BCITraining.cs
├── BandPowerDataBuffer.cs
├── BufferStream.cs
├── Config.cs
├── CortexApi
├── CortexLib.cs
├── CortexLogEventHandler.cs
├── CortexStartedEventHandler.cs
├── EmbeddedCortexClientWin.cs
├── EmotivCortexLib.cs
├── EmotivCortexLibPINVOKE.cs
└── ResponseHandlerCpp.cs
├── CortexClient.cs
├── DataBuffer.cs
├── DataStreamManager.cs
├── DataStreamProcess.cs
├── DevDataBuffer.cs
├── EegMotionDataBuffer.cs
├── EmbeddedCortexClient.cs
├── EmotivUnityItf.cs
├── Headset.cs
├── HeadsetFinder.cs
├── IdentityModel
└── IdentityModel.dll
├── IosPlugin
├── CortexLibIosEmbeddedConnection.h
├── CortexLibIosEmbeddedConnection.m
├── CortexLibIosWrapper.m
└── README.md
├── JsonNet
└── Newtonsoft.Json.dll
├── MentalStateModel.cs
├── MyLogger.cs
├── PMDataBuffer.cs
├── PostProcessBuild
├── PostProcessBuild.Editor.asmdef
└── PostProcessBuild.cs
├── RecordManager.cs
├── RegistryConfig.cs
├── SessionHandler.cs
├── SuperSocket.ClientEngine.Core.0.10.0
└── SuperSocket.ClientEngine.dll
├── TrainingHandler.cs
├── Types.cs
├── UniWebViewManager.cs
├── Utils.cs
├── WebSocket4Net.0.15.2
└── WebSocket4Net.dll
├── WebsocketCortexClient.cs
└── com.cdm.authentication
├── Plugins
└── iOS
│ ├── ASWebAuthenticationSession.mm
│ └── Common.h
├── Runtime
├── Browser
│ ├── ASWebAuthenticationSession.cs
│ ├── ASWebAuthenticationSessionBrowser.cs
│ ├── ASWebAuthenticationSessionError.cs
│ ├── ASWebAuthenticationSessionErrorCode.cs
│ ├── BrowserResult.cs
│ ├── BrowserStatus.cs
│ ├── CallbackManager.cs
│ ├── CrossPlatformBrowser.cs
│ ├── DeepLinkBrowser.cs
│ ├── IBrowser.cs
│ ├── StandaloneBrowser.cs
│ └── WindowsSystemBrowser.cs
├── Cdm.Authentication.asmdef
├── Clients
│ └── MockServerAuth.cs
├── IUserInfo.cs
├── IUserInfoProvider.cs
├── OAuth2
│ ├── AccessTokenRequest.cs
│ ├── AccessTokenRequestError.cs
│ ├── AccessTokenRequestErrorCode.cs
│ ├── AccessTokenRequestException.cs
│ ├── AccessTokenResponse.cs
│ ├── AccessTokenResponseExtensions.cs
│ ├── AuthenticationError.cs
│ ├── AuthenticationException.cs
│ ├── AuthenticationSession.cs
│ ├── AuthorizationCodeFlow.cs
│ ├── AuthorizationCodeFlowWithPkce.cs
│ ├── AuthorizationCodeRequest.cs
│ ├── AuthorizationCodeRequestError.cs
│ ├── AuthorizationCodeRequestErrorCode.cs
│ ├── AuthorizationCodeRequestException.cs
│ ├── AuthorizationCodeResponse.cs
│ └── RefreshTokenRequest.cs
├── Utils
│ ├── JsonHelper.cs
│ └── UrlBuilder.cs
└── csc.rsp
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # This .gitignore file should be placed at the root of your Unity project directory
2 | #
3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
4 | #
5 | .utmp/
6 | /[Ll]ibrary/
7 | /[Tt]emp/
8 | /[Oo]bj/
9 | /[Bb]uild/
10 | /[Bb]uilds/
11 | /[Ll]ogs/
12 | /[Uu]ser[Ss]ettings/
13 |
14 | # MemoryCaptures can get excessive in size.
15 | # They also could contain extremely sensitive data
16 | /[Mm]emoryCaptures/
17 |
18 | # Recordings can get excessive in size
19 | /[Rr]ecordings/
20 |
21 | # Uncomment this line if you wish to ignore the asset store tools plugin
22 | # /[Aa]ssets/AssetStoreTools*
23 |
24 | # Autogenerated Jetbrains Rider plugin
25 | /[Aa]ssets/Plugins/Editor/JetBrains*
26 |
27 | # Visual Studio cache directory
28 | .vs/
29 |
30 | # Gradle cache directory
31 | .gradle/
32 |
33 | # Autogenerated VS/MD/Consulo solution and project files
34 | ExportedObj/
35 | .consulo/
36 | *.csproj
37 | *.unityproj
38 | *.sln
39 | *.suo
40 | *.tmp
41 | *.user
42 | *.userprefs
43 | *.pidb
44 | *.booproj
45 | *.svd
46 | *.pdb
47 | *.mdb
48 | *.opendb
49 | *.VC.db
50 |
51 | # Unity3D generated meta files
52 | *.pidb.meta
53 | *.pdb.meta
54 | *.mdb.meta
55 | *.meta
56 |
57 | # Unity3D generated file on crash reports
58 | sysinfo.txt
59 |
60 | # Builds
61 | *.apk
62 | *.aab
63 | *.unitypackage
64 | *.unitypackage.meta
65 | *.app
66 |
67 | # Crashlytics generated file
68 | crashlytics-build.properties
69 |
70 | # Packed Addressables
71 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
72 |
73 | # Temporary auto-generated Android Assets
74 | /[Aa]ssets/[Ss]treamingAssets/aa.meta
75 | /[Aa]ssets/[Ss]treamingAssets/aa/*
76 |
77 | # macOS
78 | .DS_Store
79 |
80 | # other
81 | **/*.framework
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Src/uniwebview"]
2 | path = Src/uniwebview
3 | url = git@github.com:Emotiv/uniwebview.git
4 |
--------------------------------------------------------------------------------
/Documentation/Images/CodeStructure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Documentation/Images/CodeStructure.png
--------------------------------------------------------------------------------
/Documentation/ReleaseNotes.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | ## Version 3.7 (November 2023)
4 | ### Added
5 | - Support new headset refreshing flow where App need to to call ScanHeadsets() to start headset scanning.
6 |
7 | ## Version 2.7 2(10 July 2021)
8 | ### Added
9 | - Support injectMarker and updateMarker to EEG data stream.
10 |
11 | ## Version 2.7 0(17 Apr 2021)
12 |
13 | ### Added
14 | - Support data parsing for new channel BatteryPercent of "dev" stream which is new from Cortex version 2.7.0.
15 |
16 | ### Fixed
17 | - Fixed issue parsing "Markers" channels from eeg data stream. Actually, we exclude "Markers" data from data buffer
18 | - Fixed issue sometime can not add new method \_methodForRequestId map at CortexClient.cs
19 |
20 | ## Version 2.4 (12 May 2020)
21 | For the moment the following features are supported:
22 | - Subscribe to all data streams: EEG, Motion, Device information, Band power, detections, etc.
23 | - Create a record and stop a record
24 | - Create, load and unload profiles
25 | - Perform Mental Commands and Facial Expression training
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 EMOTIV
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Emotiv Unity Plugin
2 |
3 | This plugin enables Unity applications to work with the Emotiv Cortex Service (Cortex) for EEG headset integration, data streaming, training, and recording.
4 |
5 | ---
6 |
7 | ## Prerequisites
8 |
9 | 1. [Download and install](https://www.emotiv.com/developer/) the EMOTIV Launcher with Cortex service (Windows/macOS).
10 | 2. Install Unity from [unity3d.com](https://unity3d.com/get-unity/download).
11 |
12 | ---
13 |
14 | ## Setup
15 |
16 | Clone the repo manually or add as a submodule:
17 | ```sh
18 | git submodule add https://github.com/Emotiv/unity-plugin.git /Assets/Plugins/Emotiv-Unity-Plugin
19 | ```
20 | See the [Unity example](https://github.com/Emotiv/cortex-v2-example/tree/master/unity) for reference.
21 |
22 | ---
23 |
24 | ## Platform Support
25 |
26 | The Emotiv Unity Plugin supports:
27 | - **Cortex Service** (Windows/macOS)
28 | - **Embedded Cortex** (internal use) on **Windows**, **iOS**, and **Android**
29 |
30 | ---
31 |
32 | ## Usage Guide
33 |
34 | ### Unified Interface
35 |
36 | **You only need to use [`EmotivUnityItf`](./Src/EmotivUnityItf.cs)** for all Cortex operations.
37 | You do **not** need to interact directly with `DataStreamManager`, `BCITraining`, or `RecordManager` as previous version.
38 |
39 | All public methods and properties are documented in [`EmotivUnityItf.cs`](./Src/EmotivUnityItf.cs).
40 |
41 | ---
42 |
43 | ### Typical Workflow
44 |
45 | **1.Initialize the Plugin**
46 |
47 | Call `Init()` with your app credentials and options:
48 | ```csharp
49 | EmotivUnityPlugin.EmotivUnityItf.Instance.Init(
50 | clientId: "YOUR_CLIENT_ID",
51 | clientSecret: "YOUR_CLIENT_SECRET",
52 | appName: "YOUR_APP_NAME"
53 | allowSaveLogToFile: true,
54 | isDataBufferUsing: true // or false, see Data Streaming section below
55 | // ...other optional parameters
56 | // ...other optional parameters
57 | );
58 | ```
59 |
60 | **2.Start Connection and Authorization**
61 |
62 | Call `Start()` to connect to Cortex and authorize:
63 | ```csharp
64 | EmotivUnityPlugin.EmotivUnityItf.Instance.Start();
65 | ```
66 |
67 | **3.Query for Available Headsets**
68 |
69 | Call `QueryHeadsets()` to get the list of detected headsets:
70 | ```csharp
71 | EmotivUnityPlugin.EmotivUnityItf.Instance.QueryHeadsets();
72 | ```
73 |
74 | **4.Create a Session with a Headset**
75 |
76 | Once you have at least one headset, call `CreateSessionWithHeadset()`:
77 | ```csharp
78 | EmotivUnityPlugin.EmotivUnityItf.Instance.CreateSessionWithHeadset(headsetId);
79 | ```
80 | - `headsetId` is a string like `"EPOCX-12345"`.
81 | - If you pass an empty string, the first headset in the list will be used.
82 |
83 | **5.Subscribe to Data, Training, and Recording**
84 |
85 | After the session is created, you can:
86 | - **Subscribe to data streams:**
87 | `SubscribeData(listOfStreams)`
88 | - **Start/stop recording:**
89 | `StartRecord(title)`, `StopRecord()`
90 | - **Inject markers:**
91 | `InjectMarker(label, value)`
92 | - **Manage profiles:**
93 | `LoadProfile(profileName)`, `SaveProfile()`, `UnLoadProfile()`
94 | - **Start/stop training:**
95 | `StartMCTraining()`, `AcceptMCTraining()`, etc.
96 |
97 | See [`EmotivUnityItf.cs`](./Src/EmotivUnityItf.cs) for all available methods and detailed documentation.
98 |
99 | ---
100 |
101 | ### Data Streaming Modes
102 |
103 | When calling `Init()`, you can set the `isDataBufferUsing` parameter:
104 |
105 | - **`isDataBufferUsing = false`:**
106 | Subscribed data (EEG, motion, etc.) will be saved to the internal `_messageLog` and can be accessed via the `MessageLog` property.
107 | This is suitable for simple applications or demos.
108 |
109 | - **`isDataBufferUsing = true` (default):**
110 | Data is not automatically buffered.
111 | You must manually retrieve data using functions such as:
112 | - `GetNumberEEGSamples()`
113 | - `GetEEGData(Channel_t chan)`
114 | - `GetNumberMotionSamples()`
115 | - `GetMotionData(Channel_t chan)`
116 |
117 | **Usage Example:**
118 | ```csharp
119 | int n = emotiv.GetNumberEEGSamples();
120 | if (n > 0)
121 | {
122 | foreach (var chan in emotiv.GetEEGChannels())
123 | {
124 | double[] data = emotiv.GetEEGData(chan);
125 | // process data
126 | }
127 | }
128 | ```
129 | Always check that the number of samples is greater than 0 before retrieving data for each channel.
130 |
131 | ---
132 |
133 |
134 | ### Example
135 |
136 | ```csharp
137 | var emotiv = EmotivUnityPlugin.EmotivUnityItf.Instance;
138 | emotiv.Init("clientId", "clientSecret", "appName");
139 | emotiv.Start();
140 | // ... should wait for authorize done...
141 | emotiv.QueryHeadsets();
142 | // ...wait for headset list...
143 | emotiv.CreateSessionWithHeadset("HEADSET_ID");
144 | // Now you can subscribe to data, train, or record
145 | ```
146 |
147 | ---
148 |
149 | ## API Reference
150 |
151 | - All public methods and properties are documented in [`EmotivUnityItf.cs`](./Src/EmotivUnityItf.cs).
152 | - Please refer to the source file for up-to-date descriptions and parameter details.
153 |
154 | ---
155 |
156 | ## Additional Resources
157 |
158 | - [Unity Example Project](https://github.com/Emotiv/cortex-v2-example/tree/master/unity)
159 | - [Cortex API Documentation](https://emotiv.gitbook.io/cortex-api/)
160 |
161 | ---
162 |
163 | ## Release Notes
164 |
165 | See [Documentation/ReleaseNotes.md](Documentation/ReleaseNotes.md).
166 |
167 | ---
168 |
169 | ## License
170 |
171 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
172 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/EmotivCortexLib/build.gradle:
--------------------------------------------------------------------------------
1 | configurations.maybeCreate("default")
2 | artifacts.add("default", file('EmotivCortexLib.aar'))
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | }
4 |
5 | android {
6 | namespace 'com.emotiv.unityplugin'
7 | compileSdk 34
8 |
9 | defaultConfig {
10 | applicationId "com.emotiv.unityplugin"
11 | minSdk 24
12 | targetSdk 34
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | packagingOptions {
30 | exclude '**/*.meta'
31 | }
32 | }
33 |
34 | dependencies {
35 |
36 | implementation libs.appcompat
37 | implementation libs.material
38 | testImplementation libs.junit
39 | androidTestImplementation libs.ext.junit
40 | androidTestImplementation libs.espresso.core
41 | }
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
15 |
16 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | UnityPlugin
3 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | android.enableJetifier=true
19 | # Enables namespacing of each library's R class so that its R class includes only the
20 | # resources declared in the library itself and none from the library's dependencies,
21 | # thereby reducing the size of the R class for that library
22 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/Src/AndroidPlugin/unityplugin/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/AndroidPlugin/unityplugin/consumer-rules.pro
--------------------------------------------------------------------------------
/Src/AndroidPlugin/unityplugin/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/Src/AndroidPlugin/unityplugin/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/unityplugin/src/main/java/com/emotiv/unityplugin/CortexConnection.java:
--------------------------------------------------------------------------------
1 | package com.emotiv.unityplugin;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.StrictMode;
6 | import android.util.Log;
7 |
8 | import androidx.annotation.NonNull;
9 |
10 | import com.emotiv.CortexClient;
11 | import com.emotiv.ResponseHandler;
12 | import org.json.JSONObject;
13 |
14 | public class CortexConnection implements ResponseHandler {
15 | private final String TAG = CortexConnection.class.getName();
16 | private CortexConnectionInterface mCortexConnectionInterface;
17 | // private int mCurrentStreamID = -1;
18 | private CortexClient mCortexClient = null;
19 |
20 | public CortexConnection() {
21 | // TODO: What is thread policy
22 | StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
23 | StrictMode.setThreadPolicy(policy);
24 | }
25 |
26 | public void setCortexLibConnectionInterface(CortexConnectionInterface cortexConnectionInterface) {
27 | this.mCortexConnectionInterface = cortexConnectionInterface;
28 | }
29 |
30 | public void open() {
31 | mCortexClient = new CortexClient();
32 | mCortexClient.registerResponseHandler(this);
33 | }
34 |
35 | public void close() {
36 | mCortexClient.close();
37 | }
38 |
39 | public void sendRequest(String contentRequest) {
40 | try {
41 | if (mCortexClient != null) {
42 |
43 | mCortexClient.sendRequest(contentRequest);
44 | Log.i(TAG, contentRequest);
45 | }
46 | } catch (Exception e) { e.printStackTrace(); }
47 | }
48 |
49 | public void authenticate(@NonNull Activity activity, String clientId, int activityHandleCode)
50 | {
51 | try {
52 | if (mCortexClient != null) {
53 | mCortexClient.authenticate(activity, clientId, activityHandleCode);
54 | }
55 | } catch (Exception e) { e.printStackTrace(); }
56 |
57 | }
58 |
59 | public String getAuthenticationCode(int requestCode, @NonNull Intent intent)
60 | {
61 | String result = "";
62 | try {
63 | if (mCortexClient != null) {
64 | result = mCortexClient.getAuthenticationCode(requestCode, intent);
65 | }
66 | } catch (Exception e) { e.printStackTrace(); }
67 | return result;
68 | }
69 |
70 | @Override
71 | public void processResponse(String s) {
72 | try {
73 | mCortexConnectionInterface.onReceivedMessage(s);
74 |
75 | // JSONObject jsonObj = new JSONObject(s);
76 | // // received warning message in response to a RPC request
77 | // if (jsonObj.has("warning")) {
78 | // mCortexConnectionInterface.onReceivedWarningMessage(s);
79 | // }
80 | // // error
81 | // else if (jsonObj.has("error")) {
82 | // JSONObject error = jsonObj.getJSONObject("error");
83 | // mCortexConnectionInterface.onError(error);
84 | // }
85 | // // received data from a data stream
86 | // else if (jsonObj.has("sid")) {
87 | // mCortexConnectionInterface.onReceivedNewData(s);
88 | // }
89 | // // received message in response to a RPC request
90 | // else if (jsonObj.has("id")) {
91 | // mCortexConnectionInterface.onReceivedMessage(s, mCurrentStreamID);
92 | // }
93 |
94 | } catch (Exception e) { e.printStackTrace(); }
95 | }
96 | }
--------------------------------------------------------------------------------
/Src/AndroidPlugin/unityplugin/src/main/java/com/emotiv/unityplugin/CortexConnectionInterface.java:
--------------------------------------------------------------------------------
1 | package com.emotiv.unityplugin;
2 |
3 | import org.json.JSONObject;
4 |
5 | public interface CortexConnectionInterface {
6 | void onReceivedMessage(String msg);
7 | void onCortexStarted();
8 | }
9 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/unityplugin/src/main/java/com/emotiv/unityplugin/CortexLibActivity.java:
--------------------------------------------------------------------------------
1 | package com.emotiv.unityplugin;
2 |
3 | import android.app.Application;
4 | import android.Manifest;
5 | import android.content.pm.PackageManager;
6 | import android.os.Build;
7 | import android.os.Bundle;
8 | import android.util.Log;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.appcompat.app.AppCompatActivity;
12 | import androidx.core.app.ActivityCompat;
13 | import androidx.core.content.ContextCompat;
14 | import org.json.JSONObject;
15 |
16 | import com.emotiv.CortexLibInterface;
17 | import com.emotiv.CortexLogLevel;
18 | import com.emotiv.CortexLogHandler;
19 | import android.util.Log;
20 | /**
21 | * This class is the base class for MainActivity when they want to work with EmotivCortexLib.aar
22 | * In this activity, we will request some permissions needed by CortexLib and start/stop CortexLib.
23 | */
24 | public class CortexLibActivity implements CortexLibInterface {
25 | private final String TAG = CortexLibActivity.class.getName();
26 | private boolean mCortexStarted = false;
27 | protected CortexConnection mCortexConnection = null;
28 | protected CortexConnectionInterface mCortexConnectionItf = null;
29 |
30 | protected JavaLogInterface mJavaLogInterface = null;
31 |
32 | private static final CortexLibActivity ourInstance = new CortexLibActivity();
33 |
34 | public static CortexLibActivity getInstance() {
35 | return ourInstance;
36 | }
37 |
38 | public void load(Application application) {
39 | CortexLibManager.load(application);
40 | }
41 |
42 | public void start(CortexConnectionInterface cortexResponseInterface) {
43 | if (mCortexStarted) {
44 | onCortexStarted();
45 | }
46 | else {
47 | mCortexConnectionItf = cortexResponseInterface;
48 | CortexLibManager.setLogHandler(CortexLogLevel.INFO, s -> {
49 | if(mJavaLogInterface != null) {
50 | mJavaLogInterface.onReceivedLog(s);
51 | }
52 | });
53 | CortexLibManager.start(this);
54 | }
55 | }
56 |
57 | public void setJavaLogInterface(JavaLogInterface javaLogInterface) {
58 | mJavaLogInterface = javaLogInterface;
59 | }
60 |
61 | public void stop() {
62 | CortexLibManager.stop();
63 | }
64 |
65 | public void sendRequest(String contentRequest) {
66 | if (mCortexConnection != null) {
67 | mCortexConnection.sendRequest(contentRequest);
68 | }
69 | }
70 |
71 | @Override
72 | public void onCortexStarted() {
73 | mCortexStarted = true;
74 | mCortexConnection = CortexLibManager.createConnection(mCortexConnectionItf);
75 | mCortexConnectionItf.onCortexStarted();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/unityplugin/src/main/java/com/emotiv/unityplugin/CortexLibManager.java:
--------------------------------------------------------------------------------
1 | package com.emotiv.unityplugin;
2 |
3 | import android.app.Application;
4 | import com.emotiv.CortexLibInterface;
5 | import com.emotiv.CortexLogHandler;
6 | import com.emotiv.CortexLogLevel;
7 | import com.emotiv.EmotivLibraryLoader;
8 | import com.emotiv.CortexLib;
9 | import android.util.Log;
10 |
11 | public class CortexLibManager {
12 | private final String TAG = CortexLibManager.class.getName();
13 |
14 | // This method should be called before start(), stop()
15 | public static void load(Application application) {
16 | Log.i("CortexLibManager", "load start: ");
17 | EmotivLibraryLoader loader = new EmotivLibraryLoader(application);
18 | loader.load();
19 | }
20 |
21 | public static void testLoad(int a) {
22 | Log.i("CortexLibManager", "testLoad " + Integer.toString(a));
23 | }
24 |
25 | // This method should be called after load() and before start()
26 | public static void setLogHandler(CortexLogLevel logLevel, CortexLogHandler logHandler) {
27 | CortexLib.setLogHandler(logLevel, logHandler);
28 | }
29 |
30 | // CortexLib requires some permissions and
31 | // this method should be called after users granted permissions to the app
32 | public static boolean start(CortexLibInterface cortexLibInterface) {
33 | // start the CortexLib
34 | return CortexLib.start(cortexLibInterface);
35 | }
36 |
37 | // This method should be called before the app is about to quit
38 | public static void stop() {
39 | CortexLib.stop();
40 | }
41 |
42 | // This method should be called after CortexLibInterface.onCortexStarted() callback is triggered
43 | public static CortexConnection createConnection(CortexConnectionInterface cortexConnectionInterface) {
44 | CortexConnection connection = new CortexConnection();
45 | connection.setCortexLibConnectionInterface(cortexConnectionInterface);
46 | connection.open();
47 | return connection;
48 | }
49 |
50 | // This method should be called before stop()
51 | public static void closeConnection(CortexConnection connection) {
52 | connection.close();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Src/AndroidPlugin/unityplugin/src/main/java/com/emotiv/unityplugin/JavaLogInterface.java:
--------------------------------------------------------------------------------
1 | package com.emotiv.unityplugin;
2 |
3 | public interface JavaLogInterface {
4 | void onReceivedLog(String msg);
5 | }
6 |
--------------------------------------------------------------------------------
/Src/BandPowerDataBuffer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections;
4 | using EmotivUnityPlugin;
5 | using Newtonsoft.Json.Linq;
6 |
7 |
8 |
9 | ///
10 | /// Band power data buffer.
11 | ///
12 | public class BandPowerDataBuffer : DataBuffer
13 | {
14 | BufferStream[] bufHi; // high rate buffer
15 |
16 | List _bandPowerList = new List();
17 |
18 | public static Dictionary BandPowerMap = new Dictionary() {
19 | {BandPowerType.Thetal, "theta"},
20 | {BandPowerType.Alpha, "alpha"},
21 | {BandPowerType.BetalL, "betaL"},
22 | {BandPowerType.BetalH, "betaH"},
23 | {BandPowerType.Gamma, "gamma"}
24 | };
25 |
26 | public List BandPowerList { get => _bandPowerList; set => _bandPowerList = value; }
27 |
28 | public void SetChannels(JArray bandPowerLists)
29 | {
30 | string timestamp = ChannelStringList.ChannelToString(Channel_t.CHAN_TIME_SYSTEM);
31 | _bandPowerList.Add(timestamp);
32 | foreach(var item in bandPowerLists){
33 | string chanStr = item.ToString();
34 | _bandPowerList.Add(chanStr);
35 | }
36 | }
37 |
38 | public void Clear() {
39 |
40 | if (bufHi != null) {
41 | Array.Clear(bufHi, 0, bufHi.Length);
42 | bufHi = null;
43 | }
44 | }
45 |
46 | public override void SettingBuffer(int winSize, int step, int headerCount) {
47 | int buffSize = headerCount + 1; // include "TIMESTAMP"
48 | bufHi = new BufferStream[buffSize];
49 | UnityEngine.Debug.Log("POW Setting Buffer size" + bufHi.Length);
50 | for (int i = 0; i < buffSize; i++)
51 | {
52 | if (bufHi[i] == null){
53 | bufHi[i] = new BufferStream(winSize, step);
54 | }
55 | else {
56 | bufHi[i].Reset();
57 | bufHi[i].WindowSize = winSize;
58 | bufHi[i].StepSize = step;
59 | }
60 | }
61 | }
62 |
63 | // event handler
64 | public void OnBandPowerReceived(object sender, ArrayList data)
65 | {
66 | // UnityEngine.Debug.Log("OnBandPowerReceived " + data[2].ToString() + " at " + Utils.GetEpochTimeNow().ToString());
67 | AddDataToBuffer(data);
68 | }
69 |
70 | public override void AddDataToBuffer(ArrayList data)
71 | {
72 | for (int i=0 ; i < data.Count; i++) {
73 | if (data[i] != null) {
74 | double powerData = Convert.ToDouble(data[i]);
75 | bufHi[i].AppendData(powerData);
76 | }
77 | }
78 | }
79 | public override double[] GetDataFromBuffer(int index)
80 | {
81 | return bufHi[index].NextWithRemoval();
82 | }
83 | public override double[] GetLatestDataFromBuffer(int index)
84 | {
85 | double[] nextSegment = null;
86 | double[] lastSegment = null;
87 | do
88 | {
89 | lastSegment = nextSegment;
90 | nextSegment = GetDataFromBuffer(index);
91 | }
92 | while (nextSegment != null);
93 | return lastSegment;
94 | }
95 |
96 | public double GamaPower(Channel_t channel)
97 | {
98 | if (channel == Channel_t.CHAN_FLEX_CMS || channel == Channel_t.CHAN_FLEX_DRL)
99 | return 0;
100 |
101 | double[] chanData = GetLatestDataFromBuffer(GetPowerIndex(channel, BandPowerType.Gamma));
102 | if (chanData != null) {
103 | return chanData[0];
104 | } else {
105 | return 0;
106 | }
107 | }
108 | public double ThetalPower(Channel_t channel)
109 | {
110 | if (channel == Channel_t.CHAN_FLEX_CMS || channel == Channel_t.CHAN_FLEX_DRL)
111 | return 0;
112 |
113 | double[] chanData = GetLatestDataFromBuffer(GetPowerIndex(channel, BandPowerType.Thetal));
114 | if (chanData != null) {
115 | return chanData[0];
116 | }
117 | else {
118 | return 0;
119 | }
120 |
121 | }
122 | public double AlphaPower(Channel_t channel)
123 | {
124 | if (channel == Channel_t.CHAN_FLEX_CMS || channel == Channel_t.CHAN_FLEX_DRL)
125 | return 0;
126 |
127 | try
128 | {
129 | double[] chanData = GetLatestDataFromBuffer(GetPowerIndex(channel, BandPowerType.Alpha));
130 | if (chanData != null) {
131 | return chanData[0];
132 | } else {
133 | return 0;
134 | }
135 | }
136 | catch (System.Exception e)
137 | {
138 | UnityEngine.Debug.Log("Exception :" + e.Message + " chan " + channel + " index " + GetPowerIndex(channel, BandPowerType.Alpha));
139 | return 0;
140 | }
141 | }
142 |
143 | public double BetalLPower(Channel_t channel)
144 | {
145 | if (channel == Channel_t.CHAN_FLEX_CMS || channel == Channel_t.CHAN_FLEX_DRL)
146 | return 0;
147 |
148 | double[] chanData = GetLatestDataFromBuffer(GetPowerIndex(channel, BandPowerType.BetalL));
149 | if (chanData != null) {
150 | return chanData[0];
151 | } else {
152 | return 0;
153 | }
154 | }
155 | public double BetalHPower(Channel_t channel)
156 | {
157 | if (channel == Channel_t.CHAN_FLEX_CMS || channel == Channel_t.CHAN_FLEX_DRL) {
158 | return 0;
159 | }
160 |
161 | double[] chanData = GetLatestDataFromBuffer(GetPowerIndex(channel, BandPowerType.BetalH));
162 | if (chanData != null) {
163 | return chanData[0];
164 | } else {
165 | return 0;
166 | }
167 | }
168 |
169 | public void PrintPower(BandPowerType powerType){
170 |
171 | // TODO: Check correct data
172 | double power = 0;
173 | if (powerType == BandPowerType.Alpha){
174 | power = AlphaPower(Channel_t.CHAN_AF3);
175 | }
176 | else if (powerType == BandPowerType.Gamma) {
177 | power = GamaPower(Channel_t.CHAN_AF3);
178 | }
179 | UnityEngine.Debug.Log("======PrintPower: type" + (int)powerType + " AF3: "+ power.ToString());
180 | }
181 |
182 | public int GetPowerIndex(Channel_t channel, BandPowerType powerType) {
183 | if (channel == Channel_t.CHAN_TIME_SYSTEM)
184 | return 0;
185 | string chanStr = ChannelStringList.ChannelToString(channel);
186 | string powStr = BandPowerMap[powerType];
187 | int chanIndex = _bandPowerList.IndexOf(chanStr+ "/" + powStr); // TODO check chanIndex = -1
188 | return chanIndex;
189 | }
190 |
191 | public int GetBufferSize()
192 | {
193 | if(bufHi[2] == null)
194 | return 0;
195 |
196 | return bufHi[2].GetBufSize(); // get buffer size of "AF3/alpha"
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/Src/BufferStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections;
4 |
5 | public class BufferStream
6 | {
7 | public ArrayList _buf = null;
8 | int _winSize = 0;
9 | int _stepSize = 0;
10 | int _curPos = 0;
11 | const int BUFFER_SIZE = 1024;
12 |
13 | static readonly object _object = new object();
14 |
15 | public BufferStream(int windowSize, int stepSize)
16 | {
17 | _buf = new ArrayList();
18 | _winSize = windowSize;
19 | _stepSize = stepSize;
20 | _curPos = 0;
21 | }
22 |
23 | public int WindowSize
24 | {
25 | get {
26 | return _winSize;
27 | }
28 | set {
29 | _winSize = value;
30 | }
31 | }
32 |
33 | public int StepSize
34 | {
35 | get {
36 | return _stepSize;
37 | }
38 | set {
39 | _stepSize = value;
40 | }
41 | }
42 |
43 | void processOverFlow()
44 | {
45 | if (_buf.Count <= 0)
46 | return;
47 |
48 | int overflow = _buf.Count - BUFFER_SIZE;
49 | if (overflow > 0) {
50 | _buf.RemoveRange(0, overflow);
51 | _curPos -= overflow;
52 | if(_curPos < 0)
53 | _curPos = 0;
54 | }
55 | }
56 |
57 | public void AppendData(double data)
58 | {
59 | lock (_object)
60 | {
61 | _buf.Add(data);
62 | processOverFlow();
63 | }
64 | }
65 |
66 | public void AppendData(double[] data)
67 | {
68 | lock (_object)
69 | {
70 | _buf.AddRange(data);
71 | processOverFlow();
72 | }
73 | }
74 |
75 | // Get the next segment base on window size and step size
76 | // Return a newly allocated array
77 | private double[] Next()
78 | {
79 | if (_buf.Count == 0)
80 | return null;
81 |
82 | // Not enough data for next segment, return
83 | if (_curPos + _winSize > _buf.Count)
84 | return null;
85 |
86 | // Construct segment from buffer
87 | double[] segment = new double[_winSize];
88 |
89 | _buf.CopyTo(_curPos, segment, 0, _winSize);
90 |
91 | // Increment current position by step size
92 | _curPos += _stepSize;
93 |
94 | // Return segment
95 | return segment;
96 | }
97 |
98 | public double[] NextWithRemoval()
99 | {
100 | lock (_object)
101 | {
102 | double[] segment = Next();
103 |
104 | if (segment == null)
105 | return segment;
106 |
107 | if (_buf.Count == _stepSize) {
108 | _buf = null;
109 | _buf = new ArrayList();
110 | }
111 | else {
112 | _buf.RemoveRange(0, _curPos);
113 | }
114 |
115 | ResetCurrentPosition();
116 | return segment;
117 | }
118 | }
119 |
120 | private void ResetCurrentPosition()
121 | {
122 | _curPos = 0;
123 | }
124 |
125 | public void Reset()
126 | {
127 | lock (_object)
128 | {
129 | _buf = null;
130 | _buf = new ArrayList();
131 | ResetCurrentPosition();
132 | }
133 | }
134 |
135 | public void ViewBuffer()
136 | {
137 | int i = 0;
138 | double ss = 0;
139 | string s = string.Empty;
140 | while (0==0)
141 | {
142 | try
143 | {
144 | ss += (double)_buf[i];
145 | s += _buf[i].ToString().Substring(0, 4) + " ";
146 | i++;
147 | }
148 | catch (System.ArgumentOutOfRangeException)
149 | {
150 | break;
151 | }
152 | }
153 | ss /= i;
154 | UnityEngine.Debug.Log(s);
155 | UnityEngine.Debug.Log(ss);
156 | }
157 |
158 | public int GetBufSize()
159 | {
160 | lock (_object)
161 | {
162 | return _buf.Count;
163 | }
164 | }
165 | }
166 |
167 |
--------------------------------------------------------------------------------
/Src/Config.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace EmotivUnityPlugin
4 | {
5 | ///
6 | /// Contain common Config of a Unity App.
7 | ///
8 | public static class Config
9 | {
10 | public static string AppClientId = "";
11 | public static string AppClientSecret = "";
12 | public static string AppUrl = "";
13 | public static string AppName = "";
14 | public static string ProviderName = "";
15 | public static string LogDirectory = "";
16 | public static string DataDirectory = "";
17 |
18 | public static string EmotivAppsPath = ""; // location of emotiv Apps . Eg: C:\Program Files\EmotivApps
19 | public static string TmpVersionFileName = "version.ini";
20 | public static string TmpDataFileName = "data.dat";
21 | public static string ProfilesDir = "Profiles";
22 | public static string LogsDir = "UnityLogs";
23 | public static int QUERY_HEADSET_TIME = 1000;
24 | public static int TIME_CLOSE_STREAMS = 1000;
25 | public static int RETRY_CORTEXSERVICE_TIME = 5000;
26 | public static int WAIT_USERLOGIN_TIME = 5000;
27 |
28 | // If you use an Epoc Flex headset, then you must put your configuration here
29 | // TODO: need detail here
30 | public static string FlexMapping = @"{
31 | 'CMS':'TP8', 'DRL':'P6',
32 | 'RM':'TP10','RN':'P4','RO':'P8'}";
33 |
34 | public static void Init(
35 | string clientId,
36 | string clientSecret,
37 | string appName,
38 | bool allowSaveLogAndDataToFile,
39 | string appUrl,
40 | string providerName,
41 | string emotivAppsPath
42 | )
43 | {
44 | AppClientId = clientId;
45 | AppClientSecret = clientSecret;
46 | AppName = appName;
47 | AppUrl = appUrl;
48 | ProviderName = providerName;
49 | EmotivAppsPath = emotivAppsPath;
50 |
51 | if (allowSaveLogAndDataToFile)
52 | {
53 | // create tmp directory for unity app
54 | string tmpPath = Utils.GetAppTmpPath(appName, providerName);
55 | LogDirectory = Path.Combine(tmpPath, LogsDir);
56 | DataDirectory = Path.Combine(tmpPath, ProfilesDir);
57 |
58 | // Ensure the directories exist
59 | if (!Directory.Exists(LogDirectory))
60 | {
61 | Directory.CreateDirectory(LogDirectory);
62 | }
63 |
64 | if (!Directory.Exists(DataDirectory))
65 | {
66 | Directory.CreateDirectory(DataDirectory);
67 | }
68 | }
69 | else
70 | {
71 | LogDirectory = "";
72 | DataDirectory = "";
73 | }
74 |
75 | }
76 | }
77 |
78 | public static class DataStreamName
79 | {
80 | public const string DevInfos = "dev";
81 | public const string EEG = "eeg";
82 | public const string Motion = "mot";
83 | public const string PerformanceMetrics = "met";
84 | public const string BandPower = "pow";
85 | public const string MentalCommands = "com";
86 | public const string FacialExpressions = "fac";
87 | public const string SysEvents = "sys"; // System events of the mental commands and facial expressions
88 | public const string EQ = "eq"; // EEG quality
89 | }
90 |
91 | public static class WarningCode
92 | {
93 | public const int StreamStop = 0;
94 | public const int SessionAutoClosed = 1;
95 | public const int UserLogin = 2;
96 | public const int UserLogout = 3;
97 | public const int ExtenderExportSuccess = 4;
98 | public const int ExtenderExportFailed = 5;
99 | public const int UserNotAcceptLicense = 6;
100 | public const int UserNotHaveAccessRight = 7;
101 | public const int UserRequestAccessRight = 8;
102 | public const int AccessRightGranted = 9;
103 | public const int AccessRightRejected = 10;
104 | public const int CannotDetectOSUSerInfo = 11;
105 | public const int CannotDetectOSUSername = 12;
106 | public const int ProfileLoaded = 13;
107 | public const int ProfileUnloaded = 14;
108 | public const int CortexAutoUnloadProfile = 15;
109 | public const int UserLoginOnAnotherOsUser = 16;
110 | public const int EULAAccepted = 17;
111 | public const int StreamWritingClosed = 18;
112 | public const int CortexIsReady = 23;
113 | public const int UserNotAcceptPrivateEULA = 28;
114 | public const int HeadsetWrongInformation = 100;
115 | public const int HeadsetCannotConnected = 101;
116 | public const int HeadsetConnectingTimeout = 102;
117 | public const int HeadsetDataTimeOut = 103;
118 | public const int HeadsetConnected = 104;
119 | public const int BTLEPermissionNotGranted = 31;
120 | public const int HeadsetScanFinished = 142;
121 | }
122 |
123 | // error code
124 | public static class ErrorCode {
125 | public const int LoginTokenError = -32108;
126 | public const int AuthorizeTokenError = -32109;
127 | public const int CloudTokenIsRefreshing = -32130;
128 | public const int NotReAuthorizedError = -32170;
129 | public const int CortexTokenCompareErrorAppInfo = -32135;
130 |
131 | }
132 |
133 | public static class DevStreamParams
134 | {
135 | public const string battery = "Battery";
136 | public const string signal = "Signal";
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/Src/CortexApi/CortexLib.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | //
4 | // This file was automatically generated by SWIG (https://www.swig.org).
5 | // Version 4.2.1
6 | //
7 | // Do not make changes to this file unless you know what you are doing - modify
8 | // the SWIG interface file instead.
9 | //------------------------------------------------------------------------------
10 |
11 |
12 | public class CortexLib : global::System.IDisposable {
13 | private global::System.Runtime.InteropServices.HandleRef swigCPtr;
14 | protected bool swigCMemOwn;
15 |
16 | internal CortexLib(global::System.IntPtr cPtr, bool cMemoryOwn) {
17 | swigCMemOwn = cMemoryOwn;
18 | swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
19 | }
20 |
21 | internal static global::System.Runtime.InteropServices.HandleRef getCPtr(CortexLib obj) {
22 | return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
23 | }
24 |
25 | internal static global::System.Runtime.InteropServices.HandleRef swigRelease(CortexLib obj) {
26 | if (obj != null) {
27 | if (!obj.swigCMemOwn)
28 | throw new global::System.ApplicationException("Cannot release ownership as memory is not owned");
29 | global::System.Runtime.InteropServices.HandleRef ptr = obj.swigCPtr;
30 | obj.swigCMemOwn = false;
31 | obj.Dispose();
32 | return ptr;
33 | } else {
34 | return new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
35 | }
36 | }
37 |
38 | ~CortexLib() {
39 | Dispose(false);
40 | }
41 |
42 | public void Dispose() {
43 | Dispose(true);
44 | global::System.GC.SuppressFinalize(this);
45 | }
46 |
47 | protected virtual void Dispose(bool disposing) {
48 | lock(this) {
49 | if (swigCPtr.Handle != global::System.IntPtr.Zero) {
50 | if (swigCMemOwn) {
51 | swigCMemOwn = false;
52 | EmotivCortexLibPINVOKE.delete_CortexLib(swigCPtr);
53 | }
54 | swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
55 | }
56 | }
57 | }
58 |
59 | public static void start(CortexStartedEventHandler handler) {
60 | EmotivCortexLibPINVOKE.CortexLib_start(CortexStartedEventHandler.getCPtr(handler));
61 | }
62 |
63 | public static void stop() {
64 | EmotivCortexLibPINVOKE.CortexLib_stop();
65 | }
66 |
67 | public static void setLogHandler(int logLevel, CortexLogEventHandler handler) {
68 | EmotivCortexLibPINVOKE.CortexLib_setLogHandler(logLevel, CortexLogEventHandler.getCPtr(handler));
69 | }
70 |
71 | public CortexLib() : this(EmotivCortexLibPINVOKE.new_CortexLib(), true) {
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/Src/CortexApi/CortexLogEventHandler.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | //
4 | // This file was automatically generated by SWIG (https://www.swig.org).
5 | // Version 4.2.1
6 | //
7 | // Do not make changes to this file unless you know what you are doing - modify
8 | // the SWIG interface file instead.
9 | //------------------------------------------------------------------------------
10 |
11 |
12 | public class CortexLogEventHandler : global::System.IDisposable {
13 | private global::System.Runtime.InteropServices.HandleRef swigCPtr;
14 | protected bool swigCMemOwn;
15 |
16 | internal CortexLogEventHandler(global::System.IntPtr cPtr, bool cMemoryOwn) {
17 | swigCMemOwn = cMemoryOwn;
18 | swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
19 | }
20 |
21 | internal static global::System.Runtime.InteropServices.HandleRef getCPtr(CortexLogEventHandler obj) {
22 | return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
23 | }
24 |
25 | internal static global::System.Runtime.InteropServices.HandleRef swigRelease(CortexLogEventHandler obj) {
26 | if (obj != null) {
27 | if (!obj.swigCMemOwn)
28 | throw new global::System.ApplicationException("Cannot release ownership as memory is not owned");
29 | global::System.Runtime.InteropServices.HandleRef ptr = obj.swigCPtr;
30 | obj.swigCMemOwn = false;
31 | obj.Dispose();
32 | return ptr;
33 | } else {
34 | return new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
35 | }
36 | }
37 |
38 | ~CortexLogEventHandler() {
39 | Dispose(false);
40 | }
41 |
42 | public void Dispose() {
43 | Dispose(true);
44 | global::System.GC.SuppressFinalize(this);
45 | }
46 |
47 | protected virtual void Dispose(bool disposing) {
48 | lock(this) {
49 | if (swigCPtr.Handle != global::System.IntPtr.Zero) {
50 | if (swigCMemOwn) {
51 | swigCMemOwn = false;
52 | EmotivCortexLibPINVOKE.delete_CortexLogEventHandler(swigCPtr);
53 | }
54 | swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
55 | }
56 | }
57 | }
58 |
59 | public virtual void onLogMessage(string message) {
60 | EmotivCortexLibPINVOKE.CortexLogEventHandler_onLogMessage(swigCPtr, message);
61 | if (EmotivCortexLibPINVOKE.SWIGPendingException.Pending) throw EmotivCortexLibPINVOKE.SWIGPendingException.Retrieve();
62 | }
63 |
64 | public CortexLogEventHandler() : this(EmotivCortexLibPINVOKE.new_CortexLogEventHandler(), true) {
65 | SwigDirectorConnect();
66 | }
67 |
68 | private void SwigDirectorConnect() {
69 | if (SwigDerivedClassHasMethod("onLogMessage", swigMethodTypes0))
70 | swigDelegate0 = new SwigDelegateCortexLogEventHandler_0(SwigDirectorMethodonLogMessage);
71 | EmotivCortexLibPINVOKE.CortexLogEventHandler_director_connect(swigCPtr, swigDelegate0);
72 | }
73 |
74 | private bool SwigDerivedClassHasMethod(string methodName, global::System.Type[] methodTypes) {
75 | global::System.Reflection.MethodInfo[] methodInfos = this.GetType().GetMethods(
76 | global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance);
77 | foreach (global::System.Reflection.MethodInfo methodInfo in methodInfos) {
78 | if (methodInfo.DeclaringType == null)
79 | continue;
80 |
81 | if (methodInfo.Name != methodName)
82 | continue;
83 |
84 | var parameters = methodInfo.GetParameters();
85 | if (parameters.Length != methodTypes.Length)
86 | continue;
87 |
88 | bool parametersMatch = true;
89 | for (var i = 0; i < parameters.Length; i++) {
90 | if (parameters[i].ParameterType != methodTypes[i]) {
91 | parametersMatch = false;
92 | break;
93 | }
94 | }
95 |
96 | if (!parametersMatch)
97 | continue;
98 |
99 | if (methodInfo.IsVirtual && (methodInfo.DeclaringType.IsSubclassOf(typeof(CortexLogEventHandler))) &&
100 | methodInfo.DeclaringType != methodInfo.GetBaseDefinition().DeclaringType) {
101 | return true;
102 | }
103 | }
104 |
105 | return false;
106 | }
107 |
108 | private void SwigDirectorMethodonLogMessage(string message) {
109 | onLogMessage(message);
110 | }
111 |
112 | public delegate void SwigDelegateCortexLogEventHandler_0(string message);
113 |
114 | private SwigDelegateCortexLogEventHandler_0 swigDelegate0;
115 |
116 | private static global::System.Type[] swigMethodTypes0 = new global::System.Type[] { typeof(string) };
117 | }
118 |
--------------------------------------------------------------------------------
/Src/CortexApi/CortexStartedEventHandler.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | //
4 | // This file was automatically generated by SWIG (https://www.swig.org).
5 | // Version 4.2.1
6 | //
7 | // Do not make changes to this file unless you know what you are doing - modify
8 | // the SWIG interface file instead.
9 | //------------------------------------------------------------------------------
10 |
11 |
12 | public class CortexStartedEventHandler : global::System.IDisposable {
13 | private global::System.Runtime.InteropServices.HandleRef swigCPtr;
14 | protected bool swigCMemOwn;
15 |
16 | internal CortexStartedEventHandler(global::System.IntPtr cPtr, bool cMemoryOwn) {
17 | swigCMemOwn = cMemoryOwn;
18 | swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
19 | }
20 |
21 | internal static global::System.Runtime.InteropServices.HandleRef getCPtr(CortexStartedEventHandler obj) {
22 | return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
23 | }
24 |
25 | internal static global::System.Runtime.InteropServices.HandleRef swigRelease(CortexStartedEventHandler obj) {
26 | if (obj != null) {
27 | if (!obj.swigCMemOwn)
28 | throw new global::System.ApplicationException("Cannot release ownership as memory is not owned");
29 | global::System.Runtime.InteropServices.HandleRef ptr = obj.swigCPtr;
30 | obj.swigCMemOwn = false;
31 | obj.Dispose();
32 | return ptr;
33 | } else {
34 | return new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
35 | }
36 | }
37 |
38 | ~CortexStartedEventHandler() {
39 | Dispose(false);
40 | }
41 |
42 | public void Dispose() {
43 | Dispose(true);
44 | global::System.GC.SuppressFinalize(this);
45 | }
46 |
47 | protected virtual void Dispose(bool disposing) {
48 | lock(this) {
49 | if (swigCPtr.Handle != global::System.IntPtr.Zero) {
50 | if (swigCMemOwn) {
51 | swigCMemOwn = false;
52 | EmotivCortexLibPINVOKE.delete_CortexStartedEventHandler(swigCPtr);
53 | }
54 | swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
55 | }
56 | }
57 | }
58 |
59 | public virtual void onCortexStarted() {
60 | EmotivCortexLibPINVOKE.CortexStartedEventHandler_onCortexStarted(swigCPtr);
61 | }
62 |
63 | public CortexStartedEventHandler() : this(EmotivCortexLibPINVOKE.new_CortexStartedEventHandler(), true) {
64 | SwigDirectorConnect();
65 | }
66 |
67 | private void SwigDirectorConnect() {
68 | if (SwigDerivedClassHasMethod("onCortexStarted", swigMethodTypes0))
69 | swigDelegate0 = new SwigDelegateCortexStartedEventHandler_0(SwigDirectorMethodonCortexStarted);
70 | EmotivCortexLibPINVOKE.CortexStartedEventHandler_director_connect(swigCPtr, swigDelegate0);
71 | }
72 |
73 | private bool SwigDerivedClassHasMethod(string methodName, global::System.Type[] methodTypes) {
74 | global::System.Reflection.MethodInfo[] methodInfos = this.GetType().GetMethods(
75 | global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance);
76 | foreach (global::System.Reflection.MethodInfo methodInfo in methodInfos) {
77 | if (methodInfo.DeclaringType == null)
78 | continue;
79 |
80 | if (methodInfo.Name != methodName)
81 | continue;
82 |
83 | var parameters = methodInfo.GetParameters();
84 | if (parameters.Length != methodTypes.Length)
85 | continue;
86 |
87 | bool parametersMatch = true;
88 | for (var i = 0; i < parameters.Length; i++) {
89 | if (parameters[i].ParameterType != methodTypes[i]) {
90 | parametersMatch = false;
91 | break;
92 | }
93 | }
94 |
95 | if (!parametersMatch)
96 | continue;
97 |
98 | if (methodInfo.IsVirtual && (methodInfo.DeclaringType.IsSubclassOf(typeof(CortexStartedEventHandler))) &&
99 | methodInfo.DeclaringType != methodInfo.GetBaseDefinition().DeclaringType) {
100 | return true;
101 | }
102 | }
103 |
104 | return false;
105 | }
106 |
107 | private void SwigDirectorMethodonCortexStarted() {
108 | onCortexStarted();
109 | }
110 |
111 | public delegate void SwigDelegateCortexStartedEventHandler_0();
112 |
113 | private SwigDelegateCortexStartedEventHandler_0 swigDelegate0;
114 |
115 | private static global::System.Type[] swigMethodTypes0 = new global::System.Type[] { };
116 | }
117 |
--------------------------------------------------------------------------------
/Src/CortexApi/EmbeddedCortexClientWin.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | //
4 | // This file was automatically generated by SWIG (https://www.swig.org).
5 | // Version 4.2.1
6 | //
7 | // Do not make changes to this file unless you know what you are doing - modify
8 | // the SWIG interface file instead.
9 | //------------------------------------------------------------------------------
10 |
11 |
12 | public class EmbeddedCortexClientWin : global::System.IDisposable {
13 | private global::System.Runtime.InteropServices.HandleRef swigCPtr;
14 | protected bool swigCMemOwn;
15 |
16 | internal EmbeddedCortexClientWin(global::System.IntPtr cPtr, bool cMemoryOwn) {
17 | swigCMemOwn = cMemoryOwn;
18 | swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
19 | }
20 |
21 | internal static global::System.Runtime.InteropServices.HandleRef getCPtr(EmbeddedCortexClientWin obj) {
22 | return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
23 | }
24 |
25 | internal static global::System.Runtime.InteropServices.HandleRef swigRelease(EmbeddedCortexClientWin obj) {
26 | if (obj != null) {
27 | if (!obj.swigCMemOwn)
28 | throw new global::System.ApplicationException("Cannot release ownership as memory is not owned");
29 | global::System.Runtime.InteropServices.HandleRef ptr = obj.swigCPtr;
30 | obj.swigCMemOwn = false;
31 | obj.Dispose();
32 | return ptr;
33 | } else {
34 | return new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
35 | }
36 | }
37 |
38 | ~EmbeddedCortexClientWin() {
39 | Dispose(false);
40 | }
41 |
42 | public void Dispose() {
43 | Dispose(true);
44 | global::System.GC.SuppressFinalize(this);
45 | }
46 |
47 | protected virtual void Dispose(bool disposing) {
48 | lock(this) {
49 | if (swigCPtr.Handle != global::System.IntPtr.Zero) {
50 | if (swigCMemOwn) {
51 | swigCMemOwn = false;
52 | EmotivCortexLibPINVOKE.delete_CortexClient(swigCPtr);
53 | }
54 | swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
55 | }
56 | }
57 | }
58 |
59 | public EmbeddedCortexClientWin() : this(EmotivCortexLibPINVOKE.new_CortexClient(), true) {
60 | }
61 |
62 | public void sendRequest(string message) {
63 | EmotivCortexLibPINVOKE.CortexClient_sendRequest(swigCPtr, message);
64 | if (EmotivCortexLibPINVOKE.SWIGPendingException.Pending) throw EmotivCortexLibPINVOKE.SWIGPendingException.Retrieve();
65 | }
66 |
67 | public void registerResponseHandler(ResponseHandlerCpp handler) {
68 | EmotivCortexLibPINVOKE.CortexClient_registerResponseHandler(swigCPtr, ResponseHandlerCpp.getCPtr(handler));
69 | }
70 |
71 | public void close() {
72 | EmotivCortexLibPINVOKE.CortexClient_close(swigCPtr);
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/Src/CortexApi/EmotivCortexLib.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | //
4 | // This file was automatically generated by SWIG (https://www.swig.org).
5 | // Version 4.2.1
6 | //
7 | // Do not make changes to this file unless you know what you are doing - modify
8 | // the SWIG interface file instead.
9 | //------------------------------------------------------------------------------
10 |
11 |
12 | public class EmotivCortexLib {
13 | }
14 |
--------------------------------------------------------------------------------
/Src/CortexApi/ResponseHandlerCpp.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | //
4 | // This file was automatically generated by SWIG (https://www.swig.org).
5 | // Version 4.2.1
6 | //
7 | // Do not make changes to this file unless you know what you are doing - modify
8 | // the SWIG interface file instead.
9 | //------------------------------------------------------------------------------
10 |
11 |
12 | public class ResponseHandlerCpp : global::System.IDisposable {
13 | private global::System.Runtime.InteropServices.HandleRef swigCPtr;
14 | protected bool swigCMemOwn;
15 |
16 | internal ResponseHandlerCpp(global::System.IntPtr cPtr, bool cMemoryOwn) {
17 | swigCMemOwn = cMemoryOwn;
18 | swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
19 | }
20 |
21 | internal static global::System.Runtime.InteropServices.HandleRef getCPtr(ResponseHandlerCpp obj) {
22 | return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
23 | }
24 |
25 | internal static global::System.Runtime.InteropServices.HandleRef swigRelease(ResponseHandlerCpp obj) {
26 | if (obj != null) {
27 | if (!obj.swigCMemOwn)
28 | throw new global::System.ApplicationException("Cannot release ownership as memory is not owned");
29 | global::System.Runtime.InteropServices.HandleRef ptr = obj.swigCPtr;
30 | obj.swigCMemOwn = false;
31 | obj.Dispose();
32 | return ptr;
33 | } else {
34 | return new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
35 | }
36 | }
37 |
38 | ~ResponseHandlerCpp() {
39 | Dispose(false);
40 | }
41 |
42 | public void Dispose() {
43 | Dispose(true);
44 | global::System.GC.SuppressFinalize(this);
45 | }
46 |
47 | protected virtual void Dispose(bool disposing) {
48 | lock(this) {
49 | if (swigCPtr.Handle != global::System.IntPtr.Zero) {
50 | if (swigCMemOwn) {
51 | swigCMemOwn = false;
52 | EmotivCortexLibPINVOKE.delete_ResponseHandlerCpp(swigCPtr);
53 | }
54 | swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
55 | }
56 | }
57 | }
58 |
59 | public virtual void processResponse(string responseMessage) {
60 | EmotivCortexLibPINVOKE.ResponseHandlerCpp_processResponse(swigCPtr, responseMessage);
61 | if (EmotivCortexLibPINVOKE.SWIGPendingException.Pending) throw EmotivCortexLibPINVOKE.SWIGPendingException.Retrieve();
62 | }
63 |
64 | public ResponseHandlerCpp() : this(EmotivCortexLibPINVOKE.new_ResponseHandlerCpp(), true) {
65 | SwigDirectorConnect();
66 | }
67 |
68 | private void SwigDirectorConnect() {
69 | if (SwigDerivedClassHasMethod("processResponse", swigMethodTypes0))
70 | swigDelegate0 = new SwigDelegateResponseHandlerCpp_0(SwigDirectorMethodprocessResponse);
71 | EmotivCortexLibPINVOKE.ResponseHandlerCpp_director_connect(swigCPtr, swigDelegate0);
72 | }
73 |
74 | private bool SwigDerivedClassHasMethod(string methodName, global::System.Type[] methodTypes) {
75 | global::System.Reflection.MethodInfo[] methodInfos = this.GetType().GetMethods(
76 | global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance);
77 | foreach (global::System.Reflection.MethodInfo methodInfo in methodInfos) {
78 | if (methodInfo.DeclaringType == null)
79 | continue;
80 |
81 | if (methodInfo.Name != methodName)
82 | continue;
83 |
84 | var parameters = methodInfo.GetParameters();
85 | if (parameters.Length != methodTypes.Length)
86 | continue;
87 |
88 | bool parametersMatch = true;
89 | for (var i = 0; i < parameters.Length; i++) {
90 | if (parameters[i].ParameterType != methodTypes[i]) {
91 | parametersMatch = false;
92 | break;
93 | }
94 | }
95 |
96 | if (!parametersMatch)
97 | continue;
98 |
99 | if (methodInfo.IsVirtual && (methodInfo.DeclaringType.IsSubclassOf(typeof(ResponseHandlerCpp))) &&
100 | methodInfo.DeclaringType != methodInfo.GetBaseDefinition().DeclaringType) {
101 | return true;
102 | }
103 | }
104 |
105 | return false;
106 | }
107 |
108 | private void SwigDirectorMethodprocessResponse(string responseMessage) {
109 | processResponse(responseMessage);
110 | }
111 |
112 | public delegate void SwigDelegateResponseHandlerCpp_0(string responseMessage);
113 |
114 | private SwigDelegateResponseHandlerCpp_0 swigDelegate0;
115 |
116 | private static global::System.Type[] swigMethodTypes0 = new global::System.Type[] { typeof(string) };
117 | }
118 |
--------------------------------------------------------------------------------
/Src/DataBuffer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections;
4 | using EmotivUnityPlugin;
5 |
6 | ///
7 | /// Data buffer.
8 | ///
9 | public class DataBuffer
10 | {
11 | ///
12 | /// Seting data buffer.
13 | ///
14 | public virtual void SettingBuffer(int winSize, int step, int headerCount) {
15 | UnityEngine.Debug.Log("SettingBuffer");
16 | }
17 |
18 | public virtual double[] GetDataFromBuffer(int index)
19 | {
20 | return null;
21 | }
22 |
23 | ///
24 | /// Get latest data from buffer.
25 | ///
26 | public virtual double[] GetLatestDataFromBuffer(int index)
27 | {
28 | return null;
29 | }
30 |
31 | ///
32 | /// Add data to buffer.
33 | ///
34 | public virtual void AddDataToBuffer(ArrayList data)
35 | {
36 | UnityEngine.Debug.Log("AddDataToBuffer");
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/Src/EegMotionDataBuffer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections;
4 | using Newtonsoft.Json.Linq;
5 |
6 | namespace EmotivUnityPlugin
7 | {
8 | ///
9 | /// Buffer for eeg data or motion data .
10 | ///
11 | public class EegMotionDataBuffer : DataBuffer
12 | {
13 | BufferStream[] bufHi;
14 |
15 | public enum DataType : int {
16 | EEG, MOTION
17 | }
18 |
19 | List _channels = new List();
20 | DataType _dataType = DataType.EEG;
21 |
22 | public List DataChannels { get => _channels; set => _channels = value; }
23 |
24 | public void Clear() {
25 |
26 | if (bufHi != null) {
27 | Array.Clear(bufHi, 0, bufHi.Length);
28 | bufHi = null;
29 | }
30 | }
31 | public void SetChannels( JArray channelList)
32 | {
33 | _channels.Add(Channel_t.CHAN_TIME_SYSTEM);
34 | foreach(var item in channelList) {
35 | string chanStr = item.ToString();
36 | if (chanStr != "MARKERS") // remove MARKERS chan from eeg header
37 | _channels.Add(ChannelStringList.StringToChannel(chanStr));
38 | }
39 | }
40 | public void SetDataType(DataType type)
41 | {
42 | _dataType = type;
43 | }
44 |
45 | public override void SettingBuffer(int winSize, int step, int headerCount)
46 | {
47 | int buffSize;
48 | if (_dataType == DataType.EEG)
49 | buffSize = headerCount; // include "TIMESTAMP", exclude MARKERS channel
50 | else
51 | buffSize = headerCount + 1; // include "TIMESTAMP" channel
52 |
53 | bufHi = new BufferStream[buffSize];
54 | for (int i = 0; i < buffSize; i++)
55 | {
56 | if (bufHi[i] == null){
57 | bufHi[i] = new BufferStream(winSize, step);
58 | }
59 | else {
60 | bufHi[i].Reset();
61 | bufHi[i].WindowSize = winSize;
62 | bufHi[i].StepSize = step;
63 | }
64 | }
65 | }
66 |
67 | public override void AddDataToBuffer(ArrayList data)
68 | {
69 | if (data.Count > _channels.Count) {
70 | UnityEngine.Debug.Log("AddDataToBuffer: data contain markers channels.");
71 | }
72 |
73 | for (int i=0 ; i < _channels.Count; i++) {
74 | if (data[i] != null) {
75 | double eegData = Convert.ToDouble(data[i]);
76 | bufHi[i].AppendData(eegData);
77 | }
78 | }
79 | }
80 |
81 | // Event handler
82 | public void OnDataReceived(object sender, ArrayList data) {
83 | AddDataToBuffer(data);
84 | }
85 |
86 | public override double[] GetDataFromBuffer(int index)
87 | {
88 | return bufHi[index].NextWithRemoval();
89 | }
90 | public override double[] GetLatestDataFromBuffer(int index)
91 | {
92 | double[] nextSegment = null;
93 | double[] lastSegment = null;
94 | do
95 | {
96 | lastSegment = nextSegment;
97 | nextSegment = GetDataFromBuffer(index);
98 | }
99 | while (nextSegment != null);
100 | return lastSegment;
101 | }
102 |
103 | public double[] GetAllDataFromBuffer(int index)
104 | {
105 | List dataList = new List();
106 | double[] nextSegment = null;
107 | do {
108 | nextSegment = GetDataFromBuffer(index);
109 | if(nextSegment != null)
110 | dataList.AddRange(nextSegment);
111 | }
112 | while (nextSegment != null);
113 | return dataList.ToArray();
114 | }
115 |
116 | // get data both eeg and motion data
117 | public double[] GetData(Channel_t channel)
118 | {
119 | if (channel == Channel_t.CHAN_FLEX_CMS || channel == Channel_t.CHAN_FLEX_DRL)
120 | return null;
121 |
122 | try {
123 | return GetAllDataFromBuffer(GetChanIndex(channel));
124 | }
125 | catch (System.Exception e) {
126 | UnityEngine.Debug.Log(" exception " + e.Message + " index " + GetChanIndex(channel)
127 | + " chan " + (int)channel + " buffSize " + bufHi.Length);
128 | return null;
129 | }
130 | }
131 |
132 | public int GetBufferSize()
133 | {
134 | if(bufHi[3] == null)
135 | return 0;
136 |
137 | return bufHi[3].GetBufSize(); // get buffer size of AF3
138 | }
139 |
140 | public int GetChanIndex(Channel_t chan)
141 | {
142 | int chanIndex = _channels.IndexOf(chan);
143 | return (int)chanIndex;
144 | }
145 |
146 | public void PrintEEgData()
147 | {
148 | double[] eeg = GetData(Channel_t.CHAN_AF3);
149 | UnityEngine.Debug.Log("======PrintEEgData: AF3: size: "
150 | + eeg.Length + " [0]: " + eeg[0].ToString() );
151 | }
152 | }
153 | }
154 |
155 |
--------------------------------------------------------------------------------
/Src/Headset.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System.Collections;
3 | using System;
4 | using UnityEngine;
5 |
6 | namespace EmotivUnityPlugin
7 | {
8 | public class Headset
9 | {
10 | private string _headsetID;
11 | private string _status;
12 | private string _serialId;
13 | private string _firmwareVersion;
14 | private string _dongleSerial;
15 | private ArrayList _sensors;
16 | private ArrayList _motionSensors;
17 | private JObject _settings;
18 | private ConnectionType _connectedBy;
19 | private HeadsetTypes _headsetType;
20 | private string _mode;
21 |
22 | // Contructor
23 | public Headset()
24 | {
25 | }
26 | public Headset (JObject jHeadset)
27 | {
28 | HeadsetID = (string)jHeadset["id"];
29 |
30 | if (HeadsetID.Contains(HeadsetNames.epoc_plus))
31 | {
32 | HeadsetType = HeadsetTypes.HEADSET_TYPE_EPOC_PLUS;
33 | }
34 | else if (HeadsetID.Contains(HeadsetNames.epoc_flex))
35 | {
36 | HeadsetType = HeadsetTypes.HEADSET_TYPE_EPOC_FLEX;
37 | }
38 | else if (HeadsetID.Contains(HeadsetNames.epoc_x))
39 | {
40 | HeadsetType = HeadsetTypes.HEADSET_TYPE_EPOC_X;
41 | }
42 | else if (HeadsetID.Contains(HeadsetNames.insight2))
43 | {
44 | HeadsetType = HeadsetTypes.HEADSET_TYPE_INSIGHT2;
45 | }
46 | else if (HeadsetID.Contains(HeadsetNames.insight))
47 | {
48 | HeadsetType = HeadsetTypes.HEADSET_TYPE_INSIGHT;
49 | }
50 | else if (HeadsetID.Contains(HeadsetNames.mn8))
51 | {
52 | HeadsetType = HeadsetTypes.HEADSET_TYPE_MN8;
53 | }
54 | else if (HeadsetID.Contains(HeadsetNames.mw20))
55 | {
56 | HeadsetType = HeadsetTypes.HEADSET_TYPE_MW20;
57 | }
58 | else if (HeadsetID.Contains(HeadsetNames.xtrode))
59 | {
60 | HeadsetType = HeadsetTypes.HEADSET_TYPE_XTRODE;
61 | }
62 | else if (HeadsetID.Contains(HeadsetNames.epoc))
63 | {
64 | HeadsetType = HeadsetTypes.HEADSET_TYPE_EPOC_STD;
65 | }
66 | else if (HeadsetID.Contains(HeadsetNames.flex2))
67 | {
68 | HeadsetType = HeadsetTypes.HEADSET_TYPE_FLEX2;
69 | }
70 |
71 | Status = (string)jHeadset["status"];
72 | FirmwareVersion = (string)jHeadset["firmware"];
73 | DongleSerial = (string)jHeadset["dongle"];
74 | Sensors = new ArrayList();
75 |
76 | foreach (JToken sensor in (JArray)jHeadset["sensors"])
77 | {
78 | Sensors.Add(sensor.ToString());
79 | }
80 | MotionSensors = new ArrayList();
81 | foreach (JToken sensor in (JArray)jHeadset["motionSensors"])
82 | {
83 | MotionSensors.Add(sensor.ToString());
84 | }
85 | Mode = (string)jHeadset["mode"];
86 | string cnnBy = (string)jHeadset["connectedBy"];
87 | if (cnnBy == "dongle") {
88 | HeadsetConnection = ConnectionType.CONN_TYPE_DONGLE;
89 | }
90 | else if (cnnBy == "nRF bluetooth") {
91 | HeadsetConnection = ConnectionType.CONN_TYPE_NRF_BLUETOOTH;
92 | }
93 | else if (cnnBy == "bluetooth") {
94 | HeadsetConnection = ConnectionType.CONN_TYPE_BTLE;
95 | }
96 | else if (cnnBy == "extender") {
97 | HeadsetConnection = ConnectionType.CONN_TYPE_EXTENDER;
98 | }
99 | else if (cnnBy == "usb cable") {
100 | HeadsetConnection = ConnectionType.CONN_TYPE_USB_CABLE;
101 | }
102 | else {
103 | HeadsetConnection = ConnectionType.CONN_TYPE_UNKNOWN;
104 | }
105 | Settings = (JObject)jHeadset["settings"];
106 | }
107 |
108 | // Properties
109 | public string HeadsetID
110 | {
111 | get {
112 | return _headsetID;
113 | }
114 |
115 | set {
116 | _headsetID = value;
117 | }
118 | }
119 |
120 | public HeadsetTypes HeadsetType
121 | {
122 | get {
123 | return _headsetType;
124 | }
125 |
126 | set {
127 | _headsetType = value;
128 | }
129 | }
130 |
131 | public string Status
132 | {
133 | get {
134 | return _status;
135 | }
136 |
137 | set {
138 | _status = value;
139 | }
140 | }
141 |
142 | public string SerialId
143 | {
144 | get {
145 | return _serialId;
146 | }
147 |
148 | set {
149 | _serialId = value;
150 | }
151 | }
152 |
153 | public string FirmwareVersion
154 | {
155 | get {
156 | return _firmwareVersion;
157 | }
158 |
159 | set {
160 | _firmwareVersion = value;
161 | }
162 | }
163 |
164 | public string DongleSerial
165 | {
166 | get {
167 | return _dongleSerial;
168 | }
169 |
170 | set {
171 | _dongleSerial = value;
172 | }
173 | }
174 |
175 | public ArrayList Sensors
176 | {
177 | get {
178 | return _sensors;
179 | }
180 |
181 | set {
182 | _sensors = value;
183 | }
184 | }
185 |
186 | public ArrayList MotionSensors
187 | {
188 | get {
189 | return _motionSensors;
190 | }
191 |
192 | set {
193 | _motionSensors = value;
194 | }
195 | }
196 |
197 | public JObject Settings
198 | {
199 | get {
200 | return _settings;
201 | }
202 |
203 | set {
204 | _settings = value;
205 | }
206 | }
207 |
208 | public ConnectionType HeadsetConnection
209 | {
210 | get {
211 | return _connectedBy;
212 | }
213 |
214 | set {
215 | _connectedBy = value;
216 | }
217 | }
218 |
219 | public string Mode
220 | {
221 | get {
222 | return _mode;
223 | }
224 |
225 | set {
226 | _mode = value;
227 | }
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/Src/HeadsetFinder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Newtonsoft.Json.Linq;
5 | using System.Timers;
6 | using UnityEngine;
7 |
8 | namespace EmotivUnityPlugin
9 | {
10 | ///
11 | /// Reponsible for finding headsets.
12 | ///
13 | public class HeadsetFinder
14 | {
15 | private CortexClient _ctxClient = CortexClient.Instance;
16 |
17 | ///
18 | /// Timer for querying headsets
19 | ///
20 | private Timer _aTimer = null;
21 |
22 | // Event
23 | public event EventHandler HeadsetDisConnectedOK;
24 | public event EventHandler> QueryHeadsetOK;
25 |
26 | public HeadsetFinder()
27 | {
28 | _ctxClient = CortexClient.Instance;
29 | _ctxClient.QueryHeadsetOK += OnQueryHeadsetReceived;
30 | _ctxClient.HeadsetDisConnectedOK += OnHeadsetDisconnectedOK;
31 | }
32 |
33 | public static HeadsetFinder Instance { get; } = new HeadsetFinder();
34 |
35 | private void OnHeadsetDisconnectedOK(object sender, bool e)
36 | {
37 | HeadsetDisConnectedOK(this, true);
38 | }
39 |
40 | private void OnQueryHeadsetReceived(object sender, List headsets)
41 | {
42 | QueryHeadsetOK(this, headsets);
43 | }
44 |
45 | ///
46 | /// Init headset finder
47 | ///
48 | public void FinderInit()
49 | {
50 | SetQueryHeadsetTimer();
51 | }
52 |
53 | public void StopQueryHeadset() {
54 | if (_aTimer != null && _aTimer.Enabled) {
55 | UnityEngine.Debug.Log("Stop query headset");
56 | _aTimer.Stop();
57 | }
58 | }
59 | public void RefreshHeadset() {
60 | _ctxClient.ControlDevice("refresh", "", null);
61 | }
62 |
63 | ///
64 | /// Setup query headset timer
65 | ///
66 | private void SetQueryHeadsetTimer()
67 | {
68 | if (_aTimer != null) {
69 | _aTimer.Enabled = true;
70 | return;
71 | }
72 |
73 | _aTimer = new Timer(Config.QUERY_HEADSET_TIME);
74 |
75 | // Hook up the Elapsed event for the timer.
76 | _aTimer.Elapsed += OnTimedEvent;
77 | _aTimer.AutoReset = true;
78 | _aTimer.Enabled = true;
79 | }
80 |
81 | ///
82 | /// Handle timeout. Retry query headsets.
83 | ///
84 | private void OnTimedEvent(object sender, ElapsedEventArgs e)
85 | {
86 | _ctxClient.QueryHeadsets("");
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Src/IdentityModel/IdentityModel.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/IdentityModel/IdentityModel.dll
--------------------------------------------------------------------------------
/Src/IosPlugin/CortexLibIosEmbeddedConnection.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface CortexLibIosEmbeddedConnection : NSObject {
5 | @private
6 | CortexClient *cortexClient;
7 | }
8 |
9 | + (id _Nonnull) shareInstance;
10 | - (void)sendRequest:(NSString * _Nonnull)nsJsonString;
11 | - (void)close;
12 |
13 | # pragma mark CortexClientDelegate
14 | - (void) processResponse:(NSString * _Nonnull)responseMessage;
15 | @end
16 |
--------------------------------------------------------------------------------
/Src/IosPlugin/CortexLibIosEmbeddedConnection.m:
--------------------------------------------------------------------------------
1 | #import "CortexLibIosEmbeddedConnection.h"
2 |
3 | static CortexLibIosEmbeddedConnection *sharedInstance = nil;
4 |
5 | typedef void (*UnityResponseCallback)(const char* _Nonnull responseMessage);
6 | static UnityResponseCallback unityResponseCb = NULL;
7 |
8 | //register callback from unity
9 | void RegisterUnityResponseCallback(UnityResponseCallback callback) {
10 | unityResponseCb = callback;
11 | }
12 |
13 | @implementation CortexLibIosEmbeddedConnection
14 |
15 | +(id) shareInstance {
16 | if(!sharedInstance) {
17 | sharedInstance = [[CortexLibIosEmbeddedConnection alloc] initAfterCortexStarted];
18 | }
19 | return sharedInstance;
20 | }
21 |
22 |
23 | -(id) initAfterCortexStarted {
24 | self = [super init];
25 | if(self) {
26 | cortexClient = [[CortexClient alloc] init];
27 | cortexClient.delegate = self;
28 | }
29 | return self;
30 | }
31 |
32 | -(void)sendRequest:(NSString *)nsJsonString {
33 | [cortexClient sendRequest:nsJsonString];
34 | }
35 |
36 | -(void)close {
37 | [cortexClient close];
38 | }
39 |
40 | -(void)processResponse:(NSString *)responseMessage {
41 | if (unityResponseCb != NULL) {
42 | const char* cString = [responseMessage UTF8String];
43 | unityResponseCb(cString);
44 | }
45 | }
46 | @end
--------------------------------------------------------------------------------
/Src/IosPlugin/CortexLibIosWrapper.m:
--------------------------------------------------------------------------------
1 | #import "CortexLibIosEmbeddedConnection.h"
2 |
3 | #import
4 | #include
5 |
6 | typedef void (*UnityStartedCallback)(void);
7 | static UnityStartedCallback startedCb = NULL;
8 |
9 | void RegisterUnityStartedCallback(UnityStartedCallback callback) {
10 | startedCb = callback;
11 | }
12 |
13 | bool InitCortexLib() {
14 | NSLog(@"operatingSystemVersionString %@", [[NSProcessInfo processInfo] operatingSystemVersionString]);
15 |
16 | [CortexLib start:^(void){
17 | NSLog(@"CortexLib iOS started");
18 | [CortexLibIosEmbeddedConnection shareInstance];
19 | if (startedCb != NULL) {
20 | startedCb();
21 | }
22 | }];
23 | return true;
24 | }
25 |
26 | void StopCortexLib() {
27 | [[CortexLibIosEmbeddedConnection shareInstance] close];
28 | [CortexLib stop];
29 | }
30 |
31 | void SendRequest(const char* requestJson) {
32 | NSString *request = [NSString stringWithUTF8String:requestJson];
33 | [[CortexLibIosEmbeddedConnection shareInstance] sendRequest:request];
34 | }
--------------------------------------------------------------------------------
/Src/IosPlugin/README.md:
--------------------------------------------------------------------------------
1 | # Emotiv Unity Plugin - iOS Integration Guide
2 |
3 | ## Download and Setup
4 | Download the most recent version of [EmotivCortexLib](https://github.com/Emotiv/cortex-embedded-lib-example/releases) and place in this directory
5 | ## Integration
6 | In your Unity project, integrate the following functions to interface with CortexLib via the EmotivUnityPlugin
7 | - bool InitCortexLib();
8 | - void StopCortexLib();
9 | - void RegisterUnityResponseCallback(MessageCallback callback);
10 | - void SendRequest(string request);
--------------------------------------------------------------------------------
/Src/JsonNet/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/JsonNet/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/Src/MentalStateModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Newtonsoft.Json.Linq;
3 |
4 | namespace EmotivUnityPlugin
5 | {
6 | public struct MentalStateModel
7 | {
8 | public float totalDuration;
9 | public float overload; // duration of overload (burnout) state
10 | public float disengaged;
11 | public float flow;
12 | public float intense;
13 | public float moderate;
14 | public float optimal;
15 |
16 | public MentalStateModel(JObject jsonObject)
17 | {
18 | if (jsonObject == null || jsonObject.Count == 0)
19 | {
20 | totalDuration = 0;
21 | overload = 0;
22 | disengaged = 0;
23 | flow = 0;
24 | intense = 0;
25 | moderate = 0;
26 | optimal = 0;
27 | return;
28 | }
29 |
30 | totalDuration = (float)jsonObject["totalDuration"];
31 | overload = 0;
32 | disengaged = 0;
33 | flow = 0;
34 | intense = 0;
35 | moderate = 0;
36 | optimal = 0;
37 |
38 | JArray states = (JArray)jsonObject["states"];
39 | foreach (JObject state in states)
40 | {
41 | string stateName = (string)state["state"];
42 | float duration = (float)state["duration"];
43 |
44 | switch (stateName)
45 | {
46 | case "burnout":
47 | overload = duration;
48 | break;
49 | case "disengaged":
50 | disengaged = duration;
51 | break;
52 | case "flow":
53 | flow = duration;
54 | break;
55 | case "intense":
56 | intense = duration;
57 | break;
58 | case "moderate":
59 | moderate = duration;
60 | break;
61 | case "optimal":
62 | optimal = duration;
63 | break;
64 | }
65 | }
66 | }
67 |
68 | // create to string
69 | public string ToString()
70 | {
71 | return "TotalDuration: " + totalDuration + "\n" +
72 | "Overload: " + overload + "\n" +
73 | "Disengaged: " + disengaged + "\n" +
74 | "Flow: " + flow + "\n" +
75 | "Intense: " + intense + "\n" +
76 | "Moderate: " + moderate + "\n" +
77 | "Optimal: " + optimal + "\n";
78 | }
79 |
80 | public float[] GetPercentages()
81 | {
82 | float[] percentages = new float[6];
83 | if (totalDuration > 0)
84 | {
85 | percentages[0] = (disengaged / totalDuration);
86 | percentages[1] = (moderate / totalDuration);
87 | percentages[2] = (flow / totalDuration);
88 | percentages[3] = (optimal / totalDuration);
89 | percentages[4] = (intense / totalDuration);
90 | percentages[5] = (overload / totalDuration);
91 |
92 | // Round to two decimal places
93 | for (int i = 0; i < percentages.Length; i++)
94 | {
95 | percentages[i] = (float)Math.Round(percentages[i], 2);
96 | }
97 |
98 | // Adjust to ensure the total is 1.0
99 | float total = 0;
100 | for (int i = 0; i < percentages.Length; i++)
101 | {
102 | total += percentages[i];
103 | }
104 |
105 | if (total != 1.0f)
106 | {
107 | float difference = 1.0f - total;
108 | percentages[0] += difference; // Adjust the first element to make the total 1.0
109 | }
110 | }
111 | return percentages;
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/Src/MyLogger.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 | using System.IO;
4 | using System;
5 |
6 | namespace EmotivUnityPlugin
7 | {
8 | ///
9 | /// Logger handler: print log at file with format
10 | /// Not apply for unity editor mode.
11 | ///
12 | public class MyLogger : ILogger
13 | {
14 | static readonly object _object = new object();
15 | private FileStream m_FileStream;
16 | private StreamWriter m_StreamWriter;
17 | private ILogHandler m_DefaultLogHandler = Debug.unityLogger.logHandler;
18 | public static MyLogger Instance { get; } = new MyLogger();
19 |
20 | public ILogHandler logHandler { get; set; }
21 | public bool logEnabled { get; set; }
22 | public LogType filterLogType { get; set; }
23 | public bool saveToFile { get; set; }
24 | public bool showConsoleLog { get; set; }
25 |
26 | private MyLogger()
27 | {
28 | logHandler = this;
29 | logEnabled = true;
30 | filterLogType = LogType.Log;
31 | saveToFile = true; // Default to saving logs to files
32 | showConsoleLog = true; // Default to showing logs in the console
33 | }
34 |
35 | ///
36 | /// Initial logger handler
37 | ///
38 | public void Init(string prefixFileName , bool saveToFile)
39 | {
40 | this.saveToFile = saveToFile;
41 |
42 | if (saveToFile)
43 | {
44 | string dateTimeStr = DateTime.Now.ToString("yyyyMMdd_HHmmss");
45 | string fileName = prefixFileName + "Log_" + dateTimeStr + ".txt";
46 |
47 | string logPath = Config.LogDirectory;
48 | string filePath = Path.Combine(logPath, fileName);
49 | m_FileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
50 | m_StreamWriter = new StreamWriter(m_FileStream);
51 | }
52 |
53 | // Replace the default debug log handler
54 | UnityEngine.Debug.unityLogger.logHandler = this;
55 | }
56 |
57 | public void Log(LogType logType, object message)
58 | {
59 | if (logEnabled && logType <= filterLogType)
60 | {
61 | logHandler.LogFormat(logType, null, "{0}", message);
62 | }
63 | }
64 |
65 | public void Log(LogType logType, object message, UnityEngine.Object context)
66 | {
67 | if (logEnabled && logType <= filterLogType)
68 | {
69 | logHandler.LogFormat(logType, context, "{0}", message);
70 | }
71 | }
72 |
73 | public void Log(LogType logType, string tag, object message)
74 | {
75 | if (logEnabled && logType <= filterLogType)
76 | {
77 | logHandler.LogFormat(logType, null, "{0}: {1}", tag, message);
78 | }
79 | }
80 |
81 | public void Log(LogType logType, string tag, object message, UnityEngine.Object context)
82 | {
83 | if (logEnabled && logType <= filterLogType)
84 | {
85 | logHandler.LogFormat(logType, context, "{0}: {1}", tag, message);
86 | }
87 | }
88 |
89 | public void Log(object message)
90 | {
91 | Log(LogType.Log, message);
92 | }
93 |
94 | public void Log(string tag, object message)
95 | {
96 | Log(LogType.Log, tag, message);
97 | }
98 |
99 | public void Log(string tag, object message, UnityEngine.Object context)
100 | {
101 | Log(LogType.Log, tag, message, context);
102 | }
103 |
104 | public void LogWarning(string tag, object message)
105 | {
106 | Log(LogType.Warning, tag, message);
107 | }
108 |
109 | public void LogWarning(string tag, object message, UnityEngine.Object context)
110 | {
111 | Log(LogType.Warning, tag, message, context);
112 | }
113 |
114 | public void LogError(string tag, object message)
115 | {
116 | Log(LogType.Error, tag, message);
117 | }
118 |
119 | public void LogError(string tag, object message, UnityEngine.Object context)
120 | {
121 | Log(LogType.Error, tag, message, context);
122 | }
123 |
124 | public void LogException(Exception exception)
125 | {
126 | LogException(exception, null);
127 | }
128 |
129 | public void LogException(Exception exception, UnityEngine.Object context)
130 | {
131 | if (logEnabled && LogType.Exception <= filterLogType)
132 | {
133 | logHandler.LogException(exception, context);
134 | }
135 | }
136 |
137 | public void LogFormat(LogType logType, UnityEngine.Object context, string format, params object[] args)
138 | {
139 | lock (_object)
140 | {
141 | LogType myLogType = logType;
142 | string type = "";
143 | switch (logType)
144 | {
145 | case LogType.Log:
146 | type = "info";
147 | break;
148 | case LogType.Warning:
149 | type = "warning";
150 | myLogType = LogType.Log;
151 | break;
152 | case LogType.Error:
153 | type = "error";
154 | myLogType = LogType.Log;
155 | break;
156 | case LogType.Exception:
157 | type = "exception";
158 | break;
159 | default:
160 | type = "info";
161 | break;
162 | }
163 |
164 | string newFormat;
165 | object[] tmpArgs;
166 |
167 | if (args.Length > 1 && args[0].ToString() == "CortexLog")
168 | {
169 | newFormat = "{0}"; // log from cortex side
170 | tmpArgs = new object[1];
171 | tmpArgs[0] = args[1]; // get second element of args for message
172 | }
173 | else
174 | {
175 | newFormat = "[{0}][unity " + type + " ] {1}"; // log from unity side
176 | string dtNow = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz");
177 | tmpArgs = new object[2];
178 | tmpArgs[0] = dtNow;
179 | tmpArgs[1] = args[0]; // only get first element of args
180 | }
181 |
182 | if (saveToFile)
183 | {
184 | m_StreamWriter.WriteLine(String.Format(newFormat, tmpArgs));
185 | m_StreamWriter.Flush();
186 | }
187 |
188 | if (showConsoleLog)
189 | {
190 | m_DefaultLogHandler.LogFormat(myLogType, context, newFormat, tmpArgs);
191 | }
192 | }
193 | }
194 |
195 | public bool IsLogTypeAllowed(LogType logType)
196 | {
197 | return logEnabled && logType <= filterLogType;
198 | }
199 |
200 | public void LogFormat(LogType logType, string format, params object[] args)
201 | {
202 | if (IsLogTypeAllowed(logType))
203 | {
204 | lock (_object)
205 | {
206 | LogFormat(logType, null, format, args);
207 | }
208 | }
209 | }
210 | }
211 | }
--------------------------------------------------------------------------------
/Src/PMDataBuffer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections;
4 | using Newtonsoft.Json.Linq;
5 |
6 | namespace EmotivUnityPlugin
7 | {
8 |
9 | ///
10 | /// Performance metric data buffer .
11 | ///
12 | public class PMDataBuffer : DataBuffer
13 | {
14 | BufferStream[] bufHi; // high rate buffer
15 |
16 | const int InvalidValue = -1; // for null data value when is poor EEG signal quality
17 |
18 | List _pmList = new List(); // performance metric lists
19 |
20 | public List PmList { get => _pmList; set => _pmList = value; }
21 |
22 | public int SetChannels(JArray pmLists)
23 | {
24 | string timestamp = ChannelStringList.ChannelToString(Channel_t.CHAN_TIME_SYSTEM);
25 | int count = 1;
26 | PmList.Add(timestamp);
27 | foreach(var item in pmLists){
28 | // exclude Active flag
29 | string chanStr = item.ToString();
30 | if (!chanStr.Contains(".isActive")) {
31 | PmList.Add(chanStr);
32 | ++count;
33 | }
34 | }
35 | return count;
36 | }
37 |
38 | public void Clear() {
39 |
40 | if (bufHi != null) {
41 | Array.Clear(bufHi, 0, bufHi.Length);
42 | bufHi = null;
43 | }
44 | }
45 |
46 | public override void SettingBuffer(int winSize, int step, int headerCount) {
47 | int buffSize = headerCount;
48 | bufHi = new BufferStream[buffSize];
49 | // UnityEngine.Debug.Log("PM Setting Buffer size" + bufHi.Length);
50 | for (int i = 0; i < buffSize; i++)
51 | {
52 | if (bufHi[i] == null){
53 | bufHi[i] = new BufferStream(winSize, step);
54 | }
55 | else {
56 | bufHi[i].Reset();
57 | bufHi[i].WindowSize = winSize;
58 | bufHi[i].StepSize = step;
59 | }
60 | }
61 | }
62 |
63 | // event handler
64 | public void OnPMDataReceived(object sender, ArrayList data)
65 | {
66 | AddDataToBuffer(data);
67 | }
68 |
69 | public override void AddDataToBuffer(ArrayList data)
70 | {
71 | int i = 0;
72 | foreach (var ele in data) {
73 | // ignore active flag
74 | if (Utils.IsNumericType(ele))
75 | {
76 | try
77 | {
78 | double pmData = Convert.ToDouble(ele);
79 | bufHi[i].AppendData(pmData);
80 | i++;
81 | }
82 | catch (System.Exception e)
83 | {
84 | UnityEngine.Debug.LogError(e.Message + " index " + i + " value " +ele.ToString());
85 | break;
86 | }
87 |
88 | }
89 | }
90 | }
91 | public override double[] GetDataFromBuffer(int index)
92 | {
93 | return bufHi[index].NextWithRemoval();
94 | }
95 | public override double[] GetLatestDataFromBuffer(int index)
96 | {
97 | double[] nextSegment = null;
98 | double[] lastSegment = null;
99 | do
100 | {
101 | lastSegment = nextSegment;
102 | nextSegment = GetDataFromBuffer(index);
103 | }
104 | while (nextSegment != null);
105 | return lastSegment;
106 | }
107 |
108 | public double GetData(string label)
109 | {
110 | int index = GetLabelIndex(label);
111 | if (index == -1) {
112 | UnityEngine.Debug.LogError(" Invalid label: " + label);
113 | return InvalidValue;
114 | }
115 | double[] chanData = GetLatestDataFromBuffer(index);
116 | if (chanData != null) {
117 | return chanData[0];
118 | } else {
119 | return InvalidValue;
120 | }
121 | }
122 |
123 | public int GetLabelIndex(string chan)
124 | {
125 | int chanIndex = _pmList.IndexOf(chan);
126 | return (int)chanIndex;
127 | }
128 |
129 | public int GetBufferSize()
130 | {
131 | if(bufHi[1] == null)
132 | return 0;
133 |
134 | return bufHi[1].GetBufSize(); // buff size of "boredom"
135 | }
136 | }
137 | }
138 |
139 |
140 |
--------------------------------------------------------------------------------
/Src/PostProcessBuild/PostProcessBuild.Editor.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "PostProcessBuild.Editor",
3 | "references": [],
4 | "includePlatforms": ["Editor"],
5 | "excludePlatforms": [],
6 | "allowUnsafeCode": false,
7 | "overrideReferences": false,
8 | "precompiledReferences": [],
9 | "autoReferenced": true,
10 | "defineConstraints": ["UNITY_IOS"],
11 | "versionDefines": [],
12 | "noEngineReferences": false
13 | }
--------------------------------------------------------------------------------
/Src/PostProcessBuild/PostProcessBuild.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_IOS
2 | using UnityEditor;
3 | using UnityEditor.Callbacks;
4 | using UnityEditor.iOS.Xcode;
5 | using UnityEditor.iOS.Xcode.Extensions;
6 | using System.IO;
7 |
8 | public class PostProcessBuild
9 | {
10 | [PostProcessBuild]
11 | public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
12 | {
13 | if (target == BuildTarget.iOS)
14 | {
15 | string pbxProjectPath = PBXProject.GetPBXProjectPath(pathToBuiltProject);
16 | PBXProject pbxProject = new PBXProject();
17 | pbxProject.ReadFromFile(pbxProjectPath);
18 |
19 | string targetGuid = pbxProject.GetUnityMainTargetGuid();
20 | string unityFrameworkTargetGuid = pbxProject.GetUnityFrameworkTargetGuid();
21 |
22 | // Automatic code signing
23 | pbxProject.SetBuildProperty(targetGuid, "CODE_SIGN_STYLE", "Automatic");
24 |
25 | // DEVELOPMENT_TEAM is set from Jenkinsfile (actual team ID)
26 | string developmentTeam = System.Environment.GetEnvironmentVariable("DEVELOPMENT_TEAM");
27 | if (!string.IsNullOrEmpty(developmentTeam))
28 | {
29 | pbxProject.SetBuildProperty(targetGuid, "DEVELOPMENT_TEAM", developmentTeam);
30 | }
31 |
32 | // Add and link EmotivCortexLib.xcframework to main target
33 | string frameworkPath = Path.Combine(pathToBuiltProject, "Frameworks/EmotivCortexLib.xcframework");
34 | string fileGuid = pbxProject.AddFile(frameworkPath, "Frameworks/EmotivCortexLib.xcframework", PBXSourceTree.Source);
35 | pbxProject.AddFileToBuild(targetGuid, fileGuid);
36 | pbxProject.AddFileToBuild(unityFrameworkTargetGuid, fileGuid);
37 |
38 | // Add the framework to the "Embed Frameworks" build phase
39 | string embedPhase = pbxProject.AddCopyFilesBuildPhase(targetGuid, "Embed Frameworks", "", "10");
40 | pbxProject.AddFileToBuildSection(targetGuid, embedPhase, fileGuid);
41 |
42 | // Ensure "Code Sign On Copy" is enabled for the framework
43 | PBXProjectExtensions.AddFileToEmbedFrameworks(pbxProject, targetGuid, fileGuid);
44 |
45 | pbxProject.AddBuildProperty(targetGuid, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks");
46 | pbxProject.AddBuildProperty(targetGuid, "OTHER_LDFLAGS", "-framework EmotivCortexLib");
47 | pbxProject.AddBuildProperty(unityFrameworkTargetGuid, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks");
48 | pbxProject.AddBuildProperty(unityFrameworkTargetGuid, "OTHER_LDFLAGS", "-framework EmotivCortexLib");
49 |
50 | pbxProject.WriteToFile(pbxProjectPath);
51 |
52 | // Add NSBluetoothAlwaysUsageDescription
53 | string plistPath = Path.Combine(pathToBuiltProject, "Info.plist");
54 | PlistDocument plist = new PlistDocument();
55 | plist.ReadFromFile(plistPath);
56 |
57 | PlistElementDict rootDict = plist.root;
58 | rootDict.SetString("NSBluetoothAlwaysUsageDescription", "This will allow app to find and connect to Bluetooth accessories.");
59 |
60 | plist.WriteToFile(plistPath);
61 | }
62 | }
63 | }
64 | #endif
--------------------------------------------------------------------------------
/Src/RecordManager.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 | using System.Threading;
6 |
7 | namespace EmotivUnityPlugin
8 | {
9 | ///
10 | /// Reponsible for managing and handling records and markers.
11 | ///
12 | public class RecordManager
13 | {
14 | static readonly object _locker = new object();
15 | private CortexClient _ctxClient = CortexClient.Instance;
16 | private Authorizer _authorizer = Authorizer.Instance;
17 | private SessionHandler _sessionHandler = SessionHandler.Instance;
18 |
19 | private string _currRecordId;
20 | private string _currMarkerId;
21 |
22 | public static RecordManager Instance { get; } = new RecordManager();
23 |
24 | // Event
25 | public event EventHandler informStartRecordResult;
26 | public event EventHandler informStopRecordResult;
27 |
28 | public event EventHandler informMarkerResult;
29 |
30 | // Constructor
31 | public RecordManager ()
32 | {
33 | _sessionHandler.CreateRecordOK += OnCreateRecordOK;
34 | _sessionHandler.StopRecordOK += OnStopRecordOK;
35 | _ctxClient.InjectMarkerOK += OnInjectMarkerOK;
36 | _ctxClient.UpdateMarkerOK += OnUpdateMarkerOK;
37 | }
38 |
39 | private void OnStopRecordOK(object sender, Record record)
40 | {
41 | UnityEngine.Debug.Log("RecordManager: OnStopRecordOK recordId: " + record.Uuid +
42 | " at: " + record.EndDateTime);
43 | informStopRecordResult(this, record);
44 | _currRecordId = "";
45 | }
46 |
47 | private void OnCreateRecordOK(object sender, Record record)
48 | {
49 | informStopRecordResult(this, record);
50 | _currRecordId = record.Uuid;
51 | informStartRecordResult(this, record);
52 | }
53 | private void OnInjectMarkerOK(object sender, JObject markerObj)
54 | {
55 | _currMarkerId = markerObj["uuid"].ToString();
56 | informMarkerResult(this, markerObj);
57 | }
58 | private void OnUpdateMarkerOK(object sender, JObject markerObj)
59 | {
60 | informMarkerResult(this, markerObj);
61 | }
62 |
63 | ///
64 | /// Create a new record.
65 | ///
66 | public void StartRecord(string title, string description = null,
67 | string subjectName = null, List tags= null)
68 | {
69 | lock(_locker)
70 | {
71 | // start record
72 | _sessionHandler.StartRecord(_authorizer.CortexToken, title, description, subjectName, tags);
73 | }
74 | }
75 |
76 | ///
77 | /// Stop a record that was previously started by StartRecord
78 | ///
79 | public void StopRecord()
80 | {
81 | lock(_locker)
82 | {
83 | _sessionHandler.StopRecord(_authorizer.CortexToken);
84 | }
85 | }
86 | // TODO: Update Record
87 |
88 | ///
89 | /// inject marker
90 | ///
91 | public void InjectMarker(string markerLabel, string markerValue)
92 | {
93 | lock(_locker)
94 | {
95 | string cortexToken = _authorizer.CortexToken;
96 | string sessionId = _sessionHandler.SessionId;
97 |
98 | // inject marker
99 | _ctxClient.InjectMarker(cortexToken, sessionId, markerLabel, markerValue, Utils.GetEpochTimeNow());
100 | }
101 | }
102 |
103 | ///
104 | /// update marker to set the end date time of a marker, turning an "instance" marker into an "interval" marker
105 | ///
106 | public void UpdateMarker()
107 | {
108 | lock(_locker)
109 | {
110 | string cortexToken = _authorizer.CortexToken;
111 | string sessionId = _sessionHandler.SessionId;
112 |
113 | // update marker
114 | _ctxClient.UpdateMarker(cortexToken, sessionId, _currMarkerId, Utils.GetEpochTimeNow());
115 | }
116 | }
117 |
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Src/RegistryConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.IO;
4 | #if NETFRAMEWORK || NET5_0_OR_GREATER
5 | using Microsoft.Win32;
6 | #endif
7 | using UnityEngine;
8 |
9 | namespace EmotivUnityPlugin
10 | {
11 | public class RegistryConfig
12 | {
13 | public RegistryConfig(string uriScheme)
14 | {
15 | CustomUriScheme = uriScheme;
16 | }
17 |
18 | public void Configure()
19 | {
20 | #if NETFRAMEWORK || NET5_0_OR_GREATER
21 | if (NeedToAddKeys()) AddRegKeys();
22 | #else
23 | Debug.LogWarning("RegistryConfig is only supported on Windows.");
24 | #endif
25 | }
26 |
27 | private string CustomUriScheme { get; }
28 | #if NETFRAMEWORK || NET5_0_OR_GREATER
29 | string CustomUriSchemeKeyPath => RootKeyPath + @"\" + CustomUriScheme;
30 | string CustomUriSchemeKeyValueValue => "URL:" + CustomUriScheme;
31 | string CommandKeyPath => CustomUriSchemeKeyPath + @"\shell\open\command";
32 |
33 | const string RootKeyPath = @"Software\Classes";
34 |
35 | const string CustomUriSchemeKeyValueName = "";
36 |
37 | const string ShellKeyName = "shell";
38 | const string OpenKeyName = "open";
39 | const string CommandKeyName = "command";
40 |
41 | const string CommandKeyValueName = "";
42 | const string CommandKeyValueFormat = "\"{0}\\UnityExample.exe\" \"%1\"";
43 | static string CommandKeyValueValue => String.Format(CommandKeyValueFormat, Path.GetDirectoryName(Application.dataPath));
44 |
45 | const string UrlProtocolValueName = "URL Protocol";
46 | const string UrlProtocolValueValue = "";
47 |
48 | bool NeedToAddKeys()
49 | {
50 | var addKeys = false;
51 | using (var commandKey = Registry.CurrentUser.OpenSubKey(CommandKeyPath))
52 | {
53 | var commandValue = commandKey?.GetValue(CommandKeyValueName);
54 | addKeys |= !CommandKeyValueValue.Equals(commandValue);
55 | }
56 |
57 | using (var customUriSchemeKey = Registry.CurrentUser.OpenSubKey(CustomUriSchemeKeyPath))
58 | {
59 | var uriValue = customUriSchemeKey?.GetValue(CustomUriSchemeKeyValueName);
60 | var protocolValue = customUriSchemeKey?.GetValue(UrlProtocolValueName);
61 |
62 | addKeys |= !CustomUriSchemeKeyValueValue.Equals(uriValue);
63 | addKeys |= !UrlProtocolValueValue.Equals(protocolValue);
64 | }
65 | return addKeys;
66 | }
67 |
68 | void AddRegKeys()
69 | {
70 | using (var classesKey = Registry.CurrentUser.OpenSubKey(RootKeyPath, true))
71 | {
72 | using (var root = classesKey!.OpenSubKey(CustomUriScheme, true) ??
73 | classesKey.CreateSubKey(CustomUriScheme, true))
74 | {
75 | root.SetValue(CustomUriSchemeKeyValueName, CustomUriSchemeKeyValueValue);
76 | root.SetValue(UrlProtocolValueName, UrlProtocolValueValue);
77 |
78 | using (var shell = root.OpenSubKey(ShellKeyName, true) ??
79 | root.CreateSubKey(ShellKeyName, true))
80 | {
81 | using (var open = shell.OpenSubKey(OpenKeyName, true) ??
82 | shell.CreateSubKey(OpenKeyName, true))
83 | {
84 | using (var command = open.OpenSubKey(CommandKeyName, true) ??
85 | open.CreateSubKey(CommandKeyName, true))
86 | {
87 | command.SetValue(CommandKeyValueName, CommandKeyValueValue);
88 | }
89 | }
90 | }
91 | }
92 | }
93 | }
94 | #endif
95 | }
96 | }
--------------------------------------------------------------------------------
/Src/SessionHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace EmotivUnityPlugin
5 | {
6 | ///
7 | /// Reponsible for handling sessions and records.
8 | ///
9 | public class SessionHandler
10 | {
11 | static readonly object _locker = new object();
12 | private static string _sessionId = "";
13 | private CortexClient _ctxClient = CortexClient.Instance;
14 |
15 | //event
16 | public event EventHandler SessionActived;
17 | public event EventHandler SessionClosedOK;
18 | public event EventHandler CreateRecordOK;
19 | public event EventHandler StopRecordOK;
20 | public event EventHandler SessionClosedNotify;
21 |
22 | public static SessionHandler Instance { get; } = new SessionHandler();
23 |
24 | ///
25 | /// Gets current SessionId.
26 | ///
27 | /// The current SessionId.
28 | public string SessionId
29 | {
30 | get {
31 | lock (_locker)
32 | {
33 | return _sessionId;
34 | }
35 | }
36 | }
37 |
38 | //Constructor
39 | public SessionHandler()
40 | {
41 | _ctxClient.CreateSessionOK += CreateSessionOk;
42 | _ctxClient.UpdateSessionOK += UpdateSessionOk;
43 | _ctxClient.CreateRecordOK += OnCreateRecordOK;
44 | _ctxClient.UpdateRecordOK += OnUpdateRecordOK;
45 | _ctxClient.StopRecordOK += OnStopRecordOK;
46 | _ctxClient.SessionClosedNotify += OnSessionClosedNotify;
47 | }
48 |
49 | private void OnSessionClosedNotify(object sender, string sessionId)
50 | {
51 | UnityEngine.Debug.Log("SessionHandler: OnSessionClosedNotify " + sessionId);
52 | lock (_locker)
53 | {
54 | if (_sessionId == sessionId) {
55 | // clear session data
56 | _sessionId = "";
57 | SessionClosedNotify(this, sessionId);
58 | }
59 | }
60 |
61 | }
62 |
63 | private void OnStopRecordOK(object sender, Record record)
64 | {
65 | UnityEngine.Debug.Log("OnStopRecordOK: recordId " + record.Uuid);
66 | StopRecordOK(this, record);
67 | }
68 |
69 | private void OnUpdateRecordOK(object sender, Record record)
70 | {
71 | UnityEngine.Debug.Log("OnUpdateRecordOK: recordId " + record.Uuid);
72 | // TODO: emit signal
73 | }
74 |
75 | private void OnCreateRecordOK(object sender, Record record)
76 | {
77 | UnityEngine.Debug.Log("SessionCreator: OnCreateRecordOK recordid " + record.Uuid);
78 | CreateRecordOK(this, record);
79 | }
80 |
81 | private void CreateSessionOk(object sender, SessionEventArgs sessionInfo)
82 | {
83 | lock(_locker) _sessionId = sessionInfo.SessionId;
84 |
85 | if (sessionInfo.Status == SessionStatus.Activated) {
86 | UnityEngine.Debug.Log("Session " + sessionInfo.SessionId + " is activated successfully.");
87 | SessionActived(this, sessionInfo);
88 | }
89 | else {
90 | UnityEngine.Debug.Log("Session " + sessionInfo.SessionId + " is opened successfully.");
91 | }
92 |
93 | }
94 | private void UpdateSessionOk(object sender, SessionEventArgs sessionInfo)
95 | {
96 |
97 | if (sessionInfo.Status == SessionStatus.Closed)
98 | {
99 | lock(_locker) _sessionId = "";
100 | SessionClosedOK(this, sessionInfo.SessionId);
101 |
102 | }
103 | else if (sessionInfo.Status == SessionStatus.Activated)
104 | {
105 | lock(_locker) _sessionId = sessionInfo.SessionId;
106 | SessionActived(this, sessionInfo);
107 | }
108 | }
109 |
110 | ///
111 | /// Open a session with an EMOTIV headset.
112 | /// A application can open only one session at a time with a given headset.
113 | ///
114 | public void Create(string cortexToken, string headsetId, bool activeSession = false)
115 | {
116 | if (!String.IsNullOrEmpty(cortexToken) &&
117 | !String.IsNullOrEmpty(headsetId))
118 | {
119 | string status = activeSession ? "active" : "open";
120 | _ctxClient.CreateSession(cortexToken, headsetId, status);
121 | }
122 | else {
123 | UnityEngine.Debug.Log("CreateSession: Invalid parameters");
124 | }
125 |
126 | }
127 |
128 | ///
129 | /// Close the current session.
130 | ///
131 | public void CloseSession(string cortexToken)
132 | {
133 | lock(_locker)
134 | {
135 | if (!String.IsNullOrEmpty(_sessionId)) {
136 | _ctxClient.UpdateSession(cortexToken, _sessionId, "close");
137 | }
138 | }
139 |
140 | }
141 |
142 | ///
143 | /// Create a new record.
144 | ///
145 | public void StartRecord(string cortexToken, string title,
146 | string description = null, string subjectName = null, List tags= null)
147 | {
148 | lock(_locker)
149 | {
150 | if (!String.IsNullOrEmpty(_sessionId)) {
151 | _ctxClient.CreateRecord(cortexToken, _sessionId, title, description, subjectName, tags);
152 | }
153 | else
154 | {
155 | UnityEngine.Debug.Log("StartRecord: invalid sessionId.");
156 | }
157 | }
158 |
159 | }
160 |
161 | ///
162 | /// Stop a record that was previously started by createRecord
163 | ///
164 | public void StopRecord(string cortexToken)
165 | {
166 | lock(_locker)
167 | {
168 | if (!String.IsNullOrEmpty(_sessionId)) {
169 | _ctxClient.StopRecord(cortexToken, _sessionId);
170 | }
171 | else
172 | {
173 | UnityEngine.Debug.Log("StopRecord: invalid sessionId.");
174 | }
175 | }
176 | }
177 |
178 | ///
179 | /// Update a record.
180 | ///
181 | public void UpdateRecord(string cortexToken, string recordId, string title = null,
182 | string description = null, List tags = null)
183 | {
184 | lock(_locker)
185 | {
186 | if (!String.IsNullOrEmpty(_sessionId)) {
187 | _ctxClient.UpdateRecord(cortexToken, recordId, title, description, tags);
188 | }
189 | else
190 | {
191 | UnityEngine.Debug.Log("StartRecord: invalid sessionId.");
192 | }
193 | }
194 | }
195 | // inject marker
196 |
197 |
198 | ///
199 | /// Clear current session Data.
200 | ///
201 | public void ClearSessionData() {
202 | lock(_locker)
203 | {
204 | _sessionId = "";
205 | }
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/Src/SuperSocket.ClientEngine.Core.0.10.0/SuperSocket.ClientEngine.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/SuperSocket.ClientEngine.Core.0.10.0/SuperSocket.ClientEngine.dll
--------------------------------------------------------------------------------
/Src/UniWebViewManager.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_ANDROID || UNITY_IOS
2 | using UnityEngine;
3 | using System;
4 |
5 | public class UniWebViewManager : MonoBehaviour
6 | {
7 | private static readonly string LogTag = "[UniWebViewManager]"; // Log tag for consistency
8 |
9 | private static UniWebViewManager _instance;
10 | public static UniWebViewManager Instance
11 | {
12 | get
13 | {
14 | if (_instance == null)
15 | {
16 | var obj = new GameObject("UniWebViewManager");
17 | _instance = obj.AddComponent();
18 | DontDestroyOnLoad(obj);
19 | }
20 | return _instance;
21 | }
22 | }
23 |
24 | private UniWebViewAuthenticationSession authSession;
25 | private UniWebViewSafeBrowsing safeBrowsing;
26 | private string _authUrl;
27 | private string _urlScheme;
28 |
29 | public void Init(string authUrl, string urlScheme)
30 | {
31 | _authUrl = authUrl;
32 | _urlScheme = urlScheme;
33 | }
34 |
35 | public void StartAuthorization(Action onSuccess, Action onError)
36 | {
37 | if (string.IsNullOrEmpty(_authUrl) || string.IsNullOrEmpty(_urlScheme))
38 | {
39 | Debug.LogError($"{LogTag} Init must be called with valid authUrl and urlScheme before starting authorization.");
40 | return;
41 | }
42 |
43 | Debug.Log($"{LogTag} Starting authorization using UniWebViewAuthenticationSession...");
44 |
45 | authSession = UniWebViewAuthenticationSession.Create(_authUrl, _urlScheme);
46 |
47 | authSession.OnAuthenticationFinished += (session, result) =>
48 | {
49 | if (!string.IsNullOrEmpty(result))
50 | {
51 | Debug.Log($"{LogTag} Auth finished. Callback URL: {result}");
52 |
53 | string code = ExtractCodeFromUri(result);
54 | if (!string.IsNullOrEmpty(code))
55 | {
56 | onSuccess?.Invoke(code);
57 | }
58 | else
59 | {
60 | onError?.Invoke(-1, $"{LogTag} Authorization code not found in redirect URL.");
61 | }
62 | }
63 | else
64 | {
65 | onError?.Invoke(-2, $"{LogTag} Authentication session failed or was cancelled.");
66 | }
67 | };
68 |
69 | authSession.OnAuthenticationErrorReceived += (session, errorCode, errorMessage) =>
70 | {
71 | Debug.LogError($"{LogTag} Authentication Error: {errorCode} - {errorMessage}");
72 | onError?.Invoke(errorCode, errorMessage);
73 | };
74 |
75 | authSession.Start();
76 | }
77 |
78 | private string ExtractCodeFromUri(string uri)
79 | {
80 | try
81 | {
82 | var query = new Uri(uri).Query.TrimStart('?');
83 | foreach (var param in query.Split('&'))
84 | {
85 | var keyValue = param.Split('=');
86 | if (keyValue.Length == 2 && Uri.UnescapeDataString(keyValue[0]) == "code")
87 | {
88 | return Uri.UnescapeDataString(keyValue[1]);
89 | }
90 | }
91 | }
92 | catch (Exception e)
93 | {
94 | Debug.LogError($"{LogTag} Failed to extract code from URI: {e.Message}");
95 | }
96 |
97 | return null;
98 | }
99 |
100 | public void OpenURL(string url, ActiononClosed)
101 | {
102 | if (string.IsNullOrEmpty(url))
103 | {
104 | Debug.LogError($"{LogTag} URL cannot be null or empty.");
105 | return;
106 | }
107 |
108 | safeBrowsing = UniWebViewSafeBrowsing.Create(url);
109 | safeBrowsing.OnSafeBrowsingFinished += (browsing) => {
110 | Debug.Log("UniWebViewSafeBrowsing closed.");
111 | onClosed?.Invoke(true);
112 | };
113 |
114 | safeBrowsing.Show();
115 | }
116 |
117 | public void Cleanup()
118 | {
119 | if (authSession != null)
120 | {
121 | authSession = null;
122 | }
123 |
124 | _authUrl = null;
125 | _urlScheme = null;
126 | }
127 | }
128 | #endif
--------------------------------------------------------------------------------
/Src/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using UnityEngine;
5 |
6 | namespace EmotivUnityPlugin
7 | {
8 | public static class Utils
9 | {
10 |
11 | public static Int64 GetEpochTimeNow()
12 | {
13 | TimeSpan t = DateTime.UtcNow - new DateTime(1970, 1, 1);
14 | Int64 timeSinceEpoch = (Int64)t.TotalMilliseconds;
15 | return timeSinceEpoch;
16 |
17 | }
18 | public static string GenerateUuidProfileName(string prefix)
19 | {
20 | return prefix + "-" + GetEpochTimeNow();
21 | }
22 |
23 | public static string GetAppTmpPath(string providerName, string appName)
24 | {
25 | string homePath = "";
26 | #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
27 | homePath = Environment.GetEnvironmentVariable("LocalAppData");
28 | #elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
29 | homePath = Environment.GetEnvironmentVariable("HOME");
30 | string currentTempPath = Path.Combine(homePath, "Library/Application Support");
31 | homePath = currentTempPath;
32 | #elif UNITY_STANDALONE_LINUX
33 | homePath = Environment.GetEnvironmentVariable("HOME");
34 | #elif UNITY_IOS
35 | homePath = Application.persistentDataPath;
36 | #elif UNITY_ANDROID
37 | // return application data path on android
38 | return Application.persistentDataPath;
39 | #else
40 | homePath = Directory.GetCurrentDirectory();
41 | #endif
42 | string targetFolderName = appName;
43 | if (!string.IsNullOrEmpty(providerName))
44 | targetFolderName = Path.Combine(providerName, appName);
45 |
46 | return Path.Combine(homePath, targetFolderName);
47 | }
48 |
49 | public static DateTime StringToIsoDateTime(string time) {
50 | // UnityEngine.Debug.Log(" StringToIsoDateTime: " + time);
51 | return DateTime.Parse(time);
52 | }
53 | public static string ISODateTimeToString(DateTime isoTime) {
54 | if (isoTime.CompareTo(new DateTime()) == 0)
55 | return "";
56 | return isoTime.ToString("yyyy-MM-ddTHH:mm:ss.ffffzzz");
57 | }
58 |
59 | public static double ISODateTimeToEpocTime(DateTime isoTime) {
60 | DateTime dt1970 = new DateTime(1970, 1, 1);
61 | TimeSpan span = isoTime - dt1970;
62 | return span.TotalMilliseconds;
63 | }
64 |
65 | public static bool CheckEmotivAppInstalled(string emotivAppsPath = "", bool isRequired = false) {
66 |
67 | if (!isRequired)
68 | return true;
69 |
70 | if (string.IsNullOrEmpty(emotivAppsPath)) {
71 | UnityEngine.Debug.Log("The emotivAppsPath is empty. So will not check emotiv installed or not.");
72 | return true;
73 | }
74 | else if (!Directory.Exists(emotivAppsPath)) {
75 | UnityEngine.Debug.Log("The emotivApps directory is not existed.");
76 | return false;
77 | }
78 |
79 |
80 | #if UNITY_STANDALONE_WIN
81 | string emotivAppName = "EMOTIV Launcher.exe";
82 | string fileDir = Path.Combine(emotivAppsPath, emotivAppName);
83 | if (File.Exists(fileDir)) {
84 | return true;
85 | }
86 | UnityEngine.Debug.Log("IsEmotivAppInstalled: not exists file: " + fileDir);
87 | return false;
88 | #elif UNITY_STANDALONE_OSX
89 | string emotivAppName = "EMOTIV Launcher.app";
90 | string appDir = Path.Combine(emotivAppsPath, emotivAppName);
91 | if (Directory.Exists(appDir)) {
92 | return true;
93 | }
94 | UnityEngine.Debug.Log("IsEmotivAppInstalled: not exists bundle file: " + appDir);
95 | return false;
96 | #elif UNITY_STANDALONE_LINUX
97 | string homePath = Environment.GetEnvironmentVariable("HOME");
98 | return true;
99 | // TODO
100 | #elif UNITY_IOS
101 | // TODO
102 | return true;
103 | #elif UNITY_ANDROID
104 | // TODO
105 | return true;
106 | #else
107 | // TODO
108 | return true;
109 | #endif
110 |
111 | }
112 |
113 | public static bool IsNumericType(object o)
114 | {
115 | switch (Type.GetTypeCode(o.GetType()))
116 | {
117 | case TypeCode.Byte:
118 | case TypeCode.SByte:
119 | case TypeCode.UInt16:
120 | case TypeCode.UInt32:
121 | case TypeCode.UInt64:
122 | case TypeCode.Int16:
123 | case TypeCode.Int32:
124 | case TypeCode.Int64:
125 | case TypeCode.Decimal:
126 | case TypeCode.Double:
127 | case TypeCode.Single:
128 | return true;
129 | default:
130 | return false;
131 | }
132 | }
133 |
134 | public static HeadsetFamily GetHeadsetGroup(HeadsetTypes headsetType)
135 | {
136 | if (headsetType == HeadsetTypes.HEADSET_TYPE_INSIGHT || headsetType == HeadsetTypes.HEADSET_TYPE_INSIGHT2)
137 | return HeadsetFamily.INSIGHT;
138 | else if (headsetType == HeadsetTypes.HEADSET_TYPE_MN8 || headsetType == HeadsetTypes.HEADSET_TYPE_MW20)
139 | return HeadsetFamily.MN8;
140 | else
141 | return HeadsetFamily.EPOC;
142 | }
143 |
144 | public static bool IsInsightType(HeadsetTypes headsetType)
145 | {
146 | if (headsetType == HeadsetTypes.HEADSET_TYPE_INSIGHT || headsetType == HeadsetTypes.HEADSET_TYPE_INSIGHT2)
147 | return true;
148 | else
149 | return false;
150 | }
151 |
152 | public static TimeSpan IndexToTime(int index)
153 | {
154 | if (index < 0 || index >= 48)
155 | {
156 | throw new ArgumentOutOfRangeException(nameof(index), "Index must be between 0 and 47.");
157 | }
158 |
159 | int hours = index / 2;
160 | int minutes = (index % 2) * 30;
161 |
162 | return new TimeSpan(hours, minutes, 0);
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/Src/WebSocket4Net.0.15.2/WebSocket4Net.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Emotiv/unity-plugin/3f454c125f7446a2cf878cf395d3ab73bc62f705/Src/WebSocket4Net.0.15.2/WebSocket4Net.dll
--------------------------------------------------------------------------------
/Src/WebsocketCortexClient.cs:
--------------------------------------------------------------------------------
1 | #region License
2 | // Copyright (c) 2007 James Newton-King
3 | //
4 | // Permission is hereby granted, free of charge, to any person
5 | // obtaining a copy of this software and associated documentation
6 | // files (the "Software"), to deal in the Software without
7 | // restriction, including without limitation the rights to use,
8 | // copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the
10 | // Software is furnished to do so, subject to the following
11 | // conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be
14 | // included in all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | // OTHER DEALINGS IN THE SOFTWARE.
24 | #endregion
25 |
26 | using System;
27 | using System.Threading;
28 | using WebSocket4Net;
29 | using Newtonsoft.Json.Linq;
30 | using System.Collections.Generic;
31 | using System.Collections;
32 | using System.Timers;
33 |
34 | namespace EmotivUnityPlugin
35 | {
36 | ///
37 | /// Represents a simple client for the Cortex service.
38 | ///
39 | public class WebsocketCortexClient : CortexClient
40 | {
41 | const string Url = "wss://localhost:6868";
42 | static readonly object _locker = new object();
43 | private Dictionary _methodForRequestId;
44 |
45 | ///
46 | /// Websocket Client.
47 | ///
48 | private WebSocket _wSC;
49 |
50 | ///
51 | /// Timer for connecting to Emotiv Cortex Service
52 | ///
53 | private System.Timers.Timer _wscTimer = null;
54 |
55 | // Private constructor to prevent direct instantiation
56 | public WebsocketCortexClient() { }
57 |
58 |
59 | // override the init method
60 | public override void Init(object context = null)
61 | {
62 | _wSC = new WebSocket(Config.AppUrl);
63 | // Since Emotiv Cortex 3.7.0, the supported SSL Protocol will be TLS1.2 or later
64 | _wSC.Security.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12;
65 | _wSC.Opened += new EventHandler(WebSocketClient_Opened);
66 | _wSC.Error += new EventHandler(WebSocketClient_Error);
67 | _wSC.Closed += WebSocketClient_Closed;
68 | _wSC.MessageReceived += WebSocketClient_MessageReceived;
69 | _wSC.DataReceived += WebSocketClient_DataReceived;
70 |
71 | // open websocket
72 | Open();
73 | }
74 |
75 | // override the close method
76 | public override void Close()
77 | {
78 | UnityEngine.Debug.Log("Force close websocket client.");
79 | if (_wscTimer != null) {
80 | _wscTimer = null;
81 | }
82 | // stop websocket client
83 | if (_wSC != null)
84 | _wSC.Close();
85 | }
86 |
87 |
88 |
89 | ///
90 | /// Set up timer for connecting to Emotiv Cortex service
91 | ///
92 | private void SetWSCTimer() {
93 | if (_wscTimer != null)
94 | return;
95 | _wscTimer = new System.Timers.Timer(Config.RETRY_CORTEXSERVICE_TIME);
96 | // Hook up the Elapsed event for the timer.
97 | _wscTimer.Elapsed += OnTimerEvent;
98 | _wscTimer.AutoReset = false; // do not auto reset
99 | _wscTimer.Enabled = true;
100 | }
101 |
102 | ///
103 | /// Handle for _wscTimer timer timeout
104 | // Retry Connect when time out
105 | ///
106 | private void OnTimerEvent(object sender, ElapsedEventArgs e)
107 | {
108 | UnityEngine.Debug.Log("OnTimerEvent: Retry connect to CortexService....");
109 | RetryConnect();
110 | }
111 |
112 | private void RetryConnect() {
113 | m_OpenedEvent.Reset();
114 | if (_wSC == null || (_wSC.State != WebSocketState.None && _wSC.State != WebSocketState.Closed))
115 | return;
116 |
117 | _wSC.Open();
118 | }
119 |
120 | private void WebSocketClient_DataReceived(object sender, DataReceivedEventArgs e)
121 | {
122 | // TODO
123 | UnityEngine.Debug.Log("WebSocketClient_DataReceived");
124 | }
125 |
126 | ///
127 | /// Build a json rpc request and send message via websocket
128 | ///
129 | public override void SendTextMessage(JObject param, string method, bool hasParam = true)
130 | {
131 | lock(_locker)
132 | {
133 | string request = PrepareRequest(method, param, hasParam);
134 | // UnityEngine.Debug.Log("Send " + method);
135 | // UnityEngine.Debug.Log(request.ToString());
136 |
137 | // send the json message
138 | _wSC.Send(request);
139 | }
140 | }
141 |
142 | ///
143 | /// Handle message received return from Emotiv Cortex Service
144 | ///
145 | private void WebSocketClient_MessageReceived(object sender, MessageReceivedEventArgs e)
146 | {
147 | OnMessageReceived(e.Message);
148 | }
149 |
150 | ///
151 | /// Handle when socket close
152 | ///
153 | private void WebSocketClient_Closed(object sender, EventArgs e)
154 | {
155 | OnWSConnected(false);
156 | // start connecting cortex service again
157 | if (_wscTimer != null)
158 | _wscTimer.Start();
159 | }
160 |
161 | ///
162 | /// Handle when socket open
163 | ///
164 | private void WebSocketClient_Opened(object sender, EventArgs e)
165 | {
166 | m_OpenedEvent.Set();
167 | if (_wSC.State == WebSocketState.Open) {
168 | OnWSConnected(true);
169 | // stop timer
170 | _wscTimer.Stop();
171 |
172 | } else {
173 | UnityEngine.Debug.Log("Open Websocket unsuccessfully.");
174 | }
175 | }
176 |
177 | ///
178 | /// Handle error when try to open socket
179 | ///
180 | private void WebSocketClient_Error(object sender, SuperSocket.ClientEngine.ErrorEventArgs e)
181 | {
182 | UnityEngine.Debug.Log(e.Exception.GetType() + ":" + e.Exception.Message + Environment.NewLine + e.Exception.StackTrace);
183 |
184 | if (e.Exception.InnerException != null) {
185 | UnityEngine.Debug.Log(e.Exception.InnerException.GetType());
186 | OnWSConnected(false);
187 | // start connecting cortex service again
188 | _wscTimer.Start();
189 | }
190 | }
191 |
192 | ///
193 | /// Open a websocket client.
194 | ///
195 | private void Open()
196 | {
197 | // set timer for connect cortex service
198 | SetWSCTimer();
199 | //Open websocket
200 | m_OpenedEvent.Reset();
201 | if (_wSC == null || (_wSC.State != WebSocketState.None && _wSC.State != WebSocketState.Closed))
202 | return;
203 |
204 | _wSC.Open();
205 | }
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Plugins/iOS/ASWebAuthenticationSession.mm:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #include "Common.h"
4 |
5 | extern UIViewController* UnityGetGLViewController();
6 |
7 | typedef void (*ASWebAuthenticationSessionCompletionCallback)(void* sessionPtr, const char* callbackUrl, int errorCode, const char* errorMessage);
8 |
9 | @interface Cdm_ASWebAuthenticationSession : NSObject
10 |
11 | @property (readonly, nonatomic)ASWebAuthenticationSession* session;
12 |
13 | @end
14 |
15 | @implementation Cdm_ASWebAuthenticationSession
16 |
17 | - (instancetype)initWithURL:(NSURL *)URL callbackURLScheme:(nullable NSString *)callbackURLScheme completionCallback:(ASWebAuthenticationSessionCompletionCallback)completionCallback
18 | {
19 | _session = [[ASWebAuthenticationSession alloc] initWithURL:URL
20 | callbackURLScheme: callbackURLScheme
21 | completionHandler:^(NSURL * _Nullable callbackURL, NSError * _Nullable error)
22 | {
23 | if (error != nil)
24 | {
25 | NSLog(@"[ASWebAuthenticationSession:CompletionHandler] %@", error.description);
26 | }
27 | else
28 | {
29 | //NSLog(@"[ASWebAuthenticationSession:CompletionHandler] Callback URL: %@", callbackURL);
30 | }
31 |
32 | completionCallback((__bridge void*)self, toString(callbackURL.absoluteString), (int)error.code, toString(error.localizedDescription));
33 | }];
34 |
35 | [_session setPresentationContextProvider:self];
36 | return self;
37 | }
38 |
39 | - (nonnull ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(nonnull ASWebAuthenticationSession *)session
40 | {
41 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 || __TV_OS_VERSION_MAX_ALLOWED >= 130000
42 | return [[[UIApplication sharedApplication] delegate] window];
43 | #elif __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500
44 | return [[NSApplication sharedApplication] mainWindow];
45 | #else
46 | return nil;
47 | #endif
48 | }
49 |
50 | @end
51 |
52 | extern "C"
53 | {
54 | Cdm_ASWebAuthenticationSession* Cdm_Auth_ASWebAuthenticationSession_InitWithURL(
55 | const char* urlStr, const char* urlSchemeStr, ASWebAuthenticationSessionCompletionCallback completionCallback)
56 | {
57 | //NSLog(@"[ASWebAuthenticationSession:InitWithURL] initWithURL: %s callbackURLScheme:%s", urlStr, urlSchemeStr);
58 |
59 | NSURL* url = [NSURL URLWithString: toString(urlStr)];
60 | NSString* urlScheme = toString(urlSchemeStr);
61 |
62 | Cdm_ASWebAuthenticationSession* session = [[Cdm_ASWebAuthenticationSession alloc] initWithURL:url
63 | callbackURLScheme: urlScheme
64 | completionCallback:completionCallback];
65 | return session;
66 | }
67 |
68 | // Starts a web authentication session.
69 | // https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990953-start?language=objc
70 | int Cdm_Auth_ASWebAuthenticationSession_Start(void* sessionPtr)
71 | {
72 | Cdm_ASWebAuthenticationSession* session = (__bridge Cdm_ASWebAuthenticationSession*) sessionPtr;
73 | BOOL started = [[session session] start];
74 |
75 | //NSLog(@"[ASWebAuthenticationSession:Start]: %s", (started ? "YES" : "NO"));
76 |
77 | return toBool(started);
78 | }
79 |
80 | // Cancels a web authentication session.
81 | // https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990951-cancel?language=objc
82 | void Cdm_Auth_ASWebAuthenticationSession_Cancel(void* sessionPtr)
83 | {
84 | //NSLog(@"[ASWebAuthenticationSession:Cancel]");
85 |
86 | Cdm_ASWebAuthenticationSession* session = (__bridge Cdm_ASWebAuthenticationSession*) sessionPtr;
87 | [[session session] cancel];
88 | }
89 |
90 | int Cdm_Auth_ASWebAuthenticationSession_GetPrefersEphemeralWebBrowserSession(void* sessionPtr)
91 | {
92 | Cdm_ASWebAuthenticationSession* session = (__bridge Cdm_ASWebAuthenticationSession*) sessionPtr;
93 | return toBool([[session session] prefersEphemeralWebBrowserSession]);
94 | }
95 |
96 | void Cdm_Auth_ASWebAuthenticationSession_SetPrefersEphemeralWebBrowserSession(void* sessionPtr, int enable)
97 | {
98 | Cdm_ASWebAuthenticationSession* session = (__bridge Cdm_ASWebAuthenticationSession*) sessionPtr;
99 | [[session session] setPrefersEphemeralWebBrowserSession:toBool(enable)];
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Plugins/iOS/Common.h:
--------------------------------------------------------------------------------
1 | #ifndef Common_h
2 | #define Common_h
3 |
4 | typedef int bool_t;
5 |
6 | inline bool_t toBool(bool v)
7 | {
8 | return v ? 1 : 0;
9 | }
10 |
11 | inline bool toBool(bool_t v)
12 | {
13 | return v != 0;
14 | }
15 |
16 | inline NSString* toString(const char* string)
17 | {
18 | if (string != NULL)
19 | {
20 | return [NSString stringWithUTF8String:string];
21 | }
22 | else
23 | {
24 | return [NSString stringWithUTF8String:""];
25 | }
26 | }
27 |
28 | inline char* toString(NSString* string)
29 | {
30 | const char* cstr = [string UTF8String];
31 |
32 | if (cstr == NULL)
33 | return NULL;
34 |
35 | char* copy = (char*)malloc(strlen(cstr) + 1);
36 | strcpy(copy, cstr);
37 | return copy;
38 | }
39 |
40 | #endif /* Common_h */
41 |
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/ASWebAuthenticationSession.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using AOT;
4 |
5 | #if UNITY_IOS && !UNITY_EDITOR
6 | using System.Runtime.InteropServices;
7 | #endif
8 |
9 | namespace Cdm.Authentication.Browser
10 | {
11 | ///
12 | /// A session that an app uses to authenticate a user through a web service.
13 | ///
14 | ///
15 | public class ASWebAuthenticationSession : IDisposable
16 | {
17 | private static readonly Dictionary CompletionCallbacks =
18 | new Dictionary();
19 |
20 | private IntPtr _sessionPtr;
21 |
22 | ///
23 | /// A Boolean value that indicates whether the session should ask the browser for a private authentication
24 | /// session.
25 | ///
26 | /// Set this property before you call . Otherwise it has no effect.
27 | public bool prefersEphemeralWebBrowserSession
28 | {
29 | get => Cdm_Auth_ASWebAuthenticationSession_GetPrefersEphemeralWebBrowserSession(_sessionPtr) == 1;
30 | set => Cdm_Auth_ASWebAuthenticationSession_SetPrefersEphemeralWebBrowserSession(_sessionPtr, value ? 1 : 0);
31 | }
32 |
33 | ///
34 | /// Creates a web authentication session instance.
35 | ///
36 | /// A URL with the http or https scheme pointing to the authentication webpage.
37 | /// The custom URL scheme that the app expects in the callback URL.
38 | /// A completion handler the session calls when it completes successfully, or when the user cancels the session.
39 | ///
40 | public ASWebAuthenticationSession(string url, string callbackUrlScheme,
41 | ASWebAuthenticationSessionCompletionHandler completionHandler)
42 | {
43 | _sessionPtr =
44 | Cdm_Auth_ASWebAuthenticationSession_InitWithURL(
45 | url, callbackUrlScheme, OnAuthenticationSessionCompleted);
46 |
47 | CompletionCallbacks.Add(_sessionPtr, completionHandler);
48 | }
49 |
50 | ///
51 | /// Starts a web authentication session.
52 | ///
53 | /// A Boolean value indicating whether the web authentication session started successfully.
54 | ///
55 | public bool Start()
56 | {
57 | return Cdm_Auth_ASWebAuthenticationSession_Start(_sessionPtr) == 1;
58 | }
59 |
60 | ///
61 | /// Cancels a web authentication session.
62 | ///
63 | ///
64 | public void Cancel()
65 | {
66 | Cdm_Auth_ASWebAuthenticationSession_Cancel(_sessionPtr);
67 | }
68 |
69 | public void Dispose()
70 | {
71 | CompletionCallbacks.Remove(_sessionPtr);
72 | _sessionPtr = IntPtr.Zero;
73 | }
74 |
75 | #if UNITY_IOS && !UNITY_EDITOR
76 | private const string DllName = "__Internal";
77 |
78 | [DllImport(DllName)]
79 | private static extern IntPtr Cdm_Auth_ASWebAuthenticationSession_InitWithURL(
80 | string url, string callbackUrlScheme, AuthenticationSessionCompletedCallback completionHandler);
81 |
82 | [DllImport(DllName)]
83 | private static extern int Cdm_Auth_ASWebAuthenticationSession_Start(IntPtr session);
84 |
85 | [DllImport(DllName)]
86 | private static extern void Cdm_Auth_ASWebAuthenticationSession_Cancel(IntPtr session);
87 |
88 | [DllImport(DllName)]
89 | private static extern int Cdm_Auth_ASWebAuthenticationSession_GetPrefersEphemeralWebBrowserSession(IntPtr session);
90 |
91 | [DllImport(DllName)]
92 | private static extern void Cdm_Auth_ASWebAuthenticationSession_SetPrefersEphemeralWebBrowserSession(
93 | IntPtr session, int enable);
94 | #else
95 |
96 | private const string NotSupportedMsg = "Only iOS platform is supported.";
97 |
98 | private static IntPtr Cdm_Auth_ASWebAuthenticationSession_InitWithURL(
99 | string url, string callbackUrlScheme, AuthenticationSessionCompletedCallback completionHandler)
100 | {
101 | throw new NotImplementedException(NotSupportedMsg);
102 | }
103 |
104 | private static int Cdm_Auth_ASWebAuthenticationSession_Start(IntPtr session)
105 | {
106 | throw new NotImplementedException(NotSupportedMsg);
107 | }
108 |
109 | private static void Cdm_Auth_ASWebAuthenticationSession_Cancel(IntPtr session)
110 | {
111 | throw new NotImplementedException(NotSupportedMsg);
112 | }
113 |
114 | private static int Cdm_Auth_ASWebAuthenticationSession_GetPrefersEphemeralWebBrowserSession(IntPtr session)
115 | {
116 | throw new NotImplementedException(NotSupportedMsg);
117 | }
118 |
119 | private static void Cdm_Auth_ASWebAuthenticationSession_SetPrefersEphemeralWebBrowserSession(
120 | IntPtr session, int enable)
121 | {
122 | throw new NotImplementedException(NotSupportedMsg);
123 | }
124 | #endif
125 | public delegate void ASWebAuthenticationSessionCompletionHandler(string callbackUrl,
126 | ASWebAuthenticationSessionError error);
127 |
128 | private delegate void AuthenticationSessionCompletedCallback(IntPtr session, string callbackUrl,
129 | int errorCode, string errorMessage);
130 |
131 | [MonoPInvokeCallback(typeof(AuthenticationSessionCompletedCallback))]
132 | private static void OnAuthenticationSessionCompleted(IntPtr session, string callbackUrl,
133 | int errorCode, string errorMessage)
134 | {
135 | if (CompletionCallbacks.TryGetValue(session, out var callback))
136 | {
137 | callback?.Invoke(callbackUrl,
138 | new ASWebAuthenticationSessionError((ASWebAuthenticationSessionErrorCode) errorCode, errorMessage));
139 | }
140 | }
141 | }
142 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/ASWebAuthenticationSessionBrowser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace Cdm.Authentication.Browser
6 | {
7 | public class ASWebAuthenticationSessionBrowser : IBrowser
8 | {
9 | private TaskCompletionSource _taskCompletionSource;
10 |
11 | ///
12 | /// Indicates whether the session should ask the browser for a private authentication
13 | /// session.
14 | ///
15 | ///
16 | /// Set this property before you call . Otherwise it has no effect.
17 | ///
18 | public bool prefersEphemeralWebBrowserSession { get; set; } = false;
19 |
20 | public async Task StartAsync(
21 | string loginUrl, string redirectUrl, CancellationToken cancellationToken = default)
22 | {
23 | if (string.IsNullOrEmpty(loginUrl))
24 | throw new ArgumentNullException(nameof(loginUrl));
25 |
26 | if (string.IsNullOrEmpty(redirectUrl))
27 | throw new ArgumentNullException(nameof(redirectUrl));
28 |
29 | _taskCompletionSource = new TaskCompletionSource();
30 |
31 | // Discard URL parameters. They are not valid for iOS URL Scheme.
32 | redirectUrl = redirectUrl.Split(new char[] {':'}, StringSplitOptions.RemoveEmptyEntries)[0];
33 |
34 | using var authenticationSession =
35 | new ASWebAuthenticationSession(loginUrl, redirectUrl, AuthenticationSessionCompletionHandler);
36 | authenticationSession.prefersEphemeralWebBrowserSession = prefersEphemeralWebBrowserSession;
37 |
38 | cancellationToken.Register(() =>
39 | {
40 | _taskCompletionSource?.TrySetCanceled();
41 | });
42 |
43 | try
44 | {
45 | if (!authenticationSession.Start())
46 | {
47 | _taskCompletionSource.SetResult(
48 | new BrowserResult(BrowserStatus.UnknownError, "Browser could not be started."));
49 | }
50 |
51 | return await _taskCompletionSource.Task;
52 | }
53 | catch (TaskCanceledException)
54 | {
55 | // In case of timeout cancellation.
56 | authenticationSession?.Cancel();
57 | throw;
58 | }
59 | }
60 |
61 | private void AuthenticationSessionCompletionHandler(string callbackUrl, ASWebAuthenticationSessionError error)
62 | {
63 | if (error.code == ASWebAuthenticationSessionErrorCode.None)
64 | {
65 | _taskCompletionSource.SetResult(
66 | new BrowserResult(BrowserStatus.Success, callbackUrl));
67 | }
68 | else if (error.code == ASWebAuthenticationSessionErrorCode.CanceledLogin)
69 | {
70 | _taskCompletionSource.SetResult(
71 | new BrowserResult(BrowserStatus.UserCanceled, callbackUrl, error.message));
72 | }
73 | else
74 | {
75 | _taskCompletionSource.SetResult(
76 | new BrowserResult(BrowserStatus.UnknownError, callbackUrl, error.message));
77 | }
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/ASWebAuthenticationSessionError.cs:
--------------------------------------------------------------------------------
1 | namespace Cdm.Authentication.Browser
2 | {
3 | public class ASWebAuthenticationSessionError
4 | {
5 | public ASWebAuthenticationSessionErrorCode code { get; }
6 | public string message { get; }
7 |
8 | public ASWebAuthenticationSessionError(ASWebAuthenticationSessionErrorCode code, string message)
9 | {
10 | this.code = code;
11 | this.message = message;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/ASWebAuthenticationSessionErrorCode.cs:
--------------------------------------------------------------------------------
1 | namespace Cdm.Authentication.Browser
2 | {
3 | public enum ASWebAuthenticationSessionErrorCode
4 | {
5 | None = 0,
6 | CanceledLogin = 1,
7 | PresentationContextNotProvided = 2,
8 | PresentationContextInvalid = 3
9 | }
10 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/BrowserResult.cs:
--------------------------------------------------------------------------------
1 | namespace Cdm.Authentication.Browser
2 | {
3 | public class BrowserResult
4 | {
5 | ///
6 | /// The browser status indicates the operation is whether success or not.
7 | ///
8 | public BrowserStatus status { get; }
9 |
10 | ///
11 | /// After a user successfully authorizes an application, the authorization server will redirect the user back
12 | /// to the application with the redirect URL. Use this if only if is
13 | /// .
14 | ///
15 | public string redirectUrl { get; }
16 |
17 | ///
18 | /// The error description if an error is exist. You can use this value if is not
19 | /// .
20 | ///
21 | public string error { get; }
22 |
23 | public BrowserResult(BrowserStatus status, string redirectUrl)
24 | {
25 | this.status = status;
26 | this.redirectUrl = redirectUrl;
27 | }
28 |
29 | public BrowserResult(BrowserStatus status, string redirectUrl, string error)
30 | {
31 | this.status = status;
32 | this.redirectUrl = redirectUrl;
33 | this.error = error;
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/BrowserStatus.cs:
--------------------------------------------------------------------------------
1 | namespace Cdm.Authentication.Browser
2 | {
3 | public enum BrowserStatus
4 | {
5 | Success,
6 | UserCanceled,
7 | UnknownError,
8 | }
9 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/CallbackManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.IO.Pipes;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace Cdm.Authentication.Browser
8 | {
9 | public class CallbackManager
10 | {
11 | private readonly string _name;
12 |
13 | public CallbackManager(string name)
14 | {
15 | _name = name ?? throw new ArgumentNullException(nameof(name));
16 | }
17 |
18 | public int ClientConnectTimeoutSeconds { get; set; } = 1;
19 |
20 | public async Task RunClient(string args)
21 | {
22 | using (var client = new NamedPipeClientStream(".", _name, PipeDirection.Out))
23 | {
24 | await client.ConnectAsync(ClientConnectTimeoutSeconds * 1000);
25 |
26 | using (var sw = new StreamWriter(client) { AutoFlush = true })
27 | {
28 | await sw.WriteAsync(args);
29 | }
30 | }
31 | }
32 |
33 | public async Task RunServer(CancellationToken? token = null)
34 | {
35 | token = CancellationToken.None;
36 |
37 | using (var server = new NamedPipeServerStream(_name, PipeDirection.In))
38 | {
39 | await server.WaitForConnectionAsync(token.Value);
40 |
41 | using (var sr = new StreamReader(server))
42 | {
43 | var msg = await sr.ReadToEndAsync();
44 | return msg;
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/CrossPlatformBrowser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using UnityEngine;
7 |
8 | namespace Cdm.Authentication.Browser
9 | {
10 | public class CrossPlatformBrowser : IBrowser
11 | {
12 | public readonly Dictionary _platformBrowsers =
13 | new Dictionary();
14 |
15 | public IDictionary platformBrowsers => _platformBrowsers;
16 |
17 | public async Task StartAsync(
18 | string loginUrl, string redirectUrl, CancellationToken cancellationToken = default)
19 | {
20 | var browser = platformBrowsers.FirstOrDefault(x => x.Key == Application.platform).Value;
21 | if (browser == null)
22 | throw new NotSupportedException($"There is no browser found for '{Application.platform}' platform.");
23 |
24 | return await browser.StartAsync(loginUrl, redirectUrl, cancellationToken);
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/DeepLinkBrowser.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using UnityEngine;
4 |
5 | namespace Cdm.Authentication.Browser
6 | {
7 | ///
8 | /// OAuth 2.0 verification browser that waits for a call with
9 | /// the authorization verification code through a custom scheme (aka protocol).
10 | ///
11 | ///
12 | public class DeepLinkBrowser : IBrowser
13 | {
14 | private TaskCompletionSource _taskCompletionSource;
15 |
16 | public async Task StartAsync(string loginUrl, string redirectUrl, CancellationToken cancellationToken = default)
17 | {
18 | _taskCompletionSource = new TaskCompletionSource();
19 |
20 | cancellationToken.Register(() =>
21 | {
22 | _taskCompletionSource?.TrySetCanceled();
23 | });
24 |
25 | Application.deepLinkActivated += OnDeepLinkActivated;
26 | try
27 | {
28 | Application.OpenURL(loginUrl);
29 | return await _taskCompletionSource.Task;
30 | }
31 | finally
32 | {
33 | Application.deepLinkActivated -= OnDeepLinkActivated;
34 | }
35 | }
36 |
37 | private void OnDeepLinkActivated(string url)
38 | {
39 | _taskCompletionSource.SetResult(
40 | new BrowserResult(BrowserStatus.Success, url));
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/IBrowser.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace Cdm.Authentication.Browser
5 | {
6 | public interface IBrowser
7 | {
8 | Task StartAsync(
9 | string loginUrl, string redirectUrl, CancellationToken cancellationToken = default);
10 | }
11 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/StandaloneBrowser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using UnityEngine;
6 |
7 | namespace Cdm.Authentication.Browser
8 | {
9 | ///
10 | /// OAuth 2.0 verification browser that runs a local server and waits for a call with
11 | /// the authorization verification code.
12 | ///
13 | public class StandaloneBrowser : IBrowser
14 | {
15 | private TaskCompletionSource _taskCompletionSource;
16 |
17 | ///
18 | /// Gets or sets the close page response. This HTML response is shown to the user after redirection is done.
19 | ///
20 | public string closePageResponse { get; set; } =
21 | "DONE!
(You can close this tab/window now)";
22 |
23 | public async Task StartAsync(
24 | string loginUrl, string redirectUrl, CancellationToken cancellationToken = default)
25 | {
26 | _taskCompletionSource = new TaskCompletionSource();
27 |
28 | cancellationToken.Register(() =>
29 | {
30 | _taskCompletionSource?.TrySetCanceled();
31 | });
32 |
33 | using var httpListener = new HttpListener();
34 |
35 | try
36 | {
37 |
38 | redirectUrl = AddForwardSlashIfNecessary(redirectUrl);
39 | httpListener.Prefixes.Add(redirectUrl);
40 | httpListener.Start();
41 | httpListener.BeginGetContext(IncomingHttpRequest, httpListener);
42 |
43 | Application.OpenURL(loginUrl);
44 |
45 | return await _taskCompletionSource.Task;
46 | }
47 | finally
48 | {
49 | httpListener.Stop();
50 | }
51 | }
52 |
53 | private void IncomingHttpRequest(IAsyncResult result)
54 | {
55 | var httpListener = (HttpListener)result.AsyncState;
56 | var httpContext = httpListener.EndGetContext(result);
57 | var httpRequest = httpContext.Request;
58 |
59 | // Build a response to send an "ok" back to the browser for the user to see.
60 | var httpResponse = httpContext.Response;
61 | var buffer = System.Text.Encoding.UTF8.GetBytes(closePageResponse);
62 |
63 | // Send the output to the client browser.
64 | httpResponse.ContentLength64 = buffer.Length;
65 | var output = httpResponse.OutputStream;
66 | output.Write(buffer, 0, buffer.Length);
67 | output.Close();
68 |
69 | _taskCompletionSource.SetResult(
70 | new BrowserResult(BrowserStatus.Success, httpRequest.Url.ToString()));
71 | }
72 |
73 | ///
74 | /// Prefixes must end in a forward slash ("/")
75 | ///
76 | ///
77 | private string AddForwardSlashIfNecessary(string url)
78 | {
79 | string forwardSlash = "/";
80 | if (!url.EndsWith(forwardSlash))
81 | {
82 | url += forwardSlash;
83 | }
84 |
85 | return url;
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Browser/WindowsSystemBrowser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using System.Web;
5 | #if USE_EMBEDDED_LIB && (UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN)
6 | using IdentityModel.Client;
7 | #endif
8 | using UnityEngine;
9 |
10 | namespace Cdm.Authentication.Browser
11 | {
12 | public class WindowsSystemBrowser : IBrowser
13 | {
14 | private TaskCompletionSource _taskCompletionSource;
15 | public async Task StartAsync(
16 | string loginUrl, string redirectUrl, CancellationToken cancellationToken = default)
17 | {
18 | if (string.IsNullOrEmpty(loginUrl))
19 | throw new ArgumentNullException(nameof(loginUrl));
20 |
21 | if (string.IsNullOrEmpty(redirectUrl))
22 | throw new ArgumentNullException(nameof(redirectUrl));
23 |
24 | _taskCompletionSource = new TaskCompletionSource();
25 | cancellationToken.Register(() => { _taskCompletionSource?.TrySetCanceled(); });
26 |
27 | try
28 | {
29 | var state = ExtractStateFromUrl(loginUrl);
30 | Debug.Log("Opening browser for login: " + loginUrl + " with state: " + state);
31 | var callbackManager = new CallbackManager(state);
32 |
33 | Application.OpenURL(loginUrl);
34 | var response = await callbackManager.RunServer();
35 | // check response is not null
36 | if (response == null)
37 | {
38 | _taskCompletionSource.SetResult(
39 | new BrowserResult(BrowserStatus.UnknownError, "Browser could not be started."));
40 | return await _taskCompletionSource.Task;
41 | }
42 | if (response == "error")
43 | {
44 | _taskCompletionSource.SetResult(
45 | new BrowserResult(BrowserStatus.UserCanceled, "User canceled the login."));
46 | return await _taskCompletionSource.Task;
47 | }
48 | _taskCompletionSource.SetResult(
49 | new BrowserResult(BrowserStatus.Success, response));
50 |
51 | return await _taskCompletionSource.Task;
52 | }
53 | catch (Exception ex)
54 | {
55 | _taskCompletionSource.SetResult(
56 | new BrowserResult(BrowserStatus.UnknownError, ex.Message));
57 | return await _taskCompletionSource.Task;
58 | }
59 |
60 |
61 | }
62 | public string ExtractStateFromUrl(string url)
63 | {
64 | Uri uri = new Uri(url);
65 | string query = uri.Query;
66 | var queryParams = HttpUtility.ParseQueryString(query);
67 | return queryParams["state"];
68 | }
69 |
70 | public static async Task ProcessCallback(string args)
71 | {
72 | UnityEngine.Debug.Log("Processing callback" + args);
73 | #if USE_EMBEDDED_LIB && (UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN)
74 | var response = new AuthorizeResponse(args);
75 | if (!String.IsNullOrWhiteSpace(response.State))
76 | {
77 | UnityEngine.Debug.Log($"Found state: {response.State}");
78 | var callbackManager = new CallbackManager(response.State);
79 | await callbackManager.RunClient(args);
80 | await Task.Delay(1000);
81 | Application.Quit();
82 | }
83 | else
84 | {
85 | UnityEngine.Debug.Log("Error: no state on response");
86 | }
87 | #endif
88 | }
89 | }
90 |
91 |
92 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Cdm.Authentication.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Cdm.Authentication",
3 | "rootNamespace": "Cdm.Authentication",
4 | "references": [],
5 | "includePlatforms": [],
6 | "excludePlatforms": [],
7 | "allowUnsafeCode": false,
8 | "overrideReferences": false,
9 | "precompiledReferences": [],
10 | "autoReferenced": true,
11 | "defineConstraints": [
12 | "UNITY_2019_1_OR_NEWER"
13 | ],
14 | "versionDefines": [],
15 | "noEngineReferences": false
16 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Clients/MockServerAuth.cs:
--------------------------------------------------------------------------------
1 | using Cdm.Authentication.OAuth2;
2 |
3 | namespace Cdm.Authentication.Clients
4 | {
5 | public class MockServerAuth : AuthorizationCodeFlow
6 | {
7 | public const string AuthorizationPath = "/api/oauth/authorize/";
8 | public const string TokenPath = "/api/oauth/token/oidc/";
9 |
10 | public override string authorizationUrl => $"{serverUrl}{AuthorizationPath}";
11 | public override string accessTokenUrl => $"{serverUrl}{TokenPath}";
12 |
13 | public string serverUrl { get; }
14 |
15 | public MockServerAuth(Configuration configuration, string serverUrl) : base(configuration)
16 | {
17 | this.serverUrl = serverUrl;
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/IUserInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Cdm.Authentication
2 | {
3 | public interface IUserInfo
4 | {
5 | ///
6 | /// Gets the user identifier.
7 | ///
8 | public string id { get; }
9 |
10 | ///
11 | /// Gets the name of the user.
12 | ///
13 | public string name { get; }
14 |
15 | ///
16 | /// Gets the email address of the user.
17 | ///
18 | public string email { get; }
19 |
20 | ///
21 | /// Gets the user picture URL.
22 | ///
23 | public string picture { get; }
24 | }
25 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/IUserInfoProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace Cdm.Authentication
5 | {
6 | public interface IUserInfoProvider
7 | {
8 | ///
9 | /// Obtains user information using third-party authentication service using data provided via callback request.
10 | ///
11 | /// Optional cancellation token.
12 | Task GetUserInfoAsync(CancellationToken cancellationToken = default);
13 | }
14 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AccessTokenRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 | using UnityEngine.Scripting;
3 |
4 | namespace Cdm.Authentication.OAuth2
5 | {
6 | ///
7 | /// OAuth 2.0 request for an access token using an authorization code as specified in
8 | /// http://tools.ietf.org/html/rfc6749#section-4.1.3.
9 | ///
10 | [Preserve]
11 | [DataContract]
12 | public class AccessTokenRequest
13 | {
14 | ///
15 | /// Gets the authorization grant type as 'authorization_code'.
16 | ///
17 | [Preserve]
18 | [DataMember(IsRequired = true, Name = "grant_type")]
19 | public string grantType => "authorization_code";
20 |
21 | ///
22 | /// Gets or sets the authorization code received from the authorization server.
23 | ///
24 | [Preserve]
25 | [DataMember(IsRequired = true, Name = "code")]
26 | public string code { get; set; }
27 |
28 | ///
29 | /// Gets or sets the client identifier as described in https://www.rfc-editor.org/rfc/rfc6749#section-3.2.1.
30 | ///
31 | [Preserve]
32 | [DataMember(IsRequired = true, Name = "client_id")]
33 | public string clientId { get; set; }
34 |
35 | ///
36 | /// Gets or sets the client secret.
37 | ///
38 | [Preserve]
39 | [DataMember(Name = "client_secret")]
40 | public string clientSecret { get; set; }
41 |
42 | ///
43 | /// Gets or sets the redirect URI parameter matching the redirect URI parameter in the authorization request.
44 | ///
45 | [Preserve]
46 | [DataMember(Name = "redirect_uri")]
47 | public string redirectUri { get; set; }
48 | }
49 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AccessTokenRequestError.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 | using UnityEngine.Scripting;
3 |
4 | namespace Cdm.Authentication.OAuth2
5 | {
6 | ///
7 | /// OAuth 2.0 model for a unsuccessful access token response as specified in
8 | /// http://tools.ietf.org/html/rfc6749#section-5.2.
9 | ///
10 | [Preserve]
11 | [DataContract]
12 | public class AccessTokenRequestError
13 | {
14 | ///
15 | /// Gets or sets the error code as specified in http://tools.ietf.org/html/rfc6749#section-5.2.
16 | ///
17 | [Preserve]
18 | [DataMember(IsRequired = true, Name = "error")]
19 | public AccessTokenRequestErrorCode code { get; set; }
20 |
21 | ///
22 | /// Gets or sets a human-readable text which provides additional information used to assist the client
23 | /// developer in understanding the error occurred.
24 | ///
25 | [Preserve]
26 | [DataMember(Name = "error_description")]
27 | public string description { get; set; }
28 |
29 | ///
30 | /// Gets or sets the URI identifying a human-readable web page with provides information about the error.
31 | ///
32 | [Preserve]
33 | [DataMember(Name = "error_uri")]
34 | public string uri { get; set; }
35 | }
36 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AccessTokenRequestErrorCode.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Converters;
4 | using UnityEngine.Scripting;
5 |
6 | namespace Cdm.Authentication.OAuth2
7 | {
8 | ///
9 | /// The authorization server responds with an HTTP 400 (Bad Request) status code (unless specified otherwise) and
10 | /// includes the following parameters with the response.
11 | ///
12 | [JsonConverter(typeof(StringEnumConverter))]
13 | [DataContract]
14 | [Preserve]
15 | public enum AccessTokenRequestErrorCode
16 | {
17 | ///
18 | /// The request is missing a required parameter, includes an unsupported parameter value
19 | /// (other than grant type), repeats a parameter, includes multiple credentials, utilizes more than
20 | /// one mechanism for authenticating the client, or is otherwise malformed.
21 | ///
22 | [Preserve]
23 | [EnumMember(Value = "invalid_request")]
24 | InvalidRequest,
25 |
26 | ///
27 | /// Client authentication failed (e.g., unknown client, no client authentication included,
28 | /// or unsupported authentication method). The authorization server MAY return an HTTP 401 (Unauthorized)
29 | /// status code to indicate which HTTP authentication schemes are supported. If the client attempted to
30 | /// authenticate via the "Authorization" request header field, the authorization server MUST respond with
31 | /// an HTTP 401 (Unauthorized) status code and include the "WWW-Authenticate" response header field matching
32 | /// the authentication scheme used by the client.
33 | ///
34 | [Preserve]
35 | [EnumMember(Value = "invalid_client")]
36 | InvalidClient,
37 |
38 | ///
39 | /// The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token
40 | /// is invalid, expired, revoked, does not match the redirection URI used in the authorization request,
41 | /// or was issued to another client.
42 | ///
43 | [Preserve]
44 | [EnumMember(Value = "invalid_grant")]
45 | InvalidGrant,
46 |
47 | ///
48 | /// The authenticated client is not authorized to use this authorization grant type.
49 | ///
50 | [Preserve]
51 | [EnumMember(Value = "unauthorized_client")]
52 | UnauthorizedClient,
53 |
54 | ///
55 | /// The authorization grant type is not supported by the authorization server.
56 | ///
57 | [Preserve]
58 | [EnumMember(Value = "unsupported_grant_type")]
59 | UnsupportedGrantType,
60 |
61 | ///
62 | /// The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner.
63 | ///
64 | [Preserve]
65 | [EnumMember(Value = "invalid_scope")]
66 | InvalidScope,
67 | }
68 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AccessTokenRequestException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 |
4 | namespace Cdm.Authentication.OAuth2
5 | {
6 | ///
7 | /// Access token response exception which is thrown in case of receiving a token error when an authorization code
8 | /// or an access token is expected.
9 | ///
10 | public class AccessTokenRequestException : Exception
11 | {
12 | ///
13 | /// HTTP status code of error, or null if unknown.
14 | ///
15 | public HttpStatusCode? statusCode { get; }
16 |
17 | ///
18 | /// The error information.
19 | ///
20 | public AccessTokenRequestError error { get; }
21 |
22 | public AccessTokenRequestException(AccessTokenRequestError error, HttpStatusCode? statusCode)
23 | : base(error.description)
24 | {
25 | this.error = error;
26 | this.statusCode = statusCode;
27 | }
28 |
29 | public AccessTokenRequestException(AccessTokenRequestError error, HttpStatusCode? statusCode, string message)
30 | : base(message)
31 | {
32 | this.error = error;
33 | this.statusCode = statusCode;
34 | }
35 |
36 | public AccessTokenRequestException(AccessTokenRequestError error, HttpStatusCode? statusCode,
37 | string message, Exception innerException) : base(message, innerException)
38 | {
39 | this.error = error;
40 | this.statusCode = statusCode;
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AccessTokenResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http.Headers;
3 | using System.Runtime.Serialization;
4 | using UnityEngine.Scripting;
5 |
6 | namespace Cdm.Authentication.OAuth2
7 | {
8 | [Preserve]
9 | [DataContract]
10 | public class AccessTokenResponse
11 | {
12 | ///
13 | /// Gets or sets the access token issued by the authorization server.
14 | ///
15 | [Preserve]
16 | [DataMember(IsRequired = true, Name = "access_token")]
17 | public string accessToken { get; set; }
18 |
19 | ///
20 | /// Gets or sets the refresh token which can be used to obtain a new access token.
21 | ///
22 | [Preserve]
23 | [DataMember(Name = "refresh_token")]
24 | public string refreshToken { get; set; }
25 |
26 | ///
27 | /// Gets or sets the token type as specified in http://tools.ietf.org/html/rfc6749#section-7.1.
28 | ///
29 | [Preserve]
30 | [DataMember(IsRequired = true, Name = "token_type")]
31 | public string tokenType { get; set; }
32 |
33 | ///
34 | /// Gets or sets the lifetime in seconds of the access token.
35 | ///
36 | [Preserve]
37 | [DataMember(Name = "expires_in")]
38 | public long? expiresIn { get; set; }
39 |
40 | ///
41 | /// Gets or sets the scope of the access token as specified in http://tools.ietf.org/html/rfc6749#section-3.3.
42 | ///
43 | [Preserve]
44 | [DataMember(Name = "scope")]
45 | public string scope { get; set; }
46 |
47 | ///
48 | /// The date and time that this token was issued, expressed in UTC.
49 | ///
50 | ///
51 | /// This should be set by the client after the token was received from the server.
52 | ///
53 | public DateTime? issuedAt { get; set; }
54 |
55 | ///
56 | /// Seconds till the expires returned by provider.
57 | ///
58 | public DateTime? expiresAt
59 | {
60 | get
61 | {
62 | if (issuedAt.HasValue && expiresIn.HasValue)
63 | {
64 | return issuedAt.Value + TimeSpan.FromSeconds(expiresIn.Value);
65 | }
66 |
67 | return null;
68 | }
69 | }
70 |
71 | public AuthenticationHeaderValue GetAuthenticationHeader()
72 | {
73 | return new AuthenticationHeaderValue(tokenType, accessToken);
74 | }
75 |
76 | ///
77 | /// Returns true if the token is expired or it's going to expire soon.
78 | ///
79 | ///
80 | /// If a token response does not have then it's considered expired.
81 | /// If is null, the token is also considered expired.
82 | ///
83 | public bool IsExpired()
84 | {
85 | return string.IsNullOrEmpty(accessToken) || expiresAt == null || expiresAt < DateTime.UtcNow;
86 | }
87 |
88 | ///
89 | /// Returns true if the refresh token is exist.
90 | ///
91 | public bool HasRefreshToken()
92 | {
93 | return !string.IsNullOrEmpty(refreshToken);
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AccessTokenResponseExtensions.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Cdm.Authentication.OAuth2
3 | {
4 | public static class AccessTokenResponseExtensions
5 | {
6 | public static bool IsNullOrExpired(this AccessTokenResponse accessTokenResponse)
7 | {
8 | return accessTokenResponse == null || accessTokenResponse.IsExpired();
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AuthenticationError.cs:
--------------------------------------------------------------------------------
1 | namespace Cdm.Authentication.OAuth2
2 | {
3 | public enum AuthenticationError
4 | {
5 | Other = 0,
6 | Cancelled = 1,
7 | Timeout = 2
8 | }
9 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AuthenticationException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Cdm.Authentication.OAuth2
4 | {
5 | public class AuthenticationException : Exception
6 | {
7 | public AuthenticationError error { get; }
8 |
9 | public AuthenticationException(AuthenticationError error)
10 | {
11 | this.error = error;
12 | }
13 |
14 | public AuthenticationException(AuthenticationError error, string message) : base(message)
15 | {
16 | this.error = error;
17 | }
18 |
19 | public AuthenticationException(AuthenticationError error, string message, Exception innerException)
20 | : base(message, innerException)
21 | {
22 | this.error = error;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AuthorizationCodeFlowWithPkce.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Security.Cryptography;
4 | using System.Text;
5 |
6 | namespace Cdm.Authentication.OAuth2
7 | {
8 | ///
9 | /// OAuth 2.0 'Authorization Code' flow with PKCE (Proof Key for Code Exchange).
10 | ///
11 | /// PKCE (RFC 7636) is an extension to the Authorization Code flow to prevent CSRF and authorization code injection
12 | /// attacks. PKCE is not a form of client authentication, and PKCE is not a replacement for a client secret or
13 | /// other client authentication. PKCE is recommended even if a client is using a client secret or other form
14 | /// of client authentication like private_key_jwt.
15 | ///
16 | /// https://www.rfc-editor.org/rfc/rfc7636
17 | ///
18 | public abstract class AuthorizationCodeFlowWithPkce : AuthorizationCodeFlow
19 | {
20 | private string _codeVerifier;
21 |
22 | protected AuthorizationCodeFlowWithPkce(Configuration configuration) : base(configuration)
23 | {
24 | }
25 |
26 | protected override Dictionary GetAuthorizationUrlParameters()
27 | {
28 | var parameters = base.GetAuthorizationUrlParameters();
29 |
30 | _codeVerifier = GenerateRandomDataBase64url(32);
31 | var codeChallenge = Base64UrlEncodeNoPadding(Sha256Ascii(_codeVerifier));
32 |
33 | parameters.Add("code_challenge", codeChallenge);
34 | parameters.Add("code_challenge_method", "S256");
35 |
36 | return parameters;
37 | }
38 |
39 | protected override Dictionary GetAccessTokenParameters(string code)
40 | {
41 | var parameters = base.GetAccessTokenParameters(code);
42 | parameters.Add("code_verifier", _codeVerifier);
43 | return parameters;
44 | }
45 |
46 | private static string GenerateRandomDataBase64url(uint length)
47 | {
48 | var rng = new RNGCryptoServiceProvider();
49 | var bytes = new byte[length];
50 | rng.GetBytes(bytes);
51 | return Base64UrlEncodeNoPadding(bytes);
52 | }
53 |
54 | ///
55 | /// Base64url no-padding encodes the given input buffer.
56 | ///
57 | ///
58 | ///
59 | private static string Base64UrlEncodeNoPadding(byte[] buffer)
60 | {
61 | var base64 = Convert.ToBase64String(buffer);
62 |
63 | // Converts base64 to base64url.
64 | base64 = base64.Replace("+", "-");
65 | base64 = base64.Replace("/", "_");
66 | // Strips padding.
67 | base64 = base64.Replace("=", "");
68 |
69 | return base64;
70 | }
71 |
72 | ///
73 | /// Returns the SHA256 hash of the input string, which is assumed to be ASCII.
74 | ///
75 | private static byte[] Sha256Ascii(string text)
76 | {
77 | var bytes = Encoding.ASCII.GetBytes(text);
78 | using (SHA256Managed sha256 = new SHA256Managed())
79 | {
80 | return sha256.ComputeHash(bytes);
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AuthorizationCodeRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 | using UnityEngine.Scripting;
3 |
4 | namespace Cdm.Authentication.OAuth2
5 | {
6 | ///
7 | /// OAuth 2.0 request for an access token using an authorization code as specified in
8 | /// http://tools.ietf.org/html/rfc6749#section-4.1.1.
9 | ///
10 | [Preserve]
11 | [DataContract]
12 | public class AuthorizationCodeRequest
13 | {
14 | ///
15 | /// Gets the response type which is the 'code'.
16 | ///
17 | [Preserve]
18 | [DataMember(Name = "response_type", IsRequired = true)]
19 | public string responseType => "code";
20 |
21 | ///
22 | /// Gets or sets the client identifier as specified in https://www.rfc-editor.org/rfc/rfc6749#section-2.2.
23 | ///
24 | [Preserve]
25 | [DataMember(Name = "client_id", IsRequired = true)]
26 | public string clientId { get; set; }
27 |
28 | ///
29 | /// Gets or sets the URI that the authorization server directs the resource owner's user-agent back to the
30 | /// client after a successful authorization grant, as specified in
31 | /// http://tools.ietf.org/html/rfc6749#section-3.1.2 or null for none.
32 | ///
33 | [Preserve]
34 | [DataMember(Name = "redirect_uri")]
35 | public string redirectUri { get; set; }
36 |
37 | ///
38 | /// Gets or sets space-separated list of scopes, as specified in
39 | /// http://tools.ietf.org/html/rfc6749#section-3.3 or null for none.
40 | ///
41 | [Preserve]
42 | [DataMember(Name = "scope")]
43 | public string scope { get; set; }
44 |
45 | ///
46 | /// Gets or sets the state (an opaque value used by the client to maintain state between the request and
47 | /// callback, as mentioned in http://tools.ietf.org/html/rfc6749#section-3.1.2.2 or null for none.
48 | ///
49 | [Preserve]
50 | [DataMember(Name = "state")]
51 | public string state { get; set; }
52 | }
53 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AuthorizationCodeRequestError.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 | using UnityEngine.Scripting;
3 |
4 | namespace Cdm.Authentication.OAuth2
5 | {
6 | [Preserve]
7 | [DataContract]
8 | public class AuthorizationCodeRequestError
9 | {
10 | [Preserve]
11 | [DataMember(IsRequired = true, Name = "error")]
12 | public AuthorizationCodeRequestErrorCode code { get; set; }
13 |
14 | ///
15 | /// OPTIONAL. Human-readable ASCII [USASCII]
16 | /// text providing additional information, used to assist the client developer in understanding
17 | /// the error that occurred.
18 | ///
19 | [Preserve]
20 | [DataMember(Name = "error_description")]
21 | public string description { get; set; }
22 |
23 | ///
24 | /// OPTIONAL. A URI identifying a human-readable web page with information about the error, used to provide
25 | /// the client developer with additional information about the error.
26 | ///
27 | [Preserve]
28 | [DataMember(Name = "error_uri")]
29 | public string uri { get; set; }
30 |
31 | ///
32 | /// REQUIRED if a "state" parameter was present in the client authorization request. The exact value received
33 | /// from the client.
34 | ///
35 | [Preserve]
36 | [DataMember(Name = "state")]
37 | public string state { get; set; }
38 | }
39 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AuthorizationCodeRequestErrorCode.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Converters;
4 | using UnityEngine.Scripting;
5 |
6 | namespace Cdm.Authentication.OAuth2
7 | {
8 | ///
9 | ///
10 | ///
11 | [Preserve]
12 | [JsonConverter(typeof(StringEnumConverter))]
13 | [DataContract]
14 | public enum AuthorizationCodeRequestErrorCode
15 | {
16 | ///
17 | /// The request is missing a required parameter, includes an invalid parameter value, includes a parameter
18 | /// more than once, or is otherwise malformed.
19 | ///
20 | [Preserve]
21 | [EnumMember(Value = "invalid_request")]
22 | InvalidRequest,
23 |
24 | ///
25 | /// The client is not authorized to request an authorization code using this method.
26 | ///
27 | [Preserve]
28 | [EnumMember(Value = "unauthorized_client")]
29 | UnauthorizedClient,
30 |
31 | ///
32 | /// The resource owner or authorization server denied the request.
33 | ///
34 | [Preserve]
35 | [EnumMember(Value = "access_denied")]
36 | AccessDenied,
37 |
38 | ///
39 | /// The authorization server does not support obtaining an authorization code using this method.
40 | ///
41 | [Preserve]
42 | [EnumMember(Value = "unsupported_response_type")]
43 | UnsupportedResponseType,
44 |
45 | ///
46 | /// The requested scope is invalid, unknown, or malformed.
47 | ///
48 | [Preserve]
49 | [EnumMember(Value = "invalid_scope")]
50 | InvalidScope,
51 |
52 | ///
53 | /// The authorization server encountered an unexpected condition that prevented it from fulfilling the request.
54 | /// (This error code is needed because a 500 Internal Server Error HTTP status code cannot be returned to
55 | /// the client via an HTTP redirect.)
56 | ///
57 | [Preserve]
58 | [EnumMember(Value = "server_error")]
59 | ServerError,
60 |
61 | ///
62 | /// The authorization server is currently unable to handle the request due to a temporary overloading or
63 | /// maintenance of the server. (This error code is needed because a 503 Service Unavailable HTTP status code
64 | /// cannot be returned to the client via an HTTP redirect.)
65 | ///
66 | [Preserve]
67 | [EnumMember(Value = "temporarily_unavailable")]
68 | TemporarilyUnavailable
69 | }
70 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AuthorizationCodeRequestException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Cdm.Authentication.OAuth2
4 | {
5 | public class AuthorizationCodeRequestException : Exception
6 | {
7 | public AuthorizationCodeRequestError error { get; }
8 |
9 | public AuthorizationCodeRequestException(AuthorizationCodeRequestError error)
10 | {
11 | this.error = error;
12 | }
13 |
14 | public AuthorizationCodeRequestException(AuthorizationCodeRequestError error, string message) : base(message)
15 | {
16 | this.error = error;
17 | }
18 |
19 | public AuthorizationCodeRequestException(AuthorizationCodeRequestError error, string message, Exception innerException)
20 | : base(message, innerException)
21 | {
22 | this.error = error;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/AuthorizationCodeResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 | using UnityEngine.Scripting;
3 |
4 | namespace Cdm.Authentication.OAuth2
5 | {
6 | ///
7 | /// If the resource owner grants the access request, the authorization server issues an authorization code and
8 | /// delivers it to the client by adding the following parameters to the query component of the redirection URI
9 | /// using the "application/x-www-form-urlencoded" format,
10 | /// per Appendix B.
11 | ///
12 | [Preserve]
13 | [DataContract]
14 | public class AuthorizationCodeResponse
15 | {
16 | ///
17 | /// Gets or sets the authorization code received from the authorization server.
18 | ///
19 | [Preserve]
20 | [DataMember(IsRequired = true, Name = "code")]
21 | public string code { get; set; }
22 |
23 | ///
24 | /// The exact value received from the client while making the authorization request as specified in
25 | /// .
26 | ///
27 | [Preserve]
28 | [DataMember(Name = "state")]
29 | public string state { get; set; }
30 | }
31 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/OAuth2/RefreshTokenRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 | using UnityEngine.Scripting;
3 |
4 | namespace Cdm.Authentication.OAuth2
5 | {
6 | ///
7 | /// OAuth 2.0 request to refresh an access token using a refresh token as specified in
8 | /// http://tools.ietf.org/html/rfc6749#section-6.
9 | ///
10 | [Preserve]
11 | [DataContract]
12 | public class RefreshTokenRequest
13 | {
14 | ///
15 | /// The grant type as 'refresh_token'.
16 | ///
17 | [Preserve]
18 | [DataMember(IsRequired = true, Name = "grant_type")]
19 | public string grantType => "refresh_token";
20 |
21 | ///
22 | /// REQUIRED. The refresh token issued to the client.
23 | ///
24 | [Preserve]
25 | [DataMember(IsRequired = true, Name = "refresh_token")]
26 | public string refreshToken { get; set; }
27 |
28 | ///
29 | /// OPTIONAL. The scope of the access request as described by Section 3.3. The requested scope MUST NOT
30 | /// include any scope not originally granted by the resource owner, and if omitted is treated as equal to
31 | /// the scope originally granted by the resource owner.
32 | ///
33 | [Preserve]
34 | [DataMember(Name = "scope")]
35 | public string scope { get; set; }
36 | }
37 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Utils/JsonHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.Specialized;
3 | using System.Linq;
4 | using Newtonsoft.Json;
5 | using Newtonsoft.Json.Linq;
6 |
7 | namespace Cdm.Authentication.Utils
8 | {
9 | public static class JsonHelper
10 | {
11 | public static Dictionary ToDictionary(object obj)
12 | {
13 | var dictionary = JObject.FromObject(obj).ToObject>();
14 |
15 | if (dictionary != null)
16 | {
17 | // Remove empty parameters.
18 | var keys = dictionary.Keys.Where(key => string.IsNullOrEmpty(dictionary[key])).ToArray();
19 | foreach (var key in keys)
20 | {
21 | dictionary.Remove(key);
22 | }
23 | }
24 |
25 | return dictionary;
26 | }
27 |
28 | public static T FromDictionary(Dictionary dictionary)
29 | {
30 | return JObject.FromObject(dictionary).ToObject();
31 | }
32 |
33 | public static bool TryGetFromDictionary(Dictionary dictionary, out T value)
34 | {
35 | try
36 | {
37 | value = FromDictionary(dictionary);
38 | return true;
39 | }
40 | catch (JsonSerializationException)
41 | {
42 | // ignored
43 | }
44 |
45 | value = default;
46 | return false;
47 | }
48 |
49 | public static bool TryGetFromNameValueCollection(NameValueCollection collection, out T value)
50 | {
51 | var dictionary = new Dictionary();
52 |
53 | foreach (string s in collection)
54 | {
55 | dictionary.Add(s, collection[s]);
56 | }
57 |
58 | return TryGetFromDictionary(dictionary, out value);
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/Utils/UrlBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using System.Web;
5 |
6 | namespace Cdm.Authentication.Utils
7 | {
8 | public class UrlBuilder
9 | {
10 | private readonly UriBuilder _uriBuilder;
11 | private readonly NameValueCollection _query;
12 |
13 | private UrlBuilder(string url)
14 | {
15 | _uriBuilder = new UriBuilder(url);
16 | _query = HttpUtility.ParseQueryString("");
17 | }
18 |
19 | public static UrlBuilder New(string url)
20 | {
21 | return new UrlBuilder(url);
22 | }
23 |
24 | public UrlBuilder SetQueryParameters(Dictionary parameters)
25 | {
26 | foreach (var p in parameters)
27 | {
28 | _query.Set(p.Key, p.Value);
29 | }
30 |
31 | return this;
32 | }
33 |
34 | public override string ToString()
35 | {
36 | _uriBuilder.Query = _query.ToString();
37 | return _uriBuilder.Uri.ToString();
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/Runtime/csc.rsp:
--------------------------------------------------------------------------------
1 | -r:System.Net.Http.dll
2 | -r:System.Web.dll
--------------------------------------------------------------------------------
/Src/com.cdm.authentication/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.cdm.authentication",
3 | "displayName": "Authentication for Unity",
4 | "version": "1.2.0",
5 | "unity": "2021.3",
6 | "author": "CDM Vision",
7 | "description": "Simple OAuth2 client for Unity.",
8 | "hideInEditor": false,
9 | "dependencies": {
10 | "com.unity.nuget.newtonsoft-json": "2.0.2"
11 | }
12 | }
--------------------------------------------------------------------------------