Implement human color parsing in the command-line by using the standardized CSS 3 subset, well known for styling. Interpret first as text to avoid problems like 'darkgreen' being picked up as 0xDA.

- Also, and make case insensitive by using the strcasecmp() function. Both for colors and modes. The only improvement I can think of is stripping whitespace beforehand.
  - Also, improve digit parsing by using `strtoul()`, previously only FF00FF worked, whereas now we can type things like 0xFF00ff or 0xff00ff.

For example; something like this should work: `OpenRGB.exe -c darkgreen,ff00ff,0xff0000,red,REd,teal,pink,gOLd,AAgoldo,magenta -m breathing`

(`AAgoldo` will throw a `Error: Unknown color: 'AAgoldo', skipping.`; and pick the next color instead, this is intended behavior)
This commit is contained in:
Swyter 2020-06-21 02:54:30 +02:00 committed by Adam Honse
parent d2dfd03551
commit cb9b22804a

224
cli.cpp
View file

@ -33,6 +33,9 @@ static void Sleep(unsigned int milliseconds)
}
#endif
#ifdef _WIN32 /* swy: quirk for MSVC; which doesn't support this case-insensitive function */
#define strcasecmp strcmpi
#endif
static std::vector<RGBController*> rgb_controllers;
static ProfileManager* profile_manager;
@ -68,30 +71,218 @@ struct Options
ServerOptions servOpts;
};
/*---------------------------------------------------------------------------------------------------------*\
| Support a common subset of human colors; for easier typing: https://www.w3.org/TR/css-color-3/#svg-color |
\*---------------------------------------------------------------------------------------------------------*/
struct HumanColors { uint32_t rgb; const char* keyword; } static const human_colors[] =
{
{ 0x000000, "black" },
{ 0x000080, "navy" },
{ 0x00008b, "darkblue" },
{ 0x0000cd, "mediumblue" },
{ 0x0000ff, "blue" },
{ 0x006400, "darkgreen" },
{ 0x008000, "green" },
{ 0x008080, "teal" },
{ 0x008b8b, "darkcyan" },
{ 0x00bfff, "deepskyblue" },
{ 0x00ced1, "darkturquoise" },
{ 0x00fa9a, "mediumspringgreen" },
{ 0x00ff00, "lime" },
{ 0x00ff7f, "springgreen" },
{ 0x00ffff, "aqua" },
{ 0x00ffff, "cyan" },
{ 0x191970, "midnightblue" },
{ 0x1e90ff, "dodgerblue" },
{ 0x20b2aa, "lightseagreen" },
{ 0x228b22, "forestgreen" },
{ 0x2e8b57, "seagreen" },
{ 0x2f4f4f, "darkslategray" },
{ 0x2f4f4f, "darkslategrey" },
{ 0x32cd32, "limegreen" },
{ 0x3cb371, "mediumseagreen" },
{ 0x40e0d0, "turquoise" },
{ 0x4169e1, "royalblue" },
{ 0x4682b4, "steelblue" },
{ 0x483d8b, "darkslateblue" },
{ 0x48d1cc, "mediumturquoise" },
{ 0x4b0082, "indigo" },
{ 0x556b2f, "darkolivegreen" },
{ 0x5f9ea0, "cadetblue" },
{ 0x6495ed, "cornflowerblue" },
{ 0x66cdaa, "mediumaquamarine" },
{ 0x696969, "dimgray" },
{ 0x696969, "dimgrey" },
{ 0x6a5acd, "slateblue" },
{ 0x6b8e23, "olivedrab" },
{ 0x708090, "slategray" },
{ 0x708090, "slategrey" },
{ 0x778899, "lightslategray" },
{ 0x778899, "lightslategrey" },
{ 0x7b68ee, "mediumslateblue" },
{ 0x7cfc00, "lawngreen" },
{ 0x7fff00, "chartreuse" },
{ 0x7fffd4, "aquamarine" },
{ 0x800000, "maroon" },
{ 0x800080, "purple" },
{ 0x808000, "olive" },
{ 0x808080, "gray" },
{ 0x808080, "grey" },
{ 0x87ceeb, "skyblue" },
{ 0x87cefa, "lightskyblue" },
{ 0x8a2be2, "blueviolet" },
{ 0x8b0000, "darkred" },
{ 0x8b008b, "darkmagenta" },
{ 0x8b4513, "saddlebrown" },
{ 0x8fbc8f, "darkseagreen" },
{ 0x90ee90, "lightgreen" },
{ 0x9370db, "mediumpurple" },
{ 0x9400d3, "darkviolet" },
{ 0x98fb98, "palegreen" },
{ 0x9932cc, "darkorchid" },
{ 0x9acd32, "yellowgreen" },
{ 0xa0522d, "sienna" },
{ 0xa52a2a, "brown" },
{ 0xa9a9a9, "darkgray" },
{ 0xa9a9a9, "darkgrey" },
{ 0xadd8e6, "lightblue" },
{ 0xadff2f, "greenyellow" },
{ 0xafeeee, "paleturquoise" },
{ 0xb0c4de, "lightsteelblue" },
{ 0xb0e0e6, "powderblue" },
{ 0xb22222, "firebrick" },
{ 0xb8860b, "darkgoldenrod" },
{ 0xba55d3, "mediumorchid" },
{ 0xbc8f8f, "rosybrown" },
{ 0xbdb76b, "darkkhaki" },
{ 0xc0c0c0, "silver" },
{ 0xc71585, "mediumvioletred" },
{ 0xcd5c5c, "indianred" },
{ 0xcd853f, "peru" },
{ 0xd2691e, "chocolate" },
{ 0xd2b48c, "tan" },
{ 0xd3d3d3, "lightgray" },
{ 0xd3d3d3, "lightgrey" },
{ 0xd8bfd8, "thistle" },
{ 0xda70d6, "orchid" },
{ 0xdaa520, "goldenrod" },
{ 0xdb7093, "palevioletred" },
{ 0xdc143c, "crimson" },
{ 0xdcdcdc, "gainsboro" },
{ 0xdda0dd, "plum" },
{ 0xdeb887, "burlywood" },
{ 0xe0ffff, "lightcyan" },
{ 0xe6e6fa, "lavender" },
{ 0xe9967a, "darksalmon" },
{ 0xee82ee, "violet" },
{ 0xeee8aa, "palegoldenrod" },
{ 0xf08080, "lightcoral" },
{ 0xf0e68c, "khaki" },
{ 0xf0f8ff, "aliceblue" },
{ 0xf0fff0, "honeydew" },
{ 0xf0ffff, "azure" },
{ 0xf4a460, "sandybrown" },
{ 0xf5deb3, "wheat" },
{ 0xf5f5dc, "beige" },
{ 0xf5f5f5, "whitesmoke" },
{ 0xf5fffa, "mintcream" },
{ 0xf8f8ff, "ghostwhite" },
{ 0xfa8072, "salmon" },
{ 0xfaebd7, "antiquewhite" },
{ 0xfaf0e6, "linen" },
{ 0xfafad2, "lightgoldenrodyellow" },
{ 0xfdf5e6, "oldlace" },
{ 0xff0000, "red" },
{ 0xff00ff, "fuchsia" },
{ 0xff00ff, "magenta" },
{ 0xff1493, "deeppink" },
{ 0xff4500, "orangered" },
{ 0xff6347, "tomato" },
{ 0xff69b4, "hotpink" },
{ 0xff7f50, "coral" },
{ 0xff8c00, "darkorange" },
{ 0xffa07a, "lightsalmon" },
{ 0xffa500, "orange" },
{ 0xffb6c1, "lightpink" },
{ 0xffc0cb, "pink" },
{ 0xffd700, "gold" },
{ 0xffdab9, "peachpuff" },
{ 0xffdead, "navajowhite" },
{ 0xffe4b5, "moccasin" },
{ 0xffe4c4, "bisque" },
{ 0xffe4e1, "mistyrose" },
{ 0xffebcd, "blanchedalmond" },
{ 0xffefd5, "papayawhip" },
{ 0xfff0f5, "lavenderblush" },
{ 0xfff5ee, "seashell" },
{ 0xfff8dc, "cornsilk" },
{ 0xfffacd, "lemonchiffon" },
{ 0xfffaf0, "floralwhite" },
{ 0xfffafa, "snow" },
{ 0xffff00, "yellow" },
{ 0xffffe0, "lightyellow" },
{ 0xfffff0, "ivory" },
{ 0xffffff, "white" },
{ 0, NULL }
};
bool ParseColors(std::string colors_string, DeviceOptions *options)
{
while (colors_string.length() >= 6)
while (colors_string.length() > 0)
{
int rgb_end = colors_string.find_first_of(',');
size_t rgb_end = colors_string.find_first_of(',');
std::string color = colors_string.substr(0, rgb_end);
if (color.length() != 6)
int32_t rgb = 0;
bool parsed = false;
if (color.length() <= 0)
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 (...)
/* swy: (A) try interpreting it as text; as human keywords, otherwise strtoul() will pick up 'darkgreen' as 0xDA */
for (const struct HumanColors *hc = human_colors; hc->keyword != NULL; hc++)
{
if (strcasecmp(hc->keyword, color.c_str()) != 0)
continue;
rgb = hc->rgb; parsed = true;
break;
}
/* swy: (B) no luck, try interpreting it as an hexadecimal number instead */
if (!parsed)
{
const char *colorptr = color.c_str(); char *endptr = NULL;
rgb = strtoul(colorptr, &endptr, 16);
/* swy: check that strtoul() has advanced the read pointer until the end (NULL terminator);
that means it has read the whole thing */
if (colorptr != endptr && endptr && *endptr == '\0')
parsed = true;
}
/* swy: we got it, save the 32-bit integer as a tuple of three RGB bytes */
if (parsed)
{
options->colors.push_back(std::make_tuple(
(rgb >> (8 * 2)) & 0xFF, /* RR.... */
(rgb >> (8 * 1)) & 0xFF, /* ..GG.. */
(rgb >> (8 * 0)) & 0xFF /* ....BB */
));
}
else
{
std::cout << "Error: Unknown color: '" + color + "', skipping." << std::endl;
}
// If there are no more colors
if (rgb_end == static_cast<int>(std::string::npos))
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);
}
@ -111,7 +302,7 @@ unsigned int ParseMode(DeviceOptions& options)
| Search through all of the device modes and see if there is|
| a match. If no match is found, print an error message. |
\*---------------------------------------------------------*/
for(std::size_t mode_idx = 0; mode_idx < rgb_controllers[options.device]->modes.size(); mode_idx++)
for(unsigned int mode_idx = 0; mode_idx < rgb_controllers[options.device]->modes.size(); mode_idx++)
{
if (strcasecmp(rgb_controllers[options.device]->modes[mode_idx].name.c_str(), options.mode.c_str()) == 0)
{
@ -176,7 +367,7 @@ void OptionHelp()
help_text += " Can be specified multiple times with different modes and colors\n";
help_text += "-z, --zone [0-9] Selects zone to apply colors and/or sizes to, or applies to all zones in device if omitted\n";
help_text += " Must be specified after specifying a device\n";
help_text += "-c, --color \"FFFFFF,00AAFF...\" Sets colors on each device directly if no effect is specified, and sets the effect color if an effect is specified\n";
help_text += "-c, --color FFFFFF,00AAFF... Sets colors on each device directly if no effect is specified, and sets the effect color if an effect is specified\n";
help_text += " 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";
help_text += "-s, --size [0-N] Sets the new size of the specified device zone.\n";
@ -387,6 +578,7 @@ bool OptionMode(int *currentDev, std::string argument, Options *options)
std::cout << "Error: --mode passed with no argument" << std::endl;
return false;
}
DeviceOptions* currentDevOpts = GetDeviceOptionsForDevID(options, *currentDev);
currentDevOpts->mode = argument;
currentDevOpts->hasOption = true;
@ -793,14 +985,14 @@ unsigned int cli_main(int argc, char *argv[], std::vector<RGBController *> rgb_c
\*---------------------------------------------------------*/
if (options.hasDevice)
{
for(std::size_t device_idx = 0; device_idx < options.devices.size(); device_idx++)
for(unsigned int device_idx = 0; device_idx < options.devices.size(); device_idx++)
{
ApplyOptions(options.devices[device_idx]);
}
}
else
{
for (std::size_t device_idx = 0; device_idx < rgb_controllers.size(); device_idx++)
for (unsigned int device_idx = 0; device_idx < rgb_controllers.size(); device_idx++)
{
options.allDeviceOptions.device = device_idx;
ApplyOptions(options.allDeviceOptions);