A high-performance C++ implementation of the emotiv_lsl server. It uses hidapi, liblsl, and tiny-AES-c to directly interface with Emotiv headsets via USB, decrypt the data streams, and publish them to LSL, without the overhead of Python.
- Modern CMake (3.28+) with clean, documented structure
- Automatic dependency management via CMake FetchContent (hidapi, liblsl, tiny-AES-c)
- CLI and GUI separation with shared core library
- Qt6 for the GUI (with Qt5 intentionally dropped for simplicity)
- Cross-platform: Linux, macOS, Windows
- macOS code signing with entitlements for network capabilities
- Automated CI/CD via GitHub Actions
emotiv-lsl-cpp/
├── CMakeLists.txt # Root build configuration
├── app.entitlements # macOS network capabilities
├── LSLTemplate.cfg # Default configuration file
├── src/
│ ├── emotiv/ # Emotiv LSL application (emotiv_lsl)
│ │ ├── config.h # Sampling rate constants
│ │ ├── emotiv_base.h/cpp # Base class: HID access, LSL outlets, decryption
│ │ ├── emotiv_epoc_x.h/cpp # Emotiv EPOC X headset implementation
│ │ ├── main.cpp # Entry point
│ │ └── CMakeLists.txt
│ ├── core/ # Qt-independent core library (LSL template)
│ │ ├── include/lsltemplate/
│ │ │ ├── Device.hpp # Device interface
│ │ │ ├── LSLOutlet.hpp # LSL outlet wrapper
│ │ │ ├── Config.hpp # Configuration management
│ │ │ └── StreamThread.hpp # Background streaming
│ │ └── src/
│ ├── cli/ # Command-line application (LSL template)
│ │ └── main.cpp
│ └── gui/ # Qt6 GUI application (LSL template)
│ ├── MainWindow.hpp/cpp
│ ├── MainWindow.ui
│ └── main.cpp
├── scripts/
│ └── sign_and_notarize.sh # macOS signing script
└── .github/workflows/
└── build.yml # CI/CD workflow
- CMake 3.28 or later
- A C++17 compatible compiler (MSVC, GCC, or Clang)
- Git (for FetchContent to download dependencies automatically)
- Qt6.8 (only required for the GUI build; pass
-DLSLTEMPLATE_BUILD_GUI=OFFto skip)
Ubuntu's default repositories don't include Qt 6.8. Use aqtinstall to install it:
# Install aqtinstall
pip install aqtinstall
# Install Qt 6.8 (adjust version as needed)
aqt install-qt linux desktop 6.8.3 gcc_64 -O ~/Qt
# Set environment for CMake to find Qt
export CMAKE_PREFIX_PATH=~/Qt/6.8.3/gcc_64
# Install system dependencies
sudo apt-get install libgl1-mesa-dev libxkbcommon-dev libxcb-cursor0To run the GUI application, ensure Qt libraries are in your library path:
export LD_LIBRARY_PATH=~/Qt/6.8.3/gcc_64/lib:$LD_LIBRARY_PATHAlternatively, build CLI-only with -DLSLTEMPLATE_BUILD_GUI=OFF to avoid the Qt dependency.
# Clone and build
git clone https://github.com/CommanderPho/emotiv-lsl-cpp.git
cd emotiv-lsl-cpp
# Configure — all dependencies (hidapi, liblsl, tiny-AES-c) are fetched automatically
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DLSLTEMPLATE_BUILD_GUI=OFF
# Build
cmake --build build --config Release --parallel
# Install
cmake --install build --prefix build/install| Option | Default | Description |
|---|---|---|
EMOTIVLSL_BUILD_EMOTIV |
ON | Build the Emotiv LSL application |
LSLTEMPLATE_BUILD_GUI |
ON | Build the GUI application (requires Qt6) |
LSLTEMPLATE_BUILD_CLI |
ON | Build the CLI application |
LSL_FETCH_REF |
v1.17.4 | liblsl git ref to fetch (tag, branch, or commit) |
LSL_INSTALL_ROOT |
- | Path to a pre-installed liblsl |
cmake -S . -B build -DLSLTEMPLATE_BUILD_GUI=OFF -DLSLTEMPLATE_BUILD_CLI=OFF
# Build
cmake --build build --config Release --parallel
# Install
cmake --install build --prefix build/install
The build system searches for liblsl in this order:
- LSL_INSTALL_ROOT - Explicit installation path
- FetchContent - Automatic download from GitHub
For parallel development with liblsl:
cmake -S . -B build -DLSL_SOURCE_DIR=/path/to/liblslConnect your Emotiv headset dongle before running. The server will automatically locate the device, set up LSL outlets, and begin streaming EEG, motion, and electrode quality data.
Windows (PowerShell):
# If you used the preset (recommended): self-contained install
.\build\install\emotiv_lsl.exe
# If you built manually without install
.\build\Release\emotiv_lsl.exeLinux / macOS:
# If you used the preset (recommended): self-contained install
./build/install/emotiv_lsl
# If you built manually without install
./build/emotiv_lslYou can use standard LSL tools like bsl_stream_viewer to visualize the incoming data streams.
--record <path>— Writes an XDF at the given path. This always wins over config-based paths.-c/--config <file>— Optional explicit App-LabRecorder-style INI (same keys as upstreamLabRecorder.cfg). If omitted,emotiv_lslsearches forLabRecorder.cfgin the same order as App-LabRecorder’sfind_config_file: current working directory, then each standard config/data location used forLabRecorderon your OS (on Windows this includes%LOCALAPPDATA%\LabRecorder,%PROGRAMDATA%\LabRecorder,%APPDATA%\LabRecorder, in that order after cwd), then the directory containingemotiv_lsl. It readsStudyRoot,PathTemplate, orStorageLocationwith the same mutual-exclusion rules as LabRecorder. Wildcard expansion followsMainWindow::replaceFilename: replacements run in that order (%b,%p,%s,%a,%m, then%nor%r, then%datetime,%date,%time,%hostname).%date,%time, and%datetimeuse UTC, with the same compact formats as Qt (yyyy-MM-ddTHHmmss.zzzZfor%datetime). There is no separate%datetime_eegtoken in LabRecorder:%datetimereplaces the leading segment of%datetime_eeg, leaving a literal_eegin the filename (same as QtQString::replace). Use%nfor the run counter when aPathTemplateorStorageLocationtemplate is present; use%ronly for the built-in default BIDS template when neither template is specified (onlyStudyRootor defaults).SessionBlocks(comma-quoted list and/or Qt-styleSessionBlocks\1=… indexed entries) sets the default%bblock label to the first block. If the template includes%nor%r, the first unused counter value is chosen (same scan as LabRecorder on load). If there is no counter in the template and the target file already exists, the existing file is renamed tobasename_oldN.extbefore recording, matching LabRecorder’s start behavior.- Fallback — If no usable LabRecorder path is found,
emotiv_lslmay useLSLTemplate.cfgand its optional[Recording]block (enabled=1,directory,filename_templatewith{stream_name},{basename},{date},{time}) as before.
For deterministic tests or tooling, set EMOTIV_LABREC_FIXED_UNIX_MS to epoch milliseconds so placeholder times are fixed; the lab_recorder_cfg_smoke target exercises path logic without a headset.
Example: place a valid LabRecorder.cfg next to emotiv_lsl.exe or under %LOCALAPPDATA%\LabRecorder\, or pass -c C:\path\LabRecorder.cfg.
./LSLTemplate # Use default config
./LSLTemplate myconfig.cfg # Use custom config./LSLTemplateCLI --help
./LSLTemplateCLI --name MyStream --rate 256 --channels 8
./LSLTemplateCLI --config myconfig.cfgFor local development, the build automatically applies ad-hoc signing with network entitlements. This allows the app to use LSL's multicast discovery.
For distribution, use the signing script:
# Sign only
./scripts/sign_and_notarize.sh build/install/LSLTemplate.app
# Sign and notarize
export APPLE_CODE_SIGN_IDENTITY_APP="Developer ID Application: Your Name"
export APPLE_NOTARIZE_KEYCHAIN_PROFILE="your-profile"
./scripts/sign_and_notarize.sh build/install/LSLTemplate.app --notarizeFor automated signing and notarization, the workflow expects these secrets from the labstreaminglayer organization:
| Secret | Description |
|---|---|
PROD_MACOS_CERTIFICATE |
Base64-encoded Developer ID Application certificate (.p12) |
PROD_MACOS_CERTIFICATE_PWD |
Certificate password |
PROD_MACOS_CI_KEYCHAIN_PWD |
Password for temporary CI keychain |
PROD_MACOS_NOTARIZATION_APPLE_ID |
Apple ID email for notarization |
PROD_MACOS_NOTARIZATION_PWD |
App-specific password for Apple ID |
PROD_MACOS_NOTARIZATION_TEAM_ID |
Apple Developer Team ID |
Important: These organization secrets must be shared with your repository. In GitHub:
- Go to Organization Settings → Secrets and variables → Actions
- For each secret, click to edit and under "Repository access" select the repositories that need access
From the repo root, one command can configure, build, install, and start the server. Dependencies (liblsl, hidapi, tiny-AES-c) are fetched automatically. Requires CMake 3.25+ and PowerShell Core (pwsh) for the run script.
Windows (PowerShell):
# Build self-contained install only
cmake --workflow --preset emotiv
cmake --install build --config Release
# Build and start collecting data (single command)
pwsh ./run.ps1Linux / macOS:
# Build self-contained install only
cmake --workflow --preset emotiv
cmake --install build --config Release
# Build and start collecting data (single command)
pwsh ./run.ps1The preset builds only emotiv_lsl (no Qt/CLI). The executable and bundled LSL library end up in build/install/.