Files
OpenRGB/ProfileManager.cpp
2025-05-02 16:21:39 -05:00

541 lines
25 KiB
C++

/*---------------------------------------------------------*\
| ProfileManager.cpp |
| |
| OpenRGB profile manager |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-only |
\*---------------------------------------------------------*/
#include <fstream>
#include <iostream>
#include <cstring>
#include "ProfileManager.h"
#include "ResourceManager.h"
#include "RGBController_Dummy.h"
#include "LogManager.h"
#include "NetworkProtocol.h"
#include "filesystem.h"
#include "StringUtils.h"
#define OPENRGB_PROFILE_HEADER "OPENRGB_PROFILE"
#define OPENRGB_PROFILE_VERSION OPENRGB_SDK_PROTOCOL_VERSION
ProfileManager::ProfileManager(const filesystem::path &config_dir)
{
configuration_directory = config_dir;
UpdateProfileList();
}
ProfileManager::~ProfileManager() {}
bool ProfileManager::SaveProfile(std::string profile_name, bool sizes)
{
profile_name = StringUtils::remove_null_terminating_chars(profile_name);
/*---------------------------------------------------------*\
| Get the list of controllers from the resource manager |
\*---------------------------------------------------------*/
std::vector<RGBController *> controllers = ResourceManager::get()->GetRGBControllers();
/*---------------------------------------------------------*\
| If a name was entered, save the profile file |
\*---------------------------------------------------------*/
if (profile_name != "") {
/*---------------------------------------------------------*\
| Extension .orp - OpenRgb Profile |
\*---------------------------------------------------------*/
std::string filename = profile_name;
/*---------------------------------------------------------*\
| Determine file extension |
\*---------------------------------------------------------*/
if (sizes) {
filename += ".ors";
} else {
filename += ".orp";
}
/*---------------------------------------------------------*\
| Open an output file in binary mode |
\*---------------------------------------------------------*/
filesystem::path profile_path = configuration_directory / filesystem::u8path(filename);
std::ofstream controller_file(profile_path,
std::ios::out | std::ios::binary | std::ios::trunc);
/*---------------------------------------------------------*\
| Write header |
| 16 bytes - "OPENRGB_PROFILE" |
| 4 bytes - Version, unsigned int |
\*---------------------------------------------------------*/
unsigned int profile_version = OPENRGB_PROFILE_VERSION;
controller_file.write(OPENRGB_PROFILE_HEADER, 16);
controller_file.write((char *) &profile_version, sizeof(unsigned int));
/*---------------------------------------------------------*\
| Write controller data for each controller |
\*---------------------------------------------------------*/
for (std::size_t controller_index = 0; controller_index < controllers.size();
controller_index++) {
/*-----------------------------------------------------*\
| Ignore remote and virtual controllers when saving |
| sizes |
\*-----------------------------------------------------*/
if (sizes
&& (controllers[controller_index]->flags & CONTROLLER_FLAG_REMOTE
|| controllers[controller_index]->flags & CONTROLLER_FLAG_VIRTUAL)) {
break;
}
unsigned char *controller_data = controllers[controller_index]->GetDeviceDescription(
profile_version);
unsigned int controller_size;
memcpy(&controller_size, controller_data, sizeof(controller_size));
controller_file.write((const char *) controller_data, controller_size);
delete[] controller_data;
}
/*---------------------------------------------------------*\
| Close the file when done |
\*---------------------------------------------------------*/
controller_file.close();
/*---------------------------------------------------------*\
| Update the profile list |
\*---------------------------------------------------------*/
UpdateProfileList();
return (true);
} else {
return (false);
}
}
void ProfileManager::SetConfigurationDirectory(const filesystem::path &directory)
{
configuration_directory = directory;
UpdateProfileList();
}
bool ProfileManager::LoadProfile(std::string profile_name)
{
profile_name = StringUtils::remove_null_terminating_chars(profile_name);
return (LoadProfileWithOptions(profile_name, false, true));
}
bool ProfileManager::LoadSizeFromProfile(std::string profile_name)
{
profile_name = StringUtils::remove_null_terminating_chars(profile_name);
return (LoadProfileWithOptions(profile_name, true, false));
}
std::vector<RGBController *> ProfileManager::LoadProfileToList(std::string profile_name, bool sizes)
{
std::vector<RGBController *> temp_controllers;
unsigned int controller_size;
unsigned int controller_offset = 0;
filesystem::path filename = configuration_directory / filesystem::u8path(profile_name);
/*---------------------------------------------------------*\
| Determine file extension |
\*---------------------------------------------------------*/
if (sizes) {
filename.concat(".ors");
} else {
if (filename.extension() != ".orp") {
filename.concat(".orp");
}
}
/*---------------------------------------------------------*\
| Open input file in binary mode |
\*---------------------------------------------------------*/
std::ifstream controller_file(filename, std::ios::in | std::ios::binary);
/*---------------------------------------------------------*\
| Read and verify file header |
\*---------------------------------------------------------*/
char profile_string[16] = "";
unsigned int profile_version = 0;
controller_file.read(profile_string, 16);
controller_file.read((char *) &profile_version, sizeof(unsigned int));
/*---------------------------------------------------------*\
| Profile version started at 1 and protocol version started |
| at 0. Version 1 profiles should use protocol 0, but 2 or |
| greater should be synchronized |
\*---------------------------------------------------------*/
if (profile_version == 1) {
profile_version = 0;
}
controller_offset += 16 + sizeof(unsigned int);
controller_file.seekg(controller_offset);
if (strcmp(profile_string, OPENRGB_PROFILE_HEADER) == 0) {
if (profile_version <= OPENRGB_PROFILE_VERSION) {
/*---------------------------------------------------------*\
| Read controller data from file until EOF |
\*---------------------------------------------------------*/
while (!(controller_file.peek() == EOF)) {
controller_file.read((char *) &controller_size, sizeof(controller_size));
unsigned char *controller_data = new unsigned char[controller_size];
controller_file.seekg(controller_offset);
controller_file.read((char *) controller_data, controller_size);
RGBController_Dummy *temp_controller = new RGBController_Dummy();
temp_controller->ReadDeviceDescription(controller_data, profile_version);
temp_controllers.push_back(temp_controller);
delete[] controller_data;
controller_offset += controller_size;
controller_file.seekg(controller_offset);
}
}
}
return (temp_controllers);
}
bool ProfileManager::LoadDeviceFromListWithOptions(std::vector<RGBController *> &temp_controllers,
std::vector<bool> &temp_controller_used,
RGBController *load_controller,
bool load_size,
bool load_settings)
{
for (std::size_t temp_index = 0; temp_index < temp_controllers.size(); temp_index++) {
RGBController *temp_controller = temp_controllers[temp_index];
/*---------------------------------------------------------*\
| Do not compare location string for HID devices, as the |
| location string may change between runs as devices are |
| connected and disconnected. Also do not compare the I2C |
| bus number, since it is not persistent across reboots |
| on Linux - strip the I2C number and compare only address. |
\*---------------------------------------------------------*/
bool location_check;
if (load_controller->location.find("HID: ") == 0) {
location_check = true;
} else if (load_controller->location.find("I2C: ") == 0) {
std::size_t loc = load_controller->location.rfind(", ");
if (loc == std::string::npos) {
location_check = false;
} else {
std::string i2c_address = load_controller->location.substr(loc + 2);
location_check = temp_controller->location.find(i2c_address) != std::string::npos;
}
} else {
location_check = temp_controller->location == load_controller->location;
}
/*---------------------------------------------------------*\
| Test if saved controller data matches this controller |
\*---------------------------------------------------------*/
if ((temp_controller_used[temp_index] == false)
&& (temp_controller->type == load_controller->type)
&& (temp_controller->name == load_controller->name)
&& (temp_controller->description == load_controller->description)
&& (temp_controller->version == load_controller->version)
&& (temp_controller->serial == load_controller->serial) && (location_check == true)) {
/*---------------------------------------------------------*\
| Set used flag for this temp device |
\*---------------------------------------------------------*/
temp_controller_used[temp_index] = true;
/*---------------------------------------------------------*\
| Update zone sizes if requested |
\*---------------------------------------------------------*/
if (load_size) {
if (temp_controller->zones.size() == load_controller->zones.size()) {
for (std::size_t zone_idx = 0; zone_idx < temp_controller->zones.size();
zone_idx++) {
if ((temp_controller->zones[zone_idx].name
== load_controller->zones[zone_idx].name)
&& (temp_controller->zones[zone_idx].type
== load_controller->zones[zone_idx].type)
&& (temp_controller->zones[zone_idx].leds_min
== load_controller->zones[zone_idx].leds_min)
&& (temp_controller->zones[zone_idx].leds_max
== load_controller->zones[zone_idx].leds_max)) {
if (temp_controller->zones[zone_idx].leds_count
!= load_controller->zones[zone_idx].leds_count) {
load_controller
->ResizeZone((int) zone_idx,
temp_controller->zones[zone_idx].leds_count);
}
if (temp_controller->zones[zone_idx].segments.size()
!= load_controller->zones[zone_idx].segments.size()) {
load_controller->zones[zone_idx].segments.clear();
for (std::size_t segment_idx = 0;
segment_idx < temp_controller->zones[zone_idx].segments.size();
segment_idx++) {
load_controller->zones[zone_idx].segments.push_back(
temp_controller->zones[zone_idx].segments[segment_idx]);
}
}
}
}
}
}
/*---------------------------------------------------------*\
| Update settings if requested |
\*---------------------------------------------------------*/
if (load_settings) {
/*---------------------------------------------------------*\
| Update all modes |
\*---------------------------------------------------------*/
if (temp_controller->modes.size() == load_controller->modes.size()) {
for (std::size_t mode_index = 0; mode_index < temp_controller->modes.size();
mode_index++) {
if ((temp_controller->modes[mode_index].name
== load_controller->modes[mode_index].name)
&& (temp_controller->modes[mode_index].value
== load_controller->modes[mode_index].value)
&& (temp_controller->modes[mode_index].flags
== load_controller->modes[mode_index].flags)
&& (temp_controller->modes[mode_index].speed_min
== load_controller->modes[mode_index].speed_min)
&& (temp_controller->modes[mode_index].speed_max
== load_controller->modes[mode_index].speed_max)
//&&(temp_controller->modes[mode_index].brightness_min == load_controller->modes[mode_index].brightness_min)
//&&(temp_controller->modes[mode_index].brightness_max == load_controller->modes[mode_index].brightness_max)
&& (temp_controller->modes[mode_index].colors_min
== load_controller->modes[mode_index].colors_min)
&& (temp_controller->modes[mode_index].colors_max
== load_controller->modes[mode_index].colors_max)) {
load_controller->modes[mode_index].speed
= temp_controller->modes[mode_index].speed;
load_controller->modes[mode_index].brightness
= temp_controller->modes[mode_index].brightness;
load_controller->modes[mode_index].direction
= temp_controller->modes[mode_index].direction;
load_controller->modes[mode_index].color_mode
= temp_controller->modes[mode_index].color_mode;
load_controller->modes[mode_index].colors.resize(
temp_controller->modes[mode_index].colors.size());
for (std::size_t mode_color_index = 0;
mode_color_index < temp_controller->modes[mode_index].colors.size();
mode_color_index++) {
load_controller->modes[mode_index].colors[mode_color_index]
= temp_controller->modes[mode_index].colors[mode_color_index];
}
}
}
load_controller->active_mode = temp_controller->active_mode;
}
/*---------------------------------------------------------*\
| Update all colors |
\*---------------------------------------------------------*/
if (temp_controller->colors.size() == load_controller->colors.size()) {
for (std::size_t color_index = 0; color_index < temp_controller->colors.size();
color_index++) {
load_controller->colors[color_index] = temp_controller->colors[color_index];
}
}
}
return (true);
}
}
return (false);
}
bool ProfileManager::LoadProfileWithOptions(std::string profile_name,
bool load_size,
bool load_settings)
{
std::vector<RGBController *> temp_controllers;
std::vector<bool> temp_controller_used;
bool ret_val = false;
/*---------------------------------------------------------*\
| Get the list of controllers from the resource manager |
\*---------------------------------------------------------*/
std::vector<RGBController *> controllers = ResourceManager::get()->GetRGBControllers();
/*---------------------------------------------------------*\
| Open input file in binary mode |
\*---------------------------------------------------------*/
temp_controllers = LoadProfileToList(profile_name);
/*---------------------------------------------------------*\
| Set up used flag vector |
\*---------------------------------------------------------*/
temp_controller_used.resize(temp_controllers.size());
for (unsigned int controller_idx = 0; controller_idx < temp_controller_used.size();
controller_idx++) {
temp_controller_used[controller_idx] = false;
}
/*---------------------------------------------------------*\
| Loop through all controllers. For each controller, search|
| all saved controllers until a match is found |
\*---------------------------------------------------------*/
for (std::size_t controller_index = 0; controller_index < controllers.size();
controller_index++) {
bool temp_ret_val = LoadDeviceFromListWithOptions(temp_controllers,
temp_controller_used,
controllers[controller_index],
load_size,
load_settings);
std::string current_name = controllers[controller_index]->name + " @ "
+ controllers[controller_index]->location;
LOG_INFO("[ProfileManager] Profile loading: %s for %s",
(temp_ret_val ? "Succeeded" : "FAILED!"),
current_name.c_str());
ret_val |= temp_ret_val;
}
/*---------------------------------------------------------*\
| Delete all temporary controllers |
\*---------------------------------------------------------*/
for (unsigned int controller_idx = 0; controller_idx < temp_controllers.size();
controller_idx++) {
delete temp_controllers[controller_idx];
}
return (ret_val);
}
void ProfileManager::DeleteProfile(std::string profile_name)
{
profile_name = StringUtils::remove_null_terminating_chars(profile_name);
filesystem::path filename = configuration_directory / profile_name;
filename.concat(".orp");
filesystem::remove(filename);
UpdateProfileList();
}
void ProfileManager::UpdateProfileList()
{
profile_list.clear();
/*---------------------------------------------------------*\
| Load profiles by looking for .orp files in current dir |
\*---------------------------------------------------------*/
for (const auto &entry : filesystem::directory_iterator(configuration_directory)) {
std::string filename = entry.path().filename().string();
if (filename.find(".orp") != std::string::npos) {
LOG_INFO("[ProfileManager] Found file: %s attempting to validate header",
filename.c_str());
/*---------------------------------------------------------*\
| Open input file in binary mode |
\*---------------------------------------------------------*/
filesystem::path file_path = configuration_directory;
file_path.append(filename);
std::ifstream profile_file(file_path, std::ios::in | std::ios::binary);
/*---------------------------------------------------------*\
| Read and verify file header |
\*---------------------------------------------------------*/
char profile_string[16];
unsigned int profile_version;
profile_file.read(profile_string, 16);
profile_file.read((char *) &profile_version, sizeof(unsigned int));
if (strcmp(profile_string, OPENRGB_PROFILE_HEADER) == 0) {
if (profile_version <= OPENRGB_PROFILE_VERSION) {
/*---------------------------------------------------------*\
| Add this profile to the list |
\*---------------------------------------------------------*/
filename.erase(filename.length() - 4);
profile_list.push_back(filename);
LOG_INFO("[ProfileManager] Valid v%i profile found for %s",
profile_version,
filename.c_str());
} else {
LOG_WARNING("[ProfileManager] Profile %s isn't valid for current version (v%i, "
"expected v%i at most)",
filename.c_str(),
profile_version,
OPENRGB_PROFILE_VERSION);
}
} else {
LOG_WARNING("[ProfileManager] Profile %s isn't valid: header is missing",
filename.c_str());
}
profile_file.close();
}
}
}
unsigned char *ProfileManager::GetProfileListDescription()
{
unsigned int data_ptr = 0;
unsigned int data_size = 0;
/*---------------------------------------------------------*\
| Calculate data size |
\*---------------------------------------------------------*/
unsigned short num_profiles = (unsigned short) profile_list.size();
data_size += sizeof(data_size);
data_size += sizeof(num_profiles);
for (unsigned int i = 0; i < num_profiles; i++) {
data_size += sizeof(unsigned short);
data_size += (unsigned int) strlen(profile_list[i].c_str()) + 1;
}
/*---------------------------------------------------------*\
| Create data buffer |
\*---------------------------------------------------------*/
unsigned char *data_buf = new unsigned char[data_size];
/*---------------------------------------------------------*\
| Copy in data size |
\*---------------------------------------------------------*/
memcpy(&data_buf[data_ptr], &data_size, sizeof(data_size));
data_ptr += sizeof(data_size);
/*---------------------------------------------------------*\
| Copy in num_profiles |
\*---------------------------------------------------------*/
memcpy(&data_buf[data_ptr], &num_profiles, sizeof(num_profiles));
data_ptr += sizeof(num_profiles);
/*---------------------------------------------------------*\
| Copy in profile names (size+data) |
\*---------------------------------------------------------*/
for (unsigned int i = 0; i < num_profiles; i++) {
unsigned short name_len = (unsigned short) strlen(profile_list[i].c_str()) + 1;
memcpy(&data_buf[data_ptr], &name_len, sizeof(name_len));
data_ptr += sizeof(name_len);
strcpy((char *) &data_buf[data_ptr], profile_list[i].c_str());
data_ptr += name_len;
}
return (data_buf);
}