fix(macos): enable GUI license activation (#188)

This commit is contained in:
Dave Lane authored and GitHub committed 2026-10-09 22:44:04 -04:00
1 parent 2699cb2c23
commit 7cd296cabc
4 files changed
+262 -11

No files matched your search

+10 -5
View File
@@ -20,17 +20,22 @@ In **System Settings > Privacy & Security > Device Control and Data Access**
cannot display the permission prompt while creating a gamepad. Review the
signed application before granting this broad device-control permission.
Activate the purchased license from Terminal:
Open **Virtual HID Control** from Applications. In the **License** panel,
paste the purchased license key and click **Activate license**. Use **Refresh**
to validate the license online or **Deactivate this machine** to release its
activation. The tool clears the key field after successful activation.
You can also activate from Terminal:
```sh
/usr/local/bin/libvirtualhid-license activate
/usr/local/bin/libvirtualhid-license status
```
Activation prompts for the key without echoing it. Use `validate` to refresh
status or `deactivate` to release this machine's activation. Open **Virtual
HID Control** to create and inspect a test controller. The host application
runs in the normal user session; the broker runs as a LaunchDaemon.
Terminal activation prompts for the key without echoing it. Use `validate` to
refresh status or `deactivate` to release this machine's activation. Use
**Virtual HID Control** to create and inspect a test controller. The host
application runs in the normal user session; the broker runs as a LaunchDaemon.
## License validation during outages
+7
View File
@@ -47,6 +47,10 @@ if(TARGET virtualhid_control_model)
list(APPEND LIBVIRTUALHID_TEST_SOURCES
"$<TARGET_PROPERTY:virtualhid_control_model,SOURCES>"
"${CMAKE_CURRENT_SOURCE_DIR}/unit/test_virtualhid_control_model.cpp")
if(WIN32 OR APPLE)
list(APPEND LIBVIRTUALHID_TEST_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/unit/test_virtualhid_control_license_panel.cpp")
endif()
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
@@ -117,6 +121,9 @@ if(TARGET virtualhid_control_model)
target_include_directories(${TEST_BINARY}
PRIVATE
"$<TARGET_PROPERTY:virtualhid_control_model,INCLUDE_DIRECTORIES>")
if(WIN32 OR APPLE)
target_link_libraries(${TEST_BINARY} PRIVATE virtualhid_imgui)
endif()
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
@@ -0,0 +1,233 @@
/**
* @file tests/unit/test_virtualhid_control_license_panel.cpp
* @brief Headless UI tests for Windows and macOS machine license management.
*/
// standard includes
#include <memory>
#include <string>
#include <string_view>
#include <vector>
// third-party includes
#include <gtest/gtest.h>
#include <imgui_internal.h>
// GoogleTest owns the entry point, including on Windows.
#define SDL_MAIN_HANDLED
#include <SDL3/SDL_main.h>
#undef SDL_MAIN_HANDLED
// local includes
#include <virtualhid_control_model.hpp>
namespace {
struct LicensePanelClient {
lvh::LicenseResult result;
int status_calls = 0;
int activation_calls = 0;
int validation_calls = 0;
int deactivation_calls = 0;
std::string activated_key;
};
/**
* @brief Access the fake license client attached to the current test's ImGui context.
*
* @return Reference to the client owned by the active test fixture.
*/
LicensePanelClient &license_panel_client() {
return *static_cast<LicensePanelClient *>(ImGui::GetIO().UserData);
}
} // namespace
namespace lvh {
LicenseResult control_test_get_license_status() {
auto &client = license_panel_client();
++client.status_calls;
return client.result;
}
LicenseResult control_test_activate_license(std::string_view key) {
auto &client = license_panel_client();
++client.activation_calls;
client.activated_key = key;
return client.result;
}
LicenseResult control_test_validate_license() {
auto &client = license_panel_client();
++client.validation_calls;
return client.result;
}
LicenseResult control_test_deactivate_license() {
auto &client = license_panel_client();
++client.deactivation_calls;
return client.result;
}
} // namespace lvh
// Load the UI with a fake license client so button tests never change a real
// machine activation. Rename its entry point after loading SDL's main header.
#ifdef main
#undef main
#endif
#define main virtualhid_control_test_main
#define get_license_status control_test_get_license_status
#define activate_license control_test_activate_license
#define validate_license control_test_validate_license
#define deactivate_license control_test_deactivate_license
#include "../../tools/virtualhid_control.cpp"
#undef deactivate_license
#undef validate_license
#undef activate_license
#undef get_license_status
#undef main
namespace {
using lvh::tools::virtualhid_control::ui::LicensePanel;
class VirtualHidControlLicensePanelTest: public testing::Test {
protected:
void SetUp() override {
client_.result.status = lvh::OperationStatus::success();
client_.result.license.service_available = true;
client_.result.license.state = lvh::LicenseState::unlicensed;
ImGui::CreateContext();
auto &io = ImGui::GetIO();
io.UserData = &client_;
io.IniFilename = nullptr;
io.DisplaySize = {800.0F, 1000.0F};
io.DeltaTime = 1.0F / 60.0F;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
unsigned char *pixels = nullptr;
int width = 0;
int height = 0;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
panel_ = std::make_unique<LicensePanel>();
render();
}
void TearDown() override {
panel_.reset();
ImGui::DestroyContext();
}
void render() {
ImGui::NewFrame();
ImGui::SetNextWindowPos({0.0F, 0.0F});
ImGui::SetNextWindowSize({800.0F, 1000.0F});
ImGui::Begin("License panel test");
panel_->render([this](std::string_view error) {
errors_.emplace_back(error);
});
ImGui::End();
ImGui::Render();
}
void activate_item(const char *label) {
auto *window = ImGui::FindWindowByName("License panel test");
ASSERT_NE(window, nullptr);
ImGui::ActivateItemByID(window->GetID(label));
render();
}
void enter_key(const char *key) {
activate_item("##license-key");
ImGui::GetIO().AddInputCharactersUTF8(key);
render();
}
std::string key_text() {
activate_item("##license-key");
return ImGui::GetCurrentContext()->InputTextState.GetText();
}
LicensePanelClient &client() {
return client_;
}
const std::vector<std::string> &errors() const {
return errors_;
}
void refresh_license() {
panel_->refresh();
render();
}
private:
LicensePanelClient client_;
std::unique_ptr<LicensePanel> panel_;
std::vector<std::string> errors_;
};
} // namespace
TEST_F(VirtualHidControlLicensePanelTest, ActivatesTrimmedKeyAndClearsSuccessfulInput) {
enter_key(" test-license-key ");
activate_item("Activate license");
EXPECT_EQ(client().activation_calls, 1);
EXPECT_EQ(client().activated_key, "test-license-key");
EXPECT_TRUE(errors().empty());
EXPECT_TRUE(key_text().empty());
}
TEST_F(VirtualHidControlLicensePanelTest, DisablesActivationForEmptyAndWhitespaceKeys) {
activate_item("Activate license");
EXPECT_EQ(client().activation_calls, 0);
enter_key(" ");
activate_item("Activate license");
EXPECT_EQ(client().activation_calls, 0);
EXPECT_TRUE(errors().empty());
}
TEST_F(VirtualHidControlLicensePanelTest, ReportsFailedActivationAndRetainsKeyForRetry) {
client().result.status = lvh::OperationStatus::failure(lvh::ErrorCode::network_unavailable, "License service unavailable.");
enter_key("test-license-key");
activate_item("Activate license");
EXPECT_EQ(client().activation_calls, 1);
EXPECT_EQ(errors(), (std::vector<std::string> {"License service unavailable."}));
EXPECT_EQ(key_text(), "test-license-key");
client().result.status = lvh::OperationStatus::success();
activate_item("Activate license");
EXPECT_EQ(client().activation_calls, 2);
EXPECT_EQ(client().activated_key, "test-license-key");
EXPECT_TRUE(key_text().empty());
}
TEST_F(VirtualHidControlLicensePanelTest, RefreshValidatesOnlineAndReportsFailures) {
activate_item("Refresh");
EXPECT_EQ(client().validation_calls, 1);
EXPECT_EQ(client().status_calls, 1);
EXPECT_TRUE(errors().empty());
client().result.status = lvh::OperationStatus::failure(lvh::ErrorCode::network_unavailable, "Validation unavailable.");
activate_item("Refresh");
EXPECT_EQ(client().validation_calls, 2);
EXPECT_EQ(errors(), (std::vector<std::string> {"Validation unavailable."}));
}
TEST_F(VirtualHidControlLicensePanelTest, DeactivatesMachineAndReportsFailures) {
activate_item("Deactivate this machine");
EXPECT_EQ(client().deactivation_calls, 1);
EXPECT_TRUE(errors().empty());
client().result.status = lvh::OperationStatus::failure(lvh::ErrorCode::network_unavailable, "Deactivation unavailable.");
activate_item("Deactivate this machine");
EXPECT_EQ(client().deactivation_calls, 2);
EXPECT_EQ(errors(), (std::vector<std::string> {"Deactivation unavailable."}));
}
TEST_F(VirtualHidControlLicensePanelTest, DisablesValidationAndDeactivationWithoutBroker) {
client().result.license.service_available = false;
refresh_license();
activate_item("Refresh");
activate_item("Deactivate this machine");
EXPECT_EQ(client().validation_calls, 0);
EXPECT_EQ(client().deactivation_calls, 0);
EXPECT_TRUE(errors().empty());
}
+12 -6
View File
@@ -31,7 +31,10 @@
// local includes
#include "virtualhid_control_model.hpp"
namespace {
/**
* @brief Implementation of the diagnostic control application's UI.
*/
namespace lvh::tools::virtualhid_control::ui {
using lvh::tools::virtualhid_control::axis_choices;
using lvh::tools::virtualhid_control::axis_to_slider;
using lvh::tools::virtualhid_control::battery_choice_index;
@@ -343,6 +346,9 @@ namespace {
return license_snapshot_from(result);
}
/**
* @brief Display license status and manage machine activation on Windows and macOS.
*/
class LicensePanel {
public:
LicensePanel() {
@@ -365,7 +371,7 @@ namespace {
ImGui::TextWrapped("%s", snapshot_.message.c_str());
}
#if defined(_WIN32)
#if defined(_WIN32) || defined(__APPLE__)
ImGui::TextUnformatted("License key");
ImGui::InputText("##license-key", license_key_input_.data(), license_key_input_.size());
{
@@ -438,7 +444,7 @@ namespace {
}
}
#if defined(_WIN32)
#if defined(_WIN32) || defined(__APPLE__)
template<typename ErrorHandler>
void activate(std::string_view license_key, ErrorHandler &show_error) {
std::string error;
@@ -470,7 +476,7 @@ namespace {
#endif
LicenseSnapshot snapshot_;
#if defined(_WIN32)
#if defined(_WIN32) || defined(__APPLE__)
std::array<char, 128> license_key_input_ {};
#endif
std::string buy_url_;
@@ -1867,12 +1873,12 @@ namespace {
return 0;
}
} // namespace
} // namespace lvh::tools::virtualhid_control::ui
/**
* @brief Run the libvirtualhid diagnostic control UI.
* @return Process exit code.
*/
int main(int, char **) {
return run_control_ui();
return lvh::tools::virtualhid_control::ui::run_control_ui();
}