From 1538936758a8425b3abe717e6822e68b11514cb8 Mon Sep 17 00:00:00 2001 From: Adam Honse Date: Tue, 28 Jul 2026 18:49:03 -0500 Subject: [PATCH] Initial driver for PawnIO-based Windows controller for Valve Steam Machine --- ...oller_ValveSteamMachine_Windows_Linux.cpp} | 60 +- ...troller_ValveSteamMachine_Windows_Linux.h} | 4 + ...alveSteamMachineControllerDetect_Linux.cpp | 3 +- ...veSteamMachineControllerDetect_Windows.cpp | 51 ++ .../ValveSteamMachineController_Linux.cpp | 236 ++++++- .../ValveSteamMachineController_Linux.h | 30 +- .../ValveSteamMachineController_Windows.cpp | 641 ++++++++++++++++++ .../ValveSteamMachineController_Windows.h | 60 ++ OpenRGB.pro | 15 + dependencies/PawnIO/modules/LedsValve.bin | Bin 0 -> 43796 bytes 10 files changed, 1064 insertions(+), 36 deletions(-) rename Controllers/ValveSteamMachineController/{RGBController_ValveSteamMachine_Linux.cpp => RGBController_ValveSteamMachine_Windows_Linux.cpp} (74%) rename Controllers/ValveSteamMachineController/{RGBController_ValveSteamMachine_Linux.h => RGBController_ValveSteamMachine_Windows_Linux.h} (93%) create mode 100644 Controllers/ValveSteamMachineController/ValveSteamMachineControllerDetect_Windows.cpp create mode 100644 Controllers/ValveSteamMachineController/ValveSteamMachineController_Windows.cpp create mode 100644 Controllers/ValveSteamMachineController/ValveSteamMachineController_Windows.h create mode 100644 dependencies/PawnIO/modules/LedsValve.bin diff --git a/Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Linux.cpp b/Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Windows_Linux.cpp similarity index 74% rename from Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Linux.cpp rename to Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Windows_Linux.cpp index 5103a6cf6..2c8d96482 100644 --- a/Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Linux.cpp +++ b/Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Windows_Linux.cpp @@ -1,5 +1,5 @@ /*---------------------------------------------------------*\ -| RGBController_ValveSteamMachine_Linux.cpp | +| RGBController_ValveSteamMachine_Windows_Linux.cpp | | | | RGBController for Valve Steam Machine LEDs | | | @@ -9,7 +9,7 @@ | SPDX-License-Identifier: GPL-2.0-or-later | \*---------------------------------------------------------*/ -#include "RGBController_ValveSteamMachine_Linux.h" +#include "RGBController_ValveSteamMachine_Windows_Linux.h" #include /**------------------------------------------------------------------*\ @@ -31,7 +31,7 @@ RGBController_ValveSteamMachine::RGBController_ValveSteamMachine(ValveSteamMachi vendor = "Valve"; type = DEVICE_TYPE_LEDSTRIP; description = "Valve Steam Machine Device"; - location = controller->GetLEDPath(0); + location = controller->GetLocation(); /*-----------------------------------------------------*\ | If the device supports hardware effects, add them as | @@ -40,6 +40,17 @@ RGBController_ValveSteamMachine::RGBController_ValveSteamMachine(ValveSteamMachi \*-----------------------------------------------------*/ effects = controller->GetAvailableEffects(); + /*-----------------------------------------------------*\ + | Add a Disabled mode as the first mode. When this | + | mode is active, the device's enabled flag is set to | + | false, turning the LEDs off without applying any | + | hardware effect. | + \*-----------------------------------------------------*/ + mode disabled_mode; + disabled_mode.name = "Disabled"; + disabled_mode.color_mode = MODE_COLORS_NONE; + modes.push_back(disabled_mode); + for(unsigned int effect_idx = 0; effect_idx < effects.size(); effect_idx++) { unsigned int effect_color_mode; @@ -51,7 +62,7 @@ RGBController_ValveSteamMachine::RGBController_ValveSteamMachine(ValveSteamMachi \*-------------------------------------------------*/ if(effect_name == controller->GetEffect()) { - active_mode = effect_idx; + active_mode = effect_idx + 1; } /*-------------------------------------------------*\ @@ -108,6 +119,16 @@ RGBController_ValveSteamMachine::RGBController_ValveSteamMachine(ValveSteamMachi modes.push_back(new_mode); } + /*-----------------------------------------------------*\ + | If the device is not enabled at startup, initialize | + | active_mode to the Disabled mode (index 0) instead | + | of forcibly enabling it. | + \*-----------------------------------------------------*/ + if(!controller->GetEnabled()) + { + active_mode = 0; + } + SetupZones(); } @@ -162,19 +183,32 @@ void RGBController_ValveSteamMachine::DeviceUpdateZoneLEDs(int /*zone*/) void RGBController_ValveSteamMachine::DeviceUpdateSingleLED(int led) { - unsigned char red = RGBGetRValue(colors[led]); - unsigned char grn = RGBGetGValue(colors[led]); - unsigned char blu = RGBGetBValue(colors[led]); - - controller->SetLEDColor(led, red, grn, blu); + controller->SetLEDColor(led, colors[led]); } void RGBController_ValveSteamMachine::DeviceUpdateMode() { - if((unsigned int)active_mode < effects.size()) + /*-----------------------------------------------------*\ + | If the active mode is Disabled (index 0), set | + | enabled to false on the device instead of forcibly | + | enabling it. | + \*-----------------------------------------------------*/ + if(active_mode == 0) { - controller->SetEffect(effects[active_mode]); - controller->SetBrightness(modes[active_mode].brightness); - controller->SetDelay(modes[active_mode].speed); + controller->SetEnabled(false); + } + else + { + if(!controller->GetEnabled()) + { + controller->SetEnabled(true); + } + + if((unsigned int)(active_mode - 1) < effects.size()) + { + controller->SetEffect(effects[active_mode - 1]); + controller->SetBrightness(modes[active_mode].brightness); + controller->SetDelay(modes[active_mode].speed); + } } } diff --git a/Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Linux.h b/Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Windows_Linux.h similarity index 93% rename from Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Linux.h rename to Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Windows_Linux.h index 29a55f13d..21e269b9b 100644 --- a/Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Linux.h +++ b/Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Windows_Linux.h @@ -12,7 +12,11 @@ #pragma once #include "RGBController.h" +#ifdef __linux__ #include "ValveSteamMachineController_Linux.h" +#else +#include "ValveSteamMachineController_Windows.h" +#endif class RGBController_ValveSteamMachine : public RGBController { diff --git a/Controllers/ValveSteamMachineController/ValveSteamMachineControllerDetect_Linux.cpp b/Controllers/ValveSteamMachineController/ValveSteamMachineControllerDetect_Linux.cpp index 4f8d0a545..48b4fd3b7 100644 --- a/Controllers/ValveSteamMachineController/ValveSteamMachineControllerDetect_Linux.cpp +++ b/Controllers/ValveSteamMachineController/ValveSteamMachineControllerDetect_Linux.cpp @@ -15,7 +15,8 @@ #include #include "DetectionManager.h" #include "filesystem.h" -#include "RGBController_ValveSteamMachine_Linux.h" +#include "RGBController_ValveSteamMachine_Windows_Linux.h" +#include "ValveSteamMachineController_Linux.h" /*---------------------------------------------------------*\ | Helper function to extract the numeric index enclosed | diff --git a/Controllers/ValveSteamMachineController/ValveSteamMachineControllerDetect_Windows.cpp b/Controllers/ValveSteamMachineController/ValveSteamMachineControllerDetect_Windows.cpp new file mode 100644 index 000000000..4af1105fd --- /dev/null +++ b/Controllers/ValveSteamMachineController/ValveSteamMachineControllerDetect_Windows.cpp @@ -0,0 +1,51 @@ +/*---------------------------------------------------------*\ +| ValveSteamMachineControllerDetect_Windows.cpp | +| | +| Detector for Valve Steam Machine LEDs | +| | +| Adam Honse (calcprogrammer1@gmail.com) 28 Jul 2026 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#include +#include "dmiinfo.h" +#include "LogManager.h" +#include "PawnIOLib.h" +#include "DetectionManager.h" +#include "i2c_smbus_pawnio.h" +#include "ValveSteamMachineController_Windows.h" +#include "RGBController_ValveSteamMachine_Windows_Linux.h" + +DetectedControllers DetectValveSteamMachineControllers() +{ + DetectedControllers detected_controllers; + DMIInfo dmi; + HANDLE pawnio_handle; + HRESULT status; + + /*-----------------------------------------------------*\ + | The LedsValve PawnIO module is not capable of DMI | + | checking itself, so ensure the DMI info matches that | + | of the Steam Machine using the same DMI checks that | + | the official Linux leds-valve driver uses. | + \*-----------------------------------------------------*/ + if((dmi.getManufacturer() == "OEM" && dmi.getProductName() == "F7F") + ||(dmi.getManufacturer() == "Valve" && dmi.getProductName() == "Fremont")) + { + status = i2c_smbus_pawnio::start_pawnio("LedsValve.bin", &pawnio_handle); + + if(status == S_OK) + { + ValveSteamMachineController* controller = new ValveSteamMachineController(pawnio_handle, "Valve Steam Machine"); + RGBController_ValveSteamMachine* rgb_controller = new RGBController_ValveSteamMachine(controller); + + detected_controllers.push_back(rgb_controller); + } + } + + return(detected_controllers); +} + +REGISTER_DETECTOR("Valve Steam Machine", DetectValveSteamMachineControllers); \ No newline at end of file diff --git a/Controllers/ValveSteamMachineController/ValveSteamMachineController_Linux.cpp b/Controllers/ValveSteamMachineController/ValveSteamMachineController_Linux.cpp index 628dde200..97e474374 100644 --- a/Controllers/ValveSteamMachineController/ValveSteamMachineController_Linux.cpp +++ b/Controllers/ValveSteamMachineController/ValveSteamMachineController_Linux.cpp @@ -43,12 +43,8 @@ void ValveSteamMachineController::AddLED(std::string led_path) led_paths.push_back(path); /*-----------------------------------------------------*\ - | Open the brightness and multi_intensity files | + | Open the multi_intensity files | \*-----------------------------------------------------*/ - std::ofstream brightness_file; - brightness_file.open(path + "brightness"); - led_brightness.push_back(std::move(brightness_file)); - std::ofstream multi_intensity_file; multi_intensity_file.open(path + "multi_intensity"); led_multi_intensity.push_back(std::move(multi_intensity_file)); @@ -62,6 +58,13 @@ void ValveSteamMachineController::AddLED(std::string led_path) { led_brightness_scale.open(path + "brightness_scale"); led_delay.open(path + "delay"); + led_enabled.open(path + "enabled"); + led_breath_offset.open(path + "breath_offset"); + led_breath_level.open(path + "breath_level"); + led_patrol_num.open(path + "patrol_num"); + led_color_shift.open(path + "color_shift"); + led_multi_intensity_startup.open(path + "multi_intensity_startup"); + led_brightness_startup.open(path + "brightness_startup"); } /*-----------------------------------------------------*\ @@ -100,14 +103,9 @@ size_t ValveSteamMachineController::GetLEDCount() return(led_paths.size()); } -std::string ValveSteamMachineController::GetLEDPath(unsigned int led_idx) +std::string ValveSteamMachineController::GetLocation() { - if(led_idx < led_paths.size()) - { - return(led_paths[led_idx]); - } - - return(""); + return(led_paths[0]); } std::vector ValveSteamMachineController::GetAvailableEffects() @@ -174,6 +172,134 @@ std::string ValveSteamMachineController::GetEffect() return(""); } +bool ValveSteamMachineController::GetEnabled() +{ + if(led_enabled.is_open()) + { + std::ifstream enabled_file(led_paths[0] + "enabled"); + + if(enabled_file.good()) + { + unsigned int enabled = 0; + enabled_file >> std::hex >> enabled; + enabled_file.close(); + return(enabled != 0); + } + } + + return(false); +} + +unsigned int ValveSteamMachineController::GetBreathOffset() +{ + if(led_breath_offset.is_open()) + { + std::ifstream breath_offset_file(led_paths[0] + "breath_offset"); + + if(breath_offset_file.good()) + { + unsigned int breath_offset = 0; + breath_offset_file >> std::hex >> breath_offset; + breath_offset_file.close(); + return(breath_offset); + } + } + + return(0); +} + +unsigned int ValveSteamMachineController::GetBreathLevel() +{ + if(led_breath_level.is_open()) + { + std::ifstream breath_level_file(led_paths[0] + "breath_level"); + + if(breath_level_file.good()) + { + unsigned int breath_level = 0; + breath_level_file >> std::hex >> breath_level; + breath_level_file.close(); + return(breath_level); + } + } + + return(0); +} + +unsigned int ValveSteamMachineController::GetPatrolNum() +{ + if(led_patrol_num.is_open()) + { + std::ifstream patrol_num_file(led_paths[0] + "patrol_num"); + + if(patrol_num_file.good()) + { + unsigned int patrol_num = 0; + patrol_num_file >> std::hex >> patrol_num; + patrol_num_file.close(); + return(patrol_num); + } + } + + return(0); +} + +unsigned int ValveSteamMachineController::GetColorShift() +{ + if(led_color_shift.is_open()) + { + std::ifstream color_shift_file(led_paths[0] + "color_shift"); + + if(color_shift_file.good()) + { + unsigned int color_shift = 0; + color_shift_file >> std::hex >> color_shift; + color_shift_file.close(); + return(color_shift); + } + } + + return(0); +} + +RGBColor ValveSteamMachineController::GetStartupColor() +{ + if(led_multi_intensity_startup.is_open()) + { + std::ifstream startup_color_file(led_paths[0] + "multi_intensity_startup"); + + if(startup_color_file.good()) + { + unsigned int red = 0; + unsigned int grn = 0; + unsigned int blu = 0; + startup_color_file >> std::hex >> red >> grn >> blu; + startup_color_file.close(); + return(ToRGBColor(red, grn, blu)); + } + } + + return(0); +} + +unsigned int ValveSteamMachineController::GetBrightnessStartup() +{ + if(led_brightness_startup.is_open()) + { + std::ifstream brightness_startup_file(led_paths[0] + "brightness_startup"); + + if(brightness_startup_file.good()) + { + unsigned int brightness_startup = 0; + brightness_startup_file >> std::hex >> brightness_startup; + brightness_startup_file.close(); + return(brightness_startup); + } + } + + return(0); +} + void ValveSteamMachineController::ReadAvailableEffects(std::string first_led_path) { std::ifstream effect_index_file(first_led_path + "effect_index"); @@ -204,7 +330,7 @@ void ValveSteamMachineController::ReadAvailableEffects(std::string first_led_pat } } -void ValveSteamMachineController::SetLEDColor(unsigned int led_idx, unsigned char red, unsigned char grn, unsigned char blu) +void ValveSteamMachineController::SetLEDColor(unsigned int led_idx, RGBColor color) { if(led_idx >= led_paths.size()) { @@ -215,9 +341,9 @@ void ValveSteamMachineController::SetLEDColor(unsigned int led_idx, unsigned cha | Use multi_intensity to set RGB values. This is the | | leds-valve sysfs LED interface pattern. | \*-----------------------------------------------------*/ - std::string color_str = std::to_string((unsigned int)red) + " " - + std::to_string((unsigned int)grn) + " " - + std::to_string((unsigned int)blu); + std::string color_str = std::to_string(RGBGetRValue(color)) + " " + + std::to_string(RGBGetGValue(color)) + " " + + std::to_string(RGBGetBValue(color)); led_multi_intensity[led_idx].write(color_str.c_str(), color_str.length()); led_multi_intensity[led_idx].flush(); @@ -256,3 +382,81 @@ void ValveSteamMachineController::SetDelay(unsigned int delay) led_delay.flush(); } } + +void ValveSteamMachineController::SetEnabled(bool enabled) +{ + if(led_enabled.is_open()) + { + std::string enabled_str = std::to_string(enabled ? 1 : 0); + led_enabled.write(enabled_str.c_str(), enabled_str.length()); + led_enabled.flush(); + } +} + +void ValveSteamMachineController::SetBreathOffset(unsigned int breath_offset) +{ + if(led_breath_offset.is_open()) + { + std::string breath_offset_str = std::to_string(breath_offset); + led_breath_offset.write(breath_offset_str.c_str(), breath_offset_str.length()); + led_breath_offset.flush(); + } +} + +void ValveSteamMachineController::SetBreathLevel(unsigned int breath_level) +{ + if(led_breath_level.is_open()) + { + std::string breath_level_str = std::to_string(breath_level); + led_breath_level.write(breath_level_str.c_str(), breath_level_str.length()); + led_breath_level.flush(); + } +} + +void ValveSteamMachineController::SetPatrolNum(unsigned int patrol_num) +{ + if(led_patrol_num.is_open()) + { + std::string patrol_num_str = std::to_string(patrol_num); + led_patrol_num.write(patrol_num_str.c_str(), patrol_num_str.length()); + led_patrol_num.flush(); + } +} + +void ValveSteamMachineController::SetColorShift(unsigned int color_shift) +{ + if(led_color_shift.is_open()) + { + std::string color_shift_str = std::to_string(color_shift); + led_color_shift.write(color_shift_str.c_str(), color_shift_str.length()); + led_color_shift.flush(); + } +} + +void ValveSteamMachineController::SetStartupColor(RGBColor color) +{ + if(led_multi_intensity_startup.is_open()) + { + /*-----------------------------------------------------*\ + | Use multi_intensity_startup to set RGB values. This | + | is the leds-valve sysfs LED interface pattern for | + | the startup color. | + \*-----------------------------------------------------*/ + std::string color_str = std::to_string(RGBGetRValue(color)) + " " + + std::to_string(RGBGetGValue(color)) + " " + + std::to_string(RGBGetBValue(color)); + + led_multi_intensity_startup.write(color_str.c_str(), color_str.length()); + led_multi_intensity_startup.flush(); + } +} + +void ValveSteamMachineController::SetBrightnessStartup(unsigned int brightness_startup) +{ + if(led_brightness_startup.is_open()) + { + std::string brightness_startup_str = std::to_string(brightness_startup); + led_brightness_startup.write(brightness_startup_str.c_str(), brightness_startup_str.length()); + led_brightness_startup.flush(); + } +} diff --git a/Controllers/ValveSteamMachineController/ValveSteamMachineController_Linux.h b/Controllers/ValveSteamMachineController/ValveSteamMachineController_Linux.h index 9fbbfcac0..de148fc64 100644 --- a/Controllers/ValveSteamMachineController/ValveSteamMachineController_Linux.h +++ b/Controllers/ValveSteamMachineController/ValveSteamMachineController_Linux.h @@ -14,6 +14,7 @@ #include #include #include +#include "RGBControllerInterface.h" class ValveSteamMachineController { @@ -25,30 +26,47 @@ public: void AddLED(std::string led_path); size_t GetLEDCount(); - - std::string GetLEDPath(unsigned int led_idx); + std::string GetLocation(); std::vector GetAvailableEffects(); unsigned int GetBrightness(); unsigned int GetDelay(); std::string GetEffect(); + bool GetEnabled(); + unsigned int GetBreathOffset(); + unsigned int GetBreathLevel(); + unsigned int GetPatrolNum(); + unsigned int GetColorShift(); + RGBColor GetStartupColor(); + unsigned int GetBrightnessStartup(); - bool SupportsEffects(); - - void SetLEDColor(unsigned int led_idx, unsigned char red, unsigned char grn, unsigned char blu); + void SetLEDColor(unsigned int led_idx, RGBColor color); void SetEffect(std::string effect); void SetBrightness(unsigned int brightness); void SetDelay(unsigned int delay); + void SetEnabled(bool enabled); + void SetBreathOffset(unsigned int breath_offset); + void SetBreathLevel(unsigned int breath_level); + void SetPatrolNum(unsigned int patrol_num); + void SetColorShift(unsigned int color_shift); + void SetStartupColor(RGBColor color); + void SetBrightnessStartup(unsigned int brightness_startup); private: std::string name; std::vector led_paths; - std::vector led_brightness; std::vector led_multi_intensity; std::vector led_effect; std::ofstream led_brightness_scale; std::ofstream led_delay; + std::ofstream led_enabled; + std::ofstream led_breath_offset; + std::ofstream led_breath_level; + std::ofstream led_patrol_num; + std::ofstream led_color_shift; + std::ofstream led_multi_intensity_startup; + std::ofstream led_brightness_startup; std::vector available_effects; void ReadAvailableEffects(std::string first_led_path); diff --git a/Controllers/ValveSteamMachineController/ValveSteamMachineController_Windows.cpp b/Controllers/ValveSteamMachineController/ValveSteamMachineController_Windows.cpp new file mode 100644 index 000000000..320fbd17a --- /dev/null +++ b/Controllers/ValveSteamMachineController/ValveSteamMachineController_Windows.cpp @@ -0,0 +1,641 @@ +/*---------------------------------------------------------*\ +| ValveSteamMachineController_Windows.cpp | +| | +| Driver for Valve Steam Machine LEDs | +| | +| Adam Honse (calcprogrammer1@gmail.com) 28 Jul 2026 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#include "LogManager.h" +#include "ValveSteamMachineController_Windows.h" + +/*---------------------------------------------------------*\ +| Name for log entries | +\*---------------------------------------------------------*/ +static const char* VALVESTEAMMACHINECONTROLLER = "ValveSteamMachineController"; + +static const std::vector effects = +{ + "patrol", + "breath", + "factory", + "normal", + "off", + "rainbow", + "demo", + "manual" +}; + +ValveSteamMachineController::ValveSteamMachineController(HANDLE dev_handle, std::string dev_name) +{ + name = dev_name; + pawnio_handle = dev_handle; + + /*-----------------------------------------------------*\ + | Initialize driver | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_init", in, 0, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_init failed", VALVESTEAMMACHINECONTROLLER); + } +} + +ValveSteamMachineController::~ValveSteamMachineController() +{ + +} + +std::string ValveSteamMachineController::GetName() +{ + return(name); +} + +size_t ValveSteamMachineController::GetLEDCount() +{ + return(17); +} + +std::string ValveSteamMachineController::GetLocation() +{ + return("PawnIO leds-valve"); +} + +std::vector ValveSteamMachineController::GetAvailableEffects() +{ + return(effects); +} + +unsigned int ValveSteamMachineController::GetBrightness() +{ + /*-----------------------------------------------------*\ + | Get brightness | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_brightness_scale", in, 0, out, 1, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_brightness_scale failed", VALVESTEAMMACHINECONTROLLER); + return(0); + } + + if(out[0] <= 255) + { + return(out[0]); + } + else + { + LOG_ERROR("[%s] ioctl_get_brightness_scale delay out of range", VALVESTEAMMACHINECONTROLLER); + return(0); + } +} + +unsigned int ValveSteamMachineController::GetDelay() +{ + /*-----------------------------------------------------*\ + | Get delay | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_delay", in, 0, out, 1, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_delay failed", VALVESTEAMMACHINECONTROLLER); + return(0); + } + + if(out[0] <= 20) + { + return(out[0]); + } + else + { + LOG_ERROR("[%s] ioctl_get_delay delay out of range", VALVESTEAMMACHINECONTROLLER); + return(0); + } +} + +std::string ValveSteamMachineController::GetEffect() +{ + /*-----------------------------------------------------*\ + | Get effect | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_effect", in, 0, out, 1, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_effect failed", VALVESTEAMMACHINECONTROLLER); + return(""); + } + + if(out[0] < effects.size()) + { + return(effects[out[0]]); + } + else + { + LOG_ERROR("[%s] ioctl_get_effect effect out of range", VALVESTEAMMACHINECONTROLLER); + return(""); + } +} + +bool ValveSteamMachineController::GetEnabled() +{ + /*-----------------------------------------------------*\ + | Get enabled | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_enabled", in, 0, out, 1, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_enabled failed", VALVESTEAMMACHINECONTROLLER); + return(false); + } + + return((out[0] & 0xFF) != 0); +} + +unsigned int ValveSteamMachineController::GetBreathOffset() +{ + /*-----------------------------------------------------*\ + | Get breath offset | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_breath_offset", in, 0, out, 1, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_breath_offset failed", VALVESTEAMMACHINECONTROLLER); + return(0); + } + + if(out[0] <= 255) + { + return(out[0]); + } + else + { + LOG_ERROR("[%s] ioctl_get_breath_offset out of range", VALVESTEAMMACHINECONTROLLER); + return(0); + } +} + +unsigned int ValveSteamMachineController::GetBreathLevel() +{ + /*-----------------------------------------------------*\ + | Get breath level | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_breath_level", in, 0, out, 1, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_breath_level failed", VALVESTEAMMACHINECONTROLLER); + return(0); + } + + if(out[0] <= 255) + { + return(out[0]); + } + else + { + LOG_ERROR("[%s] ioctl_get_breath_level out of range", VALVESTEAMMACHINECONTROLLER); + return(0); + } +} + +unsigned int ValveSteamMachineController::GetPatrolNum() +{ + /*-----------------------------------------------------*\ + | Get patrol num | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_patrol_num", in, 0, out, 1, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_patrol_num failed", VALVESTEAMMACHINECONTROLLER); + return(0); + } + + if(out[0] <= 255) + { + return(out[0]); + } + else + { + LOG_ERROR("[%s] ioctl_get_patrol_num out of range", VALVESTEAMMACHINECONTROLLER); + return(0); + } +} + +unsigned int ValveSteamMachineController::GetColorShift() +{ + /*-----------------------------------------------------*\ + | Get color shift | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_color_shift", in, 0, out, 1, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_color_shift failed", VALVESTEAMMACHINECONTROLLER); + return(0); + } + + if(out[0] <= 255) + { + return(out[0]); + } + else + { + LOG_ERROR("[%s] ioctl_get_color_shift out of range", VALVESTEAMMACHINECONTROLLER); + return(0); + } +} + +bool ValveSteamMachineController::GetPersistence() +{ + /*-----------------------------------------------------*\ + | Get persistence | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_persistence", in, 0, out, 1, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_persistence failed", VALVESTEAMMACHINECONTROLLER); + return(false); + } + + return((out[0] & 0xFF) != 0); +} + +RGBColor ValveSteamMachineController::GetStartupColor() +{ + /*-----------------------------------------------------*\ + | Get startup color | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[3]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_startup_color", in, 0, out, 3, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_startup_color failed", VALVESTEAMMACHINECONTROLLER); + return(0); + } + + return(ToRGBColor(out[0], out[1], out[2])); +} + +unsigned int ValveSteamMachineController::GetBrightnessStartup() +{ + /*-----------------------------------------------------*\ + | Get brightness startup | + \*-----------------------------------------------------*/ + ULONG64 in[1]; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_get_brightness_startup", in, 0, out, 1, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_get_brightness_startup failed", VALVESTEAMMACHINECONTROLLER); + return(0); + } + + if(out[0] <= 255) + { + return(out[0]); + } + else + { + LOG_ERROR("[%s] ioctl_get_brightness_startup out of range", VALVESTEAMMACHINECONTROLLER); + return(0); + } +} + +void ValveSteamMachineController::SetLEDColor(unsigned int led_idx, RGBColor color) +{ + if(led_idx >= GetLEDCount()) + { + LOG_ERROR("[%s] ioctl_set_led_color led index out of range", VALVESTEAMMACHINECONTROLLER); + return; + } + + /*-----------------------------------------------------*\ + | Reverse order of LEDs | + \*-----------------------------------------------------*/ + led_idx = (GetLEDCount() - 1) - led_idx; + + /*-----------------------------------------------------*\ + | Set LED color | + \*-----------------------------------------------------*/ + ULONG64 in[4] = {led_idx, RGBGetRValue(color), RGBGetGValue(color), RGBGetBValue(color)}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_led_color", in, 4, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_led_color failed", VALVESTEAMMACHINECONTROLLER); + } +} + +void ValveSteamMachineController::SetEffect(std::string effect) +{ + /*-----------------------------------------------------*\ + | Determine effect index | + \*-----------------------------------------------------*/ + std::size_t effect_idx; + + for(effect_idx = 0; effect_idx < effects.size(); effect_idx++) + { + if(effect == effects[effect_idx]) + { + break; + } + } + + if(effect_idx >= effects.size()) + { + LOG_ERROR("[%s] ioctl_set_effect effect out of range", VALVESTEAMMACHINECONTROLLER); + return; + } + + /*-----------------------------------------------------*\ + | Set effect | + \*-----------------------------------------------------*/ + ULONG64 in[1] = {effect_idx}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_effect", in, 1, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_effect failed", VALVESTEAMMACHINECONTROLLER); + } +} + +void ValveSteamMachineController::SetBrightness(unsigned int brightness) +{ + if(brightness > 255) + { + LOG_ERROR("[%s] ioctl_set_brightness_scale brightness out of range", VALVESTEAMMACHINECONTROLLER); + return; + } + + /*-----------------------------------------------------*\ + | Set brightness | + \*-----------------------------------------------------*/ + ULONG64 in[1] = {brightness}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_brightness_scale", in, 1, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_brightness_scale failed", VALVESTEAMMACHINECONTROLLER); + } +} + +void ValveSteamMachineController::SetDelay(unsigned int delay) +{ + if(delay > 20) + { + LOG_ERROR("[%s] ioctl_set_delay delay out of range", VALVESTEAMMACHINECONTROLLER); + return; + } + + /*-----------------------------------------------------*\ + | Set delay | + \*-----------------------------------------------------*/ + ULONG64 in[1] = {delay}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_delay", in, 1, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_delay failed", VALVESTEAMMACHINECONTROLLER); + } +} + +void ValveSteamMachineController::SetEnabled(bool enabled) +{ + /*-----------------------------------------------------*\ + | Set enabled | + \*-----------------------------------------------------*/ + ULONG64 in[1] = {enabled ? 1 : 0}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_enabled", in, 1, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_enabled failed", VALVESTEAMMACHINECONTROLLER); + } +} + +void ValveSteamMachineController::SetBreathOffset(unsigned int breath_offset) +{ + if(breath_offset > 255) + { + LOG_ERROR("[%s] ioctl_set_breath_offset out of range", VALVESTEAMMACHINECONTROLLER); + return; + } + + /*-----------------------------------------------------*\ + | Set breath offset | + \*-----------------------------------------------------*/ + ULONG64 in[1] = {breath_offset}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_breath_offset", in, 1, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_breath_offset failed", VALVESTEAMMACHINECONTROLLER); + } +} + +void ValveSteamMachineController::SetBreathLevel(unsigned int breath_level) +{ + if(breath_level > 255) + { + LOG_ERROR("[%s] ioctl_set_breath_level out of range", VALVESTEAMMACHINECONTROLLER); + return; + } + + /*-----------------------------------------------------*\ + | Set breath level | + \*-----------------------------------------------------*/ + ULONG64 in[1] = {breath_level}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_breath_level", in, 1, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_breath_level failed", VALVESTEAMMACHINECONTROLLER); + } +} + +void ValveSteamMachineController::SetPatrolNum(unsigned int patrol_num) +{ + if(patrol_num > 255) + { + LOG_ERROR("[%s] ioctl_set_patrol_num out of range", VALVESTEAMMACHINECONTROLLER); + return; + } + + /*-----------------------------------------------------*\ + | Set patrol num | + \*-----------------------------------------------------*/ + ULONG64 in[1] = {patrol_num}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_patrol_num", in, 1, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_patrol_num failed", VALVESTEAMMACHINECONTROLLER); + } +} + +void ValveSteamMachineController::SetColorShift(unsigned int color_shift) +{ + if(color_shift > 255) + { + LOG_ERROR("[%s] ioctl_set_color_shift out of range", VALVESTEAMMACHINECONTROLLER); + return; + } + + /*-----------------------------------------------------*\ + | Set color shift | + \*-----------------------------------------------------*/ + ULONG64 in[1] = {color_shift}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_color_shift", in, 1, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_color_shift failed", VALVESTEAMMACHINECONTROLLER); + } +} + +void ValveSteamMachineController::SetStartupColor(RGBColor color) +{ + /*-----------------------------------------------------*\ + | Set startup color | + \*-----------------------------------------------------*/ + ULONG64 in[3] = {RGBGetRValue(color), RGBGetGValue(color), RGBGetBValue(color)}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_startup_color", in, 3, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_startup_color failed", VALVESTEAMMACHINECONTROLLER); + } +} + +void ValveSteamMachineController::SetBrightnessStartup(unsigned int brightness_startup) +{ + if(brightness_startup > 255) + { + LOG_ERROR("[%s] ioctl_set_brightness_startup out of range", VALVESTEAMMACHINECONTROLLER); + return; + } + + /*-----------------------------------------------------*\ + | Set brightness startup | + \*-----------------------------------------------------*/ + ULONG64 in[1] = {brightness_startup}; + ULONG64 out[1]; + SIZE_T return_size; + HRESULT status; + + status = pawnio_execute(pawnio_handle, "ioctl_set_brightness_startup", in, 1, out, 0, &return_size); + + if(status != S_OK) + { + LOG_ERROR("[%s] ioctl_set_brightness_startup failed", VALVESTEAMMACHINECONTROLLER); + } +} diff --git a/Controllers/ValveSteamMachineController/ValveSteamMachineController_Windows.h b/Controllers/ValveSteamMachineController/ValveSteamMachineController_Windows.h new file mode 100644 index 000000000..5f49d4d1a --- /dev/null +++ b/Controllers/ValveSteamMachineController/ValveSteamMachineController_Windows.h @@ -0,0 +1,60 @@ +/*---------------------------------------------------------*\ +| ValveSteamMachineController_Windows.h | +| | +| Driver for Valve Steam Machine LEDs | +| | +| Adam Honse (calcprogrammer1@gmail.com) 28 Jul 2026 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#pragma once + +#include +#include +#include +#include "PawnIOLib.h" +#include "RGBControllerInterface.h" + +class ValveSteamMachineController +{ +public: + ValveSteamMachineController(HANDLE dev_handle, std::string dev_name); + ~ValveSteamMachineController(); + + std::string GetName(); + size_t GetLEDCount(); + std::string GetLocation(); + + std::vector GetAvailableEffects(); + unsigned int GetBrightness(); + unsigned int GetDelay(); + std::string GetEffect(); + bool GetEnabled(); + unsigned int GetBreathOffset(); + unsigned int GetBreathLevel(); + unsigned int GetPatrolNum(); + unsigned int GetColorShift(); + bool GetPersistence(); + RGBColor GetStartupColor(); + unsigned int GetBrightnessStartup(); + + bool SupportsEffects(); + + void SetLEDColor(unsigned int led_idx, RGBColor color); + void SetEffect(std::string effect); + void SetBrightness(unsigned int brightness); + void SetDelay(unsigned int delay); + void SetEnabled(bool enabled); + void SetBreathOffset(unsigned int breath_offset); + void SetBreathLevel(unsigned int breath_level); + void SetPatrolNum(unsigned int patrol_num); + void SetColorShift(unsigned int color_shift); + void SetStartupColor(RGBColor color); + void SetBrightnessStartup(unsigned int brightness_startup); + +private: + std::string name; + HANDLE pawnio_handle; +}; \ No newline at end of file diff --git a/OpenRGB.pro b/OpenRGB.pro index cec129a02..325331e3e 100644 --- a/OpenRGB.pro +++ b/OpenRGB.pro @@ -338,6 +338,20 @@ win32:INCLUDEPATH += win32:SOURCES += $$CONTROLLER_CPP_WINDOWS +#-----------------------------------------------------------------------------------------------# +# Exclude controllers that depend on PawnIO from the 32-bit BUILDDATE_STRING # +#-----------------------------------------------------------------------------------------------# +win32:!contains(QMAKE_TARGET.arch, x86_64) { + win32:SOURCES -= \ + Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Windows_Linux.cpp \ + Controllers/ValveSteamMachineController/ValveSteamMachineController_Windows.cpp \ + Controllers/ValveSteamMachineController/ValveSteamMachineControllerDetect_Windows.cpp \ + + win32:HEADERS -= \ + Controllers/ValveSteamMachineController/RGBController_ValveSteamMachine_Windows_Linux.h \ + Controllers/ValveSteamMachineController/ValveSteamMachineController_Windows.h \ +} + win32:SOURCES += \ dependencies/hueplusplus-1.2.0/src/WinHttpHandler.cpp \ dependencies/NVFC/nvapi.cpp \ @@ -441,6 +455,7 @@ win32:contains(QMAKE_TARGET.arch, x86_64) { copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/SmbusIntelSkylakeIMC.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/SmbusNCT6793.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/LpcIO.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) + copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/LedsValve.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) first.depends = $(first) copydata export(first.depends) export(copydata.commands) diff --git a/dependencies/PawnIO/modules/LedsValve.bin b/dependencies/PawnIO/modules/LedsValve.bin new file mode 100644 index 0000000000000000000000000000000000000000..7798fb1f9d2f24545de22cba4310873fe9d15fb5 GIT binary patch literal 43796 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 O5BuOFKlmpf;{88pZ*$cE literal 0 HcmV?d00001