mirror of
https://github.com/LizardByte/libvirtualhid.git
synced 2026-10-23 03:27:34 +09:00
fix(macos): enable GUI license activation (#188)
This commit is contained in:
1 parent
2699cb2c23
commit
7cd296cabc
4 files changed
+262
-11
No files matched your search
+10
-5
@@ -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
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
Reference in new issue
Block a user