diff --git a/OpenRGB.pro b/OpenRGB.pro index 758c8a14..7f7755e0 100644 --- a/OpenRGB.pro +++ b/OpenRGB.pro @@ -43,6 +43,7 @@ INCLUDEPATH += \ SOURCES += \ dependencies/libe131/src/e131.c \ main.cpp \ + cli.cpp \ OpenRGB.cpp \ qt/OpenRGBDeviceInfoPage.cpp \ qt/OpenRGBDevicePage.cpp \ diff --git a/cli.cpp b/cli.cpp new file mode 100644 index 00000000..cc74cf95 --- /dev/null +++ b/cli.cpp @@ -0,0 +1,338 @@ +#include +#include +#include +#include +#include +#include "OpenRGB.h" +#include "RGBController.h" +#include "i2c_smbus.h" + +extern std::vector busses; +extern std::vector rgb_controllers; + +struct DeviceOptions +{ + int device; + std::vector> colors; + std::string mode; + bool hasOption; +}; + +struct Options +{ + std::vector devices; + + // if hasDevice is false, devices above is empty and allDeviceOptions + // shall be applied to all available devices + bool hasDevice; + DeviceOptions allDeviceOptions; +}; + +void PrintHelp() +{ + std::string help_text; + help_text += "OpenRGB, for controlling RGB lighting.\n"; + help_text += "Usage: OpenRGB (--device [--mode] [--color])...\n"; + help_text += "\nOptions:\n"; + help_text += "--gui\t\t\t\t\t Show GUI, also appears when not passing any parameters\n"; + help_text += "-l, --list-devices\t\t\t Lists every compatible device with their number\n"; + help_text += "-d, --device [0-9]\t\t\t Selects device to apply colors and/or effect to, or applies to all devices if omitted\n"; + help_text += "\t\t\t\t\t Can be specified multiple times with different modes and colors\n"; + help_text += "-c, --color \"FFFFFF,00AAFF...\"\t\t Sets colors on each device directly if no effect is specified, and sets the effect color if an effect is specified\n"; + help_text += "\t\t\t\t\t If there are more LEDs than colors given, the last color will be applied to the remaining LEDs\n"; + help_text += "-m, --mode [breathing | static | ...] Sets the mode to be applied, check --list-devices to see which modes are supported on your device\n"; + + std::cout << help_text << std::endl; +} + +bool ParseColors(std::string colors_string, DeviceOptions *options) +{ + while (colors_string.length() >= 6) + { + int rgb_end = colors_string.find_first_of(','); + std::string color = colors_string.substr(0, rgb_end); + if (color.length() != 6) + break; + + try + { + unsigned char r = std::stoi(color.substr(0, 2), nullptr, 16); + unsigned char g = std::stoi(color.substr(2, 2), nullptr, 16); + unsigned char b = std::stoi(color.substr(4, 2), nullptr, 16); + options->colors.push_back(std::make_tuple(r, g, b)); + } + catch (...) + { + break; + } + + // If there are no more colors + if (rgb_end == std::string::npos) + break; + // Remove the current color and the next color's leading comma + colors_string = colors_string.substr(color.length() + 1); + } + + return options->colors.size() > 0; +} + +int ParseMode(DeviceOptions& options) +{ + auto availableModes = rgb_controllers[options.device]->modes; + for (int i = 0; i < availableModes.size(); i++) + { + if (availableModes[i].name == options.mode) + { + return i; + } + } + + std::cout << "Error: Mode '" + options.mode + "' not available for device '" + + rgb_controllers[options.device]->name + "'" << std::endl; + return false; +} + +DeviceOptions* GetDeviceOptionsForDevID(Options *opts, int device) +{ + if (device == -1) + { + return &opts->allDeviceOptions; + } + + for (int i = 0; i < opts->devices.size(); i++) + { + if (opts->devices[i].device == device) + { + return &opts->devices[i]; + } + } + + // should never happen + std::cout << "Internal error: Tried setting an option on a device that wasn't specified" << std::endl; + abort(); +} + +std::string QuoteIfNecessary(std::string str) +{ + if (str.find(' ') == std::string::npos) + { + return str; + } + else + { + return "'" + str + "'"; + } +} + +bool ProcessOptions(int argc, char *argv[], Options *res) +{ + int arg_index = 1; + int currentDev = -1; + + while (arg_index < argc) + { + std::string option = argv[arg_index]; + + // Handle options that take no arguments + if (option == "--list-devices" || option == "-l") + { + for (int i = 0; i < rgb_controllers.size(); i++) + { + RGBController *c = rgb_controllers[i]; + std::cout << i << ": " << c->name << std::endl; + + std::cout << " Type: " << device_type_to_str(c->type) << std::endl; + + if (!c->serial.empty()) + { + std::cout << " Serial: " << c->serial << std::endl; + } + + if (!c->description.empty()) + { + std::cout << " Description: " << c->description << std::endl; + } + + if (!c->modes.empty()) + { + std::cout << " Modes:"; + int curMode = c->GetMode(); + for (int i = 0; i < c->modes.size(); i++) + { + std::string modeStr = QuoteIfNecessary(c->modes[i].name); + if (curMode == i) { + modeStr = "[" + modeStr + "]"; + } + std::cout << " " << modeStr; + } + std::cout << std::endl; + } + + if (!c->zones.empty()) + { + std::cout << " Zones:"; + for (int i = 0; i < c->zones.size(); i++) + { + std::cout << " " << QuoteIfNecessary(c->zones[i].name); + } + std::cout << std::endl; + } + + if (!c->leds.empty()) + { + std::cout << " LEDs:"; + for (int i = 0; i < c->leds.size(); i++) + { + std::cout << " " << QuoteIfNecessary(c->leds[i].name); + } + std::cout << std::endl; + } + + std::cout << std::endl; + } + exit(0); + } + else if (arg_index + 1 < argc) // Handle options that take an argument + { + std::string argument = argv[arg_index + 1]; + + if (option == "--device" || option == "-d") + { + try + { + currentDev = std::stoi(argument); + + if (rgb_controllers.size() <= currentDev || currentDev < 0) + { + throw; + } + + DeviceOptions newDev; + newDev.device = currentDev; + + if (!res->hasDevice) + { + res->hasDevice = true; + } + + res->devices.push_back(newDev); + } + catch (...) + { + std::cout << "Error: Invalid device ID: " + argument << std::endl; + return false; + } + } + else if (option == "--color" || option == "-c") + { + DeviceOptions* currentDevOpts = GetDeviceOptionsForDevID(res, currentDev); + if (ParseColors(argument, currentDevOpts)) + { + currentDevOpts->hasOption = true; + } + else + { + std::cout << "Error: Invalid argument to " + option + ": " + argument << std::endl; + return false; + } + } + else if (option == "--mode" || option == "-m") + { + DeviceOptions* currentDevOpts = GetDeviceOptionsForDevID(res, currentDev); + currentDevOpts->mode = argument; + currentDevOpts->hasOption = true; + } + else + { + std::cout << "Error: Invalid option: " + option << std::endl; + return false; + } + + arg_index++; + } + else + { + return false; + } + arg_index++; + } + + if (res->hasDevice) + { + for (int i = 0; i < res->devices.size(); i++) + { + if (!res->devices[i].hasOption) + { + std::cout << "Error: Device " + std::to_string(i) + " specified, but neither mode nor color given" << std::endl; + return false; + } + } + } + else + { + return res->allDeviceOptions.hasOption; + } + + return true; +} + +void ApplyOptions(DeviceOptions& options) +{ + RGBController *device = rgb_controllers[options.device]; + + // Set mode first, in case it's 'direct' (which affects SetLED below) + int mode = ParseMode(options); + device->SetMode(mode); + + if (options.colors.size() != 0) + { + int last_set_color; + for (int i = 0; i < device->leds.size(); i++) + { + if (i < options.colors.size()) + { + last_set_color = i; + } + + device->SetLED(i, ToRGBColor(std::get<0>(options.colors[last_set_color]), + std::get<1>(options.colors[last_set_color]), + std::get<2>(options.colors[last_set_color]))); + } + } +} + +int cli_main(int argc, char *argv[]) +{ + if (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) + { + PrintHelp(); + return 0; + } + + DetectRGBControllers(); + + Options options; + if (!ProcessOptions(argc, argv, &options)) + { + PrintHelp(); + return -1; + } + + if (options.hasDevice) + { + for (int i = 0; i < options.devices.size(); i++) + { + ApplyOptions(options.devices[i]); + } + } + else + { + for (int i = 0; i < rgb_controllers.size(); i++) + { + options.allDeviceOptions.device = i; + ApplyOptions(options.allDeviceOptions); + } + } + + return 0; +} diff --git a/main.cpp b/main.cpp index 3f4dd429..46ffe1f7 100644 --- a/main.cpp +++ b/main.cpp @@ -19,6 +19,9 @@ extern std::vector busses; extern std::vector rgb_controllers; +// See cli.cpp +extern int cli_main(int argc, char *argv[]); + /******************************************************************************************\ * * * main * @@ -29,6 +32,11 @@ extern std::vector rgb_controllers; int main(int argc, char* argv[]) { + if (argc > 1 && strcmp(argv[1], "--gui")) + { + return cli_main(argc, argv); + } + DetectRGBControllers(); QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);