C++ Client Library Documentation
The VIIPER C++ client library provides a modern, header-only C++20 client library for interacting with VIIPER servers and controlling virtual devices.
The C++ client library features:
- Header-only: No separate compilation required, just include and use
- C++20: Uses concepts, designated initializers, std::optional, smart pointers
- Type-safe: Generated structs with constants and helper maps
- Callback-based output: Register lambdas for device feedback (LEDs, rumble)
- Thread-safe: Separate mutexes for send/recv operations
- Cross-platform: Windows (MSVC) and POSIX (GCC/Clang)
JSON Parser Required
The C++ client library requires a JSON library to be provided by the user. You must define VIIPER_JSON_INCLUDE, VIIPER_JSON_NAMESPACE, and VIIPER_JSON_TYPE before including the client library headers.
Recommended: nlohmann/json - a header-only JSON library that can be easily integrated.
OpenSSL Required
The C++ client library requires OpenSSL for encrypted connections to VIIPER servers with authentication enabled.
Ensure OpenSSL is installed and linked in your project.
License
The C++ client library is licensed under the MIT License, providing maximum flexibility for integration into your projects.
The core VIIPER server remains under its original license.
Installation
1. Header-Only Integration
Copy the clients/cpp/include/viiper directory to your project's include path:
Or add it as an include directory in your build system.
2. CMake Integration
# Add viiper include directory
target_include_directories(your_target PRIVATE path/to/clients/cpp/include)
# Also ensure nlohmann/json is available
# Option A: FetchContent
include(FetchContent)
FetchContent_Declare(json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(json)
target_link_libraries(your_target PRIVATE nlohmann_json::nlohmann_json)
# Option B: Find package (if installed system-wide)
find_package(nlohmann_json REQUIRED)
target_link_libraries(your_target PRIVATE nlohmann_json::nlohmann_json)
The client library will be generated in clients/cpp/include/viiper/.
JSON Parser Configuration
Before including the VIIPER client library, you must configure a JSON parser. The client library is designed to work with any JSON library that provides a compatible interface.
Using nlohmann/json (Recommended)
// Define these BEFORE including viiper headers
#define VIIPER_JSON_INCLUDE <nlohmann/json.hpp>
#define VIIPER_JSON_NAMESPACE nlohmann
#define VIIPER_JSON_TYPE json
#include <viiper/viiper.hpp>
Using a Custom JSON Library
Your JSON type must support:
parse(const std::string&)→ JsonTypedump()→ std::stringoperator[](const std::string&)→ JsonTypecontains(const std::string&)→ boolis_number(),is_string(),is_array(),is_object()→ boolget<T>()→ Tsize()→ std::size_t (for arrays)
Example with a custom library:
#define VIIPER_JSON_INCLUDE "my_json_lib.hpp"
#define VIIPER_JSON_NAMESPACE myjson
#define VIIPER_JSON_TYPE JsonValue
#include <viiper/viiper.hpp>
Example
#define VIIPER_JSON_INCLUDE <nlohmann/json.hpp>
#define VIIPER_JSON_NAMESPACE nlohmann
#define VIIPER_JSON_TYPE json
#include <viiper/viiper.hpp>
#include <iostream>
int main() {
// Create new Viiper client
viiper::ViiperClient client("localhost", 3242);
// Find or create a bus
auto buses_result = client.buslist();
if (buses_result.is_error()) {
std::cerr << "BusList error: " << buses_result.error().to_string() << "\n";
return 1;
}
std::uint32_t bus_id;
if (buses_result.value().buses.empty()) {
auto create_result = client.buscreate(std::nullopt); // Auto-assign ID
if (create_result.is_error()) {
std::cerr << "BusCreate error: " << create_result.error().to_string() << "\n";
return 1;
}
bus_id = create_result.value().busid;
} else {
bus_id = buses_result.value().buses[0];
}
// Add device
auto device_result = client.busdeviceadd(bus_id, {.type = "keyboard"});
if (device_result.is_error()) {
std::cerr << "AddDevice error: " << device_result.error().to_string() << "\n";
return 1;
}
auto device_info = std::move(device_result.value());
// Connect to device stream
auto stream_result = client.connectDevice(device_info.busid, device_info.devid);
if (stream_result.is_error()) {
std::cerr << "Connect error: " << stream_result.error().to_string() << "\n";
return 1;
}
auto stream = std::move(stream_result.value());
std::cout << "Connected to device " << device_info.devid
<< " on bus " << device_info.busid << "\n";
// Send keyboard input
viiper::keyboard::Input input = {
.modifiers = viiper::keyboard::ModLeftShift,
.keys = {viiper::keyboard::KeyH},
};
stream->send(input);
// Cleanup
client.busdeviceremove(device_info.busid, device_info.devid);
return 0;
}
Device Control/Feedback
Creating a Device + Control/Feedback Stream
The simplest way to add a device and connect:
Or manually add and connect:
auto device_result = client.busdeviceadd(bus_id, {.type = "keyboard"});
auto device_info = device_result.value();
auto stream_result = client.connectDevice(device_info.busid, device_info.devid);
auto stream = std::move(stream_result.value());
Sending Input
Device input is sent using generated structs:
Keyboard:
viiper::keyboard::Input input = {
.modifiers = viiper::keyboard::ModLeftShift,
.keys = {viiper::keyboard::KeyH, viiper::keyboard::KeyE},
};
stream->send(input);
Mouse:
viiper::mouse::Input input = {
.buttons = viiper::mouse::ButtonLeft,
.x = 10,
.y = -5,
.wheel = 0,
};
stream->send(input);
Xbox360 Controller:
viiper::xbox360::Input input = {
.buttons = viiper::xbox360::ButtonA,
.lt = 255, // Left trigger (0-255)
.rt = 0, // Right trigger (0-255)
.lx = -32768, // Left stick X (-32768 to 32767)
.ly = 32767, // Left stick Y
.rx = 0, // Right stick X
.ry = 0, // Right stick Y
};
stream->send(input);
Receiving Feedback
For devices that send feedback (rumble, LEDs), register a callback:
Keyboard LEDs:
stream->on_output(viiper::keyboard::OUTPUT_SIZE, [](const std::uint8_t* data, std::size_t len) {
if (len < viiper::keyboard::OUTPUT_SIZE) return;
auto result = viiper::keyboard::Output::from_bytes(data, len);
if (result.is_error()) return;
auto& leds = result.value();
bool num_lock = (leds.leds & viiper::keyboard::LEDNumLock) != 0;
bool caps_lock = (leds.leds & viiper::keyboard::LEDCapsLock) != 0;
std::cout << "LEDs: Num=" << num_lock << " Caps=" << caps_lock << "\n";
});
Xbox360 Rumble:
stream->on_output(viiper::xbox360::OUTPUT_SIZE, [](const std::uint8_t* data, std::size_t len) {
if (len < viiper::xbox360::OUTPUT_SIZE) return;
auto result = viiper::xbox360::Output::from_bytes(data, len);
if (result.is_error()) return;
auto& rumble = result.value();
std::cout << "Rumble: Left=" << static_cast<int>(rumble.left)
<< ", Right=" << static_cast<int>(rumble.right) << "\n";
});
Event Handlers
// Called when the server disconnects the device
stream->on_disconnect([]() {
std::cerr << "Device disconnected by server\n";
});
// Called on stream errors
stream->on_error([](const viiper::Error& err) {
std::cerr << "Stream error: " << err.to_string() << "\n";
});
Stopping a Device
The device is also automatically stopped when the ViiperDevice is destroyed.
The VIIPER server automatically removes the device when the stream is closed after a short timeout.
Generated Constants and Maps
The C++ client library automatically generates constants and helper maps for each device type.
Keyboard Constants
Key Codes:
auto key = viiper::keyboard::KeyA; // 0x04
auto f1 = viiper::keyboard::KeyF1; // 0x3A
auto enter = viiper::keyboard::KeyEnter; // 0x28
Modifier Flags:
LED Flags:
bool num_lock = (leds & viiper::keyboard::LEDNumLock) != 0;
bool caps_lock = (leds & viiper::keyboard::LEDCapsLock) != 0;
Helper Maps
The client library generates useful lookup maps for working with keyboard input:
CHAR_TO_KEY Map - Convert ASCII characters to key codes:
auto it = viiper::keyboard::CHAR_TO_KEY.find(static_cast<std::uint8_t>('a'));
if (it != viiper::keyboard::CHAR_TO_KEY.end()) {
std::uint8_t key = it->second; // KeyA
}
KEY_NAME Array - Get human-readable key names:
for (const auto& [key, name] : viiper::keyboard::KEY_NAME) {
if (key == viiper::keyboard::KeyF1) {
std::cout << "Key name: " << name << "\n"; // "F1"
break;
}
}
SHIFT_CHARS Set - Check if a character requires shift:
Xbox360 Constants
Button Flags:
Error Handling
All API methods return Result<T>, which is either a value or an error:
auto result = client.buslist();
if (result.is_error()) {
std::cerr << "Error: " << result.error().to_string() << "\n";
return 1;
}
auto buses = result.value();
Using the value directly (throws on error):
Resource Management
ViiperDevice is managed via std::unique_ptr and automatically cleans up:
{
auto stream = client.connectDevice(bus_id, device_id).value();
// ... use stream ...
} // stream->stop() called automatically
Examples
Full working examples are available in the repository:
-
Virtual Keyboard:
examples/cpp/virtual_keyboard.cpp- Types "Hello!" every 5 seconds using generated maps
- Displays LED feedback in console
-
Virtual Mouse:
examples/cpp/virtual_mouse.cpp- Moves cursor in a circle pattern
- Demonstrates button clicks
-
Virtual Xbox360 Controller:
examples/cpp/virtual_x360_pad.cpp- Cycles through buttons A, B, X, Y
- Handles rumble feedback
Building Examples
cd examples/cpp
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release
Running Examples
Troubleshooting
Error: VIIPER_JSON_INCLUDE must be defined
You must define the JSON macros before including any VIIPER headers:
#define VIIPER_JSON_INCLUDE <nlohmann/json.hpp>
#define VIIPER_JSON_NAMESPACE nlohmann
#define VIIPER_JSON_TYPE json
#include <viiper/viiper.hpp> // Include AFTER the defines
Linker errors on Windows
Ensure Winsock2 is linked. If not using the auto-link pragma, add:
Connection refused
Verify the VIIPER server is running:
See Also
- Generator Documentation: How generated client libraries work
- Rust Client Library Documentation: Rust client library with sync/async support
- C# Client Library Documentation: .NET client library
- TypeScript Client Library Documentation: Node.js client library
- API Overview: Management API reference
- Device Documentation: Wire formats and device-specific details