Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 132 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ jobs:
if: matrix.kind != 'msvc'
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ needs.setup_release.outputs.release_tag }}
BUILD_VERSION: ${{ needs.setup_release.outputs.release_version }}
CC: ${{ matrix.cc }}
COMMIT: ${{ needs.setup_release.outputs.release_commit }}
CXX: ${{ matrix.cxx }}
Expand All @@ -214,7 +214,7 @@ jobs:
if: matrix.kind == 'msvc'
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ needs.setup_release.outputs.release_tag }}
BUILD_VERSION: ${{ needs.setup_release.outputs.release_version }}
COMMIT: ${{ needs.setup_release.outputs.release_commit }}
run: |
cmake `
Expand Down Expand Up @@ -358,6 +358,127 @@ jobs:
path: cmake-build-ci/reports
if-no-files-found: error

windows_driver:
name: Windows Driver Installer
needs: setup_release
permissions:
contents: read
runs-on: windows-2022
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
submodules: recursive

- name: Setup dotnet
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
with:
dotnet-version: '10.x'

- name: Configure Windows driver package
shell: pwsh
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ needs.setup_release.outputs.release_version }}
COMMIT: ${{ needs.setup_release.outputs.release_commit }}
run: |
$certificatePath = Join-Path $env:GITHUB_WORKSPACE "cmake-build-driver\certificates\libvirtualhid-ci-test.cer"
cmake `
-DBUILD_DOCS=OFF `
-DBUILD_EXAMPLES=OFF `
-DBUILD_TESTS=OFF `
-DLIBVIRTUALHID_BUILD_WINDOWS_DRIVER=ON `
-DLIBVIRTUALHID_ENABLE_PACKAGING=ON `
"-DLIBVIRTUALHID_DRIVER_TEST_CERTIFICATE=$certificatePath" `
-A x64 `
-B cmake-build-driver `
-G "Visual Studio 17 2022" `
-S .

- name: Build Windows driver package
shell: pwsh
run: cmake --build cmake-build-driver --config Release --target libvirtualhid_windows_catalog --parallel 2

- name: Validate Azure signing configuration
if: >-
github.event_name == 'push' &&
vars.AZURE_SIGNING_ACCOUNT == ''
shell: pwsh
run: throw "Push builds must use Azure Trusted Signing for the Windows driver package."

- name: Sign Windows driver package with local test certificate
if: github.event_name == 'pull_request'
shell: pwsh
run: |
$packagePath = Join-Path `
$env:GITHUB_WORKSPACE `
"cmake-build-driver\src\platform\windows\driver\package\Release"
$certificatePath = Join-Path `
$env:GITHUB_WORKSPACE `
"cmake-build-driver\certificates\libvirtualhid-ci-test.cer"
.\scripts\windows\sign-driver-package.ps1 `
-PackagePath $packagePath `
-CertificatePath $certificatePath

- name: Sign Windows driver package with Azure Trusted Signing
if: >-
github.event_name == 'push' &&
vars.AZURE_SIGNING_ACCOUNT != ''
uses: azure/trusted-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2.0.0
with:
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
certificate-profile-name: ${{ vars.AZURE_SIGNING_CERT_PROFILE }}
endpoint: ${{ vars.AZURE_SIGNING_ENDPOINT }}
files: cmake-build-driver/src/platform/windows/driver/package/Release/libvirtualhid.cat
signing-account-name: ${{ vars.AZURE_SIGNING_ACCOUNT }}

- name: Package Windows driver installer
shell: pwsh
run: |
Push-Location .\cmake-build-driver
cpack -G WIX
$packageExitCode = $LASTEXITCODE
Pop-Location
if ($packageExitCode -ne 0) {
exit $packageExitCode
}
New-Item -ItemType Directory -Force -Path artifacts | Out-Null
Copy-Item `
-LiteralPath .\cmake-build-driver\cpack_artifacts\libvirtualhid.msi `
-Destination .\artifacts\libvirtualhid-Windows-Driver-installer.msi

- name: Sign Windows driver installer with Azure Trusted Signing
if: >-
github.event_name == 'push' &&
vars.AZURE_SIGNING_ACCOUNT != ''
uses: azure/trusted-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2.0.0
with:
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
certificate-profile-name: ${{ vars.AZURE_SIGNING_CERT_PROFILE }}
endpoint: ${{ vars.AZURE_SIGNING_ENDPOINT }}
files-folder: artifacts
files-folder-filter: msi
files-folder-recurse: false
signing-account-name: ${{ vars.AZURE_SIGNING_ACCOUNT }}

- name: Debug wix
if: always()
shell: pwsh
run: |
Get-Content .\cmake-build-driver\cpack_artifacts\_CPack_Packages\win64\WIX\wix.log `
-ErrorAction SilentlyContinue

- name: Upload Windows driver installer artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows-driver-installer
path: artifacts
if-no-files-found: error

codecov:
name: Codecov-${{ matrix.flag }}
if: >-
Expand Down Expand Up @@ -430,9 +551,11 @@ jobs:
always() &&
needs.setup_release.outputs.publish_release == 'true' &&
needs.build.result == 'success' &&
needs.windows_driver.result == 'success' &&
startsWith(github.repository, 'LizardByte/')
needs:
- build
- windows_driver
- setup_release
permissions:
contents: read
Expand Down Expand Up @@ -468,6 +591,12 @@ jobs:
name: install-Windows-MSVC
path: install-Windows-MSVC

- name: Download Windows driver installer artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: windows-driver-installer
path: windows-driver-installer

- name: Package install artifacts
run: |
mkdir -p artifacts
Expand All @@ -476,6 +605,7 @@ jobs:
"artifacts/libvirtualhid-${{ needs.setup_release.outputs.release_tag }}-${name}.zip" \
"install-${name}"
done
cp windows-driver-installer/* artifacts/

- name: Create/Update GitHub Release
if: needs.setup_release.outputs.publish_release == 'true'
Expand Down
13 changes: 13 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@ endif()

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_version.cmake")

#
# Project optional configuration
#
option(BUILD_DOCS "Build documentation" ${LIBVIRTUALHID_IS_TOP_LEVEL})
option(BUILD_TESTS "Build tests" ${LIBVIRTUALHID_IS_TOP_LEVEL})
option(BUILD_EXAMPLES "Build examples" ${LIBVIRTUALHID_IS_TOP_LEVEL})
option(LIBVIRTUALHID_ENABLE_XTEST "Enable X11/XTest keyboard and mouse fallback on Linux" ON)
option(LIBVIRTUALHID_BUILD_WINDOWS_DRIVER "Build the Windows UMDF2 driver package with the WDK/MSVC toolchain" OFF)
option(LIBVIRTUALHID_ENABLE_PACKAGING "Enable CPack package metadata" ${LIBVIRTUALHID_IS_TOP_LEVEL})
option(LIBVIRTUALHID_WARNINGS_AS_ERRORS "Treat libvirtualhid warnings as errors" ${LIBVIRTUALHID_IS_TOP_LEVEL})

set(CMAKE_COLOR_MAKEFILE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
Expand Down Expand Up @@ -85,6 +90,10 @@ add_subdirectory(src)
# Examples, tests, and docs are top-level only
#
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
if(WIN32 AND LIBVIRTUALHID_BUILD_WINDOWS_DRIVER)
add_subdirectory(src/platform/windows/driver)
endif()

if(BUILD_DOCS)
add_subdirectory(third-party/doxyconfig docs)
endif()
Expand Down Expand Up @@ -118,3 +127,7 @@ install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/libvirtualhid-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/libvirtualhid-config-version.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/libvirtualhid")

if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND LIBVIRTUALHID_ENABLE_PACKAGING)
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/packaging/common.cmake")
endif()
52 changes: 47 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,47 @@ MSBuild, or EWDK. The boundary between the library and driver should therefore
be compiler-neutral: prefer a stable C ABI, named pipe, device interface IOCTL,
or similar control channel over passing C++ STL types across that boundary.

The current Windows backend selects a UMDF control-channel implementation for
`BackendKind::platform_default`. It probes `\\.\LibVirtualHid`, reports
`requires_installed_driver = true`, and only advertises gamepad/output-report
support when the driver package is installed and the control device can be
opened. The client library stays buildable with MSVC and MinGW/UCRT64 because
the backend talks to the driver through fixed-size C protocol structures and
Win32 `DeviceIoControl` calls. The default control device path can be overridden
for diagnostics with `LIBVIRTUALHID_WINDOWS_CONTROL_DEVICE`.

Build the UMDF package separately with the Microsoft driver toolchain:

```powershell
cmake -S . -B cmake-build-windows-driver -G "Visual Studio 17 2022" -A x64 `
-DLIBVIRTUALHID_BUILD_WINDOWS_DRIVER=ON -DLIBVIRTUALHID_ENABLE_PACKAGING=ON `
-DBUILD_TESTS=OFF -DBUILD_EXAMPLES=OFF
cmake --build cmake-build-windows-driver --config Release --target libvirtualhid_umdf
cmake --build cmake-build-windows-driver --config Release --target libvirtualhid_windows_catalog
cpack -G WIX --config .\cmake-build-windows-driver\CPackConfig.cmake
```

Developer install/uninstall helpers live under `scripts/windows`:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\windows\install-driver.ps1 `
-InfPath .\cmake-build-windows-driver\src\platform\windows\driver\package\Release\libvirtualhid.inf
powershell -ExecutionPolicy Bypass -File .\scripts\windows\uninstall-driver.ps1 `
-Force -RemoveCertificateSubject "CN=libvirtualhid CI Test Driver Signing"
```

The helper stages the INF with `pnputil` and uses `devcon.exe` when available
to create the `ROOT\LIBVIRTUALHID` development device.

Windows driver packages require a signed catalog for normal installation. Pull
request builds generate a short-lived self-signed test certificate, sign
`libvirtualhid.cat`, bundle the public `.cer` into the WiX installer, and import
that certificate into the local machine root and trusted-publisher stores during
install. The uninstall helper removes certificates matching
`CN=libvirtualhid CI Test Driver Signing`. Push/release builds must use Azure
Trusted Signing for the catalog and generated MSI, matching Sunshine's Windows
signing model, and must not ship the local PR test certificate.

### Linux

Linux should compile directly into the consuming project and use standard kernel
Expand Down Expand Up @@ -309,9 +350,9 @@ The intended project layout is:
src/include/libvirtualhid/ Public C++ headers
src/core/ Shared profile, descriptor, and report logic
src/platform/windows/ Windows client backend and UMDF control channel
src/platform/windows/driver/ Windows UMDF2 driver package sources
src/platform/linux/ Linux uhid/uinput backend
src/platform/macos/ Future macOS backend
drivers/windows/ UMDF2 driver package sources
profiles/ Built-in gamepad profiles
examples/ Minimal consumers and platform smoke tests
tests/ Unit and integration tests
Expand Down Expand Up @@ -389,13 +430,14 @@ third-party/googletest/ GoogleTest submodule

### Phase 3: Windows MVP

- [ ] Build a UMDF2 HID minidriver package with CMake/WDK integration.
- [ ] Implement the Windows backend and control channel between the C++ library and
- [x] Add CMake/WDK integration for the UMDF2 driver package.
- [x] Implement the Windows backend and control channel between the C++ library and
the UMDF driver.
- [x] Keep the client library buildable with MSVC and MinGW/UCRT64. Keep the driver
package on the Microsoft WDK toolchain.
- [ ] Add install/uninstall tooling for developer workflows.
- [ ] Support hot-plug, multi-controller instances, and output report callbacks.
- [x] Add install/uninstall tooling for developer workflows.
- [x] Support backend hot-plug, multi-controller instances, and output report callbacks
through the Windows control protocol.
- [ ] Validate visibility through DirectInput, XInput where applicable, SDL/HIDAPI,
Windows.Gaming.Input/GameInput, and browser Gamepad API.

Expand Down
39 changes: 39 additions & 0 deletions cmake/build_version.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Set build variables if env variables are defined.
if(DEFINED ENV{BRANCH})
set(GITHUB_BRANCH "$ENV{BRANCH}")
endif()
if(DEFINED ENV{BUILD_VERSION}) # cmake-lint: disable=W0106
set(BUILD_VERSION "$ENV{BUILD_VERSION}")
endif()
if(DEFINED ENV{CLONE_URL})
set(GITHUB_CLONE_URL "$ENV{CLONE_URL}")
endif()
if(DEFINED ENV{COMMIT})
set(GITHUB_COMMIT "$ENV{COMMIT}")
endif()
if(DEFINED ENV{TAG})
set(GITHUB_TAG "$ENV{TAG}")
endif()

if(DEFINED ENV{BUILD_VERSION} AND NOT "$ENV{BUILD_VERSION}" STREQUAL "") # cmake-lint: disable=W0106
message(STATUS "Using CI build version '$ENV{BUILD_VERSION}'")
set(PROJECT_VERSION "$ENV{BUILD_VERSION}")
string(REGEX REPLACE "^v" "" PROJECT_VERSION "${PROJECT_VERSION}")
set(CMAKE_PROJECT_VERSION "${PROJECT_VERSION}")
endif()

if(PROJECT_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)")
set(PROJECT_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(CMAKE_PROJECT_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(PROJECT_VERSION_MINOR "${CMAKE_MATCH_2}")
set(CMAKE_PROJECT_VERSION_MINOR "${CMAKE_MATCH_2}")
set(PROJECT_VERSION_PATCH "${CMAKE_MATCH_3}")
set(CMAKE_PROJECT_VERSION_PATCH "${CMAKE_MATCH_3}")
endif()

message(STATUS "PROJECT_VERSION: ${PROJECT_VERSION}")
message(STATUS "PROJECT_VERSION_MAJOR: ${PROJECT_VERSION_MAJOR}")
message(STATUS "PROJECT_VERSION_MINOR: ${PROJECT_VERSION_MINOR}")
message(STATUS "PROJECT_VERSION_PATCH: ${PROJECT_VERSION_PATCH}")
message(STATUS "GITHUB_BRANCH: ${GITHUB_BRANCH}")
message(STATUS "GITHUB_COMMIT: ${GITHUB_COMMIT}")
20 changes: 20 additions & 0 deletions cmake/packaging/common.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# common cpack options
set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME})
set(CPACK_PACKAGE_VENDOR "LizardByte")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/cpack_artifacts)
set(CPACK_PACKAGE_CONTACT "https://app.lizardbyte.dev")
set(CPACK_PACKAGE_DESCRIPTION ${CMAKE_PROJECT_DESCRIPTION})
set(CPACK_PACKAGE_HOMEPAGE_URL ${CMAKE_PROJECT_HOMEPAGE_URL})
set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE)
set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}")
set(CPACK_STRIP_FILES YES)

if(WIN32)
include("${CMAKE_CURRENT_LIST_DIR}/windows.cmake")
endif()

include(CPack)
34 changes: 34 additions & 0 deletions cmake/packaging/windows.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# windows specific packaging
set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}")

if(NOT LIBVIRTUALHID_BUILD_WINDOWS_DRIVER)
set(CPACK_MONOLITHIC_INSTALL ON)
return()
endif()

set(CPACK_COMPONENTS_ALL driver)
set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)
set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_BINARY_DIR};${CMAKE_PROJECT_NAME};driver;/")

set(LIBVIRTUALHID_DRIVER_TEST_CERTIFICATE "" CACHE FILEPATH
"Optional public test certificate to include in the Windows driver installer.")

install(FILES
"${PROJECT_SOURCE_DIR}/scripts/windows/install-driver.ps1"
"${PROJECT_SOURCE_DIR}/scripts/windows/uninstall-driver.ps1"
DESTINATION "scripts/windows"
COMPONENT driver)

if(LIBVIRTUALHID_DRIVER_TEST_CERTIFICATE)
install(FILES "${LIBVIRTUALHID_DRIVER_TEST_CERTIFICATE}"
DESTINATION "certificates"
RENAME "libvirtualhid-ci-test.cer"
COMPONENT driver
OPTIONAL)
endif()

set(CPACK_COMPONENT_DRIVER_DISPLAY_NAME "Windows UMDF Driver")
set(CPACK_COMPONENT_DRIVER_DESCRIPTION "libvirtualhid Windows UMDF virtual HID driver package.")
set(CPACK_COMPONENT_DRIVER_REQUIRED true)

include("${CMAKE_CURRENT_LIST_DIR}/windows_wix.cmake")
Loading