From 8918d48b127857806a16dd9dfdb4294fc1fd612b Mon Sep 17 00:00:00 2001 From: Fernando Crespo Date: Thu, 29 Jan 2026 22:12:31 -0300 Subject: [PATCH] Initial commit --- .gitignore | 2 + Makefile | 34 +++++++ acer-rgb.cpp | 281 +++++++++++++++++++++++++++++++++++++++++++++++++++ rgb.cpp | 256 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 573 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 acer-rgb.cpp create mode 100644 rgb.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd21ef4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +acer-rgb +rgb \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..197e51a --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ + +all: + +rgb: + clang++ -std=c++23 ./rgb.cpp -o ./rgb + +acer-rgb: + clang++ -std=c++23 ./acer-rgb.cpp -o ./acer-rgb + + +all-red: acer-rgb + sudo ./acer-rgb /dev/hidraw2 keyboard static --brightness 100 --rgb 255 0 0 --zone all + sudo ./acer-rgb /dev/hidraw2 lid static --brightness 100 --rgb 255 0 0 --zone all + sudo ./acer-rgb /dev/hidraw2 button static --brightness 100 --rgb 255 0 0 --zone all + +all-green: acer-rgb + sudo ./acer-rgb /dev/hidraw2 keyboard static --brightness 100 --rgb 0 255 0 --zone all + sudo ./acer-rgb /dev/hidraw2 lid static --brightness 100 --rgb 0 255 0 --zone all + sudo ./acer-rgb /dev/hidraw2 button static --brightness 100 --rgb 0 255 0 --zone all + +all-blue: acer-rgb + sudo ./acer-rgb /dev/hidraw2 keyboard static --brightness 100 --rgb 0 0 255 --zone all + sudo ./acer-rgb /dev/hidraw2 lid static --brightness 100 --rgb 0 0 255 --zone all + sudo ./acer-rgb /dev/hidraw2 button static --brightness 100 --rgb 0 0 255 --zone all + +all-magenta: acer-rgb + sudo ./acer-rgb /dev/hidraw2 keyboard static --brightness 100 --rgb 255 0 255 --zone all + sudo ./acer-rgb /dev/hidraw2 lid static --brightness 100 --rgb 255 0 255 --zone all + sudo ./acer-rgb /dev/hidraw2 button static --brightness 100 --rgb 255 0 255 --zone all + +all-cyan: acer-rgb + sudo ./acer-rgb /dev/hidraw2 keyboard static --brightness 100 --rgb 0 255 255 --zone all + sudo ./acer-rgb /dev/hidraw2 lid static --brightness 100 --rgb 0 255 255 --zone all + sudo ./acer-rgb /dev/hidraw2 button static --brightness 100 --rgb 0 255 255 --zone all \ No newline at end of file diff --git a/acer-rgb.cpp b/acer-rgb.cpp new file mode 100644 index 0000000..8d6704f --- /dev/null +++ b/acer-rgb.cpp @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RGB_FEATURE_ID 0xa4 + +#define KEYBOARD_RGB_ID 0x21 +#define LID_RGB_ID 0x83 +#define BUTTON_RGB_ID 0x65 + +#define RGB_EFFECT_STATIC 0x02 +#define RGB_EFFECT_BREATHING 0x04 +#define RGB_EFFECT_NEON 0x05 +#define RGB_EFFECT_WAVE 0x07 +#define RGB_EFFECT_RIPPLE 0x08 +#define RGB_EFFECT_ZOOM 0x09 +#define RGB_EFFECT_SNAKE 0x0a +#define RGB_EFFECT_DISCO 0x0b +#define RGB_EFFECT_SHIFTING 0xff + +static int g_fd = -1; + +static void usage(const char* prog) { + std::println( + "Usage:\n" + " sudo {0} [options]\n\n" + "Devices:\n" + " keyboard | lid | button\n\n" + "Effects:\n" + " static | breathing | neon | wave | ripple | zoom | snake | disco | shifting\n\n" + "Options:\n" + " --brightness <0-100> (required)\n" + " --speed <0-9> (required for non-static effects)\n" + " --direction (keyboard only; ignored for static/breathing)\n" + " --rgb (required for static)\n" + " --zone (keyboard only; default: all)\n\n" + "Examples:\n" + " sudo {0} /dev/hidraw2 keyboard static --brightness 80 --rgb 255 0 0 --zone all\n" + " sudo {0} /dev/hidraw2 keyboard wave --brightness 70 --speed 6 --direction right\n" + " sudo {0} /dev/hidraw2 lid breathing --brightness 60 --speed 5\n", + prog + ); +} + +static bool hidrawWrite(const std::vector& bytes) { + for (uint8_t byte : bytes) { + std::print("0x{:0x} ", static_cast(byte)); + } + std::println(" <--- hidraw values"); + + int retval = ioctl(g_fd, HIDIOCSFEATURE(bytes.size()), bytes.data()); + if (retval < 0) { + std::println("[ERR] FAILED TO WRITE TO HIDRAW DEVICE!"); + return false; + } + + std::println("Successfully wrote to hidraw file"); + return true; +} + +static bool streq(const std::string& a, const char* b) { + return a == b; +} + +static bool parse_u8_int(const std::string& s, int minv, int maxv, uint8_t& out) { + char* end = nullptr; + long v = std::strtol(s.c_str(), &end, 10); + if (!end || *end != '\0') return false; + if (v < minv || v > maxv) return false; + out = static_cast(v); + return true; +} + +static bool parse_device(const std::string& s, uint8_t& device, uint8_t& zone_default) { + if (streq(s, "keyboard")) { device = KEYBOARD_RGB_ID; zone_default = 0x0f; return true; } + if (streq(s, "lid")) { device = LID_RGB_ID; zone_default = 0x00; return true; } + if (streq(s, "button")) { device = BUTTON_RGB_ID; zone_default = 0x00; return true; } + return false; +} + +static bool parse_effect(const std::string& s, uint8_t& effect) { + if (streq(s, "static")) { effect = RGB_EFFECT_STATIC; return true; } + if (streq(s, "breathing")) { effect = RGB_EFFECT_BREATHING; return true; } + if (streq(s, "neon")) { effect = RGB_EFFECT_NEON; return true; } + if (streq(s, "wave")) { effect = RGB_EFFECT_WAVE; return true; } + if (streq(s, "ripple")) { effect = RGB_EFFECT_RIPPLE; return true; } + if (streq(s, "zoom")) { effect = RGB_EFFECT_ZOOM; return true; } + if (streq(s, "snake")) { effect = RGB_EFFECT_SNAKE; return true; } + if (streq(s, "disco")) { effect = RGB_EFFECT_DISCO; return true; } + if (streq(s, "shifting")) { effect = RGB_EFFECT_SHIFTING; return true; } + return false; +} + +static bool parse_direction(const std::string& s, uint8_t& dir) { + if (streq(s, "none")) { dir = 0; return true; } + if (streq(s, "right")) { dir = 1; return true; } + if (streq(s, "left")) { dir = 2; return true; } + return false; +} + +static bool parse_zone(const std::string& s, uint8_t& zone) { + if (streq(s, "all")) { zone = 0x0f; return true; } + if (streq(s, "1")) { zone = 0x01; return true; } + if (streq(s, "2")) { zone = 0x02; return true; } + if (streq(s, "3")) { zone = 0x04; return true; } + if (streq(s, "4")) { zone = 0x08; return true; } + return false; +} + +int main(int argc, char* argv[]) { + if (geteuid() != 0) { + std::println("[ERR] THIS APPLICATION MUST BE RUN AS ROOT!"); + return 1; + } + + if (argc < 4) { + usage(argv[0]); + return 2; + } + + std::string hidraw_path = argv[1]; + std::string device_str = argv[2]; + std::string effect_str = argv[3]; + + uint8_t device = 0x00; + uint8_t zone = 0x00; + uint8_t effect = 0x00; + + if (!parse_device(device_str, device, zone)) { + std::println("[ERR] Invalid device: {}", device_str); + usage(argv[0]); + return 2; + } + + if (!parse_effect(effect_str, effect)) { + std::println("[ERR] Invalid effect: {}", effect_str); + usage(argv[0]); + return 2; + } + + // Defaults + uint8_t brightness = 0x00; + uint8_t speed = 0x00; + uint8_t direction = 0x00; + uint8_t r = 0x00, g = 0x00, b = 0x00; + + bool brightness_set = false; + bool speed_set = false; + bool rgb_set = false; + bool zone_set = false; + bool direction_set = false; + + // Parse options + for (int i = 4; i < argc; i++) { + std::string opt = argv[i]; + + auto need_arg = [&](int n) -> bool { + if (i + n >= argc) { + std::println("[ERR] Missing argument(s) for {}", opt); + return false; + } + return true; + }; + + if (opt == "--brightness") { + if (!need_arg(1)) return 2; + if (!parse_u8_int(argv[++i], 0, 100, brightness)) { + std::println("[ERR] brightness must be 0-100"); + return 2; + } + brightness_set = true; + } else if (opt == "--speed") { + if (!need_arg(1)) return 2; + if (!parse_u8_int(argv[++i], 0, 9, speed)) { + std::println("[ERR] speed must be 0-9"); + return 2; + } + speed_set = true; + } else if (opt == "--direction") { + if (!need_arg(1)) return 2; + if (!parse_direction(argv[++i], direction)) { + std::println("[ERR] direction must be none|right|left"); + return 2; + } + direction_set = true; + } else if (opt == "--zone") { + if (!need_arg(1)) return 2; + if (!parse_zone(argv[++i], zone)) { + std::println("[ERR] zone must be all|1|2|3|4"); + return 2; + } + zone_set = true; + } else if (opt == "--rgb") { + if (!need_arg(3)) return 2; + uint8_t rr, gg, bb; + if (!parse_u8_int(argv[++i], 0, 255, rr) || + !parse_u8_int(argv[++i], 0, 255, gg) || + !parse_u8_int(argv[++i], 0, 255, bb)) { + std::println("[ERR] rgb values must be 0-255"); + return 2; + } + r = rr; g = gg; b = bb; + rgb_set = true; + } else if (opt == "--help" || opt == "-h") { + usage(argv[0]); + return 0; + } else { + std::println("[ERR] Unknown option: {}", opt); + usage(argv[0]); + return 2; + } + } + + // Validate required combos + if (!brightness_set) { + std::println("[ERR] --brightness is required"); + return 2; + } + + if (effect == RGB_EFFECT_STATIC) { + if (!rgb_set) { + std::println("[ERR] static requires --rgb R G B"); + return 2; + } + // speed ignored for static + speed = 0; + } else { + if (!speed_set) { + std::println("[ERR] non-static effects require --speed 0-9"); + return 2; + } + } + + // Only keyboard supports zone; for others force 0x00 + if (device != KEYBOARD_RGB_ID) { + zone = 0x00; + } else { + if (!zone_set) zone = 0x0f; // default all + // direction only used for certain keyboard effects (like original) + if (effect == RGB_EFFECT_STATIC || effect == RGB_EFFECT_BREATHING) { + direction = 0; + } else { + // If user didn't set, keep default 0 (none) + (void)direction_set; + } + } + + // Open + identify + std::println("Using file: {}", hidraw_path); + g_fd = open(hidraw_path.c_str(), O_RDWR | O_NONBLOCK); + if (g_fd < 0) { + std::println("[ERR] FAILED TO OPEN HIDRAW DEVICE!"); + return 1; + } + + char name[256]; + int retval = ioctl(g_fd, HIDIOCGRAWNAME(256), name); + if (retval < 0) { + std::println("[ERR] FAILED TO GET HIDRAW DEVICE NAME!"); + close(g_fd); + return 1; + } + std::println("Device name: {}", name); + + std::vector hidraw_data = { + RGB_FEATURE_ID, device, effect, brightness, speed, direction, r, g, b, zone, 0x00 + }; + + bool ok = hidrawWrite(hidraw_data); + close(g_fd); + + return ok ? 0 : 1; +} diff --git a/rgb.cpp b/rgb.cpp new file mode 100644 index 0000000..f30a8d5 --- /dev/null +++ b/rgb.cpp @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define RGB_FEATURE_ID 0xa4 + +#define KEYBOARD_RGB_ID 0x21 +#define LID_RGB_ID 0x83 +#define BUTTON_RGB_ID 0x65 + +#define RGB_EFFECT_STATIC 0x02 +#define RGB_EFFECT_BREATHING 0x04 +#define RGB_EFFECT_NEON 0x05 +#define RGB_EFFECT_WAVE 0x07 +#define RGB_EFFECT_RIPPLE 0x08 +#define RGB_EFFECT_ZOOM 0x09 +#define RGB_EFFECT_SNAKE 0x0a +#define RGB_EFFECT_DISCO 0x0b +#define RGB_EFFECT_SHIFTING 0xff + +std::string hidraw_path = "/dev/hidraw2"; +int g_fd = -1; + +bool hidrawWrite(const std::vector&bytes) { + for (uint8_t byte : bytes) { + std::print("0x{:0x} ", static_cast(byte)); + } + std::println(" <--- hidraw values"); + + int retval = ioctl(g_fd, HIDIOCSFEATURE(bytes.size()), bytes.data()); + if (retval < 0) { + std::println("[ERR] FAILED TO WRITE TO HIDRAW DEVICE!"); + return false; + } + + std::println("Successfully wrote to hidraw file"); + return true; +} + +int main(int argc, char* argv[]) { + if (argc > 1) { + hidraw_path = std::string(argv[1]); + } + + if (geteuid() != 0) { + std::println("[ERR] THIS APPLICATION MUST BE RUN AS ROOT!"); + return 1; + } + + std::println("Acer Predator Helios Neo 16S (PHN16S-71) RGB Control\nWritten by ZoeBattleSand\n"); + + std::println("Using file: {}", hidraw_path); + g_fd = open(hidraw_path.c_str(), O_RDWR | O_NONBLOCK); + if (g_fd < 0) { + std::println("[ERR] FAILED TO OPEN HIDRAW DEVICE!"); + return 1; + } + + char name[256]; + int retval = ioctl(g_fd, HIDIOCGRAWNAME(256), name); + if (retval < 0) { + std::println("[ERR] FAILED TO GET HIDRAW DEVICE NAME!"); + + return 1; + } + std::println("Device name: {}", name); + + std::string user_input; + +input_dev_start: + std::print("Enter device (0 for keyboard, 1 for lid, 2 for mode button): "); + std::cin >> user_input; + if (user_input != "0" && user_input != "1" && user_input != "2") { + std::println("Invalid input! Try again."); + goto input_dev_start; + } + + uint8_t device = 0x00; + uint8_t zone = 0x00; + uint8_t effect = 0x00; + uint8_t brightness = 0x00; + uint8_t speed = 0x00; + uint8_t direction = 0x00; + uint8_t r = 0x00; + uint8_t g = 0x00; + uint8_t b = 0x00; + + if (user_input == "0") { + device = KEYBOARD_RGB_ID; + zone = 0x0f; + } else if (user_input == "1") { + device = LID_RGB_ID; + zone = 0x00; + } else if (user_input == "2") { + device = BUTTON_RGB_ID; + zone = 0x00; + } + + int effect_val = 0; +input_effect_start: + std::print("Enter effect (0 for static, 1 for breathing, 2 for neon, 3 for wave, 4 for ripple, 5 for zoom, 6 for snake, 7 for disco, 8 for shifting): "); + std::cin >> user_input; + effect_val = std::stoi(user_input); + switch(effect_val) { + case 0: + effect = RGB_EFFECT_STATIC; + break; + case 1: + effect = RGB_EFFECT_BREATHING; + break; + case 2: + effect = RGB_EFFECT_NEON; + break; + case 3: + effect = RGB_EFFECT_WAVE; + break; + case 4: + effect = RGB_EFFECT_RIPPLE; + break; + case 5: + effect = RGB_EFFECT_ZOOM; + break; + case 6: + effect = RGB_EFFECT_SNAKE; + break; + case 7: + effect = RGB_EFFECT_DISCO; + break; + case 8: + effect = RGB_EFFECT_SHIFTING; + break; + default: + std::println("Invalid effect! Try again."); + goto input_effect_start; + } + + if (effect != RGB_EFFECT_STATIC) { + int speed_val = 0; +input_speed_start: + std::print("Enter speed (0-9): "); + std::cin >> user_input; + speed_val = std::stoi(user_input); + if (speed_val > 9) { + std::println("Invalid speed! Try again."); + goto input_speed_start; + } + speed = static_cast(speed_val); + } + + if (device == KEYBOARD_RGB_ID) { + if (effect != RGB_EFFECT_STATIC && effect != RGB_EFFECT_BREATHING) { + int direction_val = 0; +input_direction_start: + std::print("Enter direction (0 for none, 1 for right, 2 for left): "); + std::cin >> user_input; + direction_val = std::stoi(user_input); + if (direction_val > 2) { + std::println("Invalid direction! Try again."); + goto input_direction_start; + } + direction = static_cast(direction_val); + } + + int zone_val = 0; +input_zone_start: + std::print("Enter zone (0 for all, 1-4 to select a specific zone): "); + std::cin >> user_input; + zone_val = std::stoi(user_input); + if (zone_val > 4) { + std::println("Invalid zone! Try again."); + goto input_zone_start; + } + switch (zone_val) { + case 0: + zone = 0x0f; + break; + case 1: + zone = 0x01; + break; + case 2: + zone = 0x02; + break; + case 3: + zone = 0x04; + break; + case 4: + zone = 0x08; + break; + default: + zone = 0x00; + break; + } + } + + if (effect == RGB_EFFECT_STATIC) { + int red_val = 0; +input_red_start: + std::print("Enter red value (0-255): "); + std::cin >> user_input; + red_val = std::stoi(user_input); + if (red_val > std::numeric_limits::max()) { + std::println("Red value {:d} out of range! Try again.", red_val); + goto input_red_start; + } + r = static_cast(red_val); + + int green_val = 0; +input_green_start: + std::print("Enter green value (0-255): "); + std::cin >> user_input; + green_val = std::stoi(user_input); + if (green_val > std::numeric_limits::max()) { + std::println("Green value {:d} out of range! Try again.", green_val); + goto input_green_start; + } + g = static_cast(green_val); + + int blue_val = 0; +input_blue_start: + std::print("Enter blue value (0-255): "); + std::cin >> user_input; + blue_val = std::stoi(user_input); + if (blue_val > std::numeric_limits::max()) { + std::println("Blue value {:d} out of range! Try again.", blue_val); + goto input_blue_start; + } + b = static_cast(blue_val); + } + + int bright_val = 0; +input_bright_start: + std::print("Enter brightness value (0-100): "); + std::cin >> user_input; + bright_val = std::stoi(user_input); + if (bright_val > 100) { + std::println("Brightness value {:d} out of range! Try again.", bright_val); + goto input_bright_start; + } + brightness = static_cast(bright_val); + + std::vector hidraw_data = { RGB_FEATURE_ID, device, effect, brightness, speed, direction, r, g, b, zone, 0x00 }; + + if (!hidrawWrite(hidraw_data)) { + std::println("[ERR] FAILED TO WRITE HIDRAW DATA!"); + return 1; + } + + close(g_fd); + + return 0; +}