Skip to content

Commit 7662217

Browse files
fix: improve broker licensing and package install (#68)
1 parent 930a8d6 commit 7662217

20 files changed

Lines changed: 1229 additions & 132 deletions

.github/workflows/ci.yml

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -462,13 +462,6 @@ jobs:
462462
- name: Install
463463
run: cmake --install cmake-build-ci --config ${{ env.CMAKE_BUILD_CONFIG }} --prefix cmake-build-ci/install
464464

465-
- name: Upload install artifact
466-
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
467-
with:
468-
name: install-${{ matrix.name }}
469-
path: cmake-build-ci/install
470-
if-no-files-found: error
471-
472465
- name: Upload report artifact
473466
if: >-
474467
always() &&
@@ -483,6 +476,67 @@ jobs:
483476
path: cmake-build-ci/reports
484477
if-no-files-found: error
485478

479+
package_consumer:
480+
name: Installed Package Consumer (Linux)
481+
permissions:
482+
contents: read
483+
runs-on: ubuntu-latest
484+
steps:
485+
- name: Checkout
486+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
487+
with:
488+
submodules: recursive
489+
490+
- name: Install package dependencies
491+
run: |
492+
sudo apt-get update
493+
sudo apt-get install -y \
494+
build-essential \
495+
cmake \
496+
libevdev-dev \
497+
libx11-dev \
498+
libxtst-dev \
499+
ninja-build \
500+
pkg-config
501+
502+
# This intentionally uses a separate BUILD_TESTS=OFF configuration. Test
503+
# builds add lizardbyte-common for test support and therefore cannot prove
504+
# that a normal installed package is independently consumable. Debug keeps
505+
# this packaging regression isolated from the open optimized-GCC P2; switch
506+
# this job to Release when that warning is resolved.
507+
- name: Configure tests-disabled package
508+
run: |
509+
cmake \
510+
-DBUILD_DOCS=OFF \
511+
-DBUILD_EXAMPLES=OFF \
512+
-DBUILD_TESTS=OFF \
513+
-DCMAKE_BUILD_TYPE:STRING=Debug \
514+
-DCMAKE_INSTALL_PREFIX="${GITHUB_WORKSPACE}/cmake-build-package/install" \
515+
-DLIBVIRTUALHID_BUILD_TOOLS=OFF \
516+
-DLIBVIRTUALHID_ENABLE_PACKAGING=OFF \
517+
-B cmake-build-package \
518+
-G Ninja \
519+
-S .
520+
521+
- name: Build and install package
522+
run: |
523+
cmake --build cmake-build-package --parallel 2
524+
cmake --install cmake-build-package
525+
test ! -d cmake-build-package/install/lib/cmake/lizardbyte-common
526+
527+
# A separate CMake project and real symbol reference force find_package,
528+
# imported-target generation, compilation, and the final static-library link.
529+
- name: Configure, compile, and link downstream consumer
530+
run: |
531+
cmake \
532+
-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF \
533+
-DCMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY=OFF \
534+
-DCMAKE_PREFIX_PATH="${GITHUB_WORKSPACE}/cmake-build-package/install" \
535+
-B cmake-build-package-consumer \
536+
-G Ninja \
537+
-S tests/package-consumer
538+
cmake --build cmake-build-package-consumer --parallel 2
539+
486540
windows_driver:
487541
name: Windows Driver Installer
488542
needs: setup_release
@@ -759,65 +813,27 @@ jobs:
759813
always() &&
760814
needs.setup_release.outputs.publish_release == 'true' &&
761815
needs.build.result == 'success' &&
816+
needs.package_consumer.result == 'success' &&
762817
needs.windows_driver.result == 'success' &&
763818
startsWith(github.repository, 'LizardByte/')
764819
needs:
765820
- build
821+
- package_consumer
766822
- windows_driver
767823
- setup_release
768824
permissions:
769825
contents: read
770826
runs-on: ubuntu-latest
771827
steps:
772-
- name: Download install artifact (Linux-GCC)
773-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
774-
with:
775-
name: install-Linux-GCC
776-
path: install-Linux-GCC
777-
778-
- name: Download install artifact (Linux-Clang)
779-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
780-
with:
781-
name: install-Linux-Clang
782-
path: install-Linux-Clang
783-
784-
- name: Download install artifact (macOS)
785-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
786-
with:
787-
name: install-macOS
788-
path: install-macOS
789-
790-
- name: Download install artifact (Windows-MinGW-UCRT64)
791-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
792-
with:
793-
name: install-Windows-MinGW-UCRT64
794-
path: install-Windows-MinGW-UCRT64
795-
796-
- name: Download install artifact (Windows-MSVC)
797-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
798-
with:
799-
name: install-Windows-MSVC
800-
path: install-Windows-MSVC
801-
802828
- name: Download Windows driver installer artifact
803829
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
804830
with:
805831
name: windows-driver-installer
806832
path: windows-driver-installer
807833

808-
- name: Package install artifacts
834+
- name: Prepare release artifact
809835
run: |
810836
mkdir -p artifacts
811-
for name in Linux-GCC Linux-Clang macOS Windows-MinGW-UCRT64 Windows-MSVC; do
812-
release_name="${name}"
813-
case "${name}" in
814-
Windows-MinGW-UCRT64) release_name="Windows-AMD64-MinGW-UCRT64" ;;
815-
Windows-MSVC) release_name="Windows-AMD64-MSVC" ;;
816-
esac
817-
zip -r \
818-
"artifacts/libvirtualhid-${release_name}.zip" \
819-
"install-${name}"
820-
done
821837
cp windows-driver-installer/*.msi artifacts/
822838
823839
- name: Validate release metadata
@@ -829,12 +845,7 @@ jobs:
829845
test -n "${RELEASE_TAG}"
830846
test -n "${RELEASE_VERSION}"
831847
test "${RELEASE_COMMIT}" = "${GITHUB_SHA}"
832-
test -s "artifacts/libvirtualhid-Linux-GCC.zip"
833-
test -s "artifacts/libvirtualhid-Linux-Clang.zip"
834-
test -s "artifacts/libvirtualhid-macOS.zip"
835848
test -s "artifacts/libvirtualhid-Windows-AMD64-driver-installer.msi"
836-
test -s "artifacts/libvirtualhid-Windows-AMD64-MinGW-UCRT64.zip"
837-
test -s "artifacts/libvirtualhid-Windows-AMD64-MSVC.zip"
838849
839850
- name: Create/Update GitHub Release
840851
if: needs.setup_release.outputs.publish_release == 'true'

CMakeLists.txt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ set(LIBVIRTUALHID_USES_THREADS OFF)
6060
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
6161
set(LIBVIRTUALHID_USES_THREADS ON)
6262
endif()
63+
set(LIBVIRTUALHID_USES_XTEST OFF)
6364

6465
#
6566
# Additional setup for coverage
@@ -71,7 +72,7 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTS AND NOT CMAKE_CXX_CO
7172
endif()
7273

7374
set(LIBVIRTUALHID_USES_LIZARDBYTE_COMMON OFF)
74-
if(WIN32)
75+
if(WIN32 AND LIBVIRTUALHID_BUILD_WINDOWS_BROKER)
7576
set(LIBVIRTUALHID_USES_LIZARDBYTE_COMMON ON)
7677
endif()
7778

@@ -80,7 +81,7 @@ if(LIBVIRTUALHID_USES_LIZARDBYTE_COMMON OR BUILD_TESTS)
8081
${BUILD_TESTS}
8182
CACHE BOOL "Build lizardbyte-common GoogleTest support helpers" FORCE)
8283
set(LIZARDBYTE_COMMON_INSTALL
83-
${LIBVIRTUALHID_INSTALL}
84+
OFF
8485
CACHE BOOL "Install lizardbyte-common targets and package configuration" FORCE)
8586
if(NOT TARGET lizardbyte::common)
8687
add_subdirectory(third-party/lizardbyte-common)
@@ -157,6 +158,18 @@ endif()
157158
# Package config
158159
#
159160
if(LIBVIRTUALHID_INSTALL)
161+
set(LIBVIRTUALHID_CONFIG_DEPENDENCIES "")
162+
if(LIBVIRTUALHID_USES_THREADS)
163+
string(APPEND LIBVIRTUALHID_CONFIG_DEPENDENCIES
164+
"find_dependency(Threads)\n"
165+
"find_dependency(PkgConfig)\n"
166+
"pkg_check_modules(LIBEVDEV REQUIRED IMPORTED_TARGET libevdev)\n")
167+
endif()
168+
if(LIBVIRTUALHID_USES_XTEST)
169+
string(APPEND LIBVIRTUALHID_CONFIG_DEPENDENCIES
170+
"find_dependency(X11 COMPONENTS Xtst)\n")
171+
endif()
172+
160173
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE"
161174
DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/libvirtualhid"
162175
COMPONENT library)

cmake/libvirtualhid-config.cmake.in

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@
22

33
include(CMakeFindDependencyMacro)
44

5-
if(@LIBVIRTUALHID_USES_THREADS@)
6-
find_dependency(Threads)
7-
find_dependency(PkgConfig)
8-
pkg_check_modules(LIBEVDEV REQUIRED IMPORTED_TARGET libevdev)
9-
endif()
10-
11-
find_dependency(lizardbyte-common)
5+
@LIBVIRTUALHID_CONFIG_DEPENDENCIES@
126

137
include("${CMAKE_CURRENT_LIST_DIR}/libvirtualhid-targets.cmake")
148

docs/usage.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,23 @@ normalized gamepad output such as rumble, RGB LED, adaptive trigger, trigger
106106
rumble, and raw report events delivered through the normal callback path. Button
107107
controls are momentary by default, so they behave like physical gamepad buttons;
108108
on Windows, the UI also displays broker license status and can activate,
109-
refresh, or deactivate a machine license without elevation. Every Windows UMDF
110-
gamepad creation requires a current successful license validation response and
111-
there is no offline grace period. Purchase and account-management buttons use
112-
the compiled URLs in
109+
refresh, or deactivate a machine license without elevation. Windows UMDF
110+
gamepad creation requires a current machine authorization, but does not perform
111+
an online request per controller. The broker validates in the background at
112+
startup and once per day. If Polar cannot be reached, it retries every 60
113+
seconds. Existing gamepads are retained for one hour, but a new gamepad can be
114+
created only when no licensed gamepad is active. When the outage reaches one
115+
hour, the broker removes excess licensed gamepads and retains at most one.
116+
If the broker service restarts, it removes gamepads left by the previous broker
117+
instance before accepting new creation requests. Failed removals are retried.
118+
Yearly licenses stop at their exact `expires_at`; this outage behavior never
119+
extends expiration. Lifetime licenses have no expiration. Polar server time,
120+
Windows uptime, and a per-boot marker track time without relying on the
121+
user-adjustable Windows date. After Windows restarts, a yearly license must
122+
reconnect to Polar before gamepad creation; lifetime licenses can use the
123+
one-gamepad outage fallback. A confirmed missing, revoked, disabled, expired, or
124+
mismatched entitlement invalidates the license and removes all licensed gamepads. Purchase and
125+
account-management buttons use the compiled URLs in
113126
`src/platform/windows/shared/lvh_windows_broker_config.hpp`.
114127
Enable `Lock buttons` to click-to-toggle behavior for held inputs.
115128
The resizable window supports a compact width. Its device and control panels

docs/windows-driver.md

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,15 @@ submit/destroy requests include that token so stale or unrelated clients cannot
6868
control devices they did not create. Input reports are submitted through VHF,
6969
and HID output writes are normalized back to the C++ output callback path.
7070

71-
The driver rejects gamepad create and destroy IOCTLs unless the requestor token
72-
contains the `NT SERVICE\libvirtualhid_broker` service SID. On the first boot
73-
after installation, before Windows applies a newly configured service SID to
74-
the process token, the driver instead requires the requestor PID to match the
75-
SCM-registered, currently running broker service. Administrators still control
76-
installation, repair, replacement, and service diagnostics through the normal
77-
Windows service and driver-management tools, but they are not a separate runtime
78-
bypass for creating or destroying virtual devices.
71+
The driver rejects gamepad create, destroy, and broker-instance reset IOCTLs
72+
unless the requestor token contains the `NT SERVICE\libvirtualhid_broker`
73+
service SID. On the first boot after installation, before Windows applies a
74+
newly configured service SID to the process token, the driver instead requires
75+
the requestor PID to match the SCM-registered, currently running broker service.
76+
Administrators still control installation, repair, replacement, and service
77+
diagnostics through the normal Windows service and driver-management tools, but
78+
they are not a separate runtime bypass for creating or destroying virtual
79+
devices.
7980

8081
The library and installed driver must use the same control-protocol version.
8182
Protocol version 2 expands the report-descriptor capacity to 2048 bytes for the
@@ -94,7 +95,10 @@ parents that target to the control-file handle that created it. If the creating
9495
process exits or crashes, Windows cleans up gamepads that were not explicitly
9596
destroyed. In brokered driver packages, the broker owns that control-file handle.
9697
The broker tracks the requesting client process for each created device and
97-
destroys broker-owned devices when that client process exits unexpectedly.
98+
destroys broker-owned devices when that client process exits unexpectedly. A new
99+
broker process first asks the driver to remove every gamepad left by the previous
100+
broker instance and refuses new creation until that reset succeeds. Clients must
101+
recreate their gamepads after the broker service restarts.
98102

99103
The backend reports `requires_installed_driver = true` and only advertises
100104
gamepad/output-report support when the broker is reachable and the control
@@ -276,8 +280,29 @@ opens the shared persistent Polar Checkout Link. Account management opens the
276280
[LizardByte LLC Polar customer portal](https://polar.sh/lizardbyte-llc/portal),
277281
where customers can manage their five allowed machine activations.
278282

279-
Normal Windows UMDF gamepad creation requires a current successful license
280-
validation response before the broker calls the driver. The sole exception is
283+
Normal Windows UMDF gamepad creation requires a current machine authorization,
284+
but controller creation itself does not contact Polar. The broker validates the
285+
saved activation immediately after service startup and then once per day in the
286+
background. If validation cannot complete because of a temporary network or
287+
provider failure, the broker retries every 60 seconds. Controllers that already
288+
exist are retained for one hour unless the broker service restarts, but no
289+
additional controller can be created while at least one licensed controller
290+
remains active. When the outage reaches one hour, the broker removes excess
291+
licensed controllers and retains at most one until online validation succeeds.
292+
Failed driver destruction requests remain tracked and are retried instead of
293+
being treated as successful revocations.
294+
295+
Polar's HTTPS `Date` response header supplies trusted time when a new
296+
authorization is issued. A yearly license ends exactly at its reported
297+
`expires_at`; the one-hour outage retention does not extend that expiration. A
298+
lifetime license has no calendar expiration. The broker advances Polar's trusted
299+
timestamp using Windows uptime and stores a random marker in a volatile registry
300+
key for the current boot session. This works across broker service restarts and
301+
includes sleep or hibernation, but never consults the user-adjustable Windows
302+
date. After Windows restarts, the marker changes, so a yearly license must
303+
reconnect to Polar before gamepad creation; lifetime licenses can use the
304+
one-gamepad outage fallback. Explicit validation requests always contact the
305+
provider. The sole exception to normal licensing is
281306
for CI runners where the broker service itself has the `GITHUB_ACTIONS`
282307
environment marker. That environment receives one machine-scoped five-minute
283308
evaluation window beginning with its first unlicensed creation attempt. The
@@ -290,7 +315,13 @@ Polar's `limit_activations` value is the machine limit and is configured as `5`
290315
on both license-key benefits. The broker gives yearly and lifetime licenses the
291316
same full local access when the provider reports the key status as `granted`. Polar
292317
revokes a subscription benefit when its entitlement ends. Licensed access has
293-
no local active-device cap, and there is no production offline grace period.
318+
no local active-device cap after successful validation. A definitive missing
319+
activation, revoked or disabled key, activation mismatch, disallowed benefit,
320+
explicit deactivation, or exact yearly expiration prevents new gamepads and
321+
causes the broker to destroy existing licensed gamepads. A timeout or other
322+
transient provider failure starts the one-hour retention period and one-gamepad
323+
creation limit instead of immediately revoking existing controllers. WinHTTP resolve, connect, send, and receive
324+
operations have explicit timeouts of 5, 5, 5, and 10 seconds respectively.
294325

295326
## Profile Compatibility
296327

@@ -338,8 +369,10 @@ label because VHF does not provide a product/manufacturer string callback.
338369
- The published Windows driver installer is AMD64-only. Windows ARM64 release
339370
packages require a Microsoft dashboard signing path that is not part of the
340371
current Azure Trusted Signing workflow.
341-
- Every production gamepad creation requires a successful online license
342-
validation response. There is no offline grace period.
372+
- A temporary Polar outage limits a previously activated machine to one active
373+
licensed gamepad until validation succeeds. Yearly licenses receive no
374+
post-expiration grace. Definitive invalidation or exact expiration prevents
375+
new gamepads and removes active licensed gamepads.
343376

344377
## Signing
345378

src/CMakeLists.txt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ if(LIBVIRTUALHID_USES_THREADS)
2222
pkg_check_modules(LIBEVDEV REQUIRED IMPORTED_TARGET libevdev)
2323
endif()
2424
if(LIBVIRTUALHID_ENABLE_XTEST)
25-
find_package(X11 QUIET)
25+
find_package(X11 QUIET COMPONENTS Xtst)
2626
endif()
2727

2828
target_sources(${PROJECT_NAME}
@@ -34,26 +34,25 @@ if(LIBVIRTUALHID_USES_THREADS)
3434
PRIVATE
3535
PkgConfig::LIBEVDEV)
3636
if(LIBVIRTUALHID_ENABLE_XTEST AND X11_FOUND AND X11_XTest_FOUND)
37+
set(LIBVIRTUALHID_USES_XTEST ON PARENT_SCOPE)
3738
target_compile_definitions(${PROJECT_NAME}
3839
PRIVATE
3940
LIBVIRTUALHID_HAVE_XTEST=1)
40-
target_include_directories(${PROJECT_NAME}
41-
PRIVATE
42-
${X11_INCLUDE_DIR}
43-
${X11_XTest_INCLUDE_PATH})
4441
target_link_libraries(${PROJECT_NAME}
4542
PRIVATE
46-
${X11_LIBRARIES}
47-
${X11_XTest_LIB})
43+
X11::X11
44+
X11::Xtst)
4845
endif()
4946
elseif(WIN32)
5047
target_sources(${PROJECT_NAME}
5148
PRIVATE
49+
"${PROJECT_SOURCE_DIR}/third-party/lizardbyte-common/src/common/env.cpp"
5250
"${CMAKE_CURRENT_SOURCE_DIR}/platform/windows/windows_backend.cpp"
5351
"${CMAKE_CURRENT_SOURCE_DIR}/platform/windows/windows_broker_client.cpp"
5452
"${CMAKE_CURRENT_SOURCE_DIR}/platform/windows/windows_license.cpp")
5553
target_include_directories(${PROJECT_NAME}
5654
PRIVATE
55+
"${PROJECT_SOURCE_DIR}/third-party/lizardbyte-common/src/include"
5756
"${CMAKE_CURRENT_SOURCE_DIR}/platform/windows/shared")
5857
target_compile_definitions(${PROJECT_NAME}
5958
PRIVATE
@@ -63,8 +62,7 @@ elseif(WIN32)
6362
target_link_libraries(${PROJECT_NAME}
6463
PRIVATE
6564
advapi32
66-
setupapi
67-
lizardbyte::common)
65+
setupapi)
6866
elseif(APPLE)
6967
target_sources(${PROJECT_NAME}
7068
PRIVATE

0 commit comments

Comments
 (0)