C SDK Documentation
The VIIPER C SDK provides a lightweight, dependency-free client library for interacting with VIIPER servers and controlling virtual devices.
Overview
The C SDK features:
- Device-agnostic streaming API: Uniform interface for all device types
- Zero dependencies: Pure C99, no external libraries required
- Cross-platform: Windows (MSVC) and POSIX (GCC/Clang)
- Type-safe: Generated headers with packed structs and constants
- Thread-safe: Recommended: one
viiper_client_tper thread
License
The C SDK is licensed under the MIT License, providing maximum flexibility for integration into your projects.
The core VIIPER server remains under its original license.
Installation
Building from Source
The C SDK is generated from the VIIPER server codebase:
Build the SDK:
cd ../clients/c
cmake -B build -G "Visual Studio 17 2022" # Windows
cmake -B build # POSIX
cmake --build build --config Release
Linking to Your Project
CMake:
# Add viiper SDK
add_subdirectory(path/to/clients/c)
target_link_libraries(your_target PRIVATE viiper)
# Copy DLL on Windows (post-build)
if(WIN32)
add_custom_command(TARGET your_target POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:viiper>
$<TARGET_FILE_DIR:your_target>
)
endif()
Manual:
- Include:
clients/c/include/viiper/viiper.h - Link:
clients/c/build/Release/viiper.lib(Windows) orlibviiper.a(POSIX) - Runtime: Copy
viiper.dllnext to your executable (Windows)
Quick Start
#include <viiper/viiper.h>
#include <viiper/viiper_keyboard.h>
#include <stdio.h>
int main(void) {
// Connect to management API
viiper_client_t* client = NULL;
int err = viiper_client_create("127.0.0.1", 3242, &client);
if (err != 0) {
fprintf(stderr, "Failed to connect: %s\n", viiper_strerror(err));
return 1;
}
// Create or find a bus
viiper_bus_list_t buses = {0};
err = viiper_bus_list(client, &buses);
uint32_t bus_id = (buses.count > 0) ? buses.buses[0] : 0;
if (bus_id == 0) {
viiper_bus_create_response_t resp = {0};
err = viiper_bus_create(client, 1, &resp);
bus_id = resp.bus_id;
}
// Add device
viiper_device_add_response_t dev_resp = {0};
err = viiper_device_add(client, bus_id, "keyboard", &dev_resp);
// Open device stream
viiper_device_t* device = NULL;
err = viiper_device_create(client, bus_id, dev_resp.id, &device);
// Send keyboard input
viiper_keyboard_input_t input = {
.modifiers = 0,
.count = 1
};
uint8_t keys[] = {VIIPER_KEYBOARD_KEYA};
input.keys = keys;
input.keys_count = 1;
err = viiper_device_send(device, &input, sizeof(input.modifiers) + sizeof(input.count) + input.keys_count);
// Cleanup
viiper_device_close(device);
viiper_device_remove(client, bus_id, dev_resp.id);
viiper_client_destroy(client);
return 0;
}
Device Stream API
Creating a Device Stream
viiper_device_t* device = NULL;
int err = viiper_device_create(client, bus_id, device_id, &device);
if (err != 0) {
fprintf(stderr, "Failed to open device stream: %s\n", viiper_strerror(err));
}
Sending Input
viiper_mouse_input_t input = {
.buttons = VIIPER_MOUSE_BTN_LEFT,
.dx = 10,
.dy = -5,
.wheel = 0,
.pan = 0
};
int err = viiper_device_send(device, &input, sizeof(input));
Receiving Output (Callbacks)
void on_led_update(void* user_data, const void* data, size_t len) {
if (len < 1) return;
uint8_t leds = ((uint8_t*)data)[0];
printf("LEDs: NumLock=%d CapsLock=%d ScrollLock=%d\n",
!!(leds & VIIPER_KEYBOARD_LEDNUMLOCK),
!!(leds & VIIPER_KEYBOARD_LEDCAPSLOCK),
!!(leds & VIIPER_KEYBOARD_LEDSCROLLLOCK));
}
viiper_device_on_output(device, on_led_update, NULL);
Closing a Stream
Device-Specific Notes
Each device type has specific packet formats, constants, and wire protocols. For wire format details and usage patterns, see the Devices section of the documentation.
The C SDK provides generated structs and constants in device-specific headers (e.g., viiper_keyboard.h, viiper_mouse.h, viiper_xbox360.h).
Struct Packing
All device I/O structs use #pragma pack(1) to ensure wire compatibility (no padding).
#pragma pack(push, 1)
typedef struct {
uint8_t buttons;
int8_t dx;
// ...
} viiper_mouse_input_t;
#pragma pack(pop)
Important: Always ensure your compiler respects packing directives. MSVC and GCC/Clang handle this correctly by default.
Troubleshooting
Missing DLL on Windows
Symptom: Application crashes immediately with "viiper.dll not found"
Solution: Copy viiper.dll to the same directory as your executable:
add_custom_command(TARGET your_target POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:viiper>
$<TARGET_FILE_DIR:your_target>
)
Repeated Keys Not Working
Symptom: Typing "Hello" outputs "Helo" (missing duplicate letter)
Solution: Add sufficient delays between key press, release, and next action:
press_and_release(dev, VIIPER_KEYBOARD_KEYL, 0);
Sleep(100);
press_and_release(dev, VIIPER_KEYBOARD_KEYL, 0);
Struct Padding Issues
Symptom: Device input is corrupted or "spazzing"
Solution: Verify #pragma pack(1) is applied to device structs. All generated headers include this by default.
Examples
Full working examples are available in the repository:
- Virtual Keyboard:
examples/c/virtual_keyboard/main.c - Types "Hello!" every 5 seconds
-
Reads LED state (NumLock, CapsLock, ScrollLock)
-
Virtual Xbox360 Controller:
examples/c/virtual_x360_pad/main.c - Simulates button presses and stick movements
- Receives rumble feedback
Build and run:
cd examples/c
cmake -B build -G "Visual Studio 17 2022"
cmake --build build --config Release
./build/Release/virtual_keyboard.exe 127.0.0.1:3242
See Also
- Generator Documentation: How the C SDK is generated
- Go Client Documentation: Reference implementation
- API Overview: Management API reference